Skip to content

Problem schema#

problem.rbx.yml#

Checker #

Bases: CodeItem

Parameters:

Name Type Description Default
fallback_to Checker | None

Checker to fall back to if the mainly specified checker does not exist.

None
mode Literal['testlib', 'boca']

In which compatibility mode the checker should be run.

'testlib'
Source code in rbx/box/schema.py
class Checker(CodeItem):
    model_config = ConfigDict(extra='forbid')

    fallback_to: Optional[Checker] = Field(
        default=None,
        description="""Checker to fall back to if the mainly specified checker does not exist.""",
    )

    mode: Literal['testlib', 'boca'] = Field(
        default='testlib',
        description="""In which compatibility mode the checker should be run.""",
    )

CheckerTest #

Bases: BaseModel

Parameters:

Name Type Description Default
glob str | None

A glob pattern for the files to be used as unit test input for the checker.

None
testplan Path | None

A testplan to be used as unit test input for the checker.

None
outcome ExpectedOutcome | None

The expected outcome of the checker.

None
Source code in rbx/box/schema.py
class CheckerTest(BaseModel):
    model_config = ConfigDict(extra='forbid')

    glob: Optional[str] = Field(
        default=None,
        description='A glob pattern for the files to be used as unit test input for the checker.',
    )

    testplan: Optional[pathlib.Path] = Field(
        default=None,
        description='A testplan to be used as unit test input for the checker.',
    )

    outcome: Optional[ExpectedOutcome] = Field(
        default=None,
        description='The expected outcome of the checker.',
    )

    @model_validator(mode='after')
    def check_oneof(self):
        if self.glob is None and self.testplan is None:
            raise PydanticCustomError(
                'GLOB_OR_TESTPLAN_REQUIRED',
                'Either a glob or a testplan must be specified.',
            )
        if self.glob is not None and self.testplan is not None:
            raise PydanticCustomError(
                'GLOB_AND_TESTPLAN_NOT_ALLOWED',
                'Either a glob or a testplan must be specified, but not both.',
            )
        return self

    @model_validator(mode='after')
    def check_testplan(self):
        if self.testplan is not None and self.outcome is not None:
            raise PydanticCustomError(
                'OUTCOME_NOT_ALLOWED',
                'Outcome is not allowed for testplan checker tests.',
            )
        return self

    @model_validator(mode='after')
    def check_glob(self):
        if self.glob is not None and self.outcome is None:
            raise PydanticCustomError(
                'OUTCOME_REQUIRED',
                'Outcome is required for glob checker tests.',
            )
        return self

CodeItem #

Bases: BaseModel

Parameters:

Name Type Description Default
path Path

The path to the code file, relative to the package directory.

required
language str | None

The language of the code file.

None
compilationFiles List[str] | None

Extra files that should be available during the compilation of the code file, such as testlib.h, jngen.h, tgen.h, etc.

The paths should be given relative to the package directory, and are placed at the same package-relative path inside the sandbox (the package directory structure is mirrored). This means a code file in a subdirectory can include a file from elsewhere in the package via a relative path (e.g. #include "../lib.h").

Third-party libraries such as testlib.h, jngen.h and tgen.h are provided by the preset's libraries configuration (see the preset docs), not auto-injected.

[]
executionFiles List[str] | None

Extra files that should be available at execution time, the runtime equivalent of compilationFiles.

The paths should be given relative to the package directory, and are placed at the same package-relative path inside the sandbox (the package directory structure is mirrored). Use this for runtime companion files a compiled binary or interpreted script needs at run time (data files, sibling modules, etc.).

Sibling Python imports and quoted C++ includes are auto-discovered; this field is the escape hatch for files that cannot be discovered automatically.

[]
Source code in rbx/box/schema.py
class CodeItem(BaseModel):
    model_config = ConfigDict(extra='forbid')

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, CodeItem):
            return NotImplemented
        return self.path == other.path

    def __hash__(self) -> int:
        return hash(self.path)

    path: pathlib.Path = Field(
        description="""The path to the code file, relative to the package directory."""
    )

    language: Optional[str] = Field(
        default=None, description="""The language of the code file."""
    )

    compilationFiles: Optional[List[str]] = Field(
        default=[],
        description="""
Extra files that should be available during the compilation of the code file,
such as testlib.h, jngen.h, tgen.h, etc.

The paths should be given relative to the package directory, and are placed at the
same package-relative path inside the sandbox (the package directory structure is
mirrored). This means a code file in a subdirectory can include a file from
elsewhere in the package via a relative path (e.g. `#include "../lib.h"`).

Third-party libraries such as testlib.h, jngen.h and tgen.h are provided by the
preset's `libraries` configuration (see the preset docs), not auto-injected.
""",
    )

    executionFiles: Optional[List[str]] = Field(
        default=[],
        description="""
Extra files that should be available at *execution* time, the runtime equivalent of
`compilationFiles`.

The paths should be given relative to the package directory, and are placed at the
same package-relative path inside the sandbox (the package directory structure is
mirrored). Use this for runtime companion files a compiled binary or interpreted
script needs at run time (data files, sibling modules, etc.).

Sibling Python imports and quoted C++ includes are auto-discovered; this field is the
escape hatch for files that cannot be discovered automatically.
""",
    )

    def href(self, hyperlink: bool = True) -> str:
        return href(self.path, hyperlink=hyperlink)

    def display(self) -> str:
        return self.href(hyperlink=False)

CodeItemWithDigest #

Bases: CodeItem

Parameters:

Name Type Description Default
digest str

The digest of the code file.

required
Source code in rbx/box/schema.py
class CodeItemWithDigest(CodeItem):
    model_config = ConfigDict(extra='forbid')

    digest: str = Field(description="""The digest of the code file.""")

    @classmethod
    def create(cls, code_item: CodeItem, digest: str) -> 'CodeItemWithDigest':
        return cls(
            path=code_item.path,
            language=code_item.language,
            compilationFiles=code_item.compilationFiles,
            executionFiles=code_item.executionFiles,
            digest=digest,
        )

ExpectedOutcome #

Bases: AutoEnum

