Question on usage of TRandom3 in lambda function with RDataFrame Column definition

Dear experts on RDataFrame,
I have a question concerning using Random number generators inside lambda functions for column-definition.

My workflow is the following :

   TRandom3 rnd; //random number generator to align 30% of events passing on MC the HLT1_TOS requirement (AllTrackL0Decision_TOS)
	struct TrackHLT1Info{
		double p;
		double pt;
		bool   hlt1_tos;
		TrackHLT1Info() = default;
		TrackHLT1Info( double _p, double _pt, bool _hlt1_tos){
			p  = _p;
			pt = _pt;
			hlt1_tos = _hlt1_tos;
		};
	};
    //Set of helper Lambda functions to eval Alignment of HLT1AllTrackL0Decision
	auto makeTrackInfo = [](double p, double pt, bool isTOS){ return TrackHLT1Info( p, pt, isTOS); };
    auto evalTOSUpdate = [&rnd]( const TrackHLT1Info & finalStateParticle , const uint & runNumber, const unsigned long long & evtNumber , const bool & Bp_Hlt1TrackAllL0Decision_TOS ){
        if( Bp_Hlt1TrackAllL0Decision_TOS == false){ return false; } //If the global OR is already false, it keeps to be false ( we only tight up the cut, not loosen it)
        if(finalStateParticle.hlt1_tos == false){  return false; } //If the particle TOS is already false, it keeps to be false ( we only tight up the cut, not loosen it)
        //We reach this only if the particle is TOS
        rnd.SetSeed( runNumber * evtNumber);
        bool rndvalue = rnd.Uniform( 0,1); //throw random number between 0,1 uniformely ( the seet set is needed to have not random behaviour)
        bool updatedStatus = finalStateParticle.hlt1_tos;
        if ( rndvalue < 0.3){
            //For 30% of events tight up the P, PT cut [ assuming IPCHI2 is already aligned ! [ there are other cuts done by the HLT1AllTrackL0 [ IP are the same for all TCKs ]]]
            //Track Chi2 and number of hits cut is dropped for alignment since the info available in MC is "post-HLT2" reconstruction and NOT at HLT1 level
            updatedStatus = finalStateParticle.p > 10000.f && finalStateParticle.pt > 1700 && finalStateParticle.hlt1_tos;
        }
        return updatedStatus;
    };
  auto dd = df.Define("E1_HLTTRACK_PT", "TMath::Sqrt(E1_TRACK_PX*E1_TRACK_PX + E1_TRACK_PY*E1_TRACK_PY)")
	                .Define("E2_HLTTRACK_PT", "TMath::Sqrt(E2_TRACK_PX*E2_TRACK_PX + E2_TRACK_PY*E2_TRACK_PY)")
                    .Define("E1_HLTTRACK_P",  "TMath::Sqrt(E1_TRACK_PX*E1_TRACK_PX + E1_TRACK_PY*E1_TRACK_PY + E1_TRACK_PZ*E1_TRACK_PZ)")
                    .Define("E2_HLTTRACK_P",  "TMath::Sqrt(E2_TRACK_PX*E2_TRACK_PX + E2_TRACK_PY*E2_TRACK_PY + E2_TRACK_PZ*E2_TRACK_PZ)")		
                    .Define("E1_TrackHLT1Info", makeTrackInfo, {"E1_HLTTRACK_P", "E1_HLTTRACK_PT", "E1_Hlt1TrackAllL0Decision_TOS"})
                    .Define("E2_TrackHLT1Info", makeTrackInfo, {"E2_HLTTRACK_P", "E2_HLTTRACK_PT", "E2_Hlt1TrackAllL0Decision_TOS"})
                    .Define("K_TrackHLT1Info",  makeTrackInfo, {"K_P", "K_PT", "K_Hlt1TrackAllL0Decision_TOS"})
                    .Define("E1_Hlt1TrackAllL0Decision_TOS_update", evalTOSUpdate , { "E1_TrackHLT1Info", "runNumber", "eventNumber","Bp_Hlt1TrackAllL0Decision_TOS"})
                    .Define("E2_Hlt1TrackAllL0Decision_TOS_update", evalTOSUpdate , { "E2_TrackHLT1Info", "runNumber", "eventNumber","Bp_Hlt1TrackAllL0Decision_TOS"})
                    .Define("K_Hlt1TrackAllL0Decision_TOS_update", evalTOSUpdate ,  { "K_TrackHLT1Info" , "runNumber", "eventNumber","Bp_Hlt1TrackAllL0Decision_TOS"})
                    .Define("Bp_Hlt1TrackAllL0Decision_TOS_update", "E1_Hlt1TrackAllL0Decision_TOS_update || E2_Hlt1TrackAllL0Decision_TOS_update || K_Hlt1TrackAllL0Decision_TOS_update");
        dd.Snapshot(_newTreeName.Data(), _newTFileName.Data() , DropColumns(dd.GetColumnNames(),dd.GetDefinedColumnNames(), blacklist ) );

My question is the following : given the lambda defined capturing the [&rnd], can i assume that “event-by-event” the random number generated will be the same everywhere?
Also, would this code work also MT ? I have disabled implicit MT since i am not 100% that the external capture of the TRandom3 number would work if the lambda function and the Define are called with MT enabled.

Thanks in advance

Renato

Each Define lambda will be called (EDIT: at most) once per event. You can easily verify this is the case (and if it’s not, please open a bug report :smile:).

TRandom3 (or any ROOT or STL random number generator, afaik) is not thread-safe, and if you use it from a lambda inside a Define, it will be used concurrently by different threads. You should define one TRandom3 per processing “slot” (RDF::GetNSlots tells you how many you are using) and then use DefineSlot to invoke a different generator per processing slot (i.e., per thread).

Cheers,
Enrico

Ok, but in principle with
ROOT::DisableImplicitMT()
i should be safe right?

…or just no call at all. Single thread is the default

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.