Skip to content

Commit 2d1f14d

Browse files
committed
[Python] Correctly manage NumPy dependencey in pythonization tests
The dependency on Python libraries like NumPy needs to be declared in the CMakeLists.txt, such that the test gets disabled when the library is not available. In some cases, the dependency on NumPy could be avoided by using the builtin "array" library.
1 parent 4326934 commit 2d1f14d

File tree

11 files changed

+61
-42
lines changed

11 files changed

+61
-42
lines changed

bindings/pyroot/pythonizations/python/ROOT/_pythonization/_roofit/_rooglobalfunc.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,6 @@ def _bindFunctionOrPdf(name, func, is_rooabspdf, *variables):
229229
"""
230230

231231
import ROOT
232-
import numpy as np
233232

234233
# use the C++ version if dealing with C++ function
235234
if "cppyy" in repr(type(func)):
@@ -245,10 +244,13 @@ def __init__(self, name, title, *variables):
245244
super(RooPyBindDerived, self).__init__(name, title, ROOT.RooArgList(*variables))
246245

247246
def evaluate(self):
247+
import numpy as np
248+
248249
inputs = [np.array([v.getVal()]) for v in self.varlist()]
249250
return func(*inputs)[0]
250251

251252
def doEvalPy(self, ctx):
253+
import numpy as np
252254

253255
def span_to_numpy(sp):
254256
return np.frombuffer(sp.data(), dtype=np.float64, count=sp.size())

bindings/pyroot/pythonizations/python/ROOT/_pythonization/_th1.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,13 @@ def _FillWithNumpyArray(self, *args):
199199
Raises:
200200
- ValueError: If weights length doesn't match data length
201201
"""
202+
import collections.abc
203+
204+
# If the first argument has no len() method, we don't even need to consider
205+
# the array code path.
206+
if not isinstance(args[0], collections.abc.Sized):
207+
return self._Fill(*args)
208+
202209
import numpy as np
203210

204211
if args and isinstance(args[0], np.ndarray):

bindings/pyroot/pythonizations/test/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ endif()
117117
if (dataframe)
118118
ROOT_ADD_PYUNITTEST(pyroot_rdfdescription rdfdescription.py)
119119

120-
ROOT_ADD_PYUNITTEST(pyroot_pyz_rdataframe_misc rdataframe_misc.py)
120+
ROOT_ADD_PYUNITTEST(pyroot_pyz_rdataframe_misc rdataframe_misc.py PYTHON_DEPS numpy)
121121
endif()
122122

123123
# SOFIE-GNN pythonizations
@@ -139,7 +139,7 @@ if (tmva AND dataframe)
139139
endif()
140140

141141
# Passing Python callables to ROOT.TF
142-
ROOT_ADD_PYUNITTEST(pyroot_pyz_tf_pycallables tf_pycallables.py)
142+
ROOT_ADD_PYUNITTEST(pyroot_pyz_tf_pycallables tf_pycallables.py PYTHON_DEPS numpy)
143143

144144
if(roofit)
145145

bindings/pyroot/pythonizations/test/rdataframe_misc.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import cppyy
1+
import array
2+
import os
23
import platform
34
import unittest
4-
import numpy
5-
import os
65

76
import ROOT
87

8+
99
class DatasetContext:
1010
"""A helper class to create the dataset for the tutorial below."""
1111

@@ -22,8 +22,8 @@ def __init__(self):
2222
with ROOT.TFile(filename, "RECREATE") as f:
2323
t = ROOT.TTree(self.treename, self.treename)
2424

25-
x = numpy.array([0], dtype=int)
26-
y = numpy.array([0], dtype=int)
25+
x = array.array("i", [0])
26+
y = array.array("i", [0])
2727
t.Branch("x", x, "x/I")
2828
t.Branch("y", y, "y/I")
2929

@@ -69,7 +69,7 @@ def test_empty_filenames(self):
6969

7070
# When passing explicitly the vector of strings, type dispatching will not be necessary
7171
# and the real C++ exception will immediately surface
72-
with self.assertRaisesRegex(cppyy.gbl.std.invalid_argument, "RDataFrame: empty list of input files."):
72+
with self.assertRaisesRegex(ROOT.std.invalid_argument, "RDataFrame: empty list of input files."):
7373
ROOT.RDataFrame("events", ROOT.std.vector[ROOT.std.string]())
7474

