SND@LHC Software
Loading...
Searching...
No Matches
makeRunListDB.py
Go to the documentation of this file.
1import pymongo
2import argparse
3import xml.etree.ElementTree as ET
4from datetime import datetime
5from collections import defaultdict
6import os
7
8parser = argparse.ArgumentParser(
9 prog="makeRunListDB",
10 description="Extracts a list of runs from the SND@LHC DB, matching the conditions given in the arguments. Produces an XML file with the run list and a summary of the selection.")
11parser.add_argument("--username", type=str, help="Database username. Contact database maintainer to obtain it", required=True)
12parser.add_argument("--password", type=str, help="Database password. Contact database maintainer to obtain it", required=True)
13parser.add_argument("--name", type=str, help="Run list name", required=True)
14parser.add_argument("--years", nargs="+", type=int, help="Years to be included, e.g. 2022 2023", required=True)
15parser.add_argument("--min_events", type=int, help="Minimum number of events in run", default=0)
16parser.add_argument("--min_lumi", type=float, help="Minimum integrated luminosity for a run to be included, in fb-1", default=0.)
17parser.add_argument("--min_stable_time", type=float, help="Minimum stable beams time of the corresponding LHC fill for a run to be included, in minutes", default=0.)
18parser.add_argument("--particle_B1", type=str, help="Beam 1 particle, e.g. p+ or PB82", required=True)
19parser.add_argument("--particle_B2", type=str, help="Beam 2 particle, e.g. p+ or PB82", required=True)
20parser.add_argument("--min_energy", type=float, help="Minimum beam energy (in GeV)", default=0.)
21parser.add_argument("--max_energy", type=float, help="Maximum beam energy (in GeV)", default=0.)
22parser.add_argument("--min_bunches_IP1", type=int, help="Minimum number of bunches colliding at IP1", default=0)
23parser.add_argument("--max_bunches_IP1", type=int, help="Maximum number of bunches colliding at IP1", default=0)
24parser.add_argument("--include_runs", nargs="+", type=int, help="Runs to include, regardless of the other criteria", default=[])
25parser.add_argument("--exclude_runs", nargs="+", type=int, help="Runs to exclude from the list", default=[])
26args = parser.parse_args()
27
28client = pymongo.MongoClient('sndrundb.cern.ch', username=args.username, password=args.password, authSource='sndrundb', authMechanism='SCRAM-SHA-1')
29
30db = client.sndrundb
31
32pipeline = []
33
34# Get the run list corresponding to the selected years
35pipeline.append({"$match": {"$expr": {"$in": [{"$year": "$start"}, args.years]}}})
36
37# Select runs with a minimum of min_events events:
38if args.min_events > 0:
39 pipeline.append({"$match": {"events": {"$gt": args.min_events}}})
40
41# Combine data fill from the LPC
42pipeline.append({"$lookup": {"from": "FILL_LPC", "localField": "fill", "foreignField": "_id", "as": "LPC"}})
43
44if args.min_lumi > 0.:
45 # Select runs with at least min_lumi integrated luminosity
46 pipeline.append({"$match": {"LPC.ATLAS_Int_Lumi": {"$gt": args.min_lumi*1e6}}})
47
48if args.min_stable_time > 0.:
49 # Select runs with at least min_stable_time minutes of stable beams
50 pipeline.append({"$match": {"LPC.Stable_time": {"$gt": args.min_stable_time/60.}}})
51
52# Select B1 particle
53pipeline.append({"$match": {"LPC.Particle_B1": args.particle_B1}})
54
55# Select B2 particle
56pipeline.append({"$match": {"LPC.Particle_B2": args.particle_B2}})
57
58if args.min_energy > 0:
59 # Select runs with args.min_energy energy
60 pipeline.append({"$match": {"LPC.Energy": {"$gt": args.min_energy}}})
61
62if args.max_energy > 0:
63 # Select runs with args.max_energy energy
64 pipeline.append({"$match": {"LPC.Energy": {"$lt": args.max_energy}}})
65
66if args.min_bunches_IP1 > 0:
67 # Select runs with at least min_bunches_IP1 bunches colliding at IP1
68 pipeline.append({"$match": {"LPC.Coll_IP_1_5": {"$gt": args.min_bunches_IP1}}})
69
70if args.max_bunches_IP1 > 0:
71 # Select runs with at most max_bunches_IP1 bunches colliding at IP1
72 pipeline.append({"$match": {"LPC.Coll_IP_1_5": {"$lt": args.max_bunches_IP1}}})
73
74if len(args.exclude_runs) > 0:
75 pipeline.append({"$match": {"runNumber": {"$nin" : args.exclude_runs}}})
76
77# Expression for Calculating run length from start and stop datetimes
78run_length_expr = {"$dateDiff": {"startDate": "$start", "endDate": "$stop", "unit": "minute"}}
79
80# Extract the following data from the DB
81projection = {"$project":{"_id": 0, # Do not extract DB entry ID
82 "run_number": "$runNumber", # Run number
83 "n_events": "$events", # Number of events
84 "start": 1, # Start date
85 "end": 1, # End date
86 "duration": run_length_expr, # Run duration
87 "n_files": {"$size": "$files"}, # Number of files
88 "path": {"$first": "$files.file"}, # Path of the first file
89 "fill_number": "$fill", # Fill number
90 "fill_int_lumi": {"$first": "$LPC.ATLAS_Int_Lumi"}, # Integrated luminosity
91 "fill_stable_time" : {"$first": "$LPC.Stable_time"}} # Stable beams duration
92 }
93
94pipeline.append(projection)
95
96result = list(db["EcsData"].aggregate(pipeline))
97
98if len(args.include_runs) > 0:
99 include_pipeline = []
100 include_pipeline.append({"$match": {"runNumber": {"$in": args.include_runs}}})
101 include_pipeline.append(projection)
102
103 include_result = list(db["EcsData"].aggregate(include_pipeline))
104
105 result.extend(list(include_result))
106
107result.sort(key=lambda x: x["run_number"])
108
109# Get the time now
110now = datetime.now()
111
112# Format into xml tree
113root = ET.Element("runlist")
114
115meta_data = ET.SubElement(root, "meta")
116ET.SubElement(meta_data, "name").text = args.name
117ET.SubElement(meta_data, "datetime").text = now.strftime("%Y-%m-%dT%H:%M:%S.%f")
118selection = ET.SubElement(meta_data, "selection")
119ET.SubElement(selection, "years").text = ','.join([str(y) for y in args.years])
120for criterion in ["min_events", "min_lumi", "min_stable_time", "particle_B1", "particle_B2", "min_energy", "max_energy", "min_bunches_IP1", "max_bunches_IP1", "exclude_runs", "include_runs"]:
121 ET.SubElement(selection, criterion).text = str(getattr(args, criterion))
122runs = ET.SubElement(root, "runs")
123
124# Counters for summary statistics
125n_runs = 0
126totals = defaultdict(int)
127
128# Run loop
129for run in result:
130 this_run = ET.SubElement(runs, "run")
131 totals["n_runs"] += 1
132 for field_name in ["run_number", "start", "end", "n_events", "duration", "n_files", "fill_number", "fill_int_lumi", "fill_stable_time", "path"]:
133 try:
134 data = run[field_name]
135 except KeyError:
136 continue
137
138 if field_name == "path":
139 data = os.path.dirname(data)
140
141 ET.SubElement(this_run, field_name).text = str(data)
142
143 if field_name not in ["run_number", "start", "end", "fill_number", "path", "fill_int_lumi", "fill_stable_time"] and data is not None:
144 totals["tot_"+field_name] += data
145
146stats = ET.SubElement(meta_data, "statistics")
147for key, data in totals.items():
148 ET.SubElement(stats, key).text = str(data)
149
150# Write to xml file
151tree = ET.ElementTree(root)
152ET.indent(tree, space=" ")
153tree.write(args.name+"_"+str(now.timestamp())+".xml", encoding="utf-8", xml_declaration=True)