@@ -1764,15 +1764,17 @@ def __str__(self) -> str:
17641764 return 'submissions'
17651765
17661766 def check_submission (
1767- self , sub , context : Context , expected_verdict : Verdict , timelim : float , timelim_low : float , timelim_high : float
1767+ self , sub , context : Context , expected_verdict : Verdict , timelim : float , timelim_high : float
17681768 ) -> SubmissionResult :
17691769 desc = f'{ expected_verdict } submission { sub } '
17701770 partial = False
17711771 if expected_verdict == 'PAC' :
1772- # For partially accepted solutions, use the low timelim instead of the real one,
1773- # to make sure we have margin in both directions.
17741772 expected_verdict = 'AC'
17751773 partial = True
1774+ # For partially accepted, we don't want to use them to lower bound the time limit, but we do want
1775+ # to warn if they're slow enough that they would have affected the time limit, had they been used
1776+ # to compute it.
1777+ timelim_low = timelim / self .problem .metadata .limits .time_multipliers .ac_to_time_limit
17761778 else :
17771779 timelim_low = timelim
17781780
@@ -1827,28 +1829,34 @@ def start_background_work(self, context: Context) -> None:
18271829 for sub in self ._submissions [acr ]:
18281830 context .submit_background_work (lambda s : s .compile (), sub )
18291831
1832+ def _compute_time_limit (self , fixed_limit : float | None , lower_bound_runtime : float | None ) -> tuple [float , float ]:
1833+ if fixed_limit is None and lower_bound_runtime is None :
1834+ # 5 minutes is our currently hard coded upper bound for what to allow when we don't know the time limit yet
1835+ return 300.0 , 300.0
1836+
1837+ limits = self .problem .metadata .limits
1838+ if fixed_limit is not None :
1839+ timelim = fixed_limit
1840+ else :
1841+ assert lower_bound_runtime is not None , 'Assert to keep mypy happy'
1842+ exact_timelim = lower_bound_runtime * limits .time_multipliers .ac_to_time_limit
1843+ timelim = max (1 , math .ceil (exact_timelim / limits .time_resolution )) * limits .time_resolution
1844+
1845+ return timelim , timelim * limits .time_multipliers .time_limit_to_tle
1846+
18301847 def check (self , context : Context ) -> bool :
18311848 if self ._check_res is not None :
18321849 return self ._check_res
18331850 self ._check_res = True
18341851
18351852 limits = self .problem .metadata .limits
1836- time_multiplier = limits .time_multipliers .ac_to_time_limit
1837- safety_margin = limits .time_multipliers .time_limit_to_tle
1853+ ac_to_time_limit = limits .time_multipliers .ac_to_time_limit
18381854
1839- timelim_margin_lo = 300.0 # 5 minutes
1840- timelim_margin = 300.0
1841- timelim = 300.0
1855+ fixed_limit : float | None = context .fixed_timelim if context .fixed_timelim is not None else limits .time_limit
1856+ lower_bound_runtime : float | None = None # The runtime of the slowest submission used to lower bound the time limit.
18421857
1843- if limits .time_limit is not None :
1844- timelim = limits .time_limit
1845- timelim_margin = timelim * safety_margin
1846- if context .fixed_timelim is not None : # It's weird to set this both in limits and in context, but if so, context wins
1847- self .warning (
1848- 'There is a fixed time limit in problem.yaml, and you also provided one on the command line. Using command line.'
1849- )
1850- timelim = context .fixed_timelim
1851- timelim_margin = timelim * safety_margin
1858+ if limits .time_limit is not None and context .fixed_timelim is not None :
1859+ self .warning ('There is a fixed time limit in problem.yaml, and you provided one on command line. Using command line.' )
18521860
18531861 for verdict in Submissions ._VERDICTS :
18541862 acr = verdict [0 ]
@@ -1873,44 +1881,32 @@ def check(self, context: Context) -> bool:
18731881 self .error (f'Compile error for { acr } submission { sub } ' , additional_info = msg )
18741882 continue
18751883
1876- res = self .check_submission (sub , context , acr , timelim , timelim_margin_lo , timelim_margin )
1884+ timelim , timelim_high = self ._compute_time_limit (fixed_limit , lower_bound_runtime )
1885+ res = self .check_submission (sub , context , acr , timelim , timelim_high )
18771886 runtimes .append (res .runtime )
18781887
18791888 if acr == 'AC' :
18801889 if len (runtimes ) > 0 :
1881- max_runtime = max (runtimes )
1882- exact_timelim = max_runtime * time_multiplier
1883- max_runtime_str = f'{ max_runtime :.3f} '
1884- timelim = math .ceil (exact_timelim / limits .time_resolution ) * limits .time_resolution
1885- timelim_margin = timelim * safety_margin
1886- # timelim_margin_lo is a bit weird. We use it for partially_accepted, which we want to be roughly as fast as accepted
1887- # solutions. Setting it to the rounded timelim / time_multiplier means that we check that none of the submissions we
1888- # apply this to would have affected the timelim if we based it on them.
1889- timelim_margin_lo = timelim / time_multiplier
1890- else :
1891- max_runtime_str = None
1890+ lower_bound_runtime = max (runtimes )
1891+
1892+ # Helper function to format numbers with at most 3 decimals and dealing with None
1893+ def _f_n (number : float | None ) -> str :
1894+ return f'{ round (number , 3 ):g} ' if number is not None else '-'
18921895
1893- if limits . time_limit is not None :
1894- if max_runtime > limits . time_limit / time_multiplier :
1896+ if fixed_limit is not None and lower_bound_runtime is not None :
1897+ if lower_bound_runtime * ac_to_time_limit > fixed_limit :
18951898 self .error (
1896- f'Time limit set to { limits . time_limit } , but slowest AC runs in { max_runtime_str } which is within a factor { time_multiplier } .'
1899+ f'Time limit fixed to { _f_n ( fixed_limit ) } , but slowest AC runs in { _f_n ( lower_bound_runtime ) } which is within a factor { _f_n ( ac_to_time_limit ) } .'
18971900 )
1898- if not math .isclose (limits .time_limit , timelim ):
1901+ tl_from_subs , _ = self ._compute_time_limit (None , lower_bound_runtime )
1902+ if not math .isclose (fixed_limit , tl_from_subs ):
18991903 self .msg (
1900- f' Solutions give timelim of { timelim } seconds, but will use provided fixed limit of { limits . time_limit } seconds instead'
1904+ f' Solutions give timelim of { _f_n ( tl_from_subs ) } seconds, but will use provided fixed limit of { _f_n ( fixed_limit ) } seconds instead'
19011905 )
1902- timelim = limits .time_limit
1903- timelim_margin = timelim * safety_margin
1904-
1905- if context .fixed_timelim is not None and not math .isclose (context .fixed_timelim , timelim ):
1906- self .msg (
1907- f' Solutions give timelim of { timelim } seconds, but will use provided fixed limit of { context .fixed_timelim } seconds instead'
1908- )
1909- timelim = context .fixed_timelim
1910- timelim_margin = timelim * safety_margin
19111906
1907+ timelim , timelim_margin = self ._compute_time_limit (fixed_limit , lower_bound_runtime )
19121908 self .msg (
1913- f' Slowest AC runtime: { max_runtime_str } , setting timelim to { timelim } secs, safety margin to { timelim_margin } secs'
1909+ f' Slowest AC runtime: { _f_n ( lower_bound_runtime ) } , setting timelim to { _f_n ( timelim ) } secs, safety margin to { _f_n ( timelim_margin ) } secs'
19141910 )
19151911 self .problem ._set_timelim (timelim )
19161912
0 commit comments