# Submission format

> What goes in the archive, how each solver is run, and the environment it runs in.

Source: <https://frontieror-challenge.com/docs/main/submission-format>

A Main-track submission is one archive with a solver for each problem of the open round. We run each solver in a sandbox, once for every private instance of its problem, and check the solution file it writes.

## The archive

Upload a **zip**, **tar** or **tar.gz** file of at most 4 MB. The format is read from the file's contents, so its name does not matter.

`layout`

```text
submission.zip
├── barnhart2000/          # one folder per problem, named by its slug
│   ├── solve.py           # the entry point
│   ├── model.py           # any other files the solver needs
│   └── params.json
└── <another-slug>/
    └── solve.py
```

- **One folder per problem of the open round**, named exactly by the problem's slug. The slugs are on the dashboard and in [`GET /problems`](https://frontieror-challenge.com/docs/main/api#list-problems).
- **One entry point per folder**, directly inside it: `solve.py`, which we run with `python`. Solvers are Python only; for another language, use the [Self-hosted track](https://frontieror-challenge.com/docs/self-hosted/submission-flow).
- **Each folder stands alone.** A solver sees only its own folder, so code shared between problems has to be copied into every folder that uses it.
- **A problem without a folder scores 0** on all of its instances. The upload form warns you and asks you to confirm; over the API, add `acknowledge_missing=1`.
- **One wrapper folder is fine.** If the whole layout sits inside a single folder, for example because you zipped the folder itself, that folder is skipped.
- **Ignored:** `__MACOSX/`, `.DS_Store` files, and anything at the top level that is not a folder of an open-round problem.
- **Refused:** symbolic and hard links, absolute paths, paths with `..` or backslashes, more than 10,000 entries, or more than 256 MB unpacked.

## How your solver is run

For every instance, the solver's folder is copied into a fresh sandbox and the entry point is started from inside it:

```sh
python solve.py --problem barnhart2000 \
                --instance /work/in/instance.json \
                --output /work/out/solution.json \
                --time-limit 60
```

| Argument       | Value                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------ |
| `--problem`    | The problem's slug.                                                                        |
| `--instance`   | Path to the instance file: JSON in the problem's instance schema. Read-only.               |
| `--output`     | Where to write the solution file. See [The solution file](#the-solution-file).             |
| `--time-limit` | The instance's time limit in whole seconds. The sandbox stops the solver when it runs out. |

The working directory is `/work/solver`, which holds your problem folder and is read-only. Standard input is empty. Standard output and error are captured but not shown to you, since they could reveal the private instances.

### Environment variables

| Variable                                      | Value                                                       |
| --------------------------------------------- | ----------------------------------------------------------- |
| `SANDBOX_PROBLEM`                             | The problem's slug.                                         |
| `SANDBOX_TIMEOUT_S`                           | The time limit in seconds, the same as `--time-limit`.      |
| `OMP_NUM_THREADS`                             | `2`, matching the CPU limit.                                |
| `HOME`                                        | `/tmp`                                                      |
| `PYTHONUNBUFFERED`, `PYTHONDONTWRITEBYTECODE` | `1`                                                         |
| `PATH`, `VIRTUAL_ENV`                         | Point at `/opt/env` when the submission has extra packages. |

## Sandbox limits

Every instance runs alone on its own cores, so no other run shares its time.

| Resource  | Limit                                                                                                                                      |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| CPU       | 2 vCPU (x86-64)                                                                                                                            |
| Memory    | 4 GB, no swap. Going over ends the run.                                                                                                    |
| Time      | The instance's time limit: 10 s, 60 s or 5 min, by the problem's [time class](https://frontieror-challenge.com/docs/problems#time-limits). |
| Disk      | 1 GB for `/work`. Only `/work/out` and `/tmp` (64 MB) are writable.                                                                        |
| Network   | None.                                                                                                                                      |
| Processes | 256 processes and threads, 1,024 open files.                                                                                               |
| User      | An unprivileged user, uid 1000, with no capabilities.                                                                                      |

> **Finish before the limit.** A solver still running at its time limit is stopped and the instance scores 0, even if it has already written a solution. Give your solver a time budget a few seconds under `--time-limit`, write the solution file and exit.

## The solution file

Write one JSON object to the `--output` path, `/work/out/solution.json`, and exit with code 0.

- It follows the problem's **solution schema**, shown on each problem page. See [Reading the schemas](https://frontieror-challenge.com/docs/problems#reading-the-schemas).
- It has a numeric `objective_value`. The checker recomputes the objective from your decisions and rejects a solution whose claimed value does not match.
- It is at most 16 MB.

`solution.json · barnhart2000`

```json
{
  "objective_value": 111,
  "commodities": [
    { "commodity_id": 0, "rejected": true, "path_arcs": [] }
  ]
}
```

How a run turns into a score, and every message an instance can get, is on [Evaluation and scoring](https://frontieror-challenge.com/docs/main/scoring).

## Environment

Each submission runs in one environment, chosen when you submit. It is the same for every problem in the submission.

### Our images

Python 3.12 (`python-3.12`, the default) or Python 3.14 (`python-3.14`), on Debian bookworm, with numpy, scipy, pandas, networkx, ortools, pulp, highspy and pyomo installed.

**Every installed package**

| Package           | Python 3.12 | Python 3.14 |
| ----------------- | ----------- | ----------- |
| absl-py           | 2.5.0       | 2.5.0       |
| annotated-types   | 0.8.0       | 0.8.0       |
| cpsat-logutils    | 1.0.0       | 1.0.0       |
| highspy           | 1.15.1      | 1.15.1      |
| immutabledict     | 4.3.1       | 4.3.1       |
| networkx          | 3.7         | 3.7         |
| numpy             | 2.5.3       | 2.5.3       |
| orloge            | 1.0.0       | 1.0.0       |
| ortools           | 9.15.6755   | 9.15.6755   |
| pandas            | 3.0.6       | 3.0.6       |
| protobuf          | 6.33.6      | 6.33.6      |
| pulp              | 4.0.0       | 4.0.0       |
| pydantic          | 2.13.5      | 2.13.5      |
| pydantic-core     | 2.46.5      | 2.46.5      |
| pyomo             | 6.10.1      | 6.10.1      |
| python-dateutil   | 2.9.0.post0 | 2.9.0.post0 |
| scipy             | 1.18.1      | 1.18.1      |
| six               | 1.17.0      | 1.17.0      |
| typing-extensions | 4.16.0      | 4.16.0      |
| typing-inspection | 0.4.4       | 0.4.4       |

The lock files themselves are in [`GET /images`](https://frontieror-challenge.com/docs/main/api#list-images).

### Extra packages

With our images you can add open-source packages from PyPI, one per line as in a `requirements.txt`, within the limits below:

```text
pyscipopt==5.5.0
numba>=0.61
# a package you list replaces the image's version of it
numpy==2.4.1
```

- Up to 50 packages and 10 KB. Names, extras, version specifiers and environment markers are accepted.
- Packages come from PyPI by name only: no options such as `-i`, `-e` or `-r`, and no URLs, paths, VCS links or wheel files.
- They are installed once, before the first instance and outside every time limit, and mounted read-only. A later submission with the same image and list reuses them.
- An install that fails or takes longer than 10 min fails the submission, and its build log is shown on the dashboard.

**What is refused, and the message you get**

| Line                             | Message                                                                         |
| -------------------------------- | ------------------------------------------------------------------------------- |
| Ends with `\`                    | Line continuations aren't accepted. Put each package on one line.               |
| Starts with `-`                  | Options such as -i, -e or -r aren't accepted. Packages come from PyPI.          |
| A URL, path, VCS link or archive | Links, paths and archive files aren't accepted. Use the package's name on PyPI. |
| Anything else unreadable         | Not a package name with optional extras and versions, such as pyscipopt==5.5.0. |
| A bad marker after `;`           | Not a valid environment marker after ";", such as sys\_platform == "linux".     |
| More than 50 packages            | *n* packages; the limit is 50.                                                  |
| Longer than 10 KB                | The list is longer than 10 KB.                                                  |

Each message names its line.

> The Main track allows open-source dependencies only. This is not checked automatically, but prize-eligible submissions are audited.

## Checking before you submit

The [starter repository](https://frontieror-challenge.com/docs/main/example-code) runs your solvers the same way, in the same image and with the same limits:

```sh
uv run frontieror test --docker
```

It checks the run and the solution's format on the public instances, then feasibility with the problem's [checker](https://frontieror-challenge.com/docs/problems#checkers) when it is published. Scores come only from a submission.
