rbx.cp 0.13.8__py3-none-any.whl → 0.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. rbx/box/cli.py +74 -70
  2. rbx/box/code.py +3 -0
  3. rbx/box/contest/build_contest_statements.py +65 -23
  4. rbx/box/contest/contest_package.py +8 -1
  5. rbx/box/contest/main.py +9 -3
  6. rbx/box/contest/schema.py +17 -13
  7. rbx/box/contest/statements.py +12 -8
  8. rbx/box/dump_schemas.py +2 -1
  9. rbx/box/environment.py +1 -1
  10. rbx/box/fields.py +22 -4
  11. rbx/box/generators.py +32 -13
  12. rbx/box/limits_info.py +161 -0
  13. rbx/box/package.py +18 -1
  14. rbx/box/packaging/boca/boca_language_utils.py +26 -0
  15. rbx/box/packaging/boca/boca_outcome_utils.py +10 -0
  16. rbx/box/packaging/boca/packager.py +7 -5
  17. rbx/box/packaging/contest_main.py +20 -12
  18. rbx/box/packaging/packager.py +24 -14
  19. rbx/box/packaging/polygon/packager.py +7 -3
  20. rbx/box/packaging/polygon/upload.py +2 -1
  21. rbx/box/presets/__init__.py +64 -64
  22. rbx/box/remote.py +3 -3
  23. rbx/box/sanitizers/issue_stack.py +124 -0
  24. rbx/box/schema.py +87 -27
  25. rbx/box/solutions.py +74 -117
  26. rbx/box/statements/build_statements.py +12 -1
  27. rbx/box/statements/builders.py +5 -3
  28. rbx/box/statements/latex_jinja.py +73 -23
  29. rbx/box/statements/schema.py +7 -9
  30. rbx/box/stressing/generator_parser.py +3 -1
  31. rbx/box/tasks.py +10 -10
  32. rbx/box/testcase_extractors.py +8 -0
  33. rbx/box/testing/testing_preset.py +129 -2
  34. rbx/box/testing/testing_shared.py +3 -1
  35. rbx/box/timing.py +305 -0
  36. rbx/box/tooling/boca/debug_utils.py +88 -0
  37. rbx/box/tooling/boca/manual_scrape.py +20 -0
  38. rbx/box/tooling/boca/scraper.py +660 -57
  39. rbx/box/unit.py +0 -2
  40. rbx/box/validators.py +0 -4
  41. rbx/grading/judge/cacher.py +36 -0
  42. rbx/grading/judge/program.py +12 -2
  43. rbx/grading/judge/sandbox.py +1 -1
  44. rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -1
  45. rbx/grading/judge/storage.py +36 -3
  46. rbx/grading/limits.py +4 -0
  47. rbx/grading/steps.py +3 -2
  48. rbx/resources/presets/default/contest/contest.rbx.yml +7 -1
  49. rbx/resources/presets/default/contest/statement/info.rbx.tex +54 -0
  50. rbx/resources/presets/default/problem/.gitignore +1 -0
  51. rbx/resources/presets/default/problem/problem.rbx.yml +19 -3
  52. rbx/resources/presets/default/problem/rbx.h +52 -5
  53. rbx/resources/presets/default/problem/statement/statement.rbx.tex +6 -2
  54. rbx/resources/presets/default/problem/testlib.h +6299 -0
  55. rbx/resources/presets/default/problem/validator.cpp +4 -3
  56. rbx/resources/presets/default/shared/contest_template.rbx.tex +8 -4
  57. rbx/resources/presets/default/shared/icpc.sty +16 -1
  58. rbx/resources/presets/default/shared/problem_template.rbx.tex +4 -1
  59. rbx/testing_utils.py +17 -1
  60. {rbx_cp-0.13.8.dist-info → rbx_cp-0.14.0.dist-info}/METADATA +4 -2
  61. {rbx_cp-0.13.8.dist-info → rbx_cp-0.14.0.dist-info}/RECORD +65 -62
  62. {rbx_cp-0.13.8.dist-info → rbx_cp-0.14.0.dist-info}/WHEEL +1 -1
  63. {rbx_cp-0.13.8.dist-info → rbx_cp-0.14.0.dist-info}/entry_points.txt +0 -1
  64. rbx/providers/__init__.py +0 -43
  65. rbx/providers/codeforces.py +0 -73
  66. rbx/providers/provider.py +0 -26
  67. rbx/submitors/__init__.py +0 -18
  68. rbx/submitors/codeforces.py +0 -121
  69. rbx/submitors/submitor.py +0 -25
  70. /rbx/resources/presets/default/problem/sols/{wa.cpp → wa-overflow.cpp} +0 -0
  71. {rbx_cp-0.13.8.dist-info → rbx_cp-0.14.0.dist-info}/LICENSE +0 -0
