Belle II Software  light-2403-persian
root_returnvalues.py
1 #!/usr/bin/env python3
2 
3 
10 
11 # this is a test executable, not a module so we don't need doxygen warnings
12 # @cond SUPPRESS_DOXYGEN
13 
14 """
15 Check that ROOT returns the correct values (ints, floats) when calling a C++
16 function from python
17 """
18 
19 import sys
20 import ROOT
21 import numpy as np
22 
23 testclass = [
24  "#include <cstdint>",
25  "#include <limits>",
26  "struct ReturnValueTests {"
27 ]
28 
29 results = []
30 # what integer sizes to check
31 integral_types = [8, 16, 32, 64]
32 # which floating types to check
33 floating_types = ["float", "double"]
34 # nan and inf need to be expressed differently so make a mapping
35 floating_expressions = {
36  -np.inf: "-std::numeric_limits<{0}>::infinity()",
37  np.inf: "std::numeric_limits<{0}>::infinity()",
38  np.nan: "std::numeric_limits<{0}>::quiet_NaN()",
39 }
40 
41 for width in integral_types:
42  # check signed types, we want min, max -1, 0 and 1
43  dtype = np.dtype(f"int{width}")
44  for i, value in enumerate([-2**(width - 1), -1, 0, 1, 2**(width - 1) - 1]):
45  # create a unique member name
46  func_name = f"int{width}test{i}"
47  # do we need a prefix to the literal?
48  prefix = "ull" if width > 32 else ""
49  # append it to the test class
50  testclass.append(f"int{width}_t {func_name}() const {{ return {value}{prefix}; }}")
51  testclass.append(f"int{width}_t& ref_{func_name}() {{ static int{width}_t val = {value}{prefix}; return val; }}")
52  # and remember name and result for checking
53  results.append([func_name, value, dtype])
54  results.append(['ref_' + func_name, value, dtype])
55 
56  # check unsigned types, just 0, 1, and max
57  dtype = np.dtype(f"uint{width}")
58  for i, value in enumerate([0, 1, 2**(width) - 1]):
59  # create a unique member name
60  func_name = f"uint{width}test{i}"
61  # do we need a prefix to the literal?
62  prefix = "ull" if width > 32 else ""
63  # append it to the test class
64  testclass.append(f"uint{width}_t {func_name}() const {{ return {value}{prefix}; }}")
65  testclass.append(f"uint{width}_t& ref_{func_name}() {{ static uint{width}_t val = {value}{prefix}; return val; }}")
66  # and remember name and result for checking
67  results.append([func_name, value, dtype])
68  results.append(['ref_' + func_name, value, dtype])
69 
70 # now add floating types
71 for t in floating_types:
72  # and exploit that numpy offers a numeric_limits equivalent
73  info = np.finfo(t[0])
74 
75  # check some values
76  for i, value in enumerate([-np.inf, info.min, -1, 0, info.tiny, info.eps, 1, info.max, np.inf, np.nan]):
77  func_name = f"{t}test{i}"
78  # see if we can just have the literal or if we need a mapping
79  expression = repr(value) if value not in floating_expressions else floating_expressions[value].format(t)
80  testclass.append(f"{t} {func_name}() const {{ return {expression}; }}")
81  testclass.append(f"{t}& ref_{func_name}() {{ static {t} val = {expression}; return val; }}")
82  results.append([func_name, value, info.dtype])
83  results.append(['ref_' + func_name, value, info.dtype])
84 
85 
86 # compile the test class
87 testclass.append("};")
88 ROOT.gROOT.ProcessLine("\n".join(testclass))
89 # get an instance
90 tests = ROOT.ReturnValueTests()
91 # and do all the checks
92 failures = 0
93 for func, value, dtype in results:
94  ret = getattr(tests, func)()
95  # char is odd and returns strings instead of int. The simple way of using
96  # ord(string) will loose the sign so we have to convert it to the correct
97  # type using numpy but for that we have to convert the string to 8bit.
98  if isinstance(ret, str):
99  ret = np.fromstring(ret.encode("latin1"), dtype)[0]
100 
101  # print the test
102  print(f"check {func}(): {value!r} == {ret!r}: ", end="")
103  # nan needs special care because it cannot be compared to itself
104  if np.isnan(value):
105  passed = np.isnan(ret)
106  else:
107  passed = (ret == value)
108 
109  print("\033[32mOK\033[0m" if passed else "\033[31mFAIL\033[0m")
110  if not passed:
111  failures += 1
112 
113 sys.exit(failures)
114 
115 # @endcond