Source code in rbx/box/schema.py
class ExpectedOutcome(AutoEnum):
    ANY = alias('any')  # type: ignore
    """Expected outcome for any outcome."""

    ACCEPTED = alias('accepted', 'ac', 'correct')  # type: ignore
    """Expected outcome for correct solutions (AC)."""

    ACCEPTED_OR_TLE = alias(
        'accepted or time limit exceeded',  # type: ignore
        'accepted or tle',
        'ac or tle',
        'ac/tle',
        'ac+tle',
    )  # type: ignore
    """Expected outcome for solutions that finish with either AC or TLE.

    Especially useful when you do not care about the running time of this solution, and
    want it to not be considered when calculating the timelimit for the problem."""

    WRONG_ANSWER = alias('wrong answer', 'wa')  # type: ignore
    """Expected outcome for solutions that finish successfully,
    but the produced output are incorrect (WA)."""

    INCORRECT = alias('fail', 'incorrect')  # type: ignore
    """Expected outcome for solutions that finish with any non-AC verdict."""

    RUNTIME_ERROR = alias('runtime error', 'rte', 're')  # type: ignore
    """Expected outcome solutions that finish with non-zero code (RTE)."""

    TIME_LIMIT_EXCEEDED = alias('time limit exceeded', 'timeout', 'tle', 'tl')  # type: ignore
    """Expected outcome for solutions that do not finish in time."""

    MEMORY_LIMIT_EXCEEDED = alias('memory limit exceeded', 'mle', 'ml')  # type: ignore
    """Expected outcome for solutions that use more memory than allowed."""

    OUTPUT_LIMIT_EXCEEDED = alias('output limit exceeded', 'ole', 'ol')  # type: ignore
    """Expected outcome for solutions that use more output than allowed."""

    TLE_OR_RTE = alias('tle or rte', 'tle/rte', 'tle+rte', 'tle or re', 'tle+re')  # type: ignore
    """Expected outcome for solutions that finish with either TLE or RTE.

    Especially useful for environments where TLE and RTE are indistinguishable."""

    JUDGE_FAILED = alias('judge failed', 'jf')  # type: ignore
    """Expected outcome for solutions that finish with a judge failed verdict.

    Only useful for checker tests."""

    COMPILATION_ERROR = alias('compilation error', 'ce')  # type: ignore
    """Expected outcome for solutions that finish with a compilation error verdict.

    Only useful for checker tests."""

    def style(self) -> str:
        if self == ExpectedOutcome.ANY:
            return 'bold white'
        if self.match(Outcome.ACCEPTED):
            return 'green'
        if self == ExpectedOutcome.WRONG_ANSWER:
            return 'red'
        if self == ExpectedOutcome.INCORRECT:
            return 'red'
        if self.match(Outcome.TIME_LIMIT_EXCEEDED):
            return 'yellow'
        if self.match(Outcome.RUNTIME_ERROR):
            return 'blue'
        if self.match(Outcome.MEMORY_LIMIT_EXCEEDED):
            return 'yellow'
        if self.match(Outcome.COMPILATION_ERROR):
            return 'blue'
        return 'magenta'

    def icon(self) -> str:
        if self == ExpectedOutcome.ANY:
            return '?'
        if self.match(Outcome.ACCEPTED):
            return '✓'
        if self.is_slow():
            return '⧖'
        return '✗'

    def icon_markup(self, styled: bool = True) -> str:
        icon = self.icon()
        if styled:
            style = self.style()
            icon = f'[{style}]{icon}[/{style}]'
        return icon

    def full_style(self) -> str:
        style = self.style()
        if self == ExpectedOutcome.ACCEPTED:
            return f'bold {style}'
        return style

    def full_markup(self, styled: bool = True) -> str:
        icon = self.icon_markup()
        name = self.name
        if styled:
            style = self.style()
            name = f'[{style}]{name}[/{style}]'
        return f'{icon} {name}'

    def is_slow(self) -> bool:
        return self in [ExpectedOutcome.TIME_LIMIT_EXCEEDED, ExpectedOutcome.TLE_OR_RTE]

    def matches_tle_and_is_incorrect(self) -> bool:
        return self.match(Outcome.TIME_LIMIT_EXCEEDED) and not self.match(
            Outcome.ACCEPTED
        )

    def match(self, outcome: Outcome) -> bool:
        if self == ExpectedOutcome.ANY:
            return True
        if self == ExpectedOutcome.COMPILATION_ERROR:
            return outcome == Outcome.COMPILATION_ERROR
        if self == ExpectedOutcome.ACCEPTED:
            return outcome == Outcome.ACCEPTED
        if self == ExpectedOutcome.ACCEPTED_OR_TLE:
            return outcome in {Outcome.ACCEPTED} or outcome.is_slow()
        if self == ExpectedOutcome.WRONG_ANSWER:
            return outcome == Outcome.WRONG_ANSWER
        if self == ExpectedOutcome.INCORRECT:
            return (
                outcome
                in {
                    Outcome.WRONG_ANSWER,
                    Outcome.RUNTIME_ERROR,
                    Outcome.MEMORY_LIMIT_EXCEEDED,
                    Outcome.OUTPUT_LIMIT_EXCEEDED,
                }
                or outcome.is_slow()
            )
        if self == ExpectedOutcome.RUNTIME_ERROR:
            return outcome == Outcome.RUNTIME_ERROR
        if self == ExpectedOutcome.TIME_LIMIT_EXCEEDED:
            return outcome.is_slow()
        if self == ExpectedOutcome.MEMORY_LIMIT_EXCEEDED:
            return outcome == Outcome.MEMORY_LIMIT_EXCEEDED
        if self == ExpectedOutcome.TLE_OR_RTE:
            return outcome in {Outcome.RUNTIME_ERROR} or outcome.is_slow()
        if self == ExpectedOutcome.OUTPUT_LIMIT_EXCEEDED:
            return outcome == Outcome.OUTPUT_LIMIT_EXCEEDED
        if self == ExpectedOutcome.JUDGE_FAILED:
            return outcome == Outcome.JUDGE_FAILED
        return False

    def get_matches(self) -> List[Outcome]:
        return [outcome for outcome in Outcome if self.match(outcome)]

    def intersect(self, rhs: 'ExpectedOutcome') -> bool:
        return bool(set(self.get_matches()) & set(rhs.get_matches()))

ACCEPTED = alias('accepted', 'ac', 'correct') #

Expected outcome for correct solutions (AC).

ACCEPTED_OR_TLE = alias('accepted or time limit exceeded', 'accepted or tle', 'ac or tle', 'ac/tle', 'ac+tle') #

Expected outcome for solutions that finish with either AC or TLE.

Especially useful when you do not care about the running time of this solution, and want it to not be considered when calculating the timelimit for the problem.

ANY = alias('any') #

Expected outcome for any outcome.

COMPILATION_ERROR = alias('compilation error', 'ce') #

Expected outcome for solutions that finish with a compilation error verdict.

Only useful for checker tests.

INCORRECT = alias('fail', 'incorrect') #

Expected outcome for solutions that finish with any non-AC verdict.

JUDGE_FAILED = alias('judge failed', 'jf') #

Expected outcome for solutions that finish with a judge failed verdict.

Only useful for checker tests.

MEMORY_LIMIT_EXCEEDED = alias('memory limit exceeded', 'mle', 'ml') #

Expected outcome for solutions that use more memory than allowed.