rbx/box/schema.py CHANGED
@@ -3,13 +3,14 @@ from __future__ import annotations
3
3
  import os
4
4
  import pathlib
5
5
  import re
6
+ import typing
6
7
  from typing import Annotated, Any, Dict, List, Optional
7
8
 
8
9
  from pydantic import AfterValidator, BaseModel, ConfigDict, Field, model_validator
9
10
  from pydantic_core import PydanticCustomError
10
11
 
11
12
  from rbx.autoenum import AutoEnum, alias
12
- from rbx.box.fields import NameField, Primitive, expand_vars
13
+ from rbx.box.fields import NameField, Primitive, RecVars, Vars, expand_vars
13
14
  from rbx.box.statements.expander import expand_statements
14
15
  from rbx.box.statements.schema import Statement
15
16
  from rbx.grading.steps import Outcome
@@ -52,7 +53,7 @@ def convert_to_primitive(value: Any) -> Primitive:
52
53
 
53
54
  def expand_any_vars(vars: Dict[str, Any]) -> Dict[str, Primitive]:
54
55
  converted_vars = {key: convert_to_primitive(value) for key, value in vars.items()}
55
- return expand_vars(converted_vars)
56
+ return expand_vars(typing.cast(RecVars, converted_vars))
56
57
 
57
58
 
58
59
  def is_unique_by_name(statements: List['Statement']) -> List['Statement']:
