Skip to content

Quasi-Monte Carlo Tools in OpenCL & C

The qmctoolscl package provides a Python interface to tools in OpenCL and C for performing Quasi-Monte Carlo (QMC). Routines are written as OpenCL kernels. By replacing a few code snippets, these OpenCL kernels are automatically translated to C functions. Python functions provide access to both the OpenCL kernels and C functions in a unified interface. Code is available on GitHub.

Installation

pip install qmctoolscl

To use OpenCL features, please install PyOpenCL. Commands used to install PyOpenCL on Linux, MacOS, and Windows for our automated tests can be found here. Note that Apple has deprecated support for OpenCL (see this post), but installation is still possible in many cases. If you cannot install PyOpenCL, the C backend to this package will still work independently.

Full Doctests for qmctoolscl

Setup for OpenCL vs C

Let's start by importing the relevant packages

>>> import qmctoolscl
>>> import numpy as np

and seeding a random number generator used for reproducibility

>>> rng = np.random.Generator(np.random.SFC64(7))

To use the C backend supply the following keyword arguments to function calls

>>> kwargs = {
...     "backend": "C",
... }

To use the OpenCL backend supply the following keyword arguments to function calls

>>> kwargs = {
...     "backend": "CL",
...     "wait": True, ## required for accurate timing
...     "global_size": (2,2,2), ## global size 
... }

Lattice Sequences

linear order

>>> print(qmctoolscl.lat_gen_linear.__doc__)
Lattice points in linear order

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    g (np.ndarray of np.uint64): pointer to generating vector of size r*d
    x (np.ndarray of np.double): pointer to point storage of size r*n*d
>>> g = np.array([
...     [1,433461,315689,441789,501101],
...     [1,182667,469891,498753,110745]],
...     dtype=np.uint64)
>>> r = np.uint64(g.shape[0]) 
>>> n = np.uint64(8) 
>>> d = np.uint64(g.shape[1])
>>> x = np.empty((r,n,d),dtype=np.float64)
>>> time_perf,time_process = qmctoolscl.lat_gen_linear(r,n,d,g,x,**kwargs)
>>> x
array([[[0.   , 0.   , 0.   , 0.   , 0.   ],
        [0.125, 0.625, 0.125, 0.625, 0.625],
        [0.25 , 0.25 , 0.25 , 0.25 , 0.25 ],
        [0.375, 0.875, 0.375, 0.875, 0.875],
        [0.5  , 0.5  , 0.5  , 0.5  , 0.5  ],
        [0.625, 0.125, 0.625, 0.125, 0.125],
        [0.75 , 0.75 , 0.75 , 0.75 , 0.75 ],
        [0.875, 0.375, 0.875, 0.375, 0.375]],

       [[0.   , 0.   , 0.   , 0.   , 0.   ],
        [0.125, 0.375, 0.375, 0.125, 0.125],
        [0.25 , 0.75 , 0.75 , 0.25 , 0.25 ],
        [0.375, 0.125, 0.125, 0.375, 0.375],
        [0.5  , 0.5  , 0.5  , 0.5  , 0.5  ],
        [0.625, 0.875, 0.875, 0.625, 0.625],
        [0.75 , 0.25 , 0.25 , 0.75 , 0.75 ],
        [0.875, 0.625, 0.625, 0.875, 0.875]]])

natural order

>>> print(qmctoolscl.lat_gen_natural.__doc__)
Lattice points in natural order

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    n_start (np.uint64): starting index in sequence
    g (np.ndarray of np.uint64): pointer to generating vector of size r*d 
    x (np.ndarray of np.double): pointer to point storage of size r*n*d
>>> n_start = np.uint64(2)
>>> n = np.uint64(6) 
>>> x = np.empty((r,n,d),dtype=np.float64)
>>> time_perf,time_process = qmctoolscl.lat_gen_natural(r,n,d,n_start,g,x,**kwargs)
>>> x
array([[[0.25 , 0.25 , 0.25 , 0.25 , 0.25 ],
        [0.75 , 0.75 , 0.75 , 0.75 , 0.75 ],
        [0.125, 0.625, 0.125, 0.625, 0.625],
        [0.625, 0.125, 0.625, 0.125, 0.125],
        [0.375, 0.875, 0.375, 0.875, 0.875],
        [0.875, 0.375, 0.875, 0.375, 0.375]],

       [[0.25 , 0.75 , 0.75 , 0.25 , 0.25 ],
        [0.75 , 0.25 , 0.25 , 0.75 , 0.75 ],
        [0.125, 0.375, 0.375, 0.125, 0.125],
        [0.625, 0.875, 0.875, 0.625, 0.625],
        [0.375, 0.125, 0.125, 0.375, 0.375],
        [0.875, 0.625, 0.625, 0.875, 0.875]]])

Gray code order

>>> print(qmctoolscl.lat_gen_gray.__doc__)
Lattice points in Gray code order

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    n_start (np.uint64): starting index in sequence
    g (np.ndarray of np.uint64): pointer to generating vector of size r*d 
    x (np.ndarray of np.double): pointer to point storage of size r*n*d
>>> time_perf,time_process = qmctoolscl.lat_gen_gray(r,n,d,n_start,g,x,**kwargs)
>>> x
array([[[0.75 , 0.75 , 0.75 , 0.75 , 0.75 ],
        [0.25 , 0.25 , 0.25 , 0.25 , 0.25 ],
        [0.375, 0.875, 0.375, 0.875, 0.875],
        [0.875, 0.375, 0.875, 0.375, 0.375],
        [0.625, 0.125, 0.625, 0.125, 0.125],
        [0.125, 0.625, 0.125, 0.625, 0.625]],

       [[0.75 , 0.25 , 0.25 , 0.75 , 0.75 ],
        [0.25 , 0.75 , 0.75 , 0.25 , 0.25 ],
        [0.375, 0.125, 0.125, 0.375, 0.375],
        [0.875, 0.625, 0.625, 0.875, 0.875],
        [0.625, 0.875, 0.875, 0.625, 0.625],
        [0.125, 0.375, 0.375, 0.125, 0.125]]])

shift mod 1

>>> print(qmctoolscl.lat_shift_mod_1.__doc__)
Shift mod 1 for lattice points

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_x (np.uint64): replications in x
    x (np.ndarray of np.double): lattice points of size r_x*n*d
    shifts (np.ndarray of np.double): shifts of size r*d
    xr (np.ndarray of np.double): pointer to point storage of size r*n*d
>>> r_x = r 
>>> r = np.uint64(2*r_x)
>>> print(qmctoolscl.lat_get_shifts.__doc__)
Get random shifts
Args:
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications
    d (np.uint64): dimension
>>> shifts = qmctoolscl.lat_get_shifts(rng,r,d)
>>> xr = np.empty((r,n,d),dtype=np.float64)
>>> time_perf,time_process = qmctoolscl.lat_shift_mod_1(r,n,d,r_x,x,shifts,xr,**kwargs)
>>> xr
array([[[0.79386058, 0.33727432, 0.1191824 , 0.40212985, 0.44669968],
        [0.29386058, 0.83727432, 0.6191824 , 0.90212985, 0.94669968],
        [0.41886058, 0.46227432, 0.7441824 , 0.52712985, 0.57169968],
        [0.91886058, 0.96227432, 0.2441824 , 0.02712985, 0.07169968],
        [0.66886058, 0.71227432, 0.9941824 , 0.77712985, 0.82169968],
        [0.16886058, 0.21227432, 0.4941824 , 0.27712985, 0.32169968]],

       [[0.85605352, 0.88025643, 0.38630282, 0.3468363 , 0.8076251 ],
        [0.35605352, 0.38025643, 0.88630282, 0.8468363 , 0.3076251 ],
        [0.48105352, 0.75525643, 0.26130282, 0.9718363 , 0.4326251 ],
        [0.98105352, 0.25525643, 0.76130282, 0.4718363 , 0.9326251 ],
        [0.73105352, 0.50525643, 0.01130282, 0.2218363 , 0.6826251 ],
        [0.23105352, 0.00525643, 0.51130282, 0.7218363 , 0.1826251 ]],

       [[0.9528797 , 0.97909681, 0.8866783 , 0.50220658, 0.59501765],
        [0.4528797 , 0.47909681, 0.3866783 , 0.00220658, 0.09501765],
        [0.5778797 , 0.10409681, 0.5116783 , 0.62720658, 0.72001765],
        [0.0778797 , 0.60409681, 0.0116783 , 0.12720658, 0.22001765],
        [0.8278797 , 0.35409681, 0.7616783 , 0.87720658, 0.97001765],
        [0.3278797 , 0.85409681, 0.2616783 , 0.37720658, 0.47001765]],

       [[0.31269008, 0.29826852, 0.96308655, 0.55983568, 0.60383675],
        [0.81269008, 0.79826852, 0.46308655, 0.05983568, 0.10383675],
        [0.93769008, 0.17326852, 0.83808655, 0.18483568, 0.22883675],
        [0.43769008, 0.67326852, 0.33808655, 0.68483568, 0.72883675],
        [0.18769008, 0.92326852, 0.58808655, 0.43483568, 0.47883675],
        [0.68769008, 0.42326852, 0.08808655, 0.93483568, 0.97883675]]])

Digital Net Base 2

LSB to MSB integer representations

convert generating matrices from least significant bit (LSB) order to most significant bit (MSB) order

>>> print(qmctoolscl.dnb2_gmat_lsb_to_msb.__doc__)
Convert base 2 generating matrices with integers stored in Least Significant Bit order to Most Significant Bit order

Args:
    r (np.uint64): replications
    d (np.uint64): dimension
    mmax (np.uint64): columns in each generating matrix 
    tmaxes (np.ndarray of np.uint64): length r vector of bits in each integer of the resulting MSB generating matrices
    C_lsb (np.ndarray of np.uint64): original generating matrices of size r*d*mmax
    C_msb (np.ndarray of np.uint64): new generating matrices of size r*d*mmax