OUTPUT_LIMIT_EXCEEDED = alias('output limit exceeded', 'ole', 'ol') #

Expected outcome for solutions that use more output than allowed.

RUNTIME_ERROR = alias('runtime error', 'rte', 're') #

Expected outcome solutions that finish with non-zero code (RTE).

TIME_LIMIT_EXCEEDED = alias('time limit exceeded', 'timeout', 'tle', 'tl') #

Expected outcome for solutions that do not finish in time.

TLE_OR_RTE = alias('tle or rte', 'tle/rte', 'tle+rte', 'tle or re', 'tle+re') #

Expected outcome for solutions that finish with either TLE or RTE.

Especially useful for environments where TLE and RTE are indistinguishable.

WRONG_ANSWER = alias('wrong answer', 'wa') #

Expected outcome for solutions that finish successfully, but the produced output are incorrect (WA).

Generator #

Bases: CodeItem

Parameters:

Name Type Description Default
name str

The name of the generator.

required
Source code in rbx/box/schema.py
class Generator(CodeItem):
    model_config = ConfigDict(extra='forbid')

    name: str = Field(description="""The name of the generator.""")

GeneratorCall #

Bases: BaseModel

Parameters:

Name Type Description Default
name str

The name of the generator to call.

required
args str | None

The arguments to pass to the generator.

None
Source code in rbx/box/schema.py
class GeneratorCall(BaseModel):
    model_config = ConfigDict(extra='forbid')

    name: str = Field(description='The name of the generator to call.')

    args: Optional[str] = Field(
        default=None, description='The arguments to pass to the generator.'
    )

    def __str__(self) -> str:
        return f'{self.name} {self.args}'

GeneratorScript #

Bases: CodeItem

Parameters:

Name Type Description Default
root Path

The root directory where the generators should be fetched from.

<dynamic>
format Literal['rbx', 'box']

The format of the generator script.

'rbx'
Source code in rbx/box/schema.py
class GeneratorScript(CodeItem):
    model_config = ConfigDict(extra='forbid')

    root: pathlib.Path = Field(
        default_factory=pathlib.Path,
        description="""The root directory where the generators should be fetched from.""",
    )

    format: Literal['rbx', 'box'] = Field(
        default='rbx', description="""The format of the generator script."""
    )

Interactor #

Bases: CodeItem

Parameters:

Name Type Description Default
legacy bool

Whether this interactor is a legacy interactor and needs a checker to be specified.

False
capture bool

Whether the interactor should capture the pipes.

True
Source code in rbx/box/schema.py
class Interactor(CodeItem):
    model_config = ConfigDict(extra='forbid')

    legacy: bool = Field(
        default=False,
        description="""
Whether this interactor is a legacy interactor and needs a checker to be specified.
""",
    )

    capture: bool = Field(
        default=True,
        description="""Whether the interactor should capture the pipes.""",
    )

LimitModifiers #

Bases: BaseModel

Parameters:

Name Type Description Default
timeMultiplier float | None

Multiplier for time limit.

None
time int | None

Value to override time limit with, in milliseconds.

None
memory int | None

Value to override memory limit with, in MB.

None
Source code in rbx/box/schema.py
class LimitModifiers(BaseModel):
    timeMultiplier: Optional[float] = Field(
        default=None, description='Multiplier for time limit.'
    )
    time: Optional[int] = Field(
        default=None, description='Value to override time limit with, in milliseconds.'
    )
    memory: Optional[int] = Field(
        default=None, description='Value to override memory limit with, in MB.'
    )

LimitsProfile #

Bases: BaseModel

Parameters:

Name Type Description Default
inheritFromPackage bool

Whether to inherit limits from the package.

False
timeLimit int | None

Time limit of the problem, in milliseconds.

None
memoryLimit int | None

Memory limit of the problem, in MB.

None
outputLimit int | None

Output limit of the problem, in KB.

None
modifiers Dict[str, LimitModifiers]

Limit modifiers that can be specified per language.

{}
formula str | None

A formula to estimate the time limit for the problem.

None
groups List[TimingGroupReport] | None

Metadata describing the language grouping used when this profile was estimated. Presentation-only; never used for limit resolution.

None
baseEstimate TimingGroupReport | None

Metadata describing how the base (fallback) time limit was estimated, pooled across every solution. Presentation-only; never used for limit resolution.

None
Source code in rbx/box/schema.py
class LimitsProfile(BaseModel):
    model_config = ConfigDict(extra='forbid')

    inheritFromPackage: bool = Field(
        default=False,
        description="""
Whether to inherit limits from the package.
""",
    )

    timeLimit: Optional[int] = Field(
        default=None, description='Time limit of the problem, in milliseconds.'
    )

    memoryLimit: Optional[int] = Field(
        default=None, description='Memory limit of the problem, in MB.'
    )

    outputLimit: Optional[int] = Field(
        default=None, description='Output limit of the problem, in KB.'
    )

    modifiers: Dict[str, LimitModifiers] = Field(
        default={},
        description="""
    Limit modifiers that can be specified per language.
    """,
    )

    formula: Optional[str] = Field(
        default=None,
        description="""
A formula to estimate the time limit for the problem.
""",
    )

    groups: Optional[List[TimingGroupReport]] = Field(
        default=None,
        description="""
Metadata describing the language grouping used when this profile was estimated.
Presentation-only; never used for limit resolution.
""",
    )

    baseEstimate: Optional[TimingGroupReport] = Field(
        default=None,
        description="""
Metadata describing how the base (fallback) time limit was estimated, pooled
across every solution. Presentation-only; never used for limit resolution.
""",
    )

    def timelimit_for_language(self, language: Optional[str] = None) -> int:
        assert self.timeLimit is not None
        res = self.timeLimit
        if language is not None and language in self.modifiers:
            modifier = self.modifiers[language]
            if modifier.time is not None:
                res = modifier.time
            if modifier.timeMultiplier is not None:
                res = int(res * float(modifier.timeMultiplier))
        if 'RBX_TIME_MULTIPLIER' in utils.environ():
            res = int(res * float(utils.environ()['RBX_TIME_MULTIPLIER']))
        return res

    def memorylimit_for_language(self, language: Optional[str] = None) -> int:
        assert self.memoryLimit is not None
        res = self.memoryLimit
        if language is None:
            return res
        if language not in self.modifiers:
            return res
        modifier = self.modifiers[language]
        if modifier.memory is not None:
            return modifier.memory
        return res

OutputFromItem #

Bases: CodeItem

Parameters:

Name Type Description Default
stderr bool

Whether the output should be taken from stderr instead of the stdout.

False
Source code in rbx/box/schema.py
class OutputFromItem(CodeItem):
    model_config = ConfigDict(extra='forbid')

    stderr: bool = Field(
        default=False,
        description="""Whether the output should be taken from stderr instead of the stdout.""",
    )