@@ -330,6 +331,26 @@ problems that have points.
330
331
  """,
331
332
  )
332
333
 
334
+ model_solution: Optional[CodeItem] = Field(
335
+ default=None,
336
+ description="""
337
+ The solution to be used to generate outputs for this testgroup.
338
+
339
+ Can only be set for the "samples" testgroup.
340
+ """,
341
+ )
342
+
343
+ @model_validator(mode='after')
344
+ def check_model_solution_for_samples(self):
345
+ if self.name == 'samples':
346
+ return self
347
+ if self.model_solution is not None:
348
+ raise PydanticCustomError(
349
+ 'MODEL_SOLUTION_NOT_ALLOWED',
350
+ 'Model solution can only be set for the "samples" testgroup.',
351
+ )
352
+ return self
353
+
333
354
 
334
355
  class Generator(CodeItem):
335
356
  model_config = ConfigDict(extra='forbid')
@@ -419,6 +440,68 @@ class UnitTests(BaseModel):
419
440
  )
420
441
 
421
442
 
443
+ class LimitsProfile(BaseModel):
444
+ model_config = ConfigDict(extra='forbid')
445
+
446
+ inheritFromPackage: bool = Field(
447
+ default=False,
448
+ description="""
449
+ Whether to inherit limits from the package.
450
+ """,
451
+ )
452
+
453
+ timeLimit: Optional[int] = Field(
454
+ default=None, description='Time limit of the problem, in milliseconds.'
455
+ )
456
+
457
+ memoryLimit: Optional[int] = Field(
458
+ default=None, description='Memory limit of the problem, in MB.'
459
+ )
460
+
461
+ outputLimit: Optional[int] = Field(
462
+ default=None, description='Output limit of the problem, in KB.'
463
+ )
464
+
465
+ modifiers: Dict[str, LimitModifiers] = Field(
466
+ default={},
467
+ description="""
468
+ Limit modifiers that can be specified per language.
469
+ """,
470
+ )
471
+
472
+ formula: Optional[str] = Field(
473
+ default=None,
474
+ description="""
475
+ A formula to estimate the time limit for the problem.
476
+ """,
477
+ )
478
+
479
+ def timelimit_for_language(self, language: Optional[str] = None) -> int:
480
+ assert self.timeLimit is not None
481
+ res = self.timeLimit
482
+ if language is not None and language in self.modifiers:
483
+ modifier = self.modifiers[language]
484
+ if modifier.time is not None:
485
+ res = modifier.time
486
+ if modifier.timeMultiplier is not None:
487
+ res = int(res * float(modifier.timeMultiplier))
488
+ if 'RBX_TIME_MULTIPLIER' in os.environ:
489
+ res = int(res * float(os.environ['RBX_TIME_MULTIPLIER']))
490
+ return res
491
+
492
+ def memorylimit_for_language(self, language: Optional[str] = None) -> int:
493
+ assert self.memoryLimit is not None
494
+ res = self.memoryLimit
495
+ if language is None:
496
+ return res
497
+ if language not in self.modifiers:
498
+ return res
499
+ modifier = self.modifiers[language]
500
+ if modifier.memory is not None:
501
+ return modifier.memory
502
+ return res
503
+
504
+
422
505
  class Package(BaseModel):
423
506
  model_config = ConfigDict(extra='forbid')
424
507
 
@@ -486,7 +569,7 @@ that is correct and used as reference -- and should have the `accepted` outcome.
486
569
  # Vars to be re-used across the package.
487
570
  # - It will be passed as --key=value arguments to the validator.
488
571
  # - It will be available as \VAR{key} variables in the rbx statement.
489
- vars: Dict[str, Primitive] = Field(
572
+ vars: RecVars = Field(
490
573
  default={}, description='Variables to be re-used across the package.'
491
574
  )
492
575
 
@@ -500,32 +583,9 @@ that is correct and used as reference -- and should have the `accepted` outcome.
500
583
  return expand_statements(self.statements)
501
584
 
502
585
  @property
503
- def expanded_vars(self) -> Dict[str, Primitive]:
586
+ def expanded_vars(self) -> Vars:
504
587
  return expand_vars(self.vars)
505
588
 
506
- def timelimit_for_language(self, language: Optional[str]) -> int:
507
- res = self.timeLimit
508
- if language is not None and language in self.modifiers:
509
- modifier = self.modifiers[language]
510
- if modifier.time is not None:
511
- res = modifier.time
512
- if modifier.timeMultiplier is not None:
513
- res = int(res * float(modifier.timeMultiplier))
514
- if 'RBX_TIME_MULTIPLIER' in os.environ:
515
- res = int(res * float(os.environ['RBX_TIME_MULTIPLIER']))
516
- return res
517
-
518
- def memorylimit_for_language(self, language: Optional[str]) -> int:
519
- res = self.memoryLimit
520
- if language is None:
521
- return res
522
- if language not in self.modifiers:
523
- return res
524
- modifier = self.modifiers[language]
525
- if modifier.memory is not None:
526
- return modifier.memory
527
- return res
528
-
529
589
  @model_validator(mode='after')
530
590
  def check_first_solution_is_main_if_there_is_ac(self):
531
591
  if all(sol.outcome != Outcome.ACCEPTED for sol in self.solutions):
rbx/box/solutions.py CHANGED
@@ -5,7 +5,7 @@ import dataclasses
5
5
  import pathlib
6
6
  import shutil
7
7
  from collections.abc import Iterator
8
- from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
8
+ from typing import Dict, Iterable, List, Optional, Set, Tuple
9
9
 
10
10
  import rich
11
11
  import rich.live
@@ -17,7 +17,7 @@ from ordered_set import OrderedSet
17
17
  from pydantic import BaseModel
18
18
 
19
19
  from rbx import console, utils
20
- from rbx.box import checkers, environment, package, remote, state
20
+ from rbx.box import checkers, code, limits_info, package, remote, state
21
21
  from rbx.box.code import (
22
22
  SanitizationLevel,
23
23
  compile_item,
@@ -34,6 +34,7 @@ from rbx.box.generators import (
34
34
  generate_output_for_testcase,
35
35
  generate_standalone,
36
36
  )
37
+ from rbx.box.sanitizers import issue_stack
37
38
  from rbx.box.schema import (
38
39
  ExpectedOutcome,
39
40
  GeneratorCall,
@@ -92,6 +93,20 @@ class SolutionReportSkeleton(BaseModel):
92
93
  verification: VerificationLevel
93
94
  capture_pipes: bool = False
94
95
 
96
+ def get_solution_limits(self, solution: Solution) -> Limits:
97
+ lang = code.find_language_name(solution)
98
+ if lang is None:
99
+ return limits_info.get_package_limits(self.verification)
100
+ return self.limits[lang]
101
+
102
+ def get_solution_limits_from_disk(self, solution: Solution) -> Limits:
103
+ lang = code.find_language_name(solution)
104
+ return limits_info.get_limits(
105
+ language=lang,
106
+ profile=self.get_solution_limits(solution).profile,
107
+ verification=self.verification,
108
+ )
109
+
95
110
  def find_group_skeleton(self, group_name: str) -> Optional[GroupSkeleton]:
96
111
  groups = [group for group in self.groups if group.name == group_name]
97
112
  if not groups:
@@ -131,6 +146,17 @@ class RunSolutionResult:
131
146
  return self.skeleton.empty_structured_evaluation()
132
147
 
133
148
 
149
+ class FailedSolutionIssue(issue_stack.Issue):
150
+ def __init__(self, solution: Solution):
151
+ self.solution = solution
152
+
153
+ def get_detailed_section(self) -> Tuple[str, ...]:
154
+ return ('solutions',)
155
+
156
+ def get_detailed_message(self) -> str:
157
+ return f'[item]{href(self.solution.path)}[/item] has an unexpected outcome.'
158
+
159
+
134
160
  def is_fast(solution: Solution) -> bool:
135
161
  # If solution has TLE tag, it is considered slow.
136
162
  return not solution.outcome.is_slow()
@@ -535,11 +561,13 @@ async def _generate_testcase_interactively(
535
561
  if progress:
536
562
  progress.update('Generating output for test...')
537
563
  # TODO: Add stderr path
538
- await generate_output_for_testcase(
539
- main_solution_digest,
540
- testcase,
541
- interactor_digest=interactor_digest,
542
- )
564
+ if main_solution is not None:
565
+ await generate_output_for_testcase(
566
+ main_solution,
567
+ main_solution_digest,
568
+ testcase,
569
+ interactor_digest=interactor_digest,
570
+ )
543
571
 
544
572
  if check and testcase.outputPath is not None and not testcase.outputPath.is_file():
545
573
  # Output was not created, throw an error.
@@ -882,10 +910,10 @@ def get_evals_formatted_time(evals: List[Evaluation]) -> str:
882
910
 
883
911
 
884
912
  def get_capped_evals_formatted_time(
885
- solution: Solution, evals: List[Evaluation], verification: VerificationLevel
913
+ limits: Limits,
914
+ evals: List[Evaluation],
915
+ verification: VerificationLevel,
886
916
  ) -> str:
887
- pkg = package.find_problem_package_or_die()
888
-
889
917
  max_time = _get_evals_time_in_ms(evals)
890
918
  has_tle = any(eval.result.outcome.is_slow() for eval in evals)
891
919
  has_ile = any(
@@ -902,13 +930,13 @@ def get_capped_evals_formatted_time(
902
930
  if timelimits:
903
931
  tl = min(timelimits)
904
932
  if tl is None:
905
- tl = pkg.timelimit_for_language(solution.language)
933
+ tl = limits.time
906
934
 
907
- if verification.value >= VerificationLevel.FULL.value:
935
+ if tl is not None and verification.value >= VerificationLevel.FULL.value:
908
936
  # Using double TL for verification.
909
937
  tl = tl * 2
910
938
 
911
- if has_tle and max_time >= tl or has_ile:
939
+ if tl is not None and has_tle and max_time >= tl or has_ile:
912
940
  return f'>{tl} ms'
913
941
  return f'{max_time} ms'
914
942
 
@@ -930,6 +958,7 @@ def get_truncated_message(message: str, max_length: int = 100) -> str:
930
958
 
931
959
  class SolutionOutcomeReport(BaseModel):
932
960
  solution: Solution
961
+ limits: Limits
933
962
  evals: List[Evaluation]
934
963
  ok: bool
935
964
  message: Optional[Tuple[TestcaseEntry, str]]
@@ -972,7 +1001,7 @@ class SolutionOutcomeReport(BaseModel):
972
1001
 
973
1002
  def get_outcome_markup(self, print_message: bool = True) -> str:
974
1003
  res = self.get_verdict_markup_with_warnings()
975
- res += f'\nTime: {get_capped_evals_formatted_time(self.solution, self.evals, self.verification)}'
1004
+ res += f'\nTime: {get_capped_evals_formatted_time(self.limits, self.evals, self.verification)}'
976
1005
  res += f'\nMemory: {get_evals_formatted_memory(self.evals)}'
977
1006
  if print_message and self.message is not None:
978
1007
  tc, msg = self.message
@@ -989,8 +1018,6 @@ def get_solution_outcome_report(
989
1018
  verification: VerificationLevel = VerificationLevel.NONE,
990
1019
  subset: bool = False,
991
1020
  ) -> SolutionOutcomeReport:
992
- pkg = package.find_problem_package_or_die()
993
-
994
1021
  has_plain_tle = False
995
1022
  all_verdicts = set()
996
1023
  bad_verdicts = set()
@@ -1051,6 +1078,7 @@ def get_solution_outcome_report(
1051
1078
 
1052
1079
  evals_time = _get_evals_time_in_ms(evals)
1053
1080
  expected_outcome_is_tle = solution.outcome.matches_tle_and_is_incorrect()
1081
+ limits = skeleton.get_solution_limits(solution)
1054
1082
  if (
1055
1083
  # Running verification with double TL.
1056
1084
  verification.value >= VerificationLevel.FULL.value
@@ -1061,7 +1089,8 @@ def get_solution_outcome_report(
1061
1089
  # A TLE has happened.
1062
1090
  and Outcome.TIME_LIMIT_EXCEEDED in matched_bad_verdicts
1063
1091
  # The solution runs in double TL.
1064
- and evals_time < pkg.timelimit_for_language(solution.language) * 2
1092
+ and limits.time is not None
1093
+ and evals_time < limits.time * 2
1065
1094
  ):
1066
1095
  other_verdicts = (bad_verdicts | no_tle_bad_verdicts) - {
1067
1096
  Outcome.TIME_LIMIT_EXCEEDED
@@ -1078,6 +1107,7 @@ def get_solution_outcome_report(
1078
1107
 
1079
1108
  return SolutionOutcomeReport(
1080
1109
  solution=solution,
1110
+ limits=skeleton.get_solution_limits(solution),
1081
1111
  evals=evals,
1082
1112
  ok=not has_failed,
1083
1113
  message=message,
@@ -1102,11 +1132,13 @@ def _print_solution_outcome(
1102
1132
  report = get_solution_outcome_report(
1103
1133
  solution, skeleton, evals, verification, subset
1104
1134
  )
1135
+ if not report.ok:
1136
+ issue_stack.add_issue(FailedSolutionIssue(solution))
1105
1137
  console.print(report.get_outcome_markup(print_message))
1106
1138
  return report.ok
1107
1139
 
1108
1140
 
1109
- def _consume_and_key_evaluation_items(
1141
+ def consume_and_key_evaluation_items(
1110
1142
  items: Iterable[EvaluationItem],
1111
1143
  skeleton: SolutionReportSkeleton,
1112
1144
  ) -> StructuredEvaluation:
@@ -1169,9 +1201,7 @@ async def _print_timing(
1169
1201
  console: rich.console.Console,
1170
1202
  skeleton: SolutionReportSkeleton,
1171
1203
  evaluations: StructuredEvaluation,
1172
- verification: VerificationLevel,
1173
1204
  ):
1174
- pkg = package.find_problem_package_or_die()
1175
1205
  summary = TimingSummary()
1176
1206
  summary_per_language = collections.defaultdict(TimingSummary)
1177
1207
  tls_per_language = {}
@@ -1196,8 +1226,12 @@ async def _print_timing(
1196
1226
  if solution_tls:
1197
1227
  solution_tl = min(solution_tls)
1198
1228
  else:
1199
- solution_tl = pkg.timelimit_for_language(solution.language)
1200
- if verification.value >= VerificationLevel.FULL.value:
1229
+ limits = skeleton.get_solution_limits(solution)
1230
+ if limits.time is None:
1231
+ limits = skeleton.get_solution_limits_from_disk(solution)
1232
+ assert limits.time is not None
1233
+ solution_tl = limits.time
1234
+ if limits.isDoubleTL:
1201
1235
  solution_tl = solution_tl * 2
1202
1236
  all_tls.add(solution_tl)
1203
1237
  for eval in all_evals:
@@ -1306,7 +1340,8 @@ async def _render_detailed_group_table(
1306
1340
  evals_per_solution[str(solution.path)].append(eval)
1307
1341
 
1308
1342
  verdict = get_testcase_markup_verdict(eval)
1309
- time = get_capped_evals_formatted_time(solution, [eval], verification)
1343
+ limits = skeleton.get_solution_limits(solution)
1344
+ time = get_capped_evals_formatted_time(limits, [eval], verification)
1310
1345
  memory = get_evals_formatted_memory([eval])
1311
1346
  full_item = (f'[info]#{tc}[/info]', verdict, time, '/', memory, '')
1312
1347
  if eval.result.sanitizer_warnings:
@@ -1323,8 +1358,9 @@ async def _render_detailed_group_table(
1323
1358
  if not non_null_evals:
1324
1359
  summary_row.append('...')
1325
1360
  continue
1361
+ limits = skeleton.get_solution_limits(solution)
1326
1362
  formatted_time = get_capped_evals_formatted_time(
1327
- solution, non_null_evals, verification
1363
+ limits, non_null_evals, verification
1328
1364
  )
1329
1365
  formatted_memory = get_evals_formatted_memory(non_null_evals)
1330
1366
  worst_outcome = get_worst_outcome(non_null_evals)
@@ -1398,7 +1434,9 @@ async def _print_detailed_run_report(
1398
1434
 
1399
1435
  if timing:
1400
1436
  await _print_timing(
1401
- console, result.skeleton, structured_evaluations, verification=verification
1437
+ console,
1438
+ result.skeleton,
1439
+ structured_evaluations,
1402
1440
  )
1403
1441
  return ok
1404
1442
 
@@ -1408,7 +1446,10 @@ def _print_limits(limits: Dict[str, Limits]):
1408
1446
  '[bold][success]Running with the following limits (per language):[/success][/bold]'
1409
1447
  )
1410
1448
  for lang, limit in limits.items():
1411
- console.console.print(f'[bold][status]{lang}[/status][/bold]')
1449
+ extracted_from = ' (extracted from package)'
1450
+ if limit.profile:
1451
+ extracted_from = f' (extracted from profile [item]{limit.profile}[/item])'
1452
+ console.console.print(f'[bold][status]{lang}[/status][/bold]{extracted_from}')
1412
1453
  time = (
1413
1454
  '<No time limit>' if limit.time is None else get_formatted_time(limit.time)
1414
1455
  )
@@ -1435,7 +1476,7 @@ async def print_run_report(
1435
1476
  if not skip_printing_limits:
1436
1477
  _print_limits(result.skeleton.limits)
1437
1478
 
1438
- structured_evaluations = _consume_and_key_evaluation_items(
1479
+ structured_evaluations = consume_and_key_evaluation_items(
1439
1480
  result.items, result.skeleton
1440
1481
  )
1441
1482
  if detailed:
@@ -1455,6 +1496,7 @@ async def print_run_report(
1455
1496
  if single_solution:
1456
1497
  console.print()
1457
1498
  solution_evals = []
1499
+ limits = result.skeleton.get_solution_limits(solution)
1458
1500
  for group in result.skeleton.groups:
1459
1501
  if not single_solution:
1460
1502
  console.print(f'[bold][status]{group.name}[/status][/bold] ', end='')
@@ -1469,9 +1511,7 @@ async def print_run_report(
1469
1511
  console.print(f'{group.name}/{i}', end='')
1470
1512
  if eval.result.sanitizer_warnings:
1471
1513
  console.print('[warning]*[/warning]', end='')
1472
- time = get_capped_evals_formatted_time(
1473
- solution, [eval], verification
1474
- )
1514
+ time = get_capped_evals_formatted_time(limits, [eval], verification)
1475
1515
  memory = get_evals_formatted_memory([eval])
1476
1516
  console.print(f' ({time}, {memory})', end='')
1477
1517
  checker_msg = eval.result.message
@@ -1493,7 +1533,7 @@ async def print_run_report(
1493
1533
  if single_solution:
1494
1534
  console.print(f' [status]{group.name}[/status]', end=' ')
1495
1535
  console.print(
1496
- f'({get_capped_evals_formatted_time(solution, group_evals, verification)}, {get_evals_formatted_memory(group_evals)})',
1536
+ f'({get_capped_evals_formatted_time(limits, group_evals, verification)}, {get_evals_formatted_memory(group_evals)})',
1497
1537
  end='',
1498
1538
  )
1499
1539
  console.print()
@@ -1513,92 +1553,9 @@ async def print_run_report(
1513
1553
 
1514
1554
  if not single_solution:
1515
1555
  await _print_timing(
1516
- console, result.skeleton, structured_evaluations, verification=verification
1556
+ console,
1557
+ result.skeleton,
1558
+ structured_evaluations,
1517
1559
  )
1518
1560
 
1519
1561
  return ok
1520
-
1521
-
1522
- def _step_up(x: Any, step: int) -> int:
1523
- x = int(x)
1524
- return (x + step - 1) // step * step
1525
-
1526
-
1527
- def _step_down(x: Any, step: int) -> int:
1528
- x = int(x)
1529
- return x // step * step
1530
-
1531
-
1532
- async def estimate_time_limit(
1533
- console: rich.console.Console,
1534
- result: RunSolutionResult,
1535
- ) -> Optional[int]:
1536
- structured_evaluations = _consume_and_key_evaluation_items(
1537
- result.items, result.skeleton
1538
- )
1539
-
1540
- timing_per_solution = {}
1541
- timing_per_language = {}
1542
-
1543
- if not result.skeleton.solutions:
1544
- console.print('[error]No solutions to estimate time limit from.[/error]')
1545
- return None
1546
-
1547
- for solution in result.skeleton.solutions:
1548
- timings = []
1549
- for evals in structured_evaluations[str(solution.path)].values():
1550
- for ev in evals:
1551
- if ev is None:
1552
- continue
1553
- ev = await ev()
1554
- if ev.log.time is not None:
1555
- timings.append(int(ev.log.time * 1000))
1556
-
1557
- if not timings:
1558
- console.print(
1559
- f'[warning]No timings for solution {href(solution.path)}.[/warning]'
1560
- )
1561
- continue
1562
-
1563
- timing_per_solution[str(solution.path)] = max(timings)
1564
- lang = find_language_name(solution)
1565
- if lang not in timing_per_language:
1566
- timing_per_language[lang] = 0
1567
- timing_per_language[lang] = max(timing_per_language[lang], max(timings))
1568
-
1569
- console.rule('[status]Time estimation[/status]', style='status')
1570
-
1571
- fastest_time = min(timing_per_solution.values())
1572
- slowest_time = max(timing_per_solution.values())
1573
-
1574
- console.print(f'Fastest solution: {fastest_time} ms')
1575
- console.print(f'Slowest solution: {slowest_time} ms')
1576
-
1577
- if len(timing_per_language) > 0:
1578
- timing_language_list = [(t, lang) for lang, t in timing_per_language.items()]
1579
- fastest_language_time, fastest_language = min(timing_language_list)
1580
- slowest_language_time, slowest_language = max(timing_language_list)
1581
-
1582
- console.print(
1583
- f'Fastest language: {fastest_language} ({fastest_language_time} ms)'
1584
- )
1585
- console.print(
1586
- f'Slowest language: {slowest_language} ({slowest_language_time} ms)'
1587
- )
1588
-
1589
- env = environment.get_environment()
1590
- estimated_tl = int(
1591
- eval(
1592
- env.timing.formula,
1593
- {
1594
- 'fastest': fastest_time,
1595
- 'slowest': slowest_time,
1596
- 'step_up': _step_up,
1597
- 'step_down': _step_down,
1598
- },
1599
- )
1600
- )
1601
-
1602
- console.print(f'Using formula: {env.timing.formula}')
1603
- console.print(f'[success]Estimated time limit:[/success] {estimated_tl} ms')
1604
- return estimated_tl
@@ -7,7 +7,7 @@ import syncer
7
7
  import typer
8
8
 
9
9
  from rbx import annotations, console, utils
10
- from rbx.box import environment, naming, package
10
+ from rbx.box import environment, limits_info, naming, package
11
11
  from rbx.box.formatting import href
12
12
  from rbx.box.schema import Package, expand_any_vars
13
13
  from rbx.box.statements.builders import (
@@ -264,6 +264,9 @@ def build_statement_bytes(
264
264
  root=pathlib.Path(td),
265
265
  ),
266
266
  item=StatementBuilderProblem(
267
+ limits=limits_info.get_limits_profile(
268
+ profile=limits_info.get_active_profile()
269
+ ),
267
270
  package=pkg,
268
271
  statement=statement,
269
272
  samples=StatementSample.from_testcases(
@@ -302,6 +305,14 @@ def build_statement(
302
305
  statement_path = (package.get_build_path() / statement.name).with_suffix(
303
306
  last_output.get_file_suffix()
304
307
  )
308
+ active_profile = limits_info.get_active_profile()
309
+ if (
310
+ active_profile is not None
311
+ and limits_info.get_saved_limits_profile(active_profile) is not None
312
+ ):
313
+ statement_path = statement_path.with_stem(
314
+ f'{statement_path.stem}-{active_profile}'
315
+ )
305
316
  statement_path.parent.mkdir(parents=True, exist_ok=True)
306
317
  statement_path.write_bytes(last_content)
307
318
  console.console.print(
@@ -12,7 +12,7 @@ from pydantic import BaseModel
12
12
 
13
13
  from rbx import console, utils
14
14
  from rbx.box.fields import Primitive
15
- from rbx.box.schema import Package, Testcase
15
+ from rbx.box.schema import LimitsProfile, Package, Testcase
16
16
  from rbx.box.statements.latex_jinja import (
17
17
  JinjaDictWrapper,
18
18
  render_latex_template,
@@ -115,6 +115,7 @@ class ExplainedStatementSample(StatementSample):
115
115
  class StatementBuilderProblem(StatementBuilderItem):
116
116
  package: Package
117
117
  statement: Statement
118
+ limits: LimitsProfile
118
119
  samples: List[StatementSample] = dataclasses.field(default_factory=list)
119
120
  short_name: Optional[str] = None
120
121
 
@@ -128,8 +129,9 @@ class StatementBuilderProblem(StatementBuilderItem):
128
129
  'package': self.package,
129
130
  'statement': self.statement,
130
131
  'samples': self.samples,
131
- 'vars': JinjaDictWrapper(self.vars or {}, key='vars'),
132
+ 'vars': JinjaDictWrapper.from_dict(self.vars or {}, wrapper_key='vars'),
132
133
  'title': self.statement.title or self.package.name,
134
+ 'limits': self.limits,
133
135
  }
134
136
  if self.short_name is not None:
135
137
  kwargs['short_name'] = self.short_name
@@ -166,7 +168,7 @@ class StatementBuilderContest(StatementBuilderItem):
166
168
  'problems': [
167
169
  problem.build_inner_jinja_kwargs() for problem in self.problems
168
170
  ],
169
- 'vars': JinjaDictWrapper(self.vars or {}, key='vars'),
171
+ 'vars': JinjaDictWrapper.from_dict(self.vars or {}, wrapper_key='vars'),
170
172
  }
171
173
  return res
172
174