>>> C_lsb = np.array([
...     [1,2,4,8],
...     [1,3,5,15],
...     [1,3,6,9],
...     [1,3,4,10]],
...     dtype=np.uint64)
>>> r = np.uint64(1)
>>> d = np.uint64(C_lsb.shape[0])
>>> mmax = np.uint64(C_lsb.shape[1])
>>> tmax = 4
>>> tmaxes = np.tile(np.uint64(tmax),int(r))
>>> C = np.empty((d,mmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_gmat_lsb_to_msb(r,d,mmax,tmaxes,C_lsb,C,**kwargs)
>>> C
array([[ 8,  4,  2,  1],
       [ 8, 12, 10, 15],
       [ 8, 12,  6,  9],
       [ 8, 12,  2,  5]], dtype=uint64)

linear matrix scrambling (LMS)

>>> print(qmctoolscl.dnb2_linear_matrix_scramble.__doc__)
Linear matrix scrambling for base 2 generating matrices

Args:
    r (np.uint64): replications
    d (np.uint64): dimension
    mmax (np.uint64): columns in each generating matrix 
    r_C (np.uint64): original generating matrices
    tmax_new (np.uint64): bits in the integers of the resulting generating matrices
    S (np.ndarray of np.uint64): scrambling matrices of size r*d*tmax_new
    C (np.ndarray of np.uint64): original generating matrices of size r_C*d*mmax
    C_lms (np.ndarray of np.uint64): resulting generating matrices of size r*d*mmax
>>> r_C = r 
>>> r = np.uint64(2*r_C)
>>> tmax_new = np.uint64(6)
>>> print(qmctoolscl.dnb2_get_linear_scramble_matrix.__doc__)
Return a scrambling matrix for linear matrix scrambling

Args:
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications
    d (np.uint64): dimension
    tmax (np.uint64): bits in each integer
    tmax_new (np.uint64): bits in each integer of the generating matrix after scrambling
    print_mats (np.uint8): flag to print the resulting matrices
>>> print_mats = np.uint8(True)
>>> S = qmctoolscl.dnb2_get_linear_scramble_matrix(rng,r,d,tmax,tmax_new,print_mats)
S with shape (r=2, d=4, tmax_new=6)
l = 0
    j = 0
        1000
        1100
        1010
        1011
        0000
        1011
    j = 1
        1000
        1100
        1010
        1111
        0101
        0111
    j = 2
        1000
        0100
        0010
        1001
        0001
        0111
    j = 3
        1000
        1100
        1010
        1001
        0010
        0010
l = 1
    j = 0
        1000
        0100
        0010
        0101
        1101
        1000
    j = 1
        1000
        0100
        0110
        1001
        1010
        1001
    j = 2
        1000
        0100
        0110
        0111
        1101
        1110
    j = 3
        1000
        0100
        0110
        0011
        1000
        0000
>>> C_lms = np.empty((r,d,mmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_linear_matrix_scramble(r,d,mmax,r_C,tmax_new,S,C,C_lms,**kwargs)
>>> C_lms
array([[[61, 16, 13,  5],
        [60, 43, 49, 33],
        [36, 53, 24, 35],
        [60, 44, 11, 20]],

       [[35, 22,  8,  6],
        [39, 63, 45, 48],
        [35, 60, 18, 37],
        [34, 58, 12, 28]]], dtype=uint64)

digital interlacing

Digital interlacing is used to create higher order digital nets.

>>> print(qmctoolscl.dnb2_interlace.__doc__)
Interlace generating matrices or transpose of point sets to attain higher order digital nets in base 2

Args:
    r (np.uint64): replications
    d_alpha (np.uint64): dimension of resulting generating matrices 
    mmax (np.uint64): columns of generating matrices
    d (np.uint64): dimension of original generating matrices
    tmax (np.uint64): bits in integers of original generating matrices
    tmax_alpha (np.uint64): bits in integers of resulting generating matrices
    alpha (np.uint64): interlacing factor
    C (np.ndarray of np.uint64): original generating matrices of size r*d*mmax
    C_alpha (np.ndarray of np.uint64): resulting interlaced generating matrices of size r*d_alpha*mmax
>>> alpha = np.uint64(2) 
>>> d_alpha = np.uint64(d//alpha)
>>> tmax = tmax_new
>>> tmax_alpha = min(alpha*tmax_new,64)
>>> C_alpha = np.empty((r,d_alpha,mmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_interlace(r,d_alpha,mmax,d,tmax,tmax_alpha,alpha,C_lms,C_alpha,**kwargs)
>>> C_alpha
array([[[4082, 1605, 1443, 1059],
        [3440, 3698,  709, 2330]],

       [[3103, 1917, 1233, 1320],
        [3086, 4068,  600, 2418]]], dtype=uint64)

undo digital interlacing

>>> print(qmctoolscl.dnb2_undo_interlace.__doc__)
Undo interlacing of generating matrices in base 2

Args:
    r (np.uint64): replications
    d (np.uint64): dimension of resulting generating matrices 
    mmax (np.uint64): columns in generating matrices
    d_alpha (np.uint64): dimension of interlaced generating matrices
    tmax (np.uint64): bits in integers of original generating matrices 
    tmax_alpha (np.uint64): bits in integers of interlaced generating matrices
    alpha (np.uint64): interlacing factor
    C_alpha (np.ndarray of np.uint64): interlaced generating matrices of size r*d_alpha*mmax
    C (np.ndarray of np.uint64): original generating matrices of size r*d*mmax
>>> C_lms_cp = np.empty((r,d,mmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_undo_interlace(r,d,mmax,d_alpha,tmax,tmax_alpha,alpha,C_alpha,C_lms_cp,**kwargs)
>>> print((C_lms_cp==C_lms).all())
True

natural order

>>> print(qmctoolscl.dnb2_gen_natural.__doc__)
Binary representation of digital net in base 2 in natural order

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    n_start (np.uint64): starting index in sequence
    mmax (np.uint64): columns in each generating matrix
    C (np.ndarray of np.uint64): generating matrices of size r*d*mmax
    xb (np.ndarray of np.uint64): binary digital net points of size r*n*d
>>> C = C_alpha
>>> d = d_alpha
>>> n_start = np.uint64(2)
>>> n = np.uint64(14)
>>> xb = np.empty((r,n,d),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_gen_natural(r,n,d,n_start,mmax,C,xb,**kwargs)
>>> xb
array([[[1605, 3698],
        [2487,  770],
        [1443,  709],
        [2641, 4021],
        [ 998, 3255],
        [3092,  455],
        [1059, 2330],
        [3025, 1130],
        [ 614, 1896],
        [3476, 2584],
        [ 384, 3039],
        [3698, 1711],
        [1989, 1453],
        [2103, 2269]],

       [[1917, 4068],
        [2914, 1002],
        [1233,  600],
        [2254, 3670],
        [ 940, 3516],
        [4019,  434],
        [1320, 2418],
        [2359, 1404],
        [ 597, 1686],
        [3658, 2712],
        [ 505, 2858],
        [3558, 1828],
        [1668, 1230],
        [2715, 2240]]], dtype=uint64)

Gray code order

>>> print(qmctoolscl.dnb2_gen_gray.__doc__)
Binary representation of digital net in base 2 in Gray code order

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    n_start (np.uint64): starting index in sequence
    mmax (np.uint64): columns in each generating matrix
    C (np.ndarray of np.uint64): generating matrices of size r*d*mmax
    xb (np.ndarray of np.uint64): binary digital net points of size r*n*d
>>> time_perf,time_process = qmctoolscl.dnb2_gen_gray(r,n,d,n_start,mmax,C,xb,**kwargs)
>>> xb
array([[[2487,  770],
        [1605, 3698],
        [ 998, 3255],
        [3092,  455],
        [2641, 4021],
        [1443,  709],
        [ 384, 3039],
        [3698, 1711],
        [2103, 2269],
        [1989, 1453],
        [ 614, 1896],
        [3476, 2584],
        [3025, 1130],
        [1059, 2330]],

       [[2914, 1002],
        [1917, 4068],
        [ 940, 3516],
        [4019,  434],
        [2254, 3670],
        [1233,  600],
        [ 505, 2858],
        [3558, 1828],
        [2715, 2240],
        [1668, 1230],
        [ 597, 1686],
        [3658, 2712],
        [2359, 1404],
        [1320, 2418]]], dtype=uint64)

digital shift

>>> print(qmctoolscl.dnb2_digital_shift.__doc__)
Digital shift base 2 digital net

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_x (np.uint64): replications of xb
    lshifts (np.ndarray of np.uint64): left shift applied to each element of xb
    xb (np.ndarray of np.uint64): binary base 2 digital net points of size r_x*n*d
    shiftsb (np.ndarray of np.uint64): digital shifts of size r*d
    xrb (np.ndarray of np.uint64): digital shifted digital net points of size r*n*d
>>> tmax = tmax_alpha 
>>> tmax_new = np.uint64(64)
>>> lshifts = np.tile(tmax_new-tmax,int(r)) 
>>> r_x = r 
>>> r = np.uint64(2*r_x)
>>> print(qmctoolscl.dnb2_get_digital_shifts.__doc__)
Get random shifts
Args:
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications
    d (np.uint64): dimension
    tmax_new (np.uint64): bits in each integer
>>> shiftsb = qmctoolscl.dnb2_get_digital_shifts(rng,r,d,tmax_new)
>>> shiftsb
array([[ 5310653692329262186, 13368902947290702765],
       [14338104955770306630,   858526716083693676],
       [ 8506091904275275003, 16209688535125722604],
       [17447177601860792486,  9021077006476608718]], dtype=uint64)
>>> xrb = np.empty((r,n,d),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_digital_shift(r,n,d,r_x,lshifts,xb,shiftsb,xrb,**kwargs)
>>> xrb
array([[[15187047675152759914,  9919145632724902829],
        [ 3306551858149391466,  6820669089094001581],
        [ 8634310217328688234,  8284338967989412781],
        [ 9868296515228204138, 11959276263923737517],
        [17051537920884145258,  4816567254914130861],
        [ 1406032815399042154, 10797347560062149549],
        [ 5887114444632685674,   321974826798375853],
        [12579463490905242730, 15237896792649458605],
        [14610586922849336426,  3771732141364175789],
        [ 3883012610452814954, 16381811098001564589],
        [ 8057849465025264746, 14918141219106153389],
        [10444757267531627626,  1731601510165341101],
        [17627998673187568746, 18385912932181435309],
        [  829572063095618666,  2893530214026929069]],

       [[ 8132144669253763142,  3839909669402962028],
        [12766348685818003526, 17701989322449348716],
        [18175171838289969222, 14999829546027051116],
        [ 4452703773692067910,  1209807487018592364],
        [ 5339912900284055622, 17188578964929112172],
        [10082203307905187910,  3344513710392207468],
        [15666666845844602950, 13351512082409449580],
        [ 1773061995406622790,  8766847661746284652],
        [ 8019554678569500742,  9793668376786757740],
        [12590708300350554182,  5118931963576182892],
        [16405257184733364294,  7100515799619201132],
        [ 2475623537276420166, 11703194618791848044],
        [ 6164071632092856390,  6641148637627410540],
        [10699196456854945862, 11297870652328503404]],

       [[17112470792180292859, 15047759831264134636],
        [ 1322850498619333883,   564183429640619500],
        [ 5218464176294812923,  3135738816869172716],
        [13207849915250072827, 18195775970796111340],
        [15211951749429943547,  1991824511517066732],
        [ 3187340744350719227, 14746018656230311404],
        [ 7929631151971851515,  6702589721746605548],
        [10460654142554070267,  9945181453453362668],
        [17688931544483716347,  7864518425608193516],
        [  746389746315910395, 13412953166528644588],
        [ 5794924928598236411, 10841397779300091372],
        [12631389162946649339,  4716502286076216812],
        [14635490997126520059, 11985312084652197356],
        [ 3763801496654142715,  8166259600642016748]],

       [[ 4900149040006590630,  4868758150041011406],
        [ 9651446646882463910,  9471436969213658318],
        [14474801847796265126, 12029481557560100046],
        [  653254591396212902,  7354745144349525198],
        [ 9133532689734856870, 10975639244755403982],
        [13776743905553838246,  6390974824092239054],
        [17127422028317487270, 14956821315350922446],
        [ 3188781181605802150,  1112756060814017742],
        [ 6598006099525267622, 17379757914876249294],
        [11124123725032616102,  3589735855867790542],
        [15524140560973590694,  1464036831748916430],
        [ 1621528511280869542, 15326116484795303118],
        [ 7012337265243353254,  3094339896857035982],
        [11574483687769665702, 16866347557356012750]]], dtype=uint64)

convert digits to doubles

>>> print(qmctoolscl.dnb2_integer_to_float.__doc__)
Convert base 2 binary digital net points to floats

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    tmaxes (np.ndarray of np.uint64): bits in integers of each generating matrix of size r
    xb (np.ndarray of np.uint64): binary digital net points of size r*n*d
    x (np.ndarray of np.double): float digital net points of size r*n*d
>>> x = np.empty((r,n,d),dtype=np.float64)
>>> tmaxes_new = np.tile(tmax_new,int(r))
>>> time_perf,time_process = qmctoolscl.dnb2_integer_to_float(r,n,d,tmaxes_new,xrb,x,**kwargs)
>>> x
array([[[0.8232915 , 0.53771796],
        [0.17924854, 0.36974921],
        [0.46806689, 0.44909492],
        [0.53496143, 0.64831367],
        [0.92436572, 0.26110663],
        [0.07622119, 0.58532538],
        [0.31914111, 0.01745429],
        [0.68193408, 0.82604804],
        [0.7920415 , 0.20446601],
        [0.21049854, 0.88805976],
        [0.43681689, 0.80871406],
        [0.56621143, 0.09387031],
        [0.95561572, 0.99670234],
        [0.04497119, 0.15685859]],

       [[0.44084445, 0.20816192],
        [0.69206515, 0.95962676],
        [0.98527804, 0.81314239],
        [0.24138156, 0.0655838 ],
        [0.28947726, 0.93179473],
        [0.54655734, 0.18130645],
        [0.84929171, 0.72378692],
        [0.09611788, 0.47525176],
        [0.43474093, 0.53091583],
        [0.68254366, 0.27749786],
        [0.88933077, 0.38491973],
        [0.13420382, 0.63443145],
        [0.33415499, 0.36001739],
        [0.5800046 , 0.6124588 ]],

       [[0.9276689 , 0.8157407 ],
        [0.07171187, 0.03058445],
        [0.28289351, 0.16998874],
        [0.71599898, 0.98639499],
        [0.82464156, 0.10797702],
        [0.17278609, 0.79938327],
        [0.42986617, 0.36334812],
        [0.5670732 , 0.53912937],
        [0.9589189 , 0.4263364 ],
        [0.04046187, 0.72711765],
        [0.31414351, 0.58771335],
        [0.68474898, 0.2556821 ],
        [0.79339156, 0.64972507],
        [0.20403609, 0.44269382]],

       [[0.26563761, 0.26393591],
        [0.52320597, 0.51344763],
        [0.78468058, 0.6521195 ],
        [0.035413  , 0.39870153],
        [0.4951298 , 0.5949906 ],
        [0.74683878, 0.34645544],
        [0.92847941, 0.81081091],
        [0.17286417, 0.06032263],
        [0.35767863, 0.94215856],
        [0.60303996, 0.19459997],
        [0.84156535, 0.0793656 ],
        [0.08790324, 0.83083044],
        [0.38013956, 0.1677445 ],
        [0.62745402, 0.91432653]]])

nested uniform scrambling

>>> r = np.uint64(1) 
>>> n_start = np.uint64(0)
>>> n = np.uint64(8)
>>> d = np.uint64(4)
>>> C = np.array([
...   [ 8,  4,  2,  1],
...   [ 8, 12, 10, 15],
...   [ 8, 12,  6,  9],
...   [ 8, 12,  2,  5]], 
...   dtype=np.uint64)
>>> mmax = np.uint64(C.shape[1])
>>> tmax = np.uint64(np.ceil(np.log2(np.max(C))))
>>> xb = np.empty((r,n,d),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_gen_natural(r,n,d,n_start,mmax,C,xb,**kwargs)
>>> print(qmctoolscl.dnb2_nested_uniform_scramble.__doc__)
Nested uniform scramble of digital net b2

Args: 
    r (np.uint64): replications 
    n (np.uint64): points
    d (np.uint64): dimensions
    r_x (np.uint64): replications of xb
    tmax (np.uint64): maximum number of bits in each integer
    tmax_new (np.uint64): maximum number of bits in each integer after scrambling
    rngs (np.ndarray of numpy.random._generator.Generator): random number generators of size r*d
    root_nodes (np.ndarray of NUSNode_dnb2): root nodes of size r*d
    xb (np.ndarray of np.uint64): array of unrandomized points of size r*n*d
    xrb (np.ndarray of np.uint64): array to store scrambled points of size r*n*d
>>> tmax_new = np.uint64(2*tmax) 
>>> r_x = r
>>> r = np.uint64(2*r_x)
>>> base_seed_seq = np.random.SeedSequence(7)
>>> seeds = base_seed_seq.spawn(r*d)
>>> rngs = np.array([np.random.Generator(np.random.SFC64(seed)) for seed in seeds]).reshape(r,d)
>>> root_nodes = np.array([qmctoolscl.NUSNode_dnb2() for i in range(r*d)]).reshape(r,d)
>>> xrb = np.zeros((r,n,d),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.dnb2_nested_uniform_scramble(r,n,d,r_x,tmax,tmax_new,rngs,root_nodes,xb,xrb)
>>> xrb
array([[[ 94,  54, 235,  30],
        [174, 243,  26, 129],
        [ 30, 172,  85, 222],
        [254, 127, 143,  68],
        [ 96, 207, 171,  46],
        [131,  21, 125, 165],
        [ 38,  79,  47, 237],
        [199, 133, 199,  99]],

       [[ 37, 152, 170, 143],
        [219,  56,  68,  29],
        [ 65,  98,  34, 111],
        [181, 211, 196, 220],
        [ 26,   9, 242, 180],
        [245, 167,   2,  42],
        [116, 225, 105,  93],
        [138,  93, 139, 230]]], dtype=uint64)

Generalized Digital Net

Accommodates both Halton sequences and digital nets in any prime base

linear Matrix Scramble

>>> print(qmctoolscl.gdn_linear_matrix_scramble.__doc__)
Linear matrix scramble for generalized digital net

Args:
    r (np.uint64): replications 
    d (np.uint64): dimension 
    mmax (np.uint64): columns in each generating matrix
    r_C (np.uint64): number of replications of C 
    r_b (np.uint64): number of replications of bases
    tmax (np.uint64): number of rows in each generating matrix 
    tmax_new (np.uint64): new number of rows in each generating matrix 
    bases (np.ndarray of np.uint64): bases for each dimension of size r*d 
    S (np.ndarray of np.uint64): scramble matrices of size r*d*tmax_new*tmax
    C (np.ndarray of np.uint64): generating matrices of size r_C*d*mmax*tmax 
    C_lms (np.ndarray of np.uint64): new generating matrices of size r*d*mmax*tmax_new
>>> bases = np.array([[2,3],[5,7]],dtype=np.uint64)
>>> r_C = r_b = np.uint64(bases.shape[0])
>>> d = np.uint64(bases.shape[1]) 
>>> mmax = np.uint64(5)
>>> tmax = mmax
>>> print(qmctoolscl.gdn_get_halton_generating_matrix.__doc__)
Return the identity matrices comprising the Halton generating matrices

Arg:
    r (np.uint64): replications 
    d (np.uint64): dimension 
    mmax (np.uint64): maximum number rows and columns in each generating matrix
>>> C = qmctoolscl.gdn_get_halton_generating_matrix(r_C,d,mmax)
>>> tmax_new = np.uint64(2*tmax)
>>> r = np.uint64(2*r_C)
>>> print(qmctoolscl.gdn_get_linear_scramble_matrix.__doc__)
Return a scrambling matrix for linear matrix scrambling

Args:
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications
    d (np.uint64): dimension
    tmax (np.uint64): bits in each integer
    tmax_new (np.uint64): bits in each integer of the generating matrix after scrambling
    r_b (np.uint64): replications of bases 
    bases (np.ndarray of np.uint64): bases of size r_b*d
>>> S = qmctoolscl.gdn_get_linear_scramble_matrix(rng,r,d,tmax,tmax_new,r_b,bases)
>>> S
array([[[[1, 0, 0, 0, 0],
         [1, 1, 0, 0, 0],
         [0, 0, 1, 0, 0],
         [0, 0, 0, 1, 0],
         [1, 0, 0, 0, 1],
         [1, 0, 0, 0, 0],
         [0, 1, 0, 0, 0],
         [1, 0, 0, 1, 1],
         [1, 0, 1, 0, 1],
         [1, 1, 0, 1, 0]],

        [[2, 0, 0, 0, 0],
         [0, 1, 0, 0, 0],
         [1, 0, 1, 0, 0],
         [2, 0, 1, 2, 0],
         [2, 1, 1, 0, 1],
         [2, 0, 2, 2, 0],
         [2, 2, 1, 0, 2],
         [2, 0, 2, 0, 1],
         [0, 2, 2, 0, 1],
         [2, 0, 0, 1, 0]]],


       [[[3, 0, 0, 0, 0],
         [1, 1, 0, 0, 0],
         [4, 2, 4, 0, 0],
         [4, 2, 1, 2, 0],
         [3, 4, 4, 3, 1],
         [3, 3, 1, 1, 4],
         [2, 2, 0, 4, 2],
         [3, 3, 3, 1, 3],
         [3, 3, 1, 2, 0],
         [4, 3, 1, 1, 0]],

        [[3, 0, 0, 0, 0],
         [6, 1, 0, 0, 0],
         [1, 0, 2, 0, 0],
         [1, 4, 4, 1, 0],
         [4, 4, 1, 2, 1],
         [3, 4, 1, 6, 5],
         [1, 3, 5, 1, 5],
         [2, 0, 4, 3, 1],
         [4, 0, 0, 1, 3],
         [6, 0, 4, 3, 6]]],


       [[[1, 0, 0, 0, 0],
         [1, 1, 0, 0, 0],
         [1, 1, 1, 0, 0],
         [0, 1, 0, 1, 0],
         [0, 0, 0, 1, 1],
         [1, 0, 0, 0, 0],
         [1, 0, 0, 0, 1],
         [1, 1, 0, 0, 0],
         [1, 0, 0, 1, 1],
         [1, 0, 1, 1, 1]],

        [[1, 0, 0, 0, 0],
         [2, 1, 0, 0, 0],
         [2, 1, 2, 0, 0],
         [0, 0, 1, 1, 0],
         [2, 2, 2, 1, 1],
         [1, 1, 2, 2, 0],
         [2, 1, 0, 2, 2],
         [0, 0, 2, 0, 2],
         [2, 1, 0, 2, 0],
         [2, 0, 1, 0, 2]]],


       [[[2, 0, 0, 0, 0],
         [1, 4, 0, 0, 0],
         [1, 3, 3, 0, 0],
         [1, 3, 1, 1, 0],
         [1, 3, 1, 1, 4],
         [0, 2, 3, 3, 0],
         [1, 3, 3, 3, 4],
         [4, 1, 2, 4, 2],
         [1, 4, 0, 1, 3],
         [1, 3, 3, 1, 4]],

        [[3, 0, 0, 0, 0],
         [1, 5, 0, 0, 0],
         [5, 2, 6, 0, 0],
         [1, 4, 4, 4, 0],
         [4, 3, 4, 6, 3],
         [5, 6, 6, 1, 3],
         [1, 1, 5, 2, 3],
         [5, 6, 0, 3, 6],
         [0, 2, 5, 5, 6],
         [1, 1, 1, 2, 0]]]], dtype=uint64)
>>> C_lms = np.empty((r,d,mmax,tmax_new),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_linear_matrix_scramble(r,d,mmax,r_C,r_b,tmax,tmax_new,bases,S,C,C_lms,**kwargs)
>>> C_lms 
array([[[[1, 1, 0, 0, 1, 1, 0, 1, 1, 1],
         [0, 1, 0, 0, 0, 0, 1, 0, 0, 1],
         [0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
         [0, 0, 0, 1, 0, 0, 0, 1, 0, 1],
         [0, 0, 0, 0, 1, 0, 0, 1, 1, 0]],

        [[2, 0, 1, 2, 2, 2, 2, 2, 0, 2],
         [0, 1, 0, 0, 1, 0, 2, 0, 2, 0],
         [0, 0, 1, 1, 1, 2, 1, 2, 2, 0],
         [0, 0, 0, 2, 0, 2, 0, 0, 0, 1],
         [0, 0, 0, 0, 1, 0, 2, 1, 1, 0]]],


       [[[3, 1, 4, 4, 3, 3, 2, 3, 3, 4],
         [0, 1, 2, 2, 4, 3, 2, 3, 3, 3],
         [0, 0, 4, 1, 4, 1, 0, 3, 1, 1],
         [0, 0, 0, 2, 3, 1, 4, 1, 2, 1],
         [0, 0, 0, 0, 1, 4, 2, 3, 0, 0]],

        [[3, 6, 1, 1, 4, 3, 1, 2, 4, 6],
         [0, 1, 0, 4, 4, 4, 3, 0, 0, 0],
         [0, 0, 2, 4, 1, 1, 5, 4, 0, 4],
         [0, 0, 0, 1, 2, 6, 1, 3, 1, 3],
         [0, 0, 0, 0, 1, 5, 5, 1, 3, 6]]],


       [[[1, 1, 1, 0, 0, 1, 1, 1, 1, 1],
         [0, 1, 1, 1, 0, 0, 0, 1, 0, 0],
         [0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
         [0, 0, 0, 1, 1, 0, 0, 0, 1, 1],
         [0, 0, 0, 0, 1, 0, 1, 0, 1, 1]],

        [[1, 2, 2, 0, 2, 1, 2, 0, 2, 2],
         [0, 1, 1, 0, 2, 1, 1, 0, 1, 0],
         [0, 0, 2, 1, 2, 2, 0, 2, 0, 1],
         [0, 0, 0, 1, 1, 2, 2, 0, 2, 0],
         [0, 0, 0, 0, 1, 0, 2, 2, 0, 2]]],


       [[[2, 1, 1, 1, 1, 0, 1, 4, 1, 1],
         [0, 4, 3, 3, 3, 2, 3, 1, 4, 3],
         [0, 0, 3, 1, 1, 3, 3, 2, 0, 3],
         [0, 0, 0, 1, 1, 3, 3, 4, 1, 1],
         [0, 0, 0, 0, 4, 0, 4, 2, 3, 4]],

        [[3, 1, 5, 1, 4, 5, 1, 5, 0, 1],
         [0, 5, 2, 4, 3, 6, 1, 6, 2, 1],
         [0, 0, 6, 4, 4, 6, 5, 0, 5, 1],
         [0, 0, 0, 4, 6, 1, 2, 3, 5, 2],
         [0, 0, 0, 0, 3, 3, 3, 6, 6, 0]]]], dtype=uint64)

natural order

>>> print(qmctoolscl.gdn_gen_natural.__doc__)
Generalized digital net where the base can be different for each dimension e.g. for the Halton sequence

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_b (np.uint64): number of replications of bases
    mmax (np.uint64): columns in each generating matrix
    tmax (np.uint64): rows of each generating matrix
    n_start (np.uint64): starting index in sequence
    bases (np.ndarray of np.uint64): bases for each dimension of size r_b*d
    C (np.ndarray of np.uint64): generating matrices of size r*d*mmax*tmax
    xdig (np.ndarray of np.uint64): generalized digital net sequence of digits of size r*n*d*tmax
>>> tmax = tmax_new
>>> C = C_lms
>>> n_start = np.uint64(2)
>>> n = np.uint64(6)
>>> xdig = np.empty((r,n,d,tmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_gen_natural(r,n,d,r_b,mmax,tmax,n_start,bases,C,xdig,**kwargs)
>>> xdig
array([[[[0, 1, 0, 0, 0, 0, 1, 0, 0, 1],
         [1, 0, 2, 1, 1, 1, 1, 1, 0, 1]],

        [[1, 0, 0, 0, 1, 1, 1, 1, 1, 0],
         [0, 1, 0, 0, 1, 0, 2, 0, 2, 0]],

        [[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
         [2, 1, 1, 2, 0, 2, 1, 2, 2, 2]],

        [[1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
         [1, 1, 2, 1, 2, 1, 0, 1, 2, 1]],

        [[0, 1, 1, 0, 0, 0, 1, 0, 1, 1],
         [0, 2, 0, 0, 2, 0, 1, 0, 1, 0]],

        [[1, 0, 1, 0, 1, 1, 1, 1, 0, 0],
         [2, 2, 1, 2, 1, 2, 0, 2, 1, 2]]],


       [[[1, 2, 3, 3, 1, 1, 4, 1, 1, 3],
         [6, 5, 2, 2, 1, 6, 2, 4, 1, 5]],

        [[4, 3, 2, 2, 4, 4, 1, 4, 4, 2],
         [2, 4, 3, 3, 5, 2, 3, 6, 5, 4]],

        [[2, 4, 1, 1, 2, 2, 3, 2, 2, 1],
         [5, 3, 4, 4, 2, 5, 4, 1, 2, 3]],

        [[0, 1, 2, 2, 4, 3, 2, 3, 3, 3],
         [1, 2, 5, 5, 6, 1, 5, 3, 6, 2]],

        [[3, 2, 1, 1, 2, 1, 4, 1, 1, 2],
         [4, 1, 6, 6, 3, 4, 6, 5, 3, 1]],

        [[1, 3, 0, 0, 0, 4, 1, 4, 4, 1],
         [0, 1, 0, 4, 4, 4, 3, 0, 0, 0]]],


       [[[0, 1, 1, 1, 0, 0, 0, 1, 0, 0],
         [2, 1, 1, 0, 1, 2, 1, 0, 1, 1]],

        [[1, 0, 0, 1, 0, 1, 1, 0, 1, 1],
         [0, 1, 1, 0, 2, 1, 1, 0, 1, 0]],

        [[0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
         [1, 0, 0, 0, 1, 2, 0, 0, 0, 2]],

        [[1, 1, 0, 0, 0, 1, 1, 1, 1, 0],
         [2, 2, 2, 0, 0, 0, 2, 0, 2, 1]],

        [[0, 1, 0, 1, 0, 0, 0, 1, 0, 1],
         [0, 2, 2, 0, 1, 2, 2, 0, 2, 0]],

        [[1, 0, 1, 1, 0, 1, 1, 0, 1, 0],
         [1, 1, 1, 0, 0, 0, 1, 0, 1, 2]]],


       [[[4, 2, 2, 2, 2, 0, 2, 3, 2, 2],
         [6, 2, 3, 2, 1, 3, 2, 3, 0, 2]],

        [[1, 3, 3, 3, 3, 0, 3, 2, 3, 3],
         [2, 3, 1, 3, 5, 1, 3, 1, 0, 3]],

        [[3, 4, 4, 4, 4, 0, 4, 1, 4, 4],
         [5, 4, 6, 4, 2, 6, 4, 6, 0, 4]],

        [[0, 4, 3, 3, 3, 2, 3, 1, 4, 3],
         [1, 5, 4, 5, 6, 4, 5, 4, 0, 5]],

        [[2, 0, 4, 4, 4, 2, 4, 0, 0, 4],
         [4, 6, 2, 6, 3, 2, 6, 2, 0, 6]],

        [[4, 1, 0, 0, 0, 2, 0, 4, 1, 0],
         [0, 5, 2, 4, 3, 6, 1, 6, 2, 1]]]], dtype=uint64)

digital shift

>>> print(qmctoolscl.gdn_digital_shift.__doc__)
Digital shift a generalized digital net

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_x (np.uint64): replications of xdig
    r_b (np.uint64): replications of bases
    tmax (np.uint64): rows of each generating matrix
    tmax_new (np.uint64): rows of each new generating matrix
    bases (np.ndarray of np.uint64): bases for each dimension of size r_b*d
    shifts (np.ndarray of np.uint64): digital shifts of size r*d*tmax_new
    xdig (np.ndarray of np.uint64): binary digital net points of size r_x*n*d*tmax
    xdig_new (np.ndarray of np.uint64): float digital net points of size r*n*d*tmax_new
>>> r_x = r
>>> tmax_new = np.uint64(12)
>>> print(qmctoolscl.gdn_get_digital_shifts.__doc__)
Return digital shifts for gdn

Args: 
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications 
    d (np.uint64): dimension 
    tmax_new (np.uint64): number of bits in each shift 
    r_b (np.uint64): replications of bases 
    bases (np.ndarray of np.uint64): bases of size r_b*d
>>> shifts = qmctoolscl.gdn_get_digital_shifts(rng,r,d,tmax_new,r_b,bases)
>>> shifts
array([[[0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1],
        [1, 2, 0, 2, 2, 0, 0, 0, 1, 0, 0, 1]],

       [[4, 0, 0, 1, 2, 0, 0, 1, 0, 3, 2, 1],
        [1, 3, 6, 5, 0, 2, 5, 2, 1, 6, 6, 6]],

       [[1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0],
        [1, 0, 1, 0, 2, 0, 2, 2, 2, 0, 1, 1]],

       [[0, 4, 1, 0, 2, 2, 2, 2, 1, 1, 1, 1],
        [5, 5, 1, 3, 0, 4, 5, 4, 3, 0, 3, 2]]], dtype=uint64)
>>> xdig_new = np.empty((r,n,d,tmax_new),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_digital_shift(r,n,d,r_x,r_b,tmax,tmax_new,bases,shifts,xdig,xdig_new,**kwargs)
>>> xdig_new
array([[[[0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
         [2, 2, 2, 0, 0, 1, 1, 1, 1, 1, 0, 1]],

        [[1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1],
         [1, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 1]],

        [[0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1],
         [0, 0, 1, 1, 2, 2, 1, 2, 0, 2, 0, 1]],

        [[1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
         [2, 0, 2, 0, 1, 1, 0, 1, 0, 1, 0, 1]],

        [[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
         [1, 1, 0, 2, 1, 0, 1, 0, 2, 0, 0, 1]],

        [[1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1],
         [0, 1, 1, 1, 0, 2, 0, 2, 2, 2, 0, 1]]],


       [[[0, 2, 3, 4, 3, 1, 4, 2, 1, 1, 2, 1],
         [0, 1, 1, 0, 1, 1, 0, 6, 2, 4, 6, 6]],

        [[3, 3, 2, 3, 1, 4, 1, 0, 4, 0, 2, 1],
         [3, 0, 2, 1, 5, 4, 1, 1, 6, 3, 6, 6]],

        [[1, 4, 1, 2, 4, 2, 3, 3, 2, 4, 2, 1],
         [6, 6, 3, 2, 2, 0, 2, 3, 3, 2, 6, 6]],

        [[4, 1, 2, 3, 1, 3, 2, 4, 3, 1, 2, 1],
         [2, 5, 4, 3, 6, 3, 3, 5, 0, 1, 6, 6]],

        [[2, 2, 1, 2, 4, 1, 4, 2, 1, 0, 2, 1],
         [5, 4, 5, 4, 3, 6, 4, 0, 4, 0, 6, 6]],

        [[0, 3, 0, 1, 2, 4, 1, 0, 4, 4, 2, 1],
         [1, 4, 6, 2, 4, 6, 1, 2, 1, 6, 6, 6]]],


       [[[1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0],
         [0, 1, 2, 0, 0, 2, 0, 2, 0, 1, 1, 1]],

        [[0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0],
         [1, 1, 2, 0, 1, 1, 0, 2, 0, 0, 1, 1]],

        [[1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0],
         [2, 0, 1, 0, 0, 2, 2, 2, 2, 2, 1, 1]],

        [[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0],
         [0, 2, 0, 0, 2, 0, 1, 2, 1, 1, 1, 1]],

        [[1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0],
         [1, 2, 0, 0, 0, 2, 1, 2, 1, 0, 1, 1]],

        [[0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0],
         [2, 1, 2, 0, 2, 0, 0, 2, 0, 2, 1, 1]]],


       [[[4, 1, 3, 2, 4, 2, 4, 0, 3, 3, 1, 1],
         [4, 0, 4, 5, 1, 0, 0, 0, 3, 2, 3, 2]],

        [[1, 2, 4, 3, 0, 2, 0, 4, 4, 4, 1, 1],
         [0, 1, 2, 6, 5, 5, 1, 5, 3, 3, 3, 2]],

        [[3, 3, 0, 4, 1, 2, 1, 3, 0, 0, 1, 1],
         [3, 2, 0, 0, 2, 3, 2, 3, 3, 4, 3, 2]],

        [[0, 3, 4, 3, 0, 4, 0, 3, 0, 4, 1, 1],
         [6, 3, 5, 1, 6, 1, 3, 1, 3, 5, 3, 2]],

        [[2, 4, 0, 4, 1, 4, 1, 2, 1, 0, 1, 1],
         [2, 4, 3, 2, 3, 6, 4, 6, 3, 6, 3, 2]],

        [[4, 0, 1, 0, 2, 4, 2, 1, 2, 1, 1, 1],
         [5, 3, 3, 0, 3, 3, 6, 3, 5, 1, 3, 2]]]], dtype=uint64)

digital permutation

>>> print(qmctoolscl.gdn_digital_permutation.__doc__)
Permutation of digits for a generalized digital net

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_x (np.uint64): replications of xdig
    r_b (np.uint64): replications of bases
    tmax (np.uint64): rows of each generating matrix
    tmax_new (np.uint64): rows of each new generating matrix
    bmax (np.uint64): common permutation size, typically the maximum basis
    perms (np.ndarray of np.uint64): permutations of size r*d*tmax_new*bmax
    xdig (np.ndarray of np.uint64): binary digital net points of size r_x*n*d*tmax
    xdig_new (np.ndarray of np.uint64): float digital net points of size r*n*d*tmax_new
>>> print(qmctoolscl.gdn_get_digital_permutations.__doc__)
Return permutations for gdn

Args: 
    rng (np.random._generator.Generator): random number generator
    r (np.uint64): replications 
    d (np.uint64): dimension 
    tmax_new (np.uint64): number of bits in each shift 
    r_b (np.uint64): replications of bases 
    bases (np.ndarray of np.uint64): bases of size r_b*d
>>> perms = qmctoolscl.gdn_get_digital_permutations(rng,r,d,tmax_new,r_b,bases)
>>> perms
array([[[[1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0]],

        [[0, 1, 2, 0, 0, 0, 0],
         [1, 0, 2, 0, 0, 0, 0],
         [0, 1, 2, 0, 0, 0, 0],
         [2, 1, 0, 0, 0, 0, 0],
         [0, 2, 1, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [1, 0, 2, 0, 0, 0, 0],
         [1, 2, 0, 0, 0, 0, 0],
         [2, 1, 0, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [2, 1, 0, 0, 0, 0, 0]]],


       [[[3, 4, 0, 2, 1, 0, 0],
         [3, 0, 1, 4, 2, 0, 0],
         [1, 3, 2, 0, 4, 0, 0],
         [1, 0, 3, 4, 2, 0, 0],
         [3, 2, 1, 4, 0, 0, 0],
         [1, 0, 3, 4, 2, 0, 0],
         [1, 3, 0, 4, 2, 0, 0],
         [0, 4, 1, 2, 3, 0, 0],
         [2, 4, 3, 1, 0, 0, 0],
         [2, 1, 4, 0, 3, 0, 0],
         [2, 0, 4, 1, 3, 0, 0],
         [1, 4, 2, 3, 0, 0, 0]],

        [[5, 0, 1, 3, 2, 6, 4],
         [4, 2, 3, 1, 6, 0, 5],
         [6, 2, 0, 5, 1, 4, 3],
         [2, 0, 3, 5, 6, 4, 1],
         [0, 6, 2, 1, 4, 3, 5],
         [3, 2, 5, 4, 6, 0, 1],
         [1, 4, 3, 6, 5, 0, 2],
         [5, 6, 4, 2, 3, 0, 1],
         [3, 0, 1, 4, 2, 5, 6],
         [3, 2, 6, 1, 0, 4, 5],
         [1, 4, 3, 2, 5, 0, 6],
         [6, 0, 4, 1, 5, 3, 2]]],


       [[[1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [0, 1, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0],
         [1, 0, 0, 0, 0, 0, 0]],

        [[1, 2, 0, 0, 0, 0, 0],
         [1, 2, 0, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [1, 0, 2, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [2, 0, 1, 0, 0, 0, 0],
         [0, 1, 2, 0, 0, 0, 0],
         [0, 2, 1, 0, 0, 0, 0],
         [2, 1, 0, 0, 0, 0, 0],
         [1, 2, 0, 0, 0, 0, 0],
         [0, 1, 2, 0, 0, 0, 0],
         [0, 1, 2, 0, 0, 0, 0]]],


       [[[1, 3, 0, 2, 4, 0, 0],
         [4, 3, 2, 1, 0, 0, 0],
         [0, 4, 1, 3, 2, 0, 0],
         [1, 4, 2, 3, 0, 0, 0],
         [1, 2, 0, 4, 3, 0, 0],
         [1, 0, 2, 4, 3, 0, 0],
         [3, 2, 0, 4, 1, 0, 0],
         [2, 4, 0, 1, 3, 0, 0],
         [2, 3, 1, 4, 0, 0, 0],
         [4, 0, 2, 1, 3, 0, 0],
         [0, 1, 3, 4, 2, 0, 0],
         [2, 0, 1, 3, 4, 0, 0]],

        [[1, 0, 4, 5, 6, 2, 3],
         [4, 5, 0, 3, 1, 6, 2],
         [4, 2, 1, 5, 3, 0, 6],
         [3, 1, 4, 5, 6, 0, 2],
         [0, 1, 3, 6, 2, 4, 5],
         [4, 0, 2, 6, 1, 5, 3],
         [0, 6, 4, 2, 1, 3, 5],
         [5, 1, 3, 4, 0, 6, 2],
         [4, 6, 0, 1, 3, 5, 2],
         [3, 2, 6, 5, 0, 4, 1],
         [3, 4, 2, 6, 1, 5, 0],
         [2, 6, 1, 3, 0, 4, 5]]]], dtype=uint64)
>>> bmax = bases.max().astype(np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_digital_permutation(r,n,d,r_x,r_b,tmax,tmax_new,bmax,perms,xdig,xdig_new,**kwargs)
>>> xdig_new
array([[[[1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1],
         [1, 1, 2, 1, 2, 0, 0, 0, 1, 1, 2, 2]],

        [[0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1],
         [0, 0, 0, 2, 2, 2, 1, 1, 0, 2, 2, 2]],

        [[1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1],
         [2, 0, 1, 0, 0, 1, 0, 2, 0, 0, 2, 2]],

        [[0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1],
         [1, 0, 2, 1, 1, 0, 2, 0, 0, 1, 2, 2]],

        [[1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1],
         [0, 2, 0, 2, 1, 2, 0, 1, 2, 2, 2, 2]],

        [[0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1],
         [2, 2, 1, 0, 2, 1, 2, 2, 2, 0, 2, 2]]],


       [[[4, 1, 0, 4, 2, 0, 2, 4, 4, 0, 2, 1],
         [4, 0, 0, 3, 6, 1, 3, 3, 0, 4, 1, 6]],

        [[1, 4, 2, 3, 0, 2, 3, 3, 0, 4, 2, 1],
         [1, 6, 5, 5, 3, 5, 6, 1, 5, 0, 1, 6]],

        [[0, 2, 3, 0, 1, 3, 4, 1, 3, 1, 2, 1],
         [6, 1, 1, 6, 2, 0, 5, 6, 1, 1, 1, 6]],

        [[3, 0, 2, 3, 0, 4, 0, 2, 1, 0, 2, 1],
         [0, 3, 4, 4, 5, 2, 0, 2, 6, 6, 1, 6]],

        [[2, 1, 3, 0, 1, 0, 2, 4, 4, 4, 2, 1],
         [2, 2, 3, 1, 1, 6, 2, 0, 4, 2, 1, 6]],

        [[4, 4, 1, 1, 3, 2, 3, 3, 0, 1, 2, 1],
         [5, 2, 6, 6, 4, 6, 6, 5, 3, 3, 1, 6]]],


       [[[1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1],
         [0, 2, 0, 1, 0, 1, 1, 0, 1, 2, 0, 0]],

        [[0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1],
         [1, 2, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0]],

        [[1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1],
         [2, 1, 2, 1, 0, 1, 0, 0, 2, 0, 0, 0]],

        [[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1],
         [0, 0, 1, 1, 2, 2, 2, 0, 0, 2, 0, 0]],

        [[1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1],
         [1, 0, 1, 1, 0, 1, 2, 0, 0, 1, 0, 0]],

        [[0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1],
         [2, 2, 0, 1, 2, 2, 1, 0, 1, 0, 0, 0]]],


       [[[4, 2, 1, 2, 0, 1, 0, 1, 1, 2, 0, 2],
         [3, 0, 5, 4, 1, 6, 4, 4, 4, 6, 3, 2]],

        [[3, 1, 3, 3, 4, 1, 4, 0, 4, 1, 0, 2],
         [4, 3, 2, 5, 4, 0, 2, 1, 4, 5, 3, 2]],

        [[2, 0, 2, 0, 3, 1, 1, 4, 0, 3, 0, 2],
         [2, 1, 6, 6, 3, 3, 1, 2, 4, 0, 3, 2]],

        [[1, 0, 3, 3, 4, 2, 4, 4, 0, 1, 0, 2],
         [0, 6, 3, 0, 5, 1, 3, 0, 4, 4, 3, 2]],

        [[0, 4, 2, 0, 3, 2, 1, 2, 2, 3, 0, 2],
         [6, 2, 1, 2, 6, 2, 5, 3, 4, 1, 3, 2]],

        [[4, 3, 0, 1, 1, 2, 3, 3, 3, 4, 0, 2],
         [1, 6, 1, 6, 6, 3, 6, 2, 0, 2, 3, 2]]]], dtype=uint64)

convert digits to doubles

>>> print(qmctoolscl.gdn_integer_to_float.__doc__)
Convert digits of generalized digital net to floats

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    r_b (np.uint64): replications of bases 
    tmax (np.uint64): rows of each generating matrix
    bases (np.ndarray of np.uint64): bases for each dimension of size r_b*d
    xdig (np.ndarray of np.uint64): binary digital net points of size r*n*d*tmax
    x (np.ndarray of np.double): float digital net points of size r*n*d
>>> x = np.empty((r,n,d),dtype=np.float64)
>>> time_perf,time_process = qmctoolscl.gdn_integer_to_float(r,n,d,r_b,tmax_new,bases,xdig_new,x,**kwargs)
>>> x
array([[[0.97290039, 0.53917744],
        [0.20629883, 0.03632388],
        [0.60864258, 0.70539533],
        [0.33618164, 0.4248148 ],
        [0.84985352, 0.25407524],
        [0.0793457 , 0.93686411]],

       [[0.84707793, 0.57304772],
        [0.38097453, 0.28219443],
        [0.10456744, 0.88309157],
        [0.62106168, 0.07486728],
        [0.46435834, 0.33580649],
        [0.97073423, 0.77539095]],

       [[0.88549805, 0.23648157],
        [0.02124023, 0.57254145],
        [0.70288086, 0.86567088],
        [0.32885742, 0.06130502],
        [0.76147461, 0.38501922],
        [0.14526367, 0.91271656]],

       [[0.89126728, 0.44493083],
        [0.67019736, 0.64080715],
        [0.41704736, 0.32631978],
        [0.23026955, 0.13150509],
        [0.17710726, 0.90208831],
        [0.92209603, 0.27111067]]])

nested uniform scramble

>>> r = np.uint64(1)
>>> n_start = np.uint64(0) 
>>> n = np.uint64(9)
>>> bases = np.array([[2,3,5]],dtype=np.uint64)
>>> r_b = np.uint64(bases.shape[0])
>>> d = np.uint64(bases.shape[1])
>>> mmax = np.uint64(3)
>>> tmax = mmax
>>> C = np.tile(np.eye(mmax,dtype=np.uint64)[None,None,:,:],(int(r),int(d),1,1))
>>> xdig = np.empty((r,n,d,tmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_gen_natural(r,n,d,r_b,mmax,tmax,n_start,bases,C,xdig)
>>> r_x = r 
>>> r = np.uint64(2) 
>>> tmax_new = np.uint64(8)
>>> base_seed_seq = np.random.SeedSequence(7)
>>> seeds = base_seed_seq.spawn(r*d)
>>> rngs = np.array([np.random.Generator(np.random.SFC64(seed)) for seed in seeds]).reshape(r,d)
>>> root_nodes = np.array([qmctoolscl.NUSNode_gdn() for i in range(r*d)]).reshape(r,d)
>>> xrdig = np.zeros((r,n,d,tmax_new),dtype=np.uint64)
>>> print(qmctoolscl.gdn_nested_uniform_scramble.__doc__)
Nested uniform scramble of general digital nets

Args: 
    r (np.uint64): replications 
    n (np.uint64): points
    d (np.uint64): dimensions
    r_x (np.uint64): replications of xb
    r_b (np.uint64): replications of bases
    tmax (np.uint64): maximum number digits in each point representation
    tmax_new (np.uint64): maximum number digits in each point representation after scrambling
    rngs (np.ndarray of numpy.random._generator.Generator): random number generators of size r*d
    root_nodes (np.ndarray of NUSNode_gdn): root nodes of size r*d
    bases (np.ndarray of np.uint64): array of bases of size r*d
    xdig (np.ndarray of np.uint64): array of unrandomized points of size r*n*d*tmax
    xrdig (np.ndarray of np.uint64): array to store scrambled points of size r*n*d*tmax_new
>>> time_perf,time_process = qmctoolscl.gdn_nested_uniform_scramble(r,n,d,r_x,r_b,tmax,tmax_new,rngs,root_nodes,bases,xdig,xrdig)
>>> xrdig 
array([[[[0, 0, 1, 0, 0, 1, 0, 0],
         [2, 2, 2, 2, 1, 0, 2, 1],
         [3, 4, 2, 0, 3, 4, 1, 0]],

        [[1, 0, 1, 0, 1, 0, 1, 1],
         [1, 0, 1, 0, 1, 0, 1, 2],
         [1, 0, 3, 3, 3, 0, 1, 2]],

        [[0, 1, 1, 1, 1, 0, 0, 0],
         [0, 2, 2, 0, 0, 2, 0, 2],
         [4, 1, 2, 1, 4, 3, 0, 0]],

        [[1, 1, 0, 0, 0, 1, 0, 1],
         [2, 1, 1, 1, 0, 2, 2, 2],
         [2, 3, 2, 4, 2, 2, 2, 0]],

        [[0, 0, 0, 0, 0, 1, 0, 0],
         [1, 2, 2, 0, 1, 1, 2, 0],
         [0, 1, 0, 4, 3, 1, 3, 0]],

        [[1, 0, 0, 0, 1, 1, 1, 1],
         [0, 0, 2, 0, 0, 1, 0, 2],
         [3, 2, 1, 4, 2, 1, 4, 1]],

        [[0, 1, 0, 0, 0, 0, 0, 0],
         [2, 0, 1, 1, 1, 0, 1, 2],
         [1, 1, 4, 4, 4, 0, 1, 1]],

        [[1, 1, 1, 1, 1, 0, 1, 1],
         [1, 1, 1, 2, 0, 0, 0, 0],
         [4, 4, 1, 4, 1, 2, 3, 0]],

        [[1, 0, 1, 0, 0, 0, 0, 0],
         [0, 1, 2, 0, 2, 1, 0, 1],
         [2, 1, 0, 0, 1, 2, 3, 0]]],


       [[[0, 0, 0, 0, 1, 1, 0, 0],
         [0, 1, 1, 1, 0, 0, 2, 1],
         [2, 4, 0, 0, 1, 1, 0, 0]],

        [[1, 0, 1, 0, 1, 0, 0, 1],
         [1, 0, 1, 2, 0, 0, 0, 1],
         [1, 1, 1, 1, 3, 0, 1, 0]],

        [[0, 1, 0, 1, 0, 1, 0, 1],
         [2, 0, 1, 0, 0, 2, 1, 0],
         [0, 0, 2, 2, 0, 0, 4, 0]],

        [[1, 1, 0, 0, 1, 1, 1, 0],
         [0, 2, 1, 1, 2, 1, 2, 2],
         [4, 1, 4, 3, 4, 0, 1, 3]],

        [[0, 0, 1, 1, 0, 1, 0, 0],
         [1, 2, 2, 2, 1, 1, 0, 1],
         [3, 1, 0, 4, 2, 0, 1, 2]],

        [[1, 0, 0, 1, 1, 0, 0, 0],
         [2, 1, 0, 2, 0, 1, 1, 1],
         [2, 3, 2, 0, 4, 3, 2, 1]],

        [[0, 1, 1, 0, 1, 0, 1, 1],
         [0, 0, 0, 1, 2, 1, 1, 0],
         [1, 3, 3, 4, 4, 4, 0, 4]],

        [[1, 1, 1, 1, 0, 1, 0, 0],
         [1, 1, 1, 1, 0, 1, 0, 0],
         [0, 2, 1, 4, 3, 3, 2, 0]],

        [[1, 0, 1, 0, 0, 0, 0, 0],
         [2, 2, 2, 2, 2, 2, 2, 1],
         [4, 3, 0, 0, 4, 2, 0, 4]]]], dtype=uint64)
>>> xr = np.empty((r,n,d),dtype=np.float64) 
>>> time_perf,time_process = qmctoolscl.gdn_integer_to_float(r,n,d,r_b,tmax_new,bases,xrdig,xr)
>>> xr
array([[[0.140625  , 0.99283646, 0.7772288 ],
        [0.66796875, 0.37524768, 0.22977792],
        [0.46875   , 0.29934461, 0.859072  ],
        [0.76953125, 0.8311233 , 0.5431936 ],
        [0.015625  , 0.63603109, 0.0474624 ],
        [0.55859375, 0.07575065, 0.69515776],
        [0.25      , 0.72092669, 0.27969536],
        [0.98046875, 0.50617284, 0.9748864 ],
        [0.625     , 0.1949398 , 0.4404864 ]],

       [[0.046875  , 0.16156074, 0.560384  ],
        [0.66015625, 0.39521414, 0.2505728 ],
        [0.33203125, 0.70690444, 0.0192512 ],
        [0.8046875 , 0.28242646, 0.87810048],
        [0.203125  , 0.65996037, 0.64705792],
        [0.59375   , 0.80445054, 0.53750016],
        [0.41796875, 0.02240512, 0.35194624],
        [0.953125  , 0.4951989 , 0.0955776 ],
        [0.625     , 0.99969517, 0.92141824]]])

digital interlacing

>>> print(qmctoolscl.gdn_interlace.__doc__)
Interlace generating matrices or transpose of point sets to attain higher order digital nets

Args:
    r (np.uint64): replications
    d_alpha (np.uint64): dimension of resulting generating matrices 
    mmax (np.uint64): columns of generating matrices
    d (np.uint64): dimension of original generating matrices
    tmax (np.uint64): rows of original generating matrices
    tmax_alpha (np.uint64): rows of interlaced generating matrices
    alpha (np.uint64): interlacing factor
    C (np.ndarray of np.uint64): original generating matrices of size r*d*mmax*tmax
    C_alpha (np.ndarray of np.uint64): resulting interlaced generating matrices of size r*d_alpha*mmax*tmax_alpha
>>> r = np.uint64(2)
>>> d = np.uint64(6)
>>> alpha = np.uint64(3)
>>> d_alpha = np.uint64(d//alpha)
>>> mmax = np.uint64(3)
>>> tmax = np.uint64(4)
>>> tmax_alpha = np.uint64(alpha*tmax)
>>> C = rng.integers(0,5,(r,d,mmax,tmax),dtype=np.uint64)
>>> C
array([[[[0, 4, 4, 0],
         [0, 2, 4, 0],
         [1, 2, 4, 4]],

        [[2, 4, 3, 4],
         [4, 0, 1, 2],
         [0, 2, 0, 0]],

        [[1, 1, 3, 1],
         [3, 3, 1, 1],
         [1, 3, 2, 0]],

        [[1, 1, 4, 2],
         [4, 2, 4, 4],
         [1, 0, 3, 1]],

        [[1, 4, 3, 0],
         [2, 4, 4, 4],
         [1, 4, 2, 4]],

        [[0, 2, 3, 1],
         [3, 1, 1, 0],
         [3, 0, 1, 0]]],


       [[[4, 4, 3, 2],
         [3, 3, 3, 0],
         [4, 3, 3, 3]],

        [[2, 3, 2, 0],
         [0, 4, 0, 2],
         [2, 2, 1, 0]],

        [[3, 0, 1, 4],
         [4, 1, 3, 3],
         [0, 3, 1, 0]],

        [[2, 0, 3, 1],
         [4, 1, 3, 1],
         [3, 1, 0, 4]],

        [[2, 0, 2, 0],
         [4, 0, 1, 3],
         [0, 1, 1, 1]],

        [[4, 4, 0, 1],
         [2, 3, 0, 3],
         [0, 1, 4, 0]]]], dtype=uint64)
>>> C_alpha = np.empty((r,d_alpha,mmax,tmax_alpha),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_interlace(r,d_alpha,mmax,d,tmax,tmax_alpha,alpha,C,C_alpha)
>>> C_alpha
array([[[[0, 2, 1, 4, 4, 1, 4, 3, 3, 0, 4, 1],
         [0, 4, 3, 2, 0, 3, 4, 1, 1, 0, 2, 1],
         [1, 0, 1, 2, 2, 3, 4, 0, 2, 4, 0, 0]],

        [[1, 1, 0, 1, 4, 2, 4, 3, 3, 2, 0, 1],
         [4, 2, 3, 2, 4, 1, 4, 4, 1, 4, 4, 0],
         [1, 1, 3, 0, 4, 0, 3, 2, 1, 1, 4, 0]]],


       [[[4, 2, 3, 4, 3, 0, 3, 2, 1, 2, 0, 4],
         [3, 0, 4, 3, 4, 1, 3, 0, 3, 0, 2, 3],
         [4, 2, 0, 3, 2, 3, 3, 1, 1, 3, 0, 0]],

        [[2, 2, 4, 0, 0, 4, 3, 2, 0, 1, 0, 1],
         [4, 4, 2, 1, 0, 3, 3, 1, 0, 1, 3, 3],
         [3, 0, 0, 1, 1, 1, 0, 1, 4, 4, 1, 0]]]], dtype=uint64)

undo digital interlacing

>>> print(qmctoolscl.gdn_undo_interlace.__doc__)
Undo interlacing of generating matrices

Args:
    r (np.uint64): replications
    d (np.uint64): dimension of resulting generating matrices 
    mmax (np.uint64): columns in generating matrices
    d_alpha (np.uint64): dimension of interlaced generating matrices
    tmax (np.uint64): rows of original generating matrices
    tmax_alpha (np.uint64): rows of interlaced generating matrices
    alpha (np.uint64): interlacing factor
    C_alpha (np.ndarray of np.uint64): interlaced generating matrices of size r*d_alpha*mmax*tmax_alpha
    C (np.ndarray of np.uint64): original generating matrices of size r*d*mmax*tmax
>>> C_cp = np.empty((r,d,mmax,tmax),dtype=np.uint64)
>>> time_perf,time_process = qmctoolscl.gdn_undo_interlace(r,d,mmax,d_alpha,tmax,tmax_alpha,alpha,C_alpha,C_cp) 
>>> print((C==C_cp).all())
True

natural order with the same base

>>> print(qmctoolscl.gdn_gen_natural_same_base.__doc__)
Generalized digital net with the same base for each dimension e.g. a digital net in base greater than 2

Args:
    r (np.uint64): replications
    n (np.uint64): points
    d (np.uint64): dimension
    mmax (np.uint64): columns in each generating matrix
    tmax (np.uint64): rows of each generating matrix
    n_start (np.uint64): starting index in sequence
    b (np.uint64): common base
    C (np.ndarray of np.uint64): generating matrices of size r*d*mmax*tmax
    xdig (np.ndarray of np.uint64): generalized digital net sequence of digits of size r*n*d*tmax
>>> n_start = np.uint64(0)
>>> n = np.uint64(9)
>>> b = np.uint64(3) 
>>> C = np.array([[
...     [
...         [1,1,0,0],
...         [1,1,1,1],
...     ],
...     [
...         [2,1,0,0],
...         [2,1,2,1],
...     ]
...     ]],
...     dtype=np.uint64)
>>> r = np.uint64(C.shape[0])
>>> d = np.uint64(C.shape[1])
>>> mmax = np.uint64(C.shape[2])
>>> tmax = np.uint64(C.shape[3])
>>> xdig = np.empty((r,n,d,tmax),dtype=np.uint64) 
>>> time_perf,time_process = qmctoolscl.gdn_gen_natural_same_base(r,n,d,mmax,tmax,n_start,b,C,xdig)
>>> xdig
array([[[[0, 0, 0, 0],
         [0, 0, 0, 0]],

        [[1, 1, 0, 0],
         [2, 1, 0, 0]],

        [[2, 2, 0, 0],
         [1, 2, 0, 0]],

        [[1, 1, 1, 1],
         [2, 1, 2, 1]],

        [[2, 2, 1, 1],
         [1, 2, 2, 1]],

        [[0, 0, 1, 1],
         [0, 0, 2, 1]],

        [[2, 2, 2, 2],
         [1, 2, 1, 2]],

        [[0, 0, 2, 2],
         [0, 0, 1, 2]],

        [[1, 1, 2, 2],
         [2, 1, 1, 2]]]], dtype=uint64)

Fast Transforms

Fast transforms require the use of a single work group for the final dimension i.e. it is required that global_size[2]==local_size[2]

>>> if kwargs["backend"]=="CL":
...     kwargs_ft = kwargs.copy()
...     kwargs_ft["global_size"] = (2,2,2)
...     kwargs_ft["local_size"] = (1,1,2)
... else: ## kwargs["backend"]=="C"
...     kwargs_ft = kwargs 

(inverse) fast Fourier transform

>>> print(qmctoolscl.fft_bro_1d_radix2.__doc__)
Fast Fourier Transform for inputs in bit reversed order.
FFT is done in place along the last dimension where the size is required to be a power of 2.
Follows a decimation-in-time procedure described in https://www.cmlab.csie.ntu.edu.tw/cml/dsp/training/coding/transform/fft.html.

Args:
    d1 (np.uint64): first dimension
    d2 (np.uint64): second dimension
    n_half (np.uint64): half of the last dimension of size n = 2n_half along which FFT is performed
    twiddler (np.ndarray of np.double): size n vector used to store real twiddle factors
    twiddlei (np.ndarray of np.double): size n vector used to store imaginary twiddle factors 
    xr (np.ndarray of np.double): real array of size d1*d2*n on which to perform FFT in place
    xi (np.ndarray of np.double): imaginary array of size d1*d2*n on which to perform FFT in place
>>> print(qmctoolscl.ifft_bro_1d_radix2.__doc__)
Inverse Fast Fourier Transform with outputs in bit reversed order.
FFT is done in place along the last dimension where the size is required to be a power of 2.
Follows a procedure described in https://www.expertsmind.com/learning/inverse-dft-using-the-fft-algorithm-assignment-help-7342873886.aspx.

Args:
    d1 (np.uint64): first dimension
    d2 (np.uint64): second dimension
    n_half (np.uint64): half of the last dimension of size n = 2n_half along which FFT is performed
    twiddler (np.ndarray of np.double): size n vector used to store real twiddle factors
    twiddlei (np.ndarray of np.double): size n vector used to store imaginary twiddle factors 
    xr (np.ndarray of np.double): real array of size d1*d2*n on which to perform FFT in place
    xi (np.ndarray of np.double): imaginary array of size d1*d2*n on which to perform FFT in place
>>> d1 = np.uint64(1) 
>>> d2 = np.uint64(1)
>>> xr = np.array([1,0,1,0,0,1,1,0],dtype=np.double)
>>> xi = np.array([0,1,0,1,1,0,0,1],dtype=np.double)
>>> xr_og = xr.copy()
>>> xi_og = xi.copy()
>>> twiddler = np.empty_like(xr,dtype=np.double)
>>> twiddlei = np.empty_like(xr,dtype=np.double)
>>> n_half = np.uint64(len(xr)//2)
>>> time_perf,time_process = qmctoolscl.fft_bro_1d_radix2(d1,d2,n_half,twiddler,twiddlei,xr,xi,**kwargs_ft)
>>> xr
array([ 1.41421356, -0.5       ,  0.        ,  1.20710678,  0.        ,
        0.5       ,  0.        ,  0.20710678])
>>> xi
array([ 1.41421356, -0.20710678,  0.        , -0.5       ,  0.        ,
       -1.20710678,  0.        ,  0.5       ])
>>> time_perf,time_process = qmctoolscl.ifft_bro_1d_radix2(d1,d2,n_half,twiddler,twiddlei,xr,xi,**kwargs_ft)
>>> np.allclose(xr,xr_og,atol=1e-8)
True
>>> np.allclose(xi,xi_og,atol=1e-8)
True
>>> d1 = np.uint(5) 
>>> d2 = np.uint(7) 
>>> m = 8
>>> n = 2**m
>>> n_half = np.uint(n//2)
>>> bitrev = np.vectorize(lambda i,m: int('{:0{m}b}'.format(i,m=m)[::-1],2))
>>> ir = bitrev(np.arange(n),m) 
>>> xr = rng.uniform(0,1,(d1,d2,int(2*n_half))).astype(np.double)
>>> xi = rng.uniform(0,1,(d1,d2,int(2*n_half))).astype(np.double)
>>> twiddler = np.empty_like(xr,dtype=np.double)
>>> twiddlei = np.empty_like(xr,dtype=np.double)
>>> x_np = xr+1j*xi
>>> x_bro_np = x_np[:,:,ir]
>>> y = xr+1j*xi
>>> yt_np = np.fft.fft(x_bro_np,norm="ortho")
>>> time_perf,time_process = qmctoolscl.fft_bro_1d_radix2(d1,d2,n_half,twiddler,twiddlei,xr,xi,**kwargs_ft)
>>> yt = xr+1j*xi
>>> np.allclose(yt,yt_np,atol=1e-8)
True
>>> time_perf,time_process = qmctoolscl.ifft_bro_1d_radix2(d1,d2,n_half,twiddler,twiddlei,xr,xi,**kwargs_ft)
>>> np.allclose(xr+1j*xi,y,atol=1e-8)
True
>>> fft_np = lambda x: np.fft.fft(x,norm="ortho")
>>> ifft_np = lambda x: np.fft.ifft(x,norm="ortho")
>>> def fft(x):
...     assert x.ndim==1
...     n = len(x)
...     n_half = np.uint64(n//2)
...     xr = x.real.copy()
...     xi = x.imag.copy()
...     qmctoolscl.fft_bro_1d_radix2(1,1,n_half,np.empty(n,dtype=np.double),np.empty(n,dtype=np.double),xr,xi)
...     return xr+1j*xi
>>> def ifft(x):
...     assert x.ndim==1
...     n = len(x)
...     n_half = np.uint64(n//2)
...     xr = x.real.copy()
...     xi = x.imag.copy()
...     qmctoolscl.ifft_bro_1d_radix2(1,1,n_half,np.empty(n,dtype=np.double),np.empty(n,dtype=np.double),xr,xi)
...     return xr+1j*xi
>>> # parameters 
>>> m = 10
>>> n = 2**m
>>> # bit reverse used for reference solver 
>>> bitrev = np.vectorize(lambda i,m: int('{:0{m}b}'.format(i,m=m)[::-1],2))
>>> ir = bitrev(np.arange(2*n),m+1)
>>> # points 
>>> y1 = np.random.rand(n)+1j*np.random.rand(n)
>>> y2 = np.random.rand(n)+1j*np.random.rand(n)
>>> y = np.hstack([y1,y2])
>>> # kernel evaluations
>>> k1 = np.random.rand(n) 
>>> k2 = np.random.rand(n) 
>>> k = np.hstack([k1,k2]) 
>>> # fast transforms 
>>> yt = fft(y) 
>>> kt = fft(k)
>>> y1t = fft(y1) 
>>> y2t = fft(y2) 
>>> k1t = fft(k1) 
>>> k2t = fft(k2)
>>> wt = np.exp(-np.pi*1j*np.arange(n)/n)
>>> wtsq = wt**2
>>> gammat = k1t**2-wtsq*k2t**2
>>> # matrix vector product
>>> u = ifft(yt*kt)*np.sqrt(2*n)
>>> u_np = ifft_np(fft_np(y[ir])*fft_np(k[ir]))[ir]*np.sqrt(2*n)
>>> np.allclose(u,u_np,atol=1e-10)
True
>>> u_hat = np.hstack([ifft(y1t*k1t+wtsq*y2t*k2t),ifft(y2t*k1t+y1t*k2t)])*np.sqrt(n)
>>> np.allclose(u_hat,u,atol=1e-10)
True
>>> # inverse 
>>> v = ifft(yt/kt)/np.sqrt(2*n)
>>> v_np = ifft_np(fft_np(y[ir])/fft_np(k[ir]))[ir]/np.sqrt(2*n)
>>> np.allclose(v,v_np,atol=1e-10)
True
>>> v_hat = np.hstack([ifft((y1t*k1t-wtsq*y2t*k2t)/gammat),ifft((y2t*k1t-y1t*k2t)/gammat)])/np.sqrt(n)
>>> np.allclose(v,v_hat,atol=1e-10)
True

fast Walsh-Hadamard transform

>>> print(qmctoolscl.fwht_1d_radix2.__doc__)
Fast Walsh-Hadamard Transform for real valued inputs.
FWHT is done in place along the last dimension where the size is required to be a power of 2.
Follows the divide-and-conquer algorithm described in https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform

Args:
    d1 (np.uint64): first dimension
    d2 (np.uint64): second dimension
    n_half (np.uint64): half of the last dimension along which FWHT is performed
    x (np.ndarray of np.double): array of size d1*d2*2n_half on which to perform FWHT in place
>>> d1 = np.uint64(1) 
>>> d2 = np.uint64(1)
>>> x = np.array([1,0,1,0,0,1,1,0],dtype=np.double)
>>> n_half = np.uint64(len(x)//2)
>>> time_perf,time_process = qmctoolscl.fwht_1d_radix2(d1,d2,n_half,x,**kwargs_ft)
>>> x
array([ 1.41421356,  0.70710678,  0.        , -0.70710678,  0.        ,
        0.70710678,  0.        ,  0.70710678])
>>> d1 = np.uint(5) 
>>> d2 = np.uint(7)
>>> n = 2**8
>>> n_half = np.uint(n//2) 
>>> x = rng.uniform(0,1,(d1,d2,int(2*n_half))).astype(np.double)
>>> x_og = x.copy()
>>> import sympy
>>> y_sympy = np.empty_like(x,dtype=np.double) 
>>> for i in range(d1):
...     for j in range(d2): 
...         y_sympy[i,j] = np.array(sympy.fwht(x[i,j])/np.sqrt(n),dtype=np.double)
>>> time_perf,time_process = qmctoolscl.fwht_1d_radix2(d1,d2,n_half,x,**kwargs_ft)
>>> np.allclose(x,y_sympy,atol=1e-8)
True
>>> time_perf,time_process = qmctoolscl.fwht_1d_radix2(d1,d2,n_half,x,**kwargs_ft)
>>> np.allclose(x,x_og,atol=1e-8)
True
>>> def fwht_sp(x):
...     assert x.ndim==1
...     n = len(x) 
...     y = np.array(sympy.fwht(x),dtype=np.float64)
...     y_ortho = y/np.sqrt(n) 
...     return y_ortho
>>> def fwht(x):
...     assert x.ndim==1
...     n = len(x)
...     n_half = np.uint64(n//2)
...     x_cp = x.copy()
...     qmctoolscl.fwht_1d_radix2(1,1,n_half,x_cp)
...     return x_cp
>>> # parameters
>>> m = 10
>>> n = int(2**m)
>>> # points
>>> y1 = np.random.rand(n) 
>>> y2 = np.random.rand(n) 
>>> y = np.hstack([y1,y2])
>>> # kernel evaluations
>>> k1 = np.random.rand(n) 
>>> k2 = np.random.rand(n) 
>>> k = np.hstack([k1,k2]) 
>>> # fast transforms
>>> yt = fwht(y) 
>>> kt = fwht(k)
>>> y1t = fwht(y1) 
>>> y2t = fwht(y2) 
>>> k1t = fwht(k1) 
>>> k2t = fwht(k2)
>>> gammat = k1t**2-k2t**2
>>> # matrix vector product
>>> u_sp = fwht_sp(fwht_sp(y)*fwht_sp(k))*np.sqrt(2*n)
>>> u = fwht(yt*kt)*np.sqrt(2*n)
>>> np.allclose(u_sp,u,atol=1e-8)
True
>>> u_hat = np.hstack([fwht(y1t*k1t+y2t*k2t),fwht(y2t*k1t+y1t*k2t)])*np.sqrt(n)
>>> np.allclose(u,u_hat,atol=1e-10)
True
>>> # inverse 
>>> v_sp = fwht_sp(fwht_sp(y)/fwht_sp(k))/np.sqrt(2*n)
>>> v = fwht(yt/kt)/np.sqrt(2*n)
>>> np.allclose(v_sp,v,atol=1e-8)
True
>>> v_hat = np.hstack([fwht((y1t*k1t-y2t*k2t)/gammat),fwht((y2t*k1t-y1t*k2t)/gammat)])/np.sqrt(n)
>>> np.allclose(v,v_hat,atol=1e-10)
True