Skip to main content

kaggriculture-engine

A Rust port of the kaggle-environments Kaggriculture interpreter (kaggriculture.py) that gives bit-identical results. It is about 10,000× faster per episode than the Python original running under kaggle's Environment.

Given the same configuration and the same per-step agent actions, the port produces byte-identical state to the Python original. That includes float formatting, dict key order, RNG draws, rewards and statuses, and which Python exception (if any) a step raises.

Python (PyO3)

uv sync                                   # builds the extension (maturin) into .venv
.venv/bin/maturin develop --release --uv  # rebuild after changing Rust code
import kaggriculture_engine as ke

game = ke.Game({"seed": 42})                 # == make("kaggriculture", configuration=...)
game.step([{"farmer": ["WATER"]}, {"market": [["BUY_SEED", "CARROT", 1]]}])   # == env.step
obs = game.observation(0, structified=True)  # what kaggle hands agent 0 (obs.player, obs["farms"], ...)
game.statuses, game.rewards, game.current_step, game.render(), game.record()

# Whole games: named Rust agents run with the GIL released; any Python
# callable agent(obs[, config]) works too, e.g. the original kaggriculture agents.
finished = ke.run_episode(["starter", my_python_agent], {"seed": 42})
results = ke.run_batch(range(10_000), ("starter", "random:1"))   # parallel, GIL released

step mirrors env.step exactly:

  • An action can be any Python value, and each entry can instead be an exception instance, which marks that agent ERROR (a DeadlineExceeded marks it TIMEOUT).
  • When the original interpreter would raise, step raises the same exception class (TypeError, ValueError, OverflowError or ZeroDivisionError) and leaves the game unchanged.
  • Stepping a finished game raises ke.FailedPrecondition.
  • Tuples, numpy scalars, Decimals, sets, bytes and dicts with non-str keys behave as they do in kaggle.

The extension doesn't need kaggle-environments at runtime.

Build and run the CLI

cargo build --release
B=target/release/kaggriculture_engine

# Agent-driven episode (pass | starter | random[:SEED]); one JSON record per state
echo '{"seed": 42}' | $B play - starter random:7 [--render]

# Replay recorded inputs: {"configuration": {...}, "steps": [[in0, in1], ...]}
# where in = {"action": <any JSON>} | {"status": "ERROR"} | {"status": "TIMEOUT"}
$B replay scenario.json [--render]

# Throughput (single thread, then all cores via Rayon)
$B bench --episodes 8000 --agents starter,random:1 [--threads N]

Library use:

use kaggriculture_engine::{Action, AgentInput, Config, Game};
use std::sync::Arc;

let cfg = Arc::new(Config::from_json(&serde_json::json!({"seed": 42}))?);
let mut game = Game::new(cfg.clone());
while !game.done() {
    let a = Action::from_json(&serde_json::json!({"farmer": ["WATER"]}), cfg.max_orders);
    game.step(&[AgentInput::Act(a.clone()), AgentInput::Act(a)])?; // Err = Python raised; state untouched
}

batch::run_batch plays many seeds in parallel. To time a single game end to end (cold and warm, with and without serialising every state), run cargo run --release --example one_game.

Verifying bit-exactness

The Python side needs the project venv (uv sync).

.venv/bin/python tests/parity/run_parity.py      # full sweep (~146 cases, ~30k records)
cargo test --release                             # unit tests + a 14-case parity slice

tests/python/test_bindings.py (.venv/bin/python -m unittest discover -s tests/python) checks the Python bindings against the same reference. It replays fuzzed scenarios through Game.step, drives the original Python agents with the bindings' observations, and checks that Python-only action values and agent exceptions behave identically.

tests/parity/reference.py runs the original kaggriculture.py through the real kaggle Environment (makeenv.step). tests/parity/fuzz.py plays both seats with a seeded, state-aware policy, so games reach crops, animals, hands, land, shops, weeds and decay. It also injects malformed input: unhashable items, int()-hostile counts ("abc", "1_0", "٣", 1e400), non-dict actions, agent ERROR/TIMEOUT statuses, and hostile marketParams and configs. The Rust binary replays the exact same JSON text, and every output line must be byte-identical. Mismatching inputs are saved to tests/parity/failures/.

