Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
df042_ThreadSafeRNG.py
Go to the documentation of this file.
1# \file
2# \ingroup tutorial_dataframe
3# \notebook -nodraw
4# Usage of multithreading mode with random generators.
5#
6# This example illustrates how to define functions that generate random numbers and use them in an RDataFrame
7# computation graph in a thread-safe way.
8#
9# Using only one random number generator in an application running with ROOT.EnableImplicitMT() is a common pitfall.
10# This pitfall creates race conditions resulting in a distorted random distribution. In the example, this issue is
11# solved by creating one random number generator per RDataFrame processing slot, thus allowing for parallel and
12# thread-safe access. The example also illustrates the difference between non-deterministic and deterministic random
13# number generation.
14#
15# \macro_code
16# \macro_image
17# \macro_output
18#
19# \date February 2026
20# \author Bohdan Dudar (JGU Mainz), Fernando Hueso-González (IFIC, CSIC-UV), Vincenzo Eduardo Padulano (CERN)
21
22import os
23
24import ROOT
25
26
27def df042_ThreadSafeRNG():
28
29 header_path = os.path.join(str(ROOT.gROOT.GetTutorialDir()), "analysis", "dataframe", "df042_ThreadSafeRNG.hxx")
30 if not os.path.exists(header_path):
31 raise RuntimeError(f'Could not find required header file "{header_path}".')
32
33 # First, we declare the functions needed by the RDataFrame computation graph to the interpreter
34 if not ROOT.gInterpreter.Declare(f'#include "{header_path}"'):
35 raise RuntimeError("Failed to declare the functions needed by the RDataFrame computation graph.")
36
37 myCanvas = ROOT.TCanvas("myCanvas", "myCanvas", 1000, 500)
38 myCanvas.Divide(3, 1)
39
40 nEntries = 10000000
41
42 # 1. Single thread for reference
44 h1 = df1.Histo1D(("h1", "Single thread (no MT)", 1000, -4, 4), "x")
46 h1.Draw()
47
48 # 2. One generator per RDataFrame slot, with random_device seeding
49 # Notes and Caveats:
50 # - How many numbers are drawn from each generator is not deterministic
51 # and the result is not deterministic between runs.
52 nSlots = max(2, os.cpu_count() // 4)
54 # Before running the RDataFrame computation graph, we reinitialize the generators (one per slot), so they can
55 # be used accordingly during the execution.
58 h2 = df2.Histo1D(("h2", "Thread-safe (MT, non-deterministic)", 1000, -4, 4), "x")
60 h2.Draw()
61
62 # 3. One generator per RDataFrame slot, with entry seeding
63 # Notes and Caveats:
64 # - With RDataFrame(INTEGER_NUMBER) constructor (as in the example),
65 # the result is deterministic and identical on every run
66 # - With RDataFrame(TTree) constructor, the result is not guaranteed to be deterministic.
67 # To make it deterministic, use something from the dataset to act as the event identifier
68 # instead of rdfentry_, and use it as a seed.
69 # Before running the RDataFrame computation graph, we reinitialize the generators (one per slot), so they can
70 # be used accordingly during the execution.
73 h3 = df3.Histo1D(("h3", "Thread-safe (MT, deterministic)", 1000, -4, 4), "x")
75 h3.Draw()
76
77 print(f"{'{:<40}'.format('Final distributions')}: Mean +- StdDev")
78 print(f"{'{:<40}'.format('Theoretical')}: 0.000 +- 1.000")
79 print(f"{'{:<40}'.format('Single thread (no MT)')}: {h1.GetMean():.3f} +- {h1.GetStdDev():.3f}")
80 print(f"{'{:<40}'.format('Thread-safe (MT, non-deterministic)')}: {h2.GetMean():.3f} +- {h2.GetStdDev():.3f}")
81 print(f"{'{:<40}'.format('Thread-safe (MT, deterministic)')}: {h3.GetMean():.3f} +- {h3.GetStdDev():.3f}")
82
83 # We draw the canvas with block=True to stop the execution before end of the
84 # function and to be able to interact with the canvas until necessary
85 myCanvas.Draw(block=True)
86
87
88if __name__ == "__main__":
89 df042_ThreadSafeRNG()
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
ROOT's RDataFrame offers a modern, high-level interface for analysis of data stored in TTree ,...