Skip to content

Problem schema

problem.rbx.yml

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 placed alongside the code file during its compilation, such as testlib.h, jngen.h, etc.

The paths should be given relative to the package directory, but will be included relative to the path directory.

Testlib and jngen are already included by default.

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

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

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

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

The paths should be given relative to the package directory, but will be included
relative to the `path` directory.

Testlib and jngen are already included by default.
""",
    )

ExpectedOutcome

Bases: AutoEnum

Source code in robox/box/schema.py
class ExpectedOutcome(AutoEnum):
    ACCEPTED = alias('accepted', 'ac', 'correct')  # type: ignore
    """Expected outcome for correct solutions (AC)."""

    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')  # type: ignore
    """Expected outcome for solutions that do not finish in time."""

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

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

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

    def style(self) -> str:
        if self == ExpectedOutcome.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 'lnumber'
        if self.match(Outcome.MEMORY_LIMIT_EXCEEDED):
            return 'cyan'
        return 'magenta'

    def match(self, outcome: Outcome) -> bool:
        if self == ExpectedOutcome.ACCEPTED:
            return outcome == Outcome.ACCEPTED
        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.TIME_LIMIT_EXCEEDED,
            }
        if self == ExpectedOutcome.RUNTIME_ERROR:
            return outcome == Outcome.RUNTIME_ERROR
        if self == ExpectedOutcome.TIME_LIMIT_EXCEEDED:
            return outcome == Outcome.TIME_LIMIT_EXCEEDED
        if self == ExpectedOutcome.MEMORY_LIMIT_EXCEEDED:
            return outcome == Outcome.MEMORY_LIMIT_EXCEEDED
        if self == ExpectedOutcome.TLE_OR_RTE:
            return outcome in {Outcome.TIME_LIMIT_EXCEEDED, Outcome.RUNTIME_ERROR}
        return False

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

Expected outcome for correct solutions (AC).

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

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

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

Expected outcome for solutions that use more memory 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')

Expected outcome for solutions that do not finish in time.

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

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 robox/box/schema.py
class Generator(CodeItem):
    model_config = ConfigDict(extra='forbid')

    name: str = NameField(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 robox/box/schema.py
class GeneratorCall(BaseModel):
    model_config = ConfigDict(extra='forbid')

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

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

Package

Bases: BaseModel

Parameters:

Name Type Description Default
name str

The name of the problem.

required
timeLimit int

Time limit of the problem, in milliseconds.

required
memoryLimit int

Memory limit of the problem, in MB.

required
checker CodeItem | None

The checker for this problem.

None
validator CodeItem | None

The validator for this problem.

None
generators List[Generator]

Generators for this problem.

[]
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.

[]
vars Dict[str, Union[str, int, float, bool]]

Variables to be re-used across the package.

{}
Source code in robox/box/schema.py
class Package(BaseModel):
    model_config = ConfigDict(extra='forbid')

    # Name of the problem.
    name: str = NameField(description='The name 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.')

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

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

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

    solutions: List[Solution] = Field(
        [],
        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: List[TestcaseGroup] = Field([], description='Testcases for the problem.')

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

    statements: List[Statement] = Field([], description='Statements 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 robox statement.
    vars: Dict[str, Primitive] = Field(
        {}, description='Variables to be re-used across the package.'
    )

    @property
    def expanded_vars(self) -> Dict[str, Primitive]:
        return {key: _expand_var(value) for key, value in self.vars.items()}

    @model_validator(mode='after')
    def check_first_solution_is_main(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.',
                )
        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

Solution

Bases: CodeItem

Parameters:

Name Type Description Default
outcome ExpectedOutcome

The expected outcome of this solution.

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

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

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
solutions List[str]

Path of the solutions to be stress-tested.

If empty, will stress-test only the main solution for non-WA verdicts.

[]
outcome ExpectedOutcome

What verdict to look for while stress-testing.

INCORRECT
Source code in robox/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.'
    )

    solutions: List[str] = Field(
        [],
        description="""
Path of the solutions to be stress-tested.

If empty, will stress-test only the main solution for
non-WA verdicts.""",
    )

    outcome: ExpectedOutcome = Field(
        ExpectedOutcome.INCORRECT,
        description="""
What verdict to look for while stress-testing.
                                     """,
    )

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 robox/box/schema.py
class Testcase(BaseModel):
    model_config = ConfigDict(extra='forbid')

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

    outputPath: Optional[pathlib.Path] = Field(
        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 not specified, will use the package-level validator. Useful in cases where the constraints vary across test groups.

None
weight float | None

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

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

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

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

    weight: Optional[float] = Field(
        1.0,
        description="""
The weight of this group in the final score. Useful for
problems that have points.
""",
    )

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 ".out".

None
generators List[GeneratorCall]

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

[]
generatorScript CodeItem | None

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

None
Source code in robox/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(
        [],
        description="""
