@@ -56,6 +56,185 @@ TEST(RProfile, Constructor)
5656 EXPECT_EQ (profile.GetNDimensions (), 3 );
5757}
5858
59+ TEST (RProfile, GetFullMultiDimRange)
60+ {
61+ static constexpr std::size_t Bins = 20 ;
62+ RProfile profile (Bins, {0 , Bins});
63+
64+ std::mt19937 gen;
65+ std::uniform_real_distribution<double > distX (0 , Bins);
66+ std::uniform_real_distribution<double > distV (Bins, 2 * Bins);
67+ std::uniform_real_distribution<double > distW (0.8 , 1.2 );
68+ static constexpr std::uint64_t Entries = 1000 ;
69+ for (std::uint64_t i = 0 ; i < Entries; i++) {
70+ profile.Fill (distX (gen), distV (gen), RWeight (distW (gen)));
71+ }
72+ ASSERT_EQ (profile.GetNEntries (), Entries);
73+
74+ auto range = profile.GetFullMultiDimRange ();
75+ EXPECT_EQ (std::distance (range.begin (), range.end ()), Bins + 2 );
76+
77+ double sumW = 0 ;
78+ double sumValues = 0 ;
79+ for (auto &&indices : range) {
80+ auto &bin = profile.GetBinContent (indices);
81+ sumW += bin.fSum ;
82+ sumValues += bin.fSumValues ;
83+ }
84+ // Numerical differences with EXPECT_DOUBLE_EQ
85+ EXPECT_FLOAT_EQ (profile.GetStats ().GetSumW (), sumW);
86+ EXPECT_FLOAT_EQ (profile.GetStats ().GetDimensionStats (1 ).fSumWX , sumValues);
87+ }
88+
89+ TEST (RProfile, SetBinContent)
90+ {
91+ static constexpr std::size_t Bins = 20 ;
92+ const RRegularAxis axis (Bins, {0 , Bins});
93+
94+ RProfile::RProfileBin bin;
95+ bin.fSumValues = 42 ;
96+
97+ {
98+ RProfile profile (axis);
99+ ASSERT_FALSE (profile.GetStats ().IsTainted ());
100+ profile.SetBinContent (RBinIndex (1 ), bin);
101+ EXPECT_EQ (profile.GetBinContent (RBinIndex (1 )).fSumValues , 42 );
102+ EXPECT_TRUE (profile.GetStats ().IsTainted ());
103+ EXPECT_THROW (profile.GetNEntries (), std::logic_error);
104+ }
105+
106+ {
107+ RProfile profile (axis);
108+ ASSERT_FALSE (profile.GetStats ().IsTainted ());
109+ const std::array<RBinIndex, 1 > indices = {2 };
110+ profile.SetBinContent (indices, bin);
111+ EXPECT_EQ (profile.GetBinContent (indices).fSumValues , 42 );
112+ EXPECT_TRUE (profile.GetStats ().IsTainted ());
113+ EXPECT_THROW (profile.GetNEntries (), std::logic_error);
114+ }
115+ }
116+
117+ TEST (RProfile, Add)
118+ {
119+ static constexpr std::size_t Bins = 20 ;
120+ const RRegularAxis axis (Bins, {0 , Bins});
121+ RProfile profileA (axis);
122+ RProfile profileB (axis);
123+
124+ profileA.Fill (8.5 , 23.0 );
125+ profileB.Fill (9.5 , 25.0 );
126+
127+ profileA.Add (profileB);
128+
129+ EXPECT_EQ (profileA.GetNEntries (), 2 );
130+ EXPECT_EQ (profileA.GetBinContent (RBinIndex (8 )).fSumValues , 23.0 );
131+ EXPECT_EQ (profileA.GetBinContent (RBinIndex (9 )).fSumValues , 25.0 );
132+ }
133+
134+ TEST (RProfile, AddAtomic)
135+ {
136+ static constexpr std::size_t Bins = 20 ;
137+ const RRegularAxis axis (Bins, {0 , Bins});
138+ RProfile profileA (axis);
139+ RProfile profileB (axis);
140+
141+ profileA.Fill (8.5 , 23.0 );
142+ profileB.Fill (9.5 , 25.0 );
143+
144+ profileA.AddAtomic (profileB);
145+
146+ EXPECT_EQ (profileA.GetNEntries (), 2 );
147+ EXPECT_EQ (profileA.GetBinContent (RBinIndex (8 )).fSumValues , 23.0 );
148+ EXPECT_EQ (profileA.GetBinContent (RBinIndex (9 )).fSumValues , 25.0 );
149+ }
150+
151+ TEST (RProfile, StressAddAtomic)
152+ {
153+ static constexpr std::size_t NThreads = 4 ;
154+ static constexpr std::size_t NAddsPerThread = 10000 ;
155+ static constexpr std::size_t NAdds = NThreads * NAddsPerThread;
156+
157+ // Fill a single bin, to maximize contention.
158+ const RRegularAxis axis (1 , {0 , 1 });
159+ RProfile profileA (axis);
160+ RProfile profileB (axis);
161+ profileB.Fill (0.5 , 23.0 );
162+
163+ StressInParallel (NThreads, [&] {
164+ for (std::size_t i = 0 ; i < NAddsPerThread; i++) {
165+ profileA.AddAtomic (profileB);
166+ }
167+ });
168+
169+ EXPECT_EQ (profileA.GetNEntries (), NAdds);
170+ EXPECT_EQ (profileA.GetBinContent (0 ).fSumValues , 23.0 * NAdds);
171+ }
172+
173+ TEST (RProfile, AddExceptionSafety)
174+ {
175+ static constexpr std::size_t Bins = 20 ;
176+ const RRegularAxis regularAxis (Bins, {0 , Bins});
177+ const std::vector<std::string> categories = {" a" , " b" , " c" };
178+ const RCategoricalAxis categoricalAxis (categories);
179+
180+ RProfile profileA ({regularAxis, regularAxis});
181+ RProfile profileB ({regularAxis, categoricalAxis});
182+
183+ profileA.Fill (1.5 , 2.5 , 23.0 );
184+ ASSERT_EQ (profileA.GetNEntries (), 1 );
185+ ASSERT_EQ (profileA.GetBinContent (RBinIndex (1 ), RBinIndex (2 )).fSumValues , 23.0 );
186+ profileB.Fill (1.5 , " b" , 25.0 );
187+
188+ EXPECT_THROW (profileA.Add (profileB), std::invalid_argument);
189+ EXPECT_THROW (profileA.AddAtomic (profileB), std::invalid_argument);
190+
191+ // Verify exception safety. Only the original entry should be there.
192+ EXPECT_EQ (profileA.GetNEntries (), 1 );
193+ EXPECT_EQ (profileA.GetBinContent (RBinIndex (1 ), RBinIndex (2 )).fSumValues , 23.0 );
194+ EXPECT_EQ (profileA.GetStats ().GetSumW (), 1 );
195+ EXPECT_EQ (profileA.GetStats ().GetSumW2 (), 1 );
196+ EXPECT_EQ (profileA.GetStats ().GetDimensionStats (0 ).fSumWX , 1.5 );
197+ EXPECT_EQ (profileA.GetStats ().GetDimensionStats (1 ).fSumWX , 2.5 );
198+ }
199+
200+ TEST (RProfile, Clear)
201+ {
202+ static constexpr std::size_t Bins = 20 ;
203+ RProfile profile (Bins, {0 , Bins});
204+
205+ profile.Fill (8.5 , 23.0 );
206+ profile.Fill (9.5 , 25.0 );
207+
208+ profile.Clear ();
209+
210+ EXPECT_EQ (profile.GetNEntries (), 0 );
211+ EXPECT_EQ (profile.GetBinContent (RBinIndex (8 )).fSumValues , 0 );
212+ EXPECT_EQ (profile.GetBinContent (RBinIndex (9 )).fSumValues , 0 );
213+ }
214+
215+ TEST (RProfile, Clone)
216+ {
217+ static constexpr std::size_t Bins = 20 ;
218+ RProfile profileA (Bins, {0 , Bins});
219+
220+ profileA.Fill (8.5 , 23.0 );
221+
222+ RProfile profileB = profileA.Clone ();
223+ ASSERT_EQ (profileB.GetNDimensions (), 1 );
224+ ASSERT_EQ (profileB.GetTotalNBins (), Bins + 2 );
225+
226+ EXPECT_EQ (profileB.GetNEntries (), 1 );
227+ EXPECT_EQ (profileB.GetBinContent (8 ).fSumValues , 23.0 );
228+
229+ // Check that we can continue filling the clone.
230+ profileB.Fill (9.5 , 25.0 );
231+
232+ EXPECT_EQ (profileA.GetNEntries (), 1 );
233+ EXPECT_EQ (profileB.GetNEntries (), 2 );
234+ EXPECT_EQ (profileA.GetBinContent (9 ).fSumValues , 0 );
235+ EXPECT_EQ (profileB.GetBinContent (9 ).fSumValues , 25.0 );
236+ }
237+
59238TEST (RProfile, Fill)
60239{
61240 static constexpr std::size_t Bins = 20 ;
@@ -225,3 +404,26 @@ TEST(RProfile, FillExceptionSafety)
225404 EXPECT_EQ (profile.GetStats ().GetDimensionStats (1 ).fSumWX , 2.5 );
226405 EXPECT_EQ (profile.GetStats ().GetDimensionStats (2 ).fSumWX , 3.5 );
227406}
407+
408+ TEST (RProfile, Scale)
409+ {
410+ static constexpr std::size_t Bins = 20 ;
411+ RProfile profile (Bins, {0 , Bins});
412+
413+ profile.Fill (8.5 , 23.0 , RWeight (0.8 ));
414+ profile.Fill (9.5 , 25.0 , RWeight (0.9 ));
415+
416+ static constexpr double Factor = 0.8 ;
417+ profile.Scale (Factor);
418+
419+ EXPECT_FLOAT_EQ (profile.GetBinContent (8 ).fSumValues , Factor * 0.8 * 23.0 );
420+ EXPECT_FLOAT_EQ (profile.GetBinContent (9 ).fSumValues , Factor * 0.9 * 25.0 );
421+
422+ EXPECT_EQ (profile.GetNEntries (), 2 );
423+ EXPECT_FLOAT_EQ (profile.GetStats ().GetSumW (), Factor * 1.7 );
424+ EXPECT_FLOAT_EQ (profile.GetStats ().GetSumW2 (), Factor * Factor * 1.45 );
425+ // Cross-checked with TH1 - unchanged compared to FillWeight because the factor cancels out.
426+ EXPECT_FLOAT_EQ (profile.ComputeNEffectiveEntries (), 1.9931034 );
427+ EXPECT_FLOAT_EQ (profile.ComputeMean (), 9.0294118 );
428+ EXPECT_FLOAT_EQ (profile.ComputeStdDev (), 0.49913420 );
429+ }
0 commit comments