OutputFromItemWithDigest #

Bases: OutputFromItem, CodeItemWithDigest

Source code in rbx/box/schema.py
class OutputFromItemWithDigest(OutputFromItem, CodeItemWithDigest):
    model_config = ConfigDict(extra='forbid')

    @classmethod
    def create(
        cls, output_from_item: OutputFromItem, digest: str
    ) -> 'OutputFromItemWithDigest':
        return cls(
            path=output_from_item.path,
            language=output_from_item.language,
            compilationFiles=output_from_item.compilationFiles,
            executionFiles=output_from_item.executionFiles,
            digest=digest,
        )

Package #

Bases: BaseModel

Parameters:

Name Type Description Default
name str

The name of the problem.

required
titles Dict[str, str]

Titles for the problem in each language. Languages should be specified as lowercase ISO 639-1 codes.

{}
type TaskType

The type of the problem.

BATCH
scoring ScoreType

The scoring type of the problem.

BINARY
timeLimit int

Time limit of the problem, in milliseconds.

required
memoryLimit int

Memory limit of the problem, in MB.

required
outputLimit int

Output limit of the problem, in KB.

4096
modifiers Dict[str, LimitModifiers]

Limit modifiers that can be specified per language.

{}
checker Checker | None

The checker for this problem.

None
interactor Interactor | None

The interactor for this problem.

None
validator CodeItem | None

The validator for this problem.

None
extraValidators List[CodeItem]

Extra validators for this problem.

[]
outputValidators List[CodeItem]

A list of output validators to use to validate the output of the testcases of this problem.

[]
visualizer Visualizer | None

The visualizer for this problem. Used to produced visualizations for the testcases.

None
solutionVisualizer Visualizer | None

The solution visualizer for this problem. Used to produced visualizations for the outputs of the testcases.

None
generators List[Generator]

Generators for this problem.

[]
generatorScript GeneratorScript | None

A generator script used as the default for any test group or subgroup that does not specify its own test parameters (testcases, testcaseGlob, generators, generatorScript) and has no subgroups of its own.

Useful when a single script -- usually partitioned with @testgroup blocks -- drives generation for every group, so it does not need to be repeated on each one.

None
solutions List[Solution]

All tested solutions for this problem.

The first solution in this list should be the main solution -- the one that is correct and used as reference -- and should have the accepted outcome.

[]
testcases List[TestcaseGroup]

Testcases for the problem.

[]
stresses List[Stress]

Stress tests for the problem.

[]
statements List[Statement]

Statements for the problem.

[]
tutorials List[Statement]

Tutorials (editorials) for the problem.

[]
vars RecVars

Variables to be re-used across the package.

{}
unitTests UnitTests

Unit tests for components of this problem.

<dynamic>
Source code in rbx/box/schema.py
class Package(BaseModel):
    model_config = ConfigDict(extra='forbid')

    # Name of the problem.
    name: str = NameField(description='The name of the problem.')

    titles: Dict[str, str] = Field(
        default={},
        description='Titles for the problem in each language. '
        'Languages should be specified as lowercase ISO 639-1 codes.',
    )

    type: TaskType = Field(
        default=TaskType.BATCH, description='The type of the problem.'
    )

    scoring: ScoreType = Field(
        default=ScoreType.BINARY, description='The scoring type of the problem.'
    )

    timeLimit: int = Field(description='Time limit of the problem, in milliseconds.')

    memoryLimit: int = Field(description='Memory limit of the problem, in MB.')

    outputLimit: int = Field(
        default=4 * 1024, description='Output limit of the problem, in KB.'
    )

    modifiers: Dict[str, LimitModifiers] = Field(
        default={},
        description="""
    Limit modifiers that can be specified per language.
    """,
    )

    checker: Optional[Checker] = Field(
        default=None, description='The checker for this problem.'
    )

    interactor: Optional[Interactor] = Field(
        default=None, description='The interactor for this problem.'
    )

    validator: Optional[CodeItem] = Field(
        default=None, description='The validator for this problem.'
    )

    extraValidators: List[CodeItem] = Field(
        default=[], description='Extra validators for this problem.'
    )

    outputValidators: List[CodeItem] = Field(
        default=[],
        description="""
A list of output validators to use to validate the output of the testcases of this problem.
""",
    )

    visualizer: Optional[Visualizer] = Field(
        default=None,
        description='The visualizer for this problem. Used to produced visualizations for the testcases.',
    )

    solutionVisualizer: Optional[Visualizer] = Field(
        default=None,
        description='The solution visualizer for this problem. Used to produced visualizations for the outputs of the testcases.',
    )

    generators: List[Generator] = Field(
        default=[], description='Generators for this problem.'
    )

    generatorScript: Optional[GeneratorScript] = Field(
        default=None,
        description="""
A generator script used as the default for any test group or subgroup that does
not specify its own test parameters (testcases, testcaseGlob, generators,
generatorScript) and has no subgroups of its own.

Useful when a single script -- usually partitioned with `@testgroup` blocks --
drives generation for every group, so it does not need to be repeated on each one.
""",
    )

    solutions: List[Solution] = Field(
        default=[],
        description="""
All tested solutions for this problem.

The first solution in this list should be the main solution -- the one
that is correct and used as reference -- and should have the `accepted` outcome.
""",
    )

    testcases: Annotated[
        List[TestcaseGroup],
        AfterValidator(is_unique_testcase_group_names),
    ] = Field(default=[], description='Testcases for the problem.')

    stresses: List[Stress] = Field(
        default=[], description='Stress tests for the problem.'
    )

    statements: Annotated[
        List[Statement],
        AfterValidator(is_unique_problem_statements),
    ] = Field(default=[], description='Statements for the problem.')

    tutorials: Annotated[
        List[Statement],
        AfterValidator(is_unique_problem_statements),
    ] = Field(default=[], description='Tutorials (editorials) for the problem.')

    # Vars to be re-used across the package.
    #   - It will be passed as --key=value arguments to the validator.
    #   - It will be available as \VAR{key} variables in the rbx statement.
    vars: RecVars = Field(
        default={}, description='Variables to be re-used across the package.'
    )

    unitTests: UnitTests = Field(
        default_factory=UnitTests,
        description='Unit tests for components of this problem.',
    )

    @property
    def expanded_statements(self) -> List[Statement]:
        return expand_problem_statements(self.statements)

    @property
    def expanded_tutorials(self) -> List[Statement]:
        return expand_problem_statements(self.tutorials)

    @property
    def expanded_vars(self) -> Vars:
        return expand_vars(self.vars)

    @model_validator(mode='after')
    def check_first_solution_is_main_if_there_is_ac(self):
        if all(sol.outcome != Outcome.ACCEPTED for sol in self.solutions):
            # No main solution.
            return self
        if self.solutions:
            if self.solutions[0].outcome != ExpectedOutcome.ACCEPTED:
                raise PydanticCustomError(
                    'MISSING_MAIN_SOLUTION',
                    'The first solution in the package must have the "ACCEPTED" outcome if there are ACCEPTED solutions.',
                )
        return self

    @model_validator(mode='after')
    def samples_come_first(self):
        for i, group in enumerate(self.testcases):
            if group.name == 'samples' and i > 0:
                raise PydanticCustomError(
                    'SAMPLES_NOT_FIRST',
                    'The "samples" group must be the first group in the package, but is actually the {i}-th',
                    {'i': i + 1},
                )
        return self

    @model_validator(mode='after')
    def check_scoring_fields(self):
        if not self.scoring == ScoreType.POINTS:
            for group in self.testcases:
                if group.deps:
                    raise PydanticCustomError(
                        'DEPS_NOT_ALLOWED',
                        'Dependencies are not allowed for groups of problems with scoring != POINTS.',
                    )
                if group.score != 0:
                    raise PydanticCustomError(
                        'SCORE_NOT_ALLOWED',
                        'Non-zero score is not allowed for groups of problems with scoring != POINTS.',
                    )
            for solution in self.solutions:
                if solution.score is not None:
                    raise PydanticCustomError(
                        'SCORE_NOT_ALLOWED',
                        'Expected score is not allowed for solutions of problems with scoring != POINTS.',
                    )
        return self

    @model_validator(mode='after')
    def check_deps(self):
        depends = collections.defaultdict(list)
        for group in self.testcases:
            if group.name == 'samples':
                if group.deps:
                    raise PydanticCustomError(
                        'DEPS_NOT_ALLOWED',
                        'Dependencies are not allowed for the "samples" group.',
                    )
                continue
            depends[group.name].extend(group.deps)

        visiting = set()
        visited = set()

        def dfs(u):
            visiting.add(u)
            for v in depends[u]:
                if v in visiting:
                    return True
                if v not in visited:
                    if dfs(v):
                        return True
            visiting.remove(u)
            visited.add(u)
            return False

        for group in self.testcases:
            if group.name != 'samples' and group.name not in visited:
                if dfs(group.name):
                    raise PydanticCustomError(
                        'CYCLIC_DEPENDENCY',
                        'Cyclic dependency detected involving test group "{group_name}".',
                        {'group_name': group.name},
                    )
        return self

    @model_validator(mode='after')
    def check_checker_and_interactor_for_task_type(self):
        if self.type == TaskType.BATCH:
            if self.interactor is not None:
                raise PydanticCustomError(
                    'INTERACTOR_NOT_ALLOWED',
                    'Interactor is not allowed for batch problems. Change the task type to COMMUNICATION.',
                )
        if self.type == TaskType.COMMUNICATION:
            if self.checker is not None and (
                self.interactor is None or not self.interactor.legacy
            ):
                raise PydanticCustomError(
                    'CHECKER_NOT_ALLOWED',
                    'Checkers should not be specified for communication problems.',
                )
        return self

