{"data":{"kind":"file","path":"README.md","version_id":"c7tpo3obx2vj8xkwscl3iacb","entry":{"name":"README.md","path":"README.md","is_directory":false,"size":5720,"modified_at":"2026-08-07T06:30:03.658000","content_hash":"9fc61bfb96d2fcdf007ff7ce96992f3c012b2605c7ee595804290c4334ec873a"},"entries":[],"content":"# concretemath-env\r\n\r\nRL environment for **discrete mathematics**, on the topics of *Concrete Mathematics* (Graham,\r\nKnuth & Patashnik): recurrences, sums, integer functions, binomial coefficients, special\r\nnumbers and elementary number theory.\r\n\r\n- **Tasks:** 37 across 6 categories (recurrences 7, sums 7, integer-functions 6, binomials 6,\r\n  special-numbers 6, number-theory 5)\r\n- **Dataset:** [`concretemath-tasks-v1`](https://huggingface.co/datasets/eltociear/concretemath-tasks-v1)\r\n- **Reward:** binary, from an exact comparison. No LLM judge, no network, no clock.\r\n- **Dependencies in the sandbox:** none. `math`, `fractions` and `itertools` are stdlib.\r\n\r\n```bash\r\nprime env install eltociear/concretemath-env\r\n```\r\n\r\n## Original problems, not the book's exercises\r\n\r\nNothing here is copied. Every task is written fresh on the topics the book teaches — a\r\ncopyright decision and the stronger evaluation choice, since the book's exercises and their\r\nanswers are all over the public web and can be **recalled** rather than derived.\r\n\r\n## Not a single float\r\n\r\nIntegers stay integers — Python's are unbounded, so nothing ever needs approximating — and\r\nrationals are returned as `[numerator, denominator]` in lowest terms.\r\n\r\nThat is the subject, not a style rule. `H_10` is `7381/2520`, not `2.9289682539682538`. The\r\nspecific mistake this environment measures is reaching for float division on a question with an\r\nexact answer, so:\r\n\r\n- `sum(1/k for k in range(1, 11))` is a **wrong** answer;\r\n- `sum(Fraction(1, k) for k in range(1, 11))` is a right one;\r\n- and the grader must be able to tell them apart — which is why there is **no tolerance\r\n  anywhere**, and why a float never passes for an integer even when equal in value\r\n  (`7381.0 == 7381` is true in Python and is nonetheless rejected).\r\n\r\nThe builder enforces the same rule on itself: `--verify` fails the whole dataset if any task\r\nreturns a float at all.\r\n\r\n## Answer keys are executed, and cross-checked against a second method\r\n\r\nEvery expected output is produced by running a reference implementation. Where a closed form\r\nexists, the task computes the answer by **iteration** and then asserts the closed form agrees —\r\nso a key is never just \"whatever the first implementation printed\". Tower of Hanoi against\r\n`2ⁿ-1`; Josephus by simulating the circle against `2l+1`; `Σk³` against `(n(n+1)/2)²`;\r\n`ΣC(n,k)²` against `C(2n,n)`; the Stirling and Eulerian tables against their row sums of `n!`.\r\n\r\nThat earned its keep immediately. The closed form written for `a(n) = 3·a(n-1) - 1` was\r\n`(3ⁿ+1)/2` — the answer to a *different* recurrence; the correct one is `(3ⁿ⁺¹+1)/2`. The\r\niteration was right and the **check** was wrong, which is precisely the failure a cross-check\r\nexists to catch. Without it, a wrong answer key would have shipped looking perfectly plausible.\r\n\r\n## Verify it yourself\r\n\r\n```bash\r\npython environments/concretemath_env/build_tasks.py --verify   # 37/37\r\npython environments/concretemath_env/test_compare.py           # 14/14\r\n```\r\n\r\n`--verify` re-checks every task: that it runs, that it is deterministic across two fresh\r\nexecutions, that it returns a non-empty list of primitives, that **no value is a float**, that\r\nevery value is a genuine `int` (not a `bool`), and that it survives the serialisation\r\nround-trip exactly.\r\n\r\n## Environment arguments\r\n\r\n`load_environment()` deliberately exposes very little: the program's guidance is that an\r\nenvironment should have one correct way to be run, so nothing about the prompts, the parsing or\r\nthe grading is configurable.\r\n\r\n| Argument | Default | Meaning |\r\n|---|---|---|\r\n| `split` | `'train'` | Dataset split to load. |\r\n| `dataset_name` | `'eltociear/concretemath-tasks-v1'` | Hugging Face dataset of tasks. Change only to point at a fork. |\r\n| `max_turns` | `5` | Tool-use turns the model gets before the rollout ends. |\r\n| `**kwargs` | — | Passed through to the underlying `SandboxEnv`. |\r\n\r\n## Reward rubric\r\n\r\n| Reward function | Weight | What it returns |\r\n|---|---|---|\r\n| `correctness` | 1.0 | Binary: 1.0 when the answer matches the reference, else 0.0. |\r\n\r\nThere is no LLM judge and no partial credit. The score is computed on the host in\r\n`post_rollout` and read back by the rubric, so the reward is a deterministic function of the\r\nvalues the model left in `result`. A **harness** failure (dead sandbox, unreadable read-back)\r\nalso scores 0.0 but additionally sets `state[\"scoring_error\"]`, so an eval run can tell a\r\nbroken harness from a wrong answer instead of blaming the model.\r\n\r\n## Dependencies\r\n\r\n`datasets>=4.1.0`, `verifiers>=0.1.8`\r\n\r\n## Sample `vf-eval` usage\r\n\r\n```bash\r\nuv run vf-install concretemath-env\r\nuv run vf-eval -s concretemath-env -m gpt-4.1 -n 5 -r 3\r\nuv run vf-tui                      # inspect the outputs/ folder it writes\r\n```\r\n\r\n## Known limitation\r\n\r\nThe Docker **sandbox transport** has not been executed — `docker run`, `pip install`, and the\r\nmodel's code running inside the container — because that needs a Docker runtime and an\r\ninference provider key, neither available where this was authored.\r\n\r\nEverything else is exercised. `environments/test_scoring_path.py` runs this environment's\r\n`post_rollout` and `Rubric` with the sandbox mocked out, asserting that a correct answer scores\r\n1.0, a well-formed wrong answer scores 0.0, and a dead sandbox scores 0.0 *and* sets\r\n`scoring_error` so a harness failure is never mistaken for a bad model. It also checks that\r\nwhat `build_tasks.py` emits is exactly what the scorer expects. The environment mirrors the\r\nstructure of `polars_env` (already accepted into the Environments Program) and imports cleanly\r\nagainst `verifiers` 0.2.1.\r\n","encoding":"utf-8","truncated":false,"total_bytes":5720},"status":null}