7575
with self.assertRaisesRegex(TypeError, "RDataFrame: empty list of input files."):
@@ -110,6 +110,8 @@ def test_ttree_ownership(self):
110110
return
111111

112112
with DatasetContext() as dataset:
113+
import numpy
114+
113115
rdf = self._get_rdf(dataset)
114116

115117
npy_dict = rdf.AsNumpy()

bindings/pyroot/pythonizations/test/stl_vector.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import array
2+
import random
13
import unittest
4+
25
import ROOT
3-
import random
4-
import numpy as np
56

67

78
class STL_vector(unittest.TestCase):
@@ -55,9 +56,7 @@ def test_tree_with_containers(self):
5556
tree = ROOT.TTree("tree", "Tree with std::vector")
5657

5758
# list of random arrays with lengths between 0 and 5 (0 is always included)
58-
entries_to_fill = [
59-
np.array([random.uniform(10, 20) for _ in range(n % 5)]) for n in range(100)
60-
]
59+
entries_to_fill = [array.array("f", [random.uniform(10, 20) for _ in range(n % 5)]) for n in range(100)]
6160

6261
# Create variables to store std::vector elements
6362
entry_root = ROOT.std.vector(float)()
@@ -76,12 +75,14 @@ def test_tree_with_containers(self):
7675

7776
for i in range(tree.GetEntries()):
7877
tree.GetEntry(i)
79-
entry_numpy = entries_to_fill[i]
80-
entry_python_list = list(entry_root)
78+
entry_array = entries_to_fill[i]
79+
80+
self.assertEqual(len(entry_array), len(entry_root))
81+
82+
self.assertEqual(bool(list(entry_root)), bool(entry_root)) # numpy arrays cannot be converted to bool
8183

82-
self.assertEqual(len(entry_numpy), len(entry_root))
83-
self.assertEqual(bool(entry_python_list), bool(entry_root)) # numpy arrays cannot be converted to bool
84-
np.testing.assert_allclose(entry_numpy, np.array(entry_root), rtol=1e-5)
84+
for entry_array_i, entry_root_i in zip(entry_array, entry_root):
85+
self.assertEqual(entry_array_i, entry_root_i)
8586

8687

8788
if __name__ == '__main__':

bindings/pyroot/pythonizations/test/ttree_branch_attr.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import ROOT
44

5-
import numpy as np
6-
75