ScoreType #

Bases: AutoEnum

Source code in rbx/box/schema.py
class ScoreType(AutoEnum):
    BINARY = alias('binary')  # type: ignore
    """Scoring for ICPC-like problems, where the problem is considered a point if it pass all testcases."""

    POINTS = alias('points')  # type: ignore
    """Subtasks scoring, where each passing testgroup is worth a number of points that are summed up."""

BINARY = alias('binary') #

Scoring for ICPC-like problems, where the problem is considered a point if it pass all testcases.

POINTS = alias('points') #

Subtasks scoring, where each passing testgroup is worth a number of points that are summed up.

Solution #

Bases: CodeItem

Parameters:

Name Type Description Default
outcome ExpectedOutcome

The expected outcome of this solution.

ANY
tags List[str]

Tags to be associated with this solution.

[]
score int | Tuple[int | None, int | None] | None

The score of this solution in the final score. Should either be an integer, which means the solution should have this exact score, or a tuple of two integers, which means the solution should have a score between the two integers (inclusive).

If one of the integers is set to be null, it means that the solution should have a score between the other integer and negative/positive infinity.

None
Source code in rbx/box/schema.py
class Solution(CodeItem):
    model_config = ConfigDict(extra='forbid')

    outcome: ExpectedOutcome = Field(
        default=ExpectedOutcome.ANY,
        description="""The expected outcome of this solution.""",
    )

    tags: List[str] = Field(
        default=[],
        description="""Tags to be associated with this solution.""",
    )

    score: Optional[Union[int, Tuple[Optional[int], Optional[int]]]] = Field(
        default=None,
        description="""The score of this solution in the final score.
Should either be an integer, which means the solution should have this exact score,
or a tuple of two integers, which means the solution should have a score between the two integers (inclusive).

If one of the integers is set to be null, it means that the solution should have a score between the other integer and negative/positive infinity.""",
    )

    def expected_score_range(self) -> Optional[Tuple[int, int]]:
        if self.score is None:
            return None
        if isinstance(self.score, int):
            return (self.score, self.score)
        assert isinstance(self.score, tuple)
        assert len(self.score) == 2

        lo, hi = self.score
        if lo is None:
            lo = 0
        if hi is None:
            hi = 10**9
        return (lo, hi)

    def href(self, hyperlink: bool = True) -> str:
        return href(self.path, style=self.outcome.full_style(), hyperlink=hyperlink)

Stress #

Bases: BaseModel

Parameters:

Name Type Description Default
name str

The name of the stress test.

required
generator GeneratorCall

Generator pattern to call during stress-test.

required
finder str

Finder expression to be used to match against generated tests.

required
Source code in rbx/box/schema.py
class Stress(BaseModel):
    model_config = ConfigDict(extra='forbid')

    name: str = NameField(description='The name of the stress test.')

    generator: GeneratorCall = Field(
        description='Generator pattern to call during stress-test.'
    )

    finder: str = Field(
        description='Finder expression to be used to match against generated tests.'
    )

TaskType #

Bases: AutoEnum

Source code in rbx/box/schema.py
class TaskType(AutoEnum):
    BATCH = alias('batch')  # type: ignore
    """Batch task."""

    COMMUNICATION = alias('communication')  # type: ignore
    """Communication task."""

BATCH = alias('batch') #

Batch task.

COMMUNICATION = alias('communication') #

Communication task.

Testcase #

Bases: BaseModel

Parameters:

Name Type Description Default
inputPath Path

The path of the input file.

required
outputPath Path | None

The path of the output file.

None
Source code in rbx/box/schema.py
class Testcase(BaseModel):
    __test__ = False

    model_config = ConfigDict(extra='forbid')

    inputPath: pathlib.Path = Field(description="""The path of the input file.""")

    outputPath: Optional[pathlib.Path] = Field(
        default=None, description="""The path of the output file."""
    )

