|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Offline test: the ITS ramp-up shift must move the first orbit, not only the timestamp.""" |
| 3 | +import os |
| 4 | +import sys |
| 5 | +import unittest |
| 6 | + |
| 7 | +HERE = os.path.dirname(os.path.abspath(__file__)) |
| 8 | +sys.path.insert(0, os.path.dirname(HERE)) |
| 9 | + |
| 10 | +os.environ.setdefault("O2DPG_ROOT", os.path.dirname(os.path.dirname(os.path.dirname(HERE)))) |
| 11 | + |
| 12 | +try: |
| 13 | + import o2dpg_sim_workflow_anchored as anchored # needs ROOT |
| 14 | +except ImportError as exc: # pragma: no cover |
| 15 | + anchored = None |
| 16 | + IMPORT_ERROR = exc |
| 17 | + |
| 18 | +# Run 571781 of LHC26e9, whose SPLITID=1 job reconstructed no collisions at all: |
| 19 | +# the ITS time-dead map masks all 24120 chips up to orbit 20539968 and the job was |
| 20 | +# placed at the run's first orbit. See https://its.cern.ch/jira/browse/O2-6894 |
| 21 | +FIRST_ORBIT = 20505888 |
| 22 | +SOR = 1778806732526 |
| 23 | +ITS_RAMPUP_MS = 5000 |
| 24 | +FIRST_ALIVE_ORBIT = 20539968 |
| 25 | + |
| 26 | + |
| 27 | +@unittest.skipIf(anchored is None, "o2dpg_sim_workflow_anchored not importable (needs ROOT)") |
| 28 | +class TestRampUpShift(unittest.TestCase): |
| 29 | + |
| 30 | + def test_orbits_from_milliseconds(self): |
| 31 | + orbit_ms = anchored.LHCOrbitMUS / 1000. |
| 32 | + self.assertEqual(anchored.milliseconds_to_orbits(0), 0) |
| 33 | + # a span of one orbit minus an epsilon still has to cover a full orbit |
| 34 | + self.assertEqual(anchored.milliseconds_to_orbits(0.5 * orbit_ms), 1) |
| 35 | + self.assertEqual(anchored.milliseconds_to_orbits(orbit_ms), 1) |
| 36 | + |
| 37 | + def test_both_coordinates_move(self): |
| 38 | + """A ramp-up of a few seconds must move the orbit as well as the timestamp.""" |
| 39 | + start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS) |
| 40 | + self.assertEqual(start, SOR + ITS_RAMPUP_MS) |
| 41 | + self.assertGreater(orbit, FIRST_ORBIT) |
| 42 | + |
| 43 | + def test_nothing_moves_without_a_ramp(self): |
| 44 | + self.assertEqual(anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, 0), |
| 45 | + (SOR, FIRST_ORBIT)) |
| 46 | + |
| 47 | + def test_shifted_orbit_is_never_inside_the_ramp(self): |
| 48 | + """The shifted orbit must sit at or after the shifted timestamp, never before.""" |
| 49 | + for ramp_ms in (0, 1, 500, ITS_RAMPUP_MS, 30000): |
| 50 | + start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ramp_ms) |
| 51 | + time_of_orbit = SOR + (orbit - FIRST_ORBIT) * anchored.LHCOrbitMUS / 1000. |
| 52 | + self.assertGreaterEqual(time_of_orbit, start, |
| 53 | + f"orbit shift falls short of the ramp for {ramp_ms} ms") |
| 54 | + |
| 55 | + def test_shift_agrees_with_the_timestamp_to_orbit_conversion(self): |
| 56 | + """Closure: the shifted orbit is what main() derives from the shifted timestamp.""" |
| 57 | + start, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS) |
| 58 | + # this is the conversion main() uses for the exclude_timestamp() check |
| 59 | + derived = FIRST_ORBIT + int((start - SOR) / (anchored.LHCOrbitMUS / 1000.)) |
| 60 | + self.assertLessEqual(abs(orbit - derived), 1) |
| 61 | + |
| 62 | + def test_split_id_one_clears_the_its_dead_window(self): |
| 63 | + """Regression: the first job of a production must not sample the dead window.""" |
| 64 | + _, orbit = anchored.shift_anchor_past_ITS_rampup(SOR, FIRST_ORBIT, ITS_RAMPUP_MS) |
| 65 | + self.assertGreater(orbit, FIRST_ALIVE_ORBIT) |
| 66 | + |
| 67 | + |
| 68 | +if __name__ == "__main__": |
| 69 | + unittest.main() |
0 commit comments