86
class TTreeBranchAttr(unittest.TestCase):
97
"""

tutorials/CMakeLists.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,10 +890,16 @@ if(ROOT_pyroot_FOUND)
890890
file(GLOB requires_numpy RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
891891
analysis/dataframe/df026_AsNumpyArrays.py
892892
analysis/dataframe/df032_RDFFromNumpy.py
893+
math/fit/NumericalMinimization.py
893894
math/fit/combinedFit.py
895+
math/fit/fitConvolution.py
894896
math/fit/multifit.py
895-
math/fit/NumericalMinimization.py
896-
roofit/roofit/rf409_NumPyPandasToRooFit.py)
897+
math/pdf/pdf012_tStudent.py
898+
roofit/roofit/rf102_dataimport.py
899+
roofit/roofit/rf401_importttreethx.py
900+
roofit/roofit/rf409_NumPyPandasToRooFit.py
901+
roofit/roofit/rf617_simulation_based_inference_multidimensional.py
902+
)
897903
file(GLOB requires_numba RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} analysis/dataframe/df038_NumbaDeclare.py)
898904
file(GLOB requires_pandas RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
899905
analysis/dataframe/df026_AsNumpyArrays.py

tutorials/analysis/dataframe/df036_missingBranches.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
#
1313
# \date September 2024
1414
# \author Vincenzo Eduardo Padulano (CERN)
15+
import array
1516
import os
17+
1618
import ROOT
17-
import numpy
1819

1920

2021
class DatasetContext:
@@ -31,8 +32,8 @@ class DatasetContext:
3132
def __init__(self):
3233
with ROOT.TFile(self.filenames[0], "RECREATE") as f:
3334
t = ROOT.TTree(self.treenames[0], self.treenames[0])
34-
x = numpy.array([0], dtype=int)
35-
y = numpy.array([0], dtype=int)
35+
x = array.array('i', [0]) # any array can also be a numpy array
36+
y = array.array('i', [0])
3637
t.Branch("x", x, "x/I")
3738
t.Branch("y", y, "y/I")
3839

@@ -45,7 +46,7 @@ def __init__(self):
4546

4647
with ROOT.TFile(self.filenames[1], "RECREATE") as f:
4748
t = ROOT.TTree(self.treenames[1], self.treenames[1])
48-
y = numpy.array([0], dtype=int)
49+
y = array.array('i', [0]) # any array can also be a numpy array
4950
t.Branch("y", y, "y/I")
5051

5152
for i in range(1, self.nentries + 1):
@@ -56,7 +57,7 @@ def __init__(self):
5657

5758
with ROOT.TFile(self.filenames[2], "RECREATE") as f:
5859
t = ROOT.TTree(self.treenames[2], self.treenames[2])
59-
x = numpy.array([0], dtype=int)
60+
x = array.array('i', [0]) # any array can also be a numpy array
6061
t.Branch("x", x, "x/I")
6162

6263
for i in range(1, self.nentries + 1):

tutorials/analysis/dataframe/df037_TTreeEventMatching.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
#
1414
# \date September 2024
1515
# \author Vincenzo Eduardo Padulano (CERN)
16+
import array
1617
import os
18+
1719
import ROOT
18-
import numpy
1920

2021

2122
class DatasetContext:
@@ -31,8 +32,8 @@ class DatasetContext:
3132
def __init__(self):
3233
with ROOT.TFile(self.main_file, "RECREATE") as f:
3334
main_tree = ROOT.TTree(self.main_tree_name, self.main_tree_name)
34-
idx = numpy.array([0], dtype=int)
35-
x = numpy.array([0], dtype=int)
35+
idx = array.array('i', [0]) # any array can also be a numpy array
36+
x = array.array('i', [0])
3637
main_tree.Branch("idx", idx, "idx/I")
3738
main_tree.Branch("x", x, "x/I")
3839

@@ -51,8 +52,8 @@ def __init__(self):
5152
# The first auxiliary file has matching indices 1 and 2, but not 3
5253
with ROOT.TFile(self.aux_file_1, "RECREATE") as f:
5354
aux_tree_1 = ROOT.TTree(self.aux_tree_name_1, self.aux_tree_name_1)
54-
idx = numpy.array([0], dtype=int)
55-
y = numpy.array([0], dtype=int)
55+
idx = array.array('i', [0]) # any array can also be a numpy array
56+
y = array.array('i', [0])
5657
aux_tree_1.Branch("idx", idx, "idx/I")
5758
aux_tree_1.Branch("y", y, "y/I")
5859

@@ -68,8 +69,8 @@ def __init__(self):
6869
# The second auxiliary file has matching indices 1 and 3, but not 2
6970
with ROOT.TFile(self.aux_file_2, "RECREATE") as f:
7071
aux_tree_2 = ROOT.TTree(self.aux_tree_name_2, self.aux_tree_name_2)
71-
idx = numpy.array([0], dtype=int)
72-
z = numpy.array([0], dtype=int)
72+
idx = array.array('i', [0]) # any array can also be a numpy array
73+
z = array.array('i', [0])
7374
aux_tree_2.Branch("idx", idx, "idx/I")
7475
aux_tree_2.Branch("z", z, "z/I")
7576

tutorials/roofit/roostats/StandardHypoTestDemo.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
#
2929
# \author Lorenzo Moneta (C++ version), and P. P. (Python translation)
3030

31+
import array
3132
from warnings import warn
3233
from gc import collect
33-
import numpy as np
3434

3535
import ROOT
3636

@@ -396,8 +396,8 @@ def StandardHypoTestDemo(
396396
htExp = ROOT.RooStats.HypoTestResult("Expected Result")
397397
htExp.Append(htr)
398398
# find quantiles in alt (S+B) distribution
399-
p = np.array([i for i in range(5)], dtype=float)
400-
q = np.array([0.5 for i in range(5)], dtype=float)
399+
p = array.array('d', [i for i in range(5)])
400+
q = array.array('d', [0.5 for i in range(5)])
401401
for i in range(5):
402402
sig = -2 + i
403403
p[i] = ROOT.Math.normal_cdf(sig, 1)

0 commit comments

Comments
 (0)