The path of testcases to add to this group,
in the order they're defined.""",
    )

    testcaseGlob: Optional[str] = Field(
        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 ".out".
""",
    )

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

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

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

Statements

Statement

Bases: BaseModel

Parameters:

Name Type Description Default
title str

Name of the problem, as it appears in the statement.

required
path Path

Path to the input statement file.

required
type StatementType

Type of the input statement file.

required
steps List[Union[TexToPDF, JinjaTeX, roboxToTeX]]

Describes a sequence of conversion steps that should be applied to the statement file.

Usually, it is not necessary to specify these, as they can be inferred from the input statement type and the output statement type, but you can use this to force certain conversion steps to happen.

[]
configure List[Union[TexToPDF, JinjaTeX, roboxToTeX]]

Configure how certain conversion steps should happen when applied to the statement file.

Different from the steps field, this does not force the steps to happen, but rather only configure them in case they are applied.

[]
assets List[str]

Assets relative to the package directory that should be included while building the statement. Files will be included in the same folder as the statement file, preserving their relativeness. Can be glob pattern as well, such as imgs/*.png.

[]
language str

Language this is statement is written in.

'en'
Source code in robox/box/statements/schema.py
class Statement(BaseModel):
    model_config = ConfigDict(extra='forbid')

    title: str = Field(
        description='Name of the problem, as it appears in the statement.'
    )

    path: pathlib.Path = Field(description='Path to the input statement file.')

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

    steps: List[ConversionStep] = Field(
        [],
        discriminator='type',
        description="""
Describes a sequence of conversion steps that should be applied to the statement file.

Usually, it is not necessary to specify these, as they can be inferred from the
input statement type and the output statement type, but you can use this to force
certain conversion steps to happen.
""",
    )

    configure: List[ConversionStep] = Field(
        [],
        discriminator='type',
        description="""
Configure how certain conversion steps should happen when applied to the statement file.

Different from the `steps` field, this does not force the steps to happen, but rather only
configure them in case they are applied.
""",
    )

    assets: List[str] = Field(
        [],
        description="""
Assets relative to the package directory that should be included while building
the statement. Files will be included in the same folder as the statement file, preserving
their relativeness. Can be glob pattern as well, such as `imgs/*.png`.
""",
    )

    language: str = Field('en', description='Language this is statement is written in.')

StatementType

Bases: AutoEnum

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

    TeX = alias('tex')
    """Statement written in pure LaTeX format."""

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

    PDF = alias('pdf')
    """Statement is a PDF."""

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

roboxTeX = alias('robox-tex', 'rbx-tex', 'rbx')

Statement written in roboxTeX format.

TeX = alias('tex')

Statement written in pure LaTeX format.

JinjaTeX = alias('jinja-tex')

Statement written in LaTeX format with Jinja2 expressions.

PDF = alias('pdf')

Statement is a PDF.

Conversion nodes

ConversionType

Bases: str, Enum

Source code in robox/box/statements/schema.py
class ConversionType(str, Enum):
    roboxToTex = 'rbx-tex'
    """Conversion from roboxTeX 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.

roboxToTex = 'rbx-tex' class-attribute instance-attribute

Conversion from roboxTeX to LaTeX.

JinjaTeX

Bases: BaseModel

Parameters:

Name Type Description Default
type Literal[ConversionType]
required
Source code in robox/box/statements/schema.py
class JinjaTeX(BaseModel):
    type: Literal[ConversionType.JinjaTeX]

JoinTexToPDF

Bases: BaseModel

Configures the joining of contest and problem texes to PDF.

Parameters:

Name Type Description Default
type Literal[JoinerType]
required
Source code in robox/box/statements/schema.py
class JoinTexToPDF(BaseModel):
    """Configures the joining of contest and problem texes to PDF."""

    type: Literal[JoinerType.TexToPDF]

JoinerType

Bases: str, Enum

Source code in robox/box/statements/schema.py
class JoinerType(str, Enum):
    TexToPDF = 'tex2pdf'
    """Join contest tex and problem texs to PDF using pdfLaTeX."""

    def __repr__(self):
        return str.__repr__(self.value)
TexToPDF = 'tex2pdf' class-attribute instance-attribute

Join contest tex and problem texs to PDF using pdfLaTeX.

TexToPDF

Bases: BaseModel

Configures the conversion between LaTeX and PDF using pdfLaTeX.

Parameters:

Name Type Description Default
type Literal[ConversionType]
required
Source code in robox/box/statements/schema.py
class TexToPDF(BaseModel):
    """Configures the conversion between LaTeX and PDF using pdfLaTeX."""

    type: Literal[ConversionType.TexToPDF]

roboxToTeX

Bases: BaseModel

Configures the conversion between roboxTeX and LaTeX.

Parameters:

Name Type Description Default
type Literal[ConversionType]
required
template Path

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

PosixPath('template.rbx.tex')
Source code in robox/box/statements/schema.py
class roboxToTeX(BaseModel):
    """Configures the conversion between roboxTeX and LaTeX."""

    type: Literal[ConversionType.roboxToTex]

    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.',
    )