TestcaseGroup #

Bases: TestcaseSubgroup

Parameters:

Name Type Description Default
subgroups List[TestcaseSubgroup]

A list of test subgroups to define for this group.

[]
validator CodeItem | None

A validator to use to validate the testcases of this group. If specified, will use this validator instead of the package-level validator. Useful in cases where the constraints vary across test groups.

None
score int

The score of this group in the final score. Useful for problems that have points.

0
deps List[str]

A list of other groups this group depends on to run and be considered accepted.

The samples group is implicitly a dependency of every other group.

[]
model_solution ForwardRef

The solution to be used to generate outputs for this testgroup.

Can only be set for the "samples" testgroup.

None
Source code in rbx/box/schema.py
class TestcaseGroup(TestcaseSubgroup):
    model_config = ConfigDict(extra='forbid')

    subgroups: Annotated[
        List[TestcaseSubgroup],
        AfterValidator(is_unique_testcase_subgroup_names),
    ] = Field(
        default=[],
        description="""
A list of test subgroups to define for this group.
        """,
    )

    validator: Optional[CodeItem] = Field(
        default=None,
        description="""
A validator to use to validate the testcases of this group.
If specified, will use this validator instead of the package-level validator.
Useful in cases where the constraints vary across test groups.
""",
    )

    score: int = Field(
        default=0,
        description="""
The score of this group in the final score. Useful for
problems that have points.
""",
    )

    deps: List[str] = Field(
        default=[],
        description="""
A list of other groups this group depends on to run and be considered accepted.

The `samples` group is implicitly a dependency of every other group.
""",
    )

    model_solution: Optional[Solution] = Field(
        default=None,
        description="""
The solution to be used to generate outputs for this testgroup.

Can only be set for the "samples" testgroup.
""",
    )

    @model_validator(mode='after')
    def check_model_solution_for_samples(self):
        if self.name == 'samples':
            return self
        if self.model_solution is not None:
            raise PydanticCustomError(
                'MODEL_SOLUTION_NOT_ALLOWED',
                'Model solution can only be set for the "samples" testgroup.',
            )
        return self

TestcaseSubgroup #

Bases: BaseModel

Parameters:

Name Type Description Default
name str

The name of the test group.

required
testcases List[Testcase]

The path of testcases to add to this group, in the order they're defined.

[]
testcaseGlob str | None

A Python glob that matches input file paths relative to the package directory. The globbed files should end with the extension ".in", and their corresponding outputs, if defined, should have the same file name, but ending with ".ans".

None
generators List[GeneratorCall]

A list of generators to call to generate testcases for this group.

[]
generatorScript GeneratorScript | None

A generator script to call to generate testcases for this group.

None
extraValidators List[CodeItem]

A list of extra validators to use to validate the testcases of this subgroup.

[]
outputValidators List[CodeItem]

A list of output validators to use to validate the output of the testcases of this subgroup.

[]
visualizer Visualizer | None

The visualizer for this problem. Used to produced visualizations for the testcases. Has priority over the visualizer specified in the package.

None
solutionVisualizer Visualizer | None

The solution visualizer for this problem. Used to produced visualizations for the outputs of the testcases. Has priority over the solution visualizer specified in the package.

None
Source code in rbx/box/schema.py
class TestcaseSubgroup(BaseModel):
    model_config = ConfigDict(extra='forbid')

    name: str = NameField(description='The name of the test group.')

    testcases: List[Testcase] = Field(
        default=[],
        description="""
The path of testcases to add to this group,
in the order they're defined.""",
    )

    testcaseGlob: Optional[str] = Field(
        default=None,
        description="""
A Python glob that matches input file paths relative to the
package directory. The globbed files should end with the extension
".in", and their corresponding outputs, if defined, should have the same file name,
but ending with ".ans".
""",
    )

    generators: List[GeneratorCall] = Field(
        default=[],
        description="""
A list of generators to call to generate testcases for this group.
""",
    )

    generatorScript: Optional[GeneratorScript] = Field(
        default=None,
        description="""
A generator script to call to generate testcases for this group.
""",
    )

    extraValidators: List[CodeItem] = Field(
        default=[],
        description="""
A list of extra validators to use to validate the testcases of this subgroup.
""",
    )

    outputValidators: List[CodeItem] = Field(
        default=[],
        description="""
A list of output validators to use to validate the output of the testcases of this subgroup.
""",
    )

    visualizer: Optional[Visualizer] = Field(
        default=None,
        description='The visualizer for this problem. Used to produced visualizations for the testcases. '
        'Has priority over the visualizer specified in the package.',
    )

    solutionVisualizer: Optional[Visualizer] = Field(
        default=None,
        description='The solution visualizer for this problem. Used to produced visualizations for the outputs of the testcases. '
        'Has priority over the solution visualizer specified in the package.',
    )

    @model_validator(mode='after')
    def check_oneof(self) -> 'TestcaseSubgroup':
        _check_oneof(
            self,
            [
                'testcases',
                'testcaseGlob',
                'generators',
                'generatorScript',
            ],
        )
        return self

TimingGroupReport #

Bases: BaseModel

Parameters:

Name Type Description Default
languages List[str]
required
timeLimit int
required
origin TimingGroupOrigin
required
solutionCount int
0
fastest int | None
None
slowest int | None
None
relativeToLanguage str | None
None
multiplier float | None
None
increment int | None
None
isLeftover bool
False
Source code in rbx/box/schema.py
class TimingGroupReport(BaseModel):
    model_config = ConfigDict(extra='forbid')

    languages: List[str]
    timeLimit: int
    origin: TimingGroupOrigin
    solutionCount: int = 0
    fastest: Optional[int] = None
    slowest: Optional[int] = None
    relativeToLanguage: Optional[str] = None
    multiplier: Optional[float] = None
    increment: Optional[int] = None
    isLeftover: bool = False

UnitTests #

Bases: BaseModel

Parameters:

Name Type Description Default
validator List[ValidatorTest]

Unit tests for the validator.

[]
checker List[CheckerTest]

Unit tests for the checker.

[]
Source code in rbx/box/schema.py
class UnitTests(BaseModel):
    model_config = ConfigDict(extra='forbid')

    validator: List[ValidatorTest] = Field(
        default=[],
        description='Unit tests for the validator.',
    )

    checker: List[CheckerTest] = Field(
        default=[],
        description='Unit tests for the checker.',
    )

ValidatorOutcome #

Bases: AutoEnum

Source code in rbx/box/schema.py
class ValidatorOutcome(AutoEnum):
    VALID = alias('valid')  # type: ignore
    """Expected outcome for valid tests."""

    INVALID = alias('invalid')  # type: ignore
    """Expected outcome for invalid tests."""

