Skip to content

Commit c1e7f7b

Browse files
runtingtanigamova
authored andcommitted
Add AD comparison script
1 parent b26dd36 commit c1e7f7b

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

.github/workflows/development-tests-ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88

99
jobs:
1010
test_workflow:
11+
if: github.event.label.name == 'testAD'
1112
runs-on: ubuntu-22.04
1213
strategy:
1314
fail-fast: false
@@ -69,7 +70,7 @@ jobs:
6970
image: ${{ matrix.IMAGE }}
7071
shell: bash
7172
options: -v /cvmfs:/cvmfs:shared -v ${{ github.workspace }}:/work/CombinedLimit --mount source=cmsusr,destination=/home/cmsusr -w /home/cmsusr -e CMSSW_VERSION=${{ matrix.CMSSW_VERSION }} -e SCRAM_ARCH=${{ matrix.SCRAM_ARCH }}
72-
run: |
73+
run: |
7374
cd /home/cmsusr/
7475
source /cvmfs/cms.cern.ch/cmsset_default.sh
7576
scram project ${CMSSW_VERSION}
@@ -275,3 +276,8 @@ jobs:
275276
path: outputs/
276277
retention-days: 7
277278

279+
- uses: ./.github/actions/run-in-cvmfs
280+
name: Compare outputs
281+
with:
282+
script: |
283+
python3 test/codegen/compare_codegen.py ${{ steps.get_output_dir.outputs.OUTPUT_DIR }}

test/codegen/compare_codegen.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import sys
2+
import os
3+
import ROOT
4+
from enum import Enum, auto
5+
6+
7+
class ComparisonResult(Enum):
8+
OK = 0
9+
NO_LIMIT_TREE = auto()
10+
DIFFERENT_ENTRIES = auto()
11+
DIFFERENT_POIS = auto()
12+
VALUE_MISMATCH = auto()
13+
14+
15+
def detect_keys(startpath) -> set:
16+
keys = set()
17+
dirs = os.listdir(startpath)
18+
for d in dirs:
19+
if os.path.isdir(os.path.join(startpath, d)):
20+
if d.endswith("_codegen"):
21+
key = d[: -len("_codegen")]
22+
keys.add(key)
23+
else:
24+
keys.add(d)
25+
26+
def get_mtime(k):
27+
nominal_path = os.path.join(startpath, k)
28+
return os.path.getmtime(nominal_path) if os.path.exists(nominal_path) else 0
29+
30+
return sorted(keys, key=get_mtime)
31+
32+
33+
def check_codegen_counterparts(startpath, keys) -> list:
34+
missing_codegen = []
35+
for key in keys:
36+
codegen_dir = os.path.join(startpath, f"{key}_codegen")
37+
if not os.path.exists(codegen_dir):
38+
missing_codegen.append(key)
39+
return missing_codegen
40+
41+
42+
def check_codegen_counterpart_files(startpath, keys, missing_codegen) -> dict[str, set]:
43+
discrepancies = {}
44+
for key in keys:
45+
if key in missing_codegen:
46+
continue
47+
48+
nominal_files = set(os.listdir(os.path.join(startpath, key)))
49+
codegen_files = set(os.listdir(os.path.join(startpath, f"{key}_codegen")))
50+
51+
# Union should equal both sets
52+
missing_in_codegen = nominal_files - codegen_files
53+
missing_in_nominal = codegen_files - nominal_files
54+
discrepancies[key] = {"missing_in_codegen": missing_in_codegen, "missing_in_nominal": missing_in_nominal}
55+
return discrepancies
56+
57+
58+
def compare_file_contents(file1, file2, tol: float = 1e-3) -> ComparisonResult:
59+
f1 = ROOT.TFile.Open(file1)
60+
f2 = ROOT.TFile.Open(file2)
61+
try:
62+
# For now, just check the 'limit' tree
63+
keys1 = [k.GetName() for k in f1.GetListOfKeys()]
64+
keys2 = [k.GetName() for k in f2.GetListOfKeys()]
65+
if "limit" not in keys1 or "limit" not in keys2:
66+
if "fitDiagnosticsTest" in file1:
67+
return ComparisonResult.OK # Skip fitDiagnosticsTest for now
68+
return ComparisonResult.NO_LIMIT_TREE
69+
70+
tree1 = f1.Get("limit")
71+
tree2 = f2.Get("limit")
72+
if tree1.GetEntries() != tree2.GetEntries():
73+
return ComparisonResult.DIFFERENT_ENTRIES
74+
75+
# Check POIs match
76+
pois_1 = [b.GetName() for b in tree1.GetListOfBranches() if "r_" in b.GetName() or "r" == b.GetName()]
77+
pois_2 = [b.GetName() for b in tree2.GetListOfBranches() if "r_" in b.GetName() or "r" == b.GetName()]
78+
if set(pois_1) != set(pois_2):
79+
return ComparisonResult.DIFFERENT_POIS
80+
81+
# Check entry by entry
82+
for i in range(tree1.GetEntries()):
83+
tree1.GetEntry(i)
84+
tree2.GetEntry(i)
85+
for poi in pois_1:
86+
val1 = getattr(tree1, poi)
87+
val2 = getattr(tree2, poi)
88+
deltaNLL1 = tree1.deltaNLL
89+
deltaNLL2 = tree2.deltaNLL
90+
if abs(val1 - val2) > tol or abs(deltaNLL1 - deltaNLL2) > tol:
91+
return ComparisonResult.VALUE_MISMATCH
92+
93+
return ComparisonResult.OK
94+
finally:
95+
f1.Close()
96+
f2.Close()
97+
98+
99+
if __name__ == "__main__":
100+
if len(sys.argv) != 2:
101+
print("Usage: python compare_codegen.py <comparison_input_directory>")
102+
sys.exit(1)
103+
comparison_input_dir = sys.argv[1]
104+
keys = detect_keys(comparison_input_dir)
105+
status = {k: "OK" for k in keys}
106+
107+
# ---- Comparisons ---
108+
109+
# 1. Check every directory has a codegen counterpart
110+
missing_codegen = check_codegen_counterparts(comparison_input_dir, keys)
111+
for key in missing_codegen:
112+
status[key] = "MISSING_CODEGEN"
113+
114+
# 2. Check every codegen directory has the same files as its counterpart
115+
discrepancies = check_codegen_counterpart_files(comparison_input_dir, keys, missing_codegen)
116+
for key, diff in discrepancies.items():
117+
if diff["missing_in_codegen"] or diff["missing_in_nominal"]:
118+
status[key] = "FILE_MISMATCH"
119+
120+
# 3. Check file contents
121+
for key in keys:
122+
if status[key] != "OK":
123+
continue
124+
125+
nominal_files = os.listdir(os.path.join(comparison_input_dir, key))
126+
codegen_files = os.listdir(os.path.join(comparison_input_dir, f"{key}_codegen"))
127+
if len(nominal_files) == 0 or len(codegen_files) == 0:
128+
status[key] = "NO_FILES"
129+
continue
130+
131+
for f in nominal_files:
132+
nominal_file = os.path.join(comparison_input_dir, key, f)
133+
codegen_file = os.path.join(comparison_input_dir, f"{key}_codegen", f)
134+
res = compare_file_contents(nominal_file, codegen_file)
135+
if res != ComparisonResult.OK:
136+
status[key] = res.name
137+
138+
# ---- Report ----
139+
print("Comparison Summary:")
140+
for key, stat in status.items():
141+
print(f" {key}: {stat}")

0 commit comments

Comments
 (0)