As a check on the harness itself, I planted small bugs (care-bonus arithmetic, round-half-up instead of Python's round-half-even) and the sweep flagged each of them.

Performance (Apple M1, 8 cores; 720-step episodes)

agents Python (kaggle env.run) Rust, 1 thread Rust, 8 threads (Rayon)
starter / starter ~1.2 s 137 µs 21 µs
random / random ~1.0–1.3 s 690 µs 107 µs

From Python, with starter vs starter:

  • ke.run_episode with named agents takes 0.09 ms per game.
  • The original Python agents, called through ke.run_episode, take 29 ms per game. That's still 29× faster than kaggle's env.run, and building the observation dicts is now the bottleneck.
  • A plain Python loop costs 0.65 µs per Game.step.

The Python figures include kaggle's framework overhead (it deep-copies the state every step), which is how the game is normally run.

Where the time went and what was done about it:

  • Rayon runs episodes in parallel (batch.rs). A single step (~150 ns) is far too small to split across threads.
  • SIMD (NEON): CPython's random.Random(seed) (MT19937 init_by_array) is a long serial multiply chain. The interpreter builds a new generator every in-game day, and random_agent builds one every turn. All of these seeds are known ahead of time, so pyrandom::LaneBatch seeds 32 of them at once across NEON lanes. That is 28× faster per generator than scalar. It also twists and tempers lazily: the first 227 outputs depend only on the untwisted state. The single-state twist and tempering also use NEON. Other architectures fall back to auto-vectorised lane arrays.
  • Price curves are memoised per inventory (sqrt/log calls were half of the step time), the decay scan is skipped until a plant can decay, the shed total is kept incrementally, and inventories are allocation-free ordered maps.
  • Steps that cannot raise run in place. A step that might raise (malformed actions, marketParams overrides, …) runs on a copy, matching kaggle's behaviour of discarding the state when the interpreter raises.

Layout

file role
engine.rs interpreter() / _initialize and the env.step bookkeeping (step, status, reward, rollback)
market.rs market_price / _shape with Python's exact int/float semantics
action.rs typed actions; Python exceptions are kept as lazy errors that fire where Python would raise
pyvalue.rs JSON as Python sees it: unbounded ints, int() coercion, json.dumps-exact output
pyrandom.rs, simd.rs CPython-compatible MT19937, scalar and 32-lane SIMD
bigint.rs minimal big integers (huge seeds, hire costs, exact int/int division)
config.rs kaggle schema defaults and validation, and the interpreter's coercions
agents.rs, render.rs, batch.rs bundled agents, text renderer, parallel runner
python.rs, python/kaggriculture_engine/ PyO3 bindings (feature python), package and type stubs

Known divergences (all outside realistic inputs)

  • marketParams integers beyond ±2⁶², boardSize > 1024 and turnsPerDay > 2⁵⁸ are rejected at config time. Python would accept them.
  • Inputs must be standard JSON. Python's json additionally accepts NaN/Infinity literals and lone surrogates.
  • Exceptions are reported by class (TypeError, …), not by message text.
  • random_agent takes an explicit seed; the Python original seeds from OS entropy on every call. The parity harness patches Python to use the same seeds. With no configured seed, both sides pick a random episode seed, so those runs aren't comparable.
  • Interpreter stdout (warnings) is returned in full; kaggle truncates logs at maxLogLength (10,000 chars per step).
  • The renderer prints non-ASCII characters verbatim; CPython escapes the few non-printable ones. This only matters for strings inside marketParams.
  • html_renderer (which only serves a bundled HTML file) is not ported.
  • Python bindings, callable agents: there is no act-timeout or overage-time accounting, and observations omit kaggle's framework-only remainingOverageTime. Observations use the package's own Struct rather than kaggle's class (same behaviour, different isinstance). Nested dicts in the configuration passed to agents are left as plain dicts.

License

Licensed under the Apache License, Version 2.0.

This project is a port of kaggriculture.py from kaggle-environments (Copyright 2020 Kaggle Inc, Apache-2.0). See NOTICE for attribution, including the CPython algorithms reimplemented for bit-exact compatibility.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kaggriculture_engine-0.1.0.tar.gz (266.7 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

kaggriculture_engine-0.1.0-cp313-abi3-win_arm64.whl (286.7 kB view details)

Uploaded CPython 3.13+Windows ARM64

kaggriculture_engine-0.1.0-cp313-abi3-win_amd64.whl (298.9 kB view details)

Uploaded CPython 3.13+Windows x86-64

kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (422.4 kB view details)

Uploaded CPython 3.13+manylinux: glibc 2.17+ x86-64

kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (417.7 kB view details)

Uploaded CPython 3.13+manylinux: glibc 2.17+ ARM64

kaggriculture_engine-0.1.0-cp313-abi3-macosx_11_0_arm64.whl (379.3 kB view details)

Uploaded CPython 3.13+macOS 11.0+ ARM64

File details

Details for the file kaggriculture_engine-0.1.0.tar.gz.

File metadata

  • Download URL: kaggriculture_engine-0.1.0.tar.gz
  • Upload date:
  • Size: 266.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0.tar.gz
Algorithm Hash digest
SHA256 63856ef07e80aaa132ed658b55671a695bf836af0dd4dcb6548d0e0801eef9d4
MD5 9d9a18fbf04db9b70140cf8a417f5b5b
BLAKE2b-256 5402906b2d2f5de1ecc9c1ae7f06bd2b50ca54e1a22823ee3c5598426cc4da6c

See more details on using hashes here.

File details

Details for the file kaggriculture_engine-0.1.0-cp313-abi3-win_arm64.whl.

File metadata

  • Download URL: kaggriculture_engine-0.1.0-cp313-abi3-win_arm64.whl
  • Upload date:
  • Size: 286.7 kB
  • Tags: CPython 3.13+, Windows ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0-cp313-abi3-win_arm64.whl
Algorithm Hash digest
SHA256 cb416b500d9bf464d9871321fb9aa5793235eed234544a65bae0e23aed160a2e
MD5 8df2f1d0c30707e79be457fcc8222392
BLAKE2b-256 529f18b91e7d038a42c1ace5b1c956d41d05ba313d5194248c4a34e11265e12c

See more details on using hashes here.

File details

Details for the file kaggriculture_engine-0.1.0-cp313-abi3-win_amd64.whl.

File metadata

  • Download URL: kaggriculture_engine-0.1.0-cp313-abi3-win_amd64.whl
  • Upload date:
  • Size: 298.9 kB
  • Tags: CPython 3.13+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0-cp313-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a5eb49401e8599c05c885d3276ae2c5c04d85b51d6eadbbebce21b732b27b656
MD5 83157d2c4ad7bc0ed79dc09ddf903fbf
BLAKE2b-256 2ea2da9e6629bc5aec615594923cc308617a0fa655d3734dc88c81af5c7ed2cc

See more details on using hashes here.

File details

Details for the file kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 422.4 kB
  • Tags: CPython 3.13+, manylinux: glibc 2.17+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9cae78ae216c948c16608edfcdd6b9598c94bda01454c594fe29a8a292e79706
MD5 005251a0783bd41c817de6903cac3def
BLAKE2b-256 9bb62b54ba4e4efbb080073af67a38b152c2a280d53b0b2ccb4b2e84cecb3c6a

See more details on using hashes here.

File details

Details for the file kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

  • Download URL: kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
  • Upload date:
  • Size: 417.7 kB
  • Tags: CPython 3.13+, manylinux: glibc 2.17+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 2b5439a1d1c4c67311121c8732bd343ffae63bafc87d12f89e068599668e87da
MD5 792578cab91bc74efdf83207c4aa753a
BLAKE2b-256 226a5de9618da05f288123b68f182e7ff8dfcfe062c961dcc4f51915004150e5

See more details on using hashes here.

File details

Details for the file kaggriculture_engine-0.1.0-cp313-abi3-macosx_11_0_arm64.whl.

File metadata

  • Download URL: kaggriculture_engine-0.1.0-cp313-abi3-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 379.3 kB
  • Tags: CPython 3.13+, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for kaggriculture_engine-0.1.0-cp313-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3c8c826bec1360375ce0c0553eb9dd81e15dc5cfd9cb164ae433a1dc657c8e98
MD5 9e8fffd29edd9fcc00d5ddfd762afa09
BLAKE2b-256 4432e5e6ef28696936b8043317cc71ac277a2aa0f95d2e9d8f4c1130e3fe5074

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page