INVALID = alias('invalid') #

Expected outcome for invalid tests.

VALID = alias('valid') #

Expected outcome for valid tests.

ValidatorTest #

Bases: BaseModel

Parameters:

Name Type Description Default
glob str | None

A glob pattern for the input files to be used as unit test input for the validator.

None
testplan Path | None

A testplan to be used as unit test input for the validator.

None
outcome ValidatorOutcome | None

The expected outcome of the validator.

None
validator CodeItem | None

The validator to use for this test. If not specified, will use the package-level validator.

None
Source code in rbx/box/schema.py
class ValidatorTest(BaseModel):
    model_config = ConfigDict(extra='forbid')

    glob: Optional[str] = Field(
        default=None,
        description='A glob pattern for the input files to be used as unit test input for the validator.',
    )

    testplan: Optional[pathlib.Path] = Field(
        default=None,
        description='A testplan to be used as unit test input for the validator.',
    )

    outcome: Optional[ValidatorOutcome] = Field(
        default=None,
        description='The expected outcome of the validator.',
    )

    validator: Optional[CodeItem] = Field(
        default=None,
        description='The validator to use for this test. If not specified, will use the package-level validator.',
    )

    @model_validator(mode='after')
    def check_oneof(self):
        if self.glob is None and self.testplan is None:
            raise PydanticCustomError(
                'GLOB_OR_TESTPLAN_REQUIRED',
                'Either a glob or a testplan must be specified.',
            )
        if self.glob is not None and self.testplan is not None:
            raise PydanticCustomError(
                'GLOB_AND_TESTPLAN_NOT_ALLOWED',
                'Either a glob or a testplan must be specified, but not both.',
            )
        return self

    @model_validator(mode='after')
    def check_testplan(self):
        if self.testplan is not None and self.outcome is not None:
            raise PydanticCustomError(
                'OUTCOME_NOT_ALLOWED',
                'Outcome is not allowed for testplan validator tests.',
            )
        return self

    @model_validator(mode='after')
    def check_glob(self):
        if self.glob is not None and self.outcome is None:
            raise PydanticCustomError(
                'OUTCOME_REQUIRED',
                'Outcome is required for glob validator tests.',
            )
        return self

Visualizer #

Bases: CodeItem

Parameters:

Name Type Description Default
extension str

The extension of the visualization file generated by the visualizer.

required
answer_from Literal['stderr'] | OutputFromItem | None

Program to generate additional answer file to pass to the visualizer. If not specified, the reference answer file will be used.

None
Source code in rbx/box/schema.py
class Visualizer(CodeItem):
    model_config = ConfigDict(extra='forbid')

    extension: str = Field(
        description="""The extension of the visualization file generated by the visualizer.
        """,
    )

    answer_from: Optional[OutputFrom] = Field(
        default=None,
        description="""Program to generate additional answer file to pass to the visualizer.
        If not specified, the reference answer file will be used.""",
    )

    def get_suffix(self) -> str:
        return f'.{self.extension}'

Statements#

Statement #

Bases: BaseStatement

A problem-level statement. Identified by (language, variant) — it has no name (design §3.1).

Parameters:

Name Type Description Default
extends str | StatementVariantRef | None

Another problem statement to inherit the build recipe from, referenced by language (extends: en) or by {language, variant}.

None
Source code in rbx/box/statements/schema.py
class Statement(BaseStatement):
    """A problem-level statement. Identified by (language, variant) — it has no
    `name` (design §3.1)."""

    extends: Optional[ProblemStatementExtends] = Field(
        default=None,
        description='Another problem statement to inherit the build recipe from, '
        'referenced by language (`extends: en`) or by '
        '`{language, variant}`.',
    )

    @model_validator(mode='after')
    def _require_file_or_extends(self):
        if self.file is None and self.extends is None:
            raise ValueError(
                'A statement must specify a `file` unless it `extends` another statement.'
            )
        return self

    @property
    def key(self) -> Tuple[str, str]:
        return (self.language, self.variant)

StatementType #

Bases: AutoEnum

Source code in rbx/box/statements/schema.py
class StatementType(AutoEnum):
    rbxTeX = alias('rbx-tex')  # type: ignore
    """Statement written in rbxTeX format."""

    rbxMarkdown = alias('rbxMd', 'rbx-markdown', 'rbx-md')  # type: ignore
    """Statement written in rbxMarkdown format."""

    TeX = alias('tex')  # type: ignore
    """Statement written in pure LaTeX format."""

    Markdown = alias('md', 'markdown')  # type: ignore
    """Statement written in pure Markdown format."""

    JinjaTeX = alias('jinja-tex')  # type: ignore
    """Statement written in LaTeX format with Jinja2 expressions."""

    JinjaMarkdown = alias('jinja-md', 'jinja-markdown')  # type: ignore
    """Statement written in Markdown format with Jinja2 expressions."""

    PDF = alias('pdf')  # type: ignore
    """Statement is a PDF."""

    def get_file_suffix(self) -> str:
        if self == StatementType.TeX:
            return '.tex'
        if self == StatementType.Markdown:
            return '.md'
        if self == StatementType.rbxTeX:
            return '.rbx.tex'
        if self == StatementType.rbxMarkdown:
            return '.rbx.md'
        if self == StatementType.JinjaTeX:
            return '.jinja.tex'
        if self == StatementType.JinjaMarkdown:
            return '.jinja.md'
        if self == StatementType.PDF:
            return '.pdf'
        raise ValueError(f'Unknown statement type: {self}')

    def is_rbx(self) -> bool:
        """rbx* types are the only ones that can JOIN problems into a contest."""
        return self in (StatementType.rbxTeX, StatementType.rbxMarkdown)

rbxTeX = alias('rbx-tex') #

Statement written in rbxTeX format.

rbxMarkdown = alias('rbxMd', 'rbx-markdown', 'rbx-md') #

Statement written in rbxMarkdown format.

TeX = alias('tex') #

Statement written in pure LaTeX format.

Markdown = alias('md', 'markdown') #

Statement written in pure Markdown format.

JinjaTeX = alias('jinja-tex') #

Statement written in LaTeX format with Jinja2 expressions.

JinjaMarkdown = alias('jinja-md', 'jinja-markdown') #

Statement written in Markdown format with Jinja2 expressions.

PDF = alias('pdf') #

Statement is a PDF.

is_rbx() #

rbx* types are the only ones that can JOIN problems into a contest.

Source code in rbx/box/statements/schema.py
def is_rbx(self) -> bool:
    """rbx* types are the only ones that can JOIN problems into a contest."""
    return self in (StatementType.rbxTeX, StatementType.rbxMarkdown)

Conversion nodes#

BaseStatement #

Bases: BaseModel

Fields shared by problem statements, contest statements and documents (design §2.5, "one shared schema").

Parameters:

Name Type Description Default
language str

Language code of this statement (ISO 639-1).

'en'
variant str

Optional discriminator between formats of the same language. Together with language it forms the join key with contest statements.

'default'
title str | None

Title as it appears in the statement. Can be left unset to fall back to the package/contest title.

None
file Path | None

Path to the input statement file. Required unless this statement extends another one to inherit its file.

None
type StatementType

Type of the input statement file.

rbxTeX
params RecVars

This statement's own parameters, exposed to the template as the params namespace (kept separate from problem/contest vars).

{}
samples bool

Whether to build the statement with samples.

True
assets List[str]

Globs (relative to the package root) selecting files to ship as statement resources (e.g. images/PDFs). Inherited via extends. At build time the default image/PDF globs over the statement subtree and each sample subtree are concatenated to this list.

<dynamic>
Source code in rbx/box/statements/schema.py
class BaseStatement(BaseModel):
    """Fields shared by problem statements, contest statements and documents
    (design §2.5, "one shared schema")."""

    model_config = ConfigDict(extra='forbid')

    language: StatementLanguage = Field(
        default='en', description='Language code of this statement (ISO 639-1).'
    )

    variant: str = Field(
        default=DEFAULT_VARIANT,
        description='Optional discriminator between formats of the same language. '
        'Together with `language` it forms the join key with contest statements.',
    )

    title: Optional[str] = Field(
        default=None,
        description='Title as it appears in the statement. Can be left unset to '
        'fall back to the package/contest title.',
    )

    file: Optional[pathlib.Path] = Field(
        default=None,
        description='Path to the input statement file. Required unless this '
        'statement `extends` another one to inherit its file.',
    )

    type: StatementType = Field(
        default=StatementType.rbxTeX, description='Type of the input statement file.'
    )

    params: RecVars = Field(
        default={},
        description="This statement's own parameters, exposed to the template as "
        'the `params` namespace (kept separate from problem/contest `vars`).',
    )

    samples: bool = Field(
        default=True,
        description='Whether to build the statement with samples.',
    )

    assets: List[str] = Field(
        default_factory=list,
        description='Globs (relative to the package root) selecting files to ship '
        'as statement resources (e.g. images/PDFs). Inherited via `extends`. At '
        'build time the default image/PDF globs over the statement subtree and '
        'each sample subtree are concatenated to this list.',
    )

    @property
    def expanded_params(self) -> Vars:
        return expand_vars(self.params)

ConversionType #

Bases: str, Enum

Source code in rbx/box/statements/schema.py
class ConversionType(str, Enum):
    rbxToTex = 'rbx-tex'
    """Conversion from rbxTeX to LaTeX."""

    rbxMarkdownToTeX = 'rbx-md-tex'
    """Conversion from rbxMarkdown to LaTeX."""
    TexToPDF = 'tex2pdf'
    """Conversion from LaTeX to PDF using pdfLaTeX."""

    JinjaTeX = 'jinja-tex'
    """Conversion from LaTeX with Jinja2 expressions to LaTeX."""

    def __repr__(self):
        return str.__repr__(self.value)
JinjaTeX = 'jinja-tex' class-attribute instance-attribute #

Conversion from LaTeX with Jinja2 expressions to LaTeX.

TexToPDF = 'tex2pdf' class-attribute instance-attribute #

Conversion from LaTeX to PDF using pdfLaTeX.

rbxMarkdownToTeX = 'rbx-md-tex' class-attribute instance-attribute #

Conversion from rbxMarkdown to LaTeX.

rbxToTex = 'rbx-tex' class-attribute instance-attribute #

Conversion from rbxTeX to LaTeX.

StatementKind #

Bases: str, Enum

Which parallel section a statement build operates on (design §3): the primary statements or the tutorials (editorials). Both share the same models, engine and build paths — the kind only selects which list to read on the problem and the contest, and the output filename prefix.

Source code in rbx/box/statements/schema.py
class StatementKind(str, Enum):
    """Which parallel section a statement build operates on (design §3): the
    primary ``statements`` or the ``tutorials`` (editorials). Both share the
    same models, engine and build paths — the kind only selects which list to
    read on the problem and the contest, and the output filename prefix."""

    STATEMENTS = 'statements'
    TUTORIALS = 'tutorials'

    @property
    def singular(self) -> str:
        """Noun used in messages and output filenames (statement / tutorial)."""
        return 'tutorial' if self == StatementKind.TUTORIALS else 'statement'
singular property #

Noun used in messages and output filenames (statement / tutorial).

StatementVariantRef #

Bases: BaseModel

A problem-statement extends target referenced by (language, variant).

A bare string extends: en is shorthand for {language: en} with the default variant (design §5).

Parameters:

Name Type Description Default
language str
required
variant str
'default'
Source code in rbx/box/statements/schema.py
class StatementVariantRef(BaseModel):
    """A problem-statement `extends` target referenced by (language, variant).

    A bare string `extends: en` is shorthand for `{language: en}` with the
    default variant (design §5).
    """

    model_config = ConfigDict(extra='forbid')

    language: StatementLanguage
    variant: str = Field(default=DEFAULT_VARIANT)

TexToPDF #

Bases: BaseModel

Configures the conversion between LaTeX and PDF using pdfLaTeX.

Parameters:

Name Type Description Default
type Literal['tex2pdf']
required
externalize bool

Whether to externalize TikZ graphics.

False
demacro bool

Whether to save macro definitions to a JSON file.

False
Source code in rbx/box/statements/schema.py
class TexToPDF(BaseModel):
    """Configures the conversion between LaTeX and PDF using pdfLaTeX."""

    type: Literal[ConversionType.TexToPDF]

    externalize: bool = Field(
        default=False,
        description='Whether to externalize TikZ graphics.',
    )

    demacro: bool = Field(
        default=False,
        description='Whether to save macro definitions to a JSON file.',
    )

rbxToTeX #

Bases: BaseModel

Configures the conversion between rbxTeX and LaTeX.

Parameters:

Name Type Description Default
type Literal['rbx-tex']
required
template Path

Path to the template that should be used to render the rbx-tex blocks.

PosixPath('template.rbx.tex')
externalize bool

Whether to externalize TikZ graphics.

False
Source code in rbx/box/statements/schema.py
class rbxToTeX(BaseModel):
    """Configures the conversion between rbxTeX and LaTeX."""

    type: Literal[ConversionType.rbxToTex]

    template: pathlib.Path = Field(
        default=pathlib.Path('template.rbx.tex'),
        description='Path to the template that should be used to render the rbx-tex blocks.',
    )

    externalize: bool = Field(
        default=False,
        description='Whether to externalize TikZ graphics.',
    )