Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions MC/bin/o2dpg_sim_workflow_anchored.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,24 @@ def retrieve_ITS_RampDuration(ccdbreader, timestamp):
print("WARNING: ITS ramp duration vector is empty, using 0")
return 0

def milliseconds_to_orbits(milliseconds):
"""
Number of complete LHC orbits contained in a time span given in milliseconds.
Rounded up, so that a span used to skip something is never cut short.
"""
return int(math.ceil(1000. * milliseconds / LHCOrbitMUS))

def shift_anchor_past_ITS_rampup(run_start, first_orbit, ITS_rampup):
"""
Moves the anchoring point past the ITS ramp-up period, in both of its
coordinates, and returns the pair (start of run in ms, first orbit).
The two have to move together: the timeframes a job simulates are placed by
orbit (first orbit plus the production offset), so shifting only the
timestamp leaves a job at production offset 0 inside the ramp, where the ITS
time-dead map masks every chip.
"""
return run_start + ITS_rampup, first_orbit + milliseconds_to_orbits(ITS_rampup)

def retrieve_MinBias_CTPScaler_Rate(raw_rate_at, finaltime, trig_eff_arg, NBunches, ColSystem, eCM, run_number = -1):
"""
Turns the raw CTP counting rate at finaltime (in milliseconds) into the interaction rate for
Expand Down Expand Up @@ -590,10 +608,10 @@ def main():
run_start = GLOparams["SOR"]
run_end = GLOparams["EOR"]

# Adjust start of run using ITS ramp-up period
# Adjust the anchoring point using the ITS ramp-up period
ITS_rampup = retrieve_ITS_RampDuration(ccdbreader, run_start)
print(f"ITS ramp-up time: {ITS_rampup} ms")
effective_run_start = run_start + ITS_rampup
effective_run_start, effective_first_orbit = shift_anchor_past_ITS_rampup(run_start, GLOparams["FirstOrbit"], ITS_rampup)
mid_run_timestamp = (effective_run_start + run_end) // 2

# --------
Expand Down Expand Up @@ -676,6 +694,7 @@ def main():

# this is anchored to
print ("Determined start-of-run to be: ", effective_run_start)
print ("Determined first orbit to be: ", effective_first_orbit)
print ("Determined end-of-run to be: ", run_end)
print ("Determined timestamp to be : ", timestamp)
print ("Determined offset to be : ", prod_offset)
Expand Down Expand Up @@ -721,7 +740,7 @@ def main():
# needs to be handled as further below:
energyarg = (" -eCM " + str(eCM)) if A1 == A2 else (" -eA " + str(eA) + " -eB " + str(eB))
forwardargs += " -tf " + str(args.tf) + " --sor " + str(effective_run_start) + " --timestamp " + str(timestamp) + " --production-offset " + str(prod_offset) + " -run " + str(args.run_number) + " --run-anchored --first-orbit " \
+ str(GLOparams["FirstOrbit"]) + " --orbitsPerTF " + str(GLOparams["OrbitsPerTF"]) + str(energyarg)
+ str(effective_first_orbit) + " --orbitsPerTF " + str(GLOparams["OrbitsPerTF"]) + str(energyarg)
# the following options can be overwritten/influenced from the outside
if not '-col' in forwardargs:
forwardargs += ' -col ' + ColSystem
Expand Down
69 changes: 69 additions & 0 deletions MC/bin/tests/test_anchoring_rampup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Offline test: the ITS ramp-up shift must move the first orbit, not only the timestamp."""
import os
import sys
import unittest

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.dirname(HERE))

os.environ.setdefault("O2DPG_ROOT", os.path.dirname(os.path.dirname(os.path.dirname(HERE))))

try:
import o2dpg_sim_workflow_anchored as anchored # needs ROOT
except ImportError as exc: # pragma: no cover
anchored = None
IMPORT_ERROR = exc

# Run 571781 of LHC26e9, whose SPLITID=1 job reconstructed no collisions at all:
# the ITS time-dead map masks all 24120 chips up to orbit 20539968 and the job was
# placed at the run's first orbit. See https://its.cern.ch/jira/browse/O2-6894
FIRST_ORBIT = 20505888
SOR = 1778806732526
ITS_RAMPUP_MS = 5000
FIRST_ALIVE_ORBIT = 20539968


@unittest.skipIf(anchored is None, "o2dpg_sim_workflow_anchored not importable (needs ROOT)")
class TestRampUpShift(unittest.TestCase):

def test_orbits_from_milliseconds(self):
orbit_ms = anchored.LHCOrbitMUS / 1000.
self.assertEqual(anchored.milliseconds_to_orbits(0), 0)
# a span of one orbit minus an epsilon still has to cover a full orbit
self.assertEqual(anchored.milliseconds_to_orbits(0.5 * orbit_ms), 1)
self.assertEqual(anchored.milliseconds_to_orbits(orbit_ms), 1)

def test_both_coordinates_move(self):
"""A ramp-up of a few seconds must move the orbit as well as the timestamp."""
start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS)
self.assertEqual(start, SOR + ITS_RAMPUP_MS)
self.assertGreater(orbit, FIRST_ORBIT)

def test_nothing_moves_without_a_ramp(self):
self.assertEqual(anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, 0),
(SOR, FIRST_ORBIT))

def test_shifted_orbit_is_never_inside_the_ramp(self):
"""The shifted orbit must sit at or after the shifted timestamp, never before."""
for ramp_ms in (0, 1, 500, ITS_RAMPUP_MS, 30000):
start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ramp_ms)
time_of_orbit = SOR + (orbit - FIRST_ORBIT) * anchored.LHCOrbitMUS / 1000.
self.assertGreaterEqual(time_of_orbit, start,
f"orbit shift falls short of the ramp for {ramp_ms} ms")

def test_shift_agrees_with_the_timestamp_to_orbit_conversion(self):
"""Closure: the shifted orbit is what main() derives from the shifted timestamp."""
start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS)
# this is the conversion main() uses for the exclude_timestamp() check
derived = FIRST_ORBIT + int((start - SOR) / (anchored.LHCOrbitMUS / 1000.))
self.assertLessEqual(abs(orbit - derived), 1)

def test_split_id_one_clears_the_its_dead_window(self):
"""Regression: the first job of a production must not sample the dead window."""
_, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS)
self.assertGreater(orbit, FIRST_ALIVE_ORBIT)


if __name__ == "__main__":
unittest.main()
Loading