positronic-evals 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. positronic_evals-0.1.0/.gitignore +167 -0
  2. positronic_evals-0.1.0/PKG-INFO +108 -0
  3. positronic_evals-0.1.0/README.md +97 -0
  4. positronic_evals-0.1.0/positronic_evals/__init__.py +0 -0
  5. positronic_evals-0.1.0/positronic_evals/evals.py +107 -0
  6. positronic_evals-0.1.0/positronic_evals/labels.py +589 -0
  7. positronic_evals-0.1.0/positronic_evals/molmo_spaces.py +18 -0
  8. positronic_evals-0.1.0/positronic_evals/py.typed +0 -0
  9. positronic_evals-0.1.0/positronic_evals/right_view_blocked.json +190 -0
  10. positronic_evals-0.1.0/positronic_evals/scoring.py +108 -0
  11. positronic_evals-0.1.0/positronic_evals/slug.py +57 -0
  12. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000000/meta.json +5 -0
  13. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000000/robot_state.q.parquet +0 -0
  14. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000000/static.json +68 -0
  15. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000001/meta.json +5 -0
  16. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000001/robot_state.q.parquet +0 -0
  17. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000001/static.json +68 -0
  18. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000002/meta.json +5 -0
  19. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000002/robot_state.q.parquet +0 -0
  20. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000002/static.json +68 -0
  21. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000003/meta.json +5 -0
  22. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000003/robot_state.q.parquet +0 -0
  23. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000003/static.json +68 -0
  24. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000004/meta.json +5 -0
  25. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000004/robot_state.q.parquet +0 -0
  26. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000004/static.json +68 -0
  27. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000005/meta.json +5 -0
  28. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000005/robot_state.q.parquet +0 -0
  29. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000005/static.json +68 -0
  30. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000006/meta.json +5 -0
  31. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000006/robot_state.q.parquet +0 -0
  32. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000006/static.json +68 -0
  33. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000007/meta.json +5 -0
  34. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000007/robot_state.q.parquet +0 -0
  35. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000007/static.json +68 -0
  36. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000008/meta.json +5 -0
  37. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000008/robot_state.q.parquet +0 -0
  38. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000008/static.json +68 -0
  39. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000009/meta.json +5 -0
  40. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000009/robot_state.q.parquet +0 -0
  41. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000009/static.json +68 -0
  42. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000010/meta.json +5 -0
  43. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000010/robot_state.q.parquet +0 -0
  44. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000010/static.json +68 -0
  45. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000011/meta.json +5 -0
  46. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000011/robot_state.q.parquet +0 -0
  47. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000011/static.json +68 -0
  48. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000012/meta.json +5 -0
  49. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000012/robot_state.q.parquet +0 -0
  50. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000012/static.json +68 -0
  51. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000013/meta.json +5 -0
  52. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000013/robot_state.q.parquet +0 -0
  53. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000013/static.json +68 -0
  54. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000014/meta.json +5 -0
  55. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000014/robot_state.q.parquet +0 -0
  56. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000014/static.json +68 -0
  57. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000015/meta.json +5 -0
  58. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000015/robot_state.q.parquet +0 -0
  59. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000015/static.json +68 -0
  60. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000016/meta.json +5 -0
  61. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000016/robot_state.q.parquet +0 -0
  62. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000016/static.json +68 -0
  63. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000017/meta.json +5 -0
  64. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000017/robot_state.q.parquet +0 -0
  65. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000017/static.json +68 -0
  66. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000018/meta.json +5 -0
  67. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000018/robot_state.q.parquet +0 -0
  68. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000018/static.json +68 -0
  69. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000019/meta.json +5 -0
  70. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000019/robot_state.q.parquet +0 -0
  71. positronic_evals-0.1.0/positronic_evals/tests/fixtures/molmo_sweep/000000000000/000000000019/static.json +68 -0
  72. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000000/meta.json +5 -0
  73. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000000/static.json +76 -0
  74. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000000/subtask.parquet +0 -0
  75. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000001/meta.json +5 -0
  76. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000001/static.json +76 -0
  77. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000001/subtask.parquet +0 -0
  78. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000002/meta.json +5 -0
  79. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000002/static.json +76 -0
  80. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000002/subtask.parquet +0 -0
  81. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000003/meta.json +5 -0
  82. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000003/static.json +76 -0
  83. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000003/subtask.parquet +0 -0
  84. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000004/meta.json +5 -0
  85. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000004/static.json +76 -0
  86. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000004/subtask.parquet +0 -0
  87. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000005/meta.json +5 -0
  88. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000005/static.json +76 -0
  89. positronic_evals-0.1.0/positronic_evals/tests/fixtures/robolab_sweep/000000000000/000000000005/subtask.parquet +0 -0
  90. positronic_evals-0.1.0/positronic_evals/tests/test_evals.py +94 -0
  91. positronic_evals-0.1.0/positronic_evals/tests/test_labels.py +701 -0
  92. positronic_evals-0.1.0/positronic_evals/tests/test_molmo_spaces.py +22 -0
  93. positronic_evals-0.1.0/positronic_evals/tests/test_scoring.py +228 -0
  94. positronic_evals-0.1.0/positronic_evals/tests/test_slug.py +67 -0
  95. positronic_evals-0.1.0/pyproject.toml +27 -0
@@ -0,0 +1,167 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .env
124
+ .venv
125
+ .venv-typecheck
126
+ env/
127
+ venv/
128
+ ENV/
129
+ env.bak/
130
+ venv.bak/
131
+
132
+ # Spyder project settings
133
+ .spyderproject
134
+ .spyproject
135
+
136
+ # Rope project settings
137
+ .ropeproject
138
+
139
+ # mkdocs documentation
140
+ /site
141
+
142
+ # mypy
143
+ .mypy_cache/
144
+ .dmypy.json
145
+ dmypy.json
146
+
147
+ # Pyre type checker
148
+ .pyre/
149
+
150
+ # pytype static type analyzer
151
+ .pytype/
152
+
153
+ # Cython debug symbols
154
+ cython_debug/
155
+
156
+ # PyCharm
157
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
158
+ # be added to the global gitignore or merged into this project gitignore. For a PyCharm
159
+ # project, it is generally recommended to exclude comment-based line additions.
160
+ .idea/
161
+
162
+ # VSCode
163
+ .vscode/
164
+ *.code-workspace
165
+
166
+ # Environment variables
167
+ .env*
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: positronic-evals
3
+ Version: 0.1.0
4
+ Summary: The public evals of the Positronic platform: their definitions, labelers, scoring rules and episode lists
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: positronic-eval-vocabulary==0.4.0
8
+ Requires-Dist: pyarrow
9
+ Requires-Dist: pydantic>=2.11
10
+ Description-Content-Type: text/markdown
11
+
12
+ # positronic-evals
13
+
14
+ The definitions of the public evals, the labelers that read a recorded episode, the rules that score
15
+ a run, and MolmoSpaces' committed episode lists.
16
+
17
+ `positronic` runs these evals on your machine, `positronic-platform-client` names them on the wire,
18
+ and a service that scores a run reads them. Each of them installs this distribution. It depends on
19
+ `pydantic`, `pyarrow` and `positronic-eval-vocabulary`, and on no robotics stack and no client, so a
20
+ service installs it alone:
21
+
22
+ ```bash
23
+ uv add "positronic-evals @ git+https://github.com/Positronic-Robotics/positronic@<tag or commit>#subdirectory=evals"
24
+ ```
25
+
26
+ `positronic_evals.slug` carries an `IntEnum` on the wire as its lowercase name.
27
+
28
+ ## Public evals
29
+
30
+ `positronic_evals.evals` carries the definitions of the public evals. A definition pins what the
31
+ platform runs under the eval's name: the positronic eval config and its arguments, the trials, the
32
+ time limit of one run, its scoring, and the positronic commit that the platform runs the config at.
33
+ `public_eval(name)` returns one definition, and `PUBLIC_EVALS` names them all. The platform also
34
+ offers evals that it does not publish, and `public_eval` refuses their names.
35
+
36
+ `positronic eval run` with `--policy` takes a public eval's name as `--eval`, and runs that eval on
37
+ your machine. The run uses the positronic you installed. It warns when that positronic is not the
38
+ commit the definition names, because the two runs can then differ. A local run does not stop at the
39
+ time limit.
40
+
41
+ ## Labels and scores
42
+
43
+ `positronic_evals.labels` reads what happened in a recorded episode. A labeler reads one episode
44
+ directory and gives its `Label`: the episode's id and task, its end, and a list of timed events. An
45
+ event is a stage that the arm reached, a count of items delivered, a count of subtasks achieved, or
46
+ one of the three end events: `Success`, `Fail` and `Safety`. The earliest end event gives the
47
+ `outcome`. A label with no end event ran out of time. The label's other views, such as
48
+ `stage_seconds` and `placements`, are properties computed from its events. `Label.until(seconds)`
49
+ gives the label as it stood at that time.
50
+
51
+ `positronic_evals.scoring` states what a label is worth. An eval's `Scoring` names its labelers in
52
+ order of precedence, and its primary metric. `label(episode, scoring.labelers)` gives a pair: the
53
+ first labeler that labels the episode, and its label. It gives None when no labeler labels the
54
+ episode. `score(scoring, labels)` takes the label of each episode, or None. For the episode
55
+ directories `episodes` of one run:
56
+
57
+ ```python
58
+ from positronic_evals.evals import EvalRef, public_eval
59
+ from positronic_evals.labels import label
60
+ from positronic_evals.scoring import score
61
+
62
+ scoring = public_eval(EvalRef('molmo.franka_pick_mini')).scoring
63
+ picked = [label(episode, scoring.labelers) for episode in episodes]
64
+ scores = score(scoring, [None if given is None else given[1] for given in picked])
65
+ ```
66
+
67
+ `score` gives `Scores`, which holds three numbers. `primary` is the mean episode score, or the mean
68
+ of the task means where the metric sets `per_task`, and a board ranks on it. `episodes` counts every
69
+ episode, and `unscored` counts the episodes that no labeler labelled. `primary` does not count those
70
+ as failures. No file holds a score: a score is a function of the labels.
71
+
72
+ `PUBLIC_LABELERS` holds the labelers that this package publishes, `molmo` and `robolab`. A caller with
73
+ labelers of its own passes `label` a table that holds them beside `PUBLIC_LABELERS`. The MolmoSpaces
74
+ labeler takes the end of a trial from the episode's signal files: the time from the first sample to
75
+ the last, on the world clock. That time can exceed the trial by up to one control step.
76
+
77
+ The RoboLab labeler reads the end of a trial in the same way. Its task is the task id that RoboLab
78
+ names, and not the instruction. It also reads the `subtask` signal, which RoboLab records at each
79
+ step, and gives a `SubtaskAchieved` event, with RoboLab's score, for each change of the subtasks
80
+ completed. A change of the score alone is not an event. A RoboLab eval scores a success 1.0, and any
81
+ other episode the score at its last change of the count, or 0 where it completed no subtask. Where
82
+ RoboLab grades progress within a subtask, this score does not count that progress:
83
+
84
+ ```python
85
+ from positronic_evals.labels import ROBOLAB_LABELER
86
+ from positronic_evals.scoring import EpisodeScore, MeanEpisodeScore, Scoring
87
+
88
+ robolab = Scoring(
89
+ labelers=(ROBOLAB_LABELER,), primary=MeanEpisodeScore(episode_score=EpisodeScore(use_subtask_score=True))
90
+ )
91
+ ```
92
+
93
+ ## MolmoSpaces episode lists
94
+
95
+ `positronic_evals.molmo_spaces` holds the episode lists that an offline sweep builds, by benchmark
96
+ path below the asset packs' `benchmarks` directory. `RIGHT_VIEW_BLOCKED_PATH` lists the episodes where
97
+ scene geometry hides the start grasp site from the right shoulder camera. The MolmoSpaces sim server
98
+ leaves those episodes out of an open selection and refuses a pinned one. No public eval pins one.
99
+
100
+ ```python
101
+ from positronic_evals.molmo_spaces import RIGHT_VIEW_BLOCKED_PATH, read_episode_list
102
+
103
+ blocked = read_episode_list(RIGHT_VIEW_BLOCKED_PATH)
104
+ ```
105
+
106
+ A benchmark that a list does not name is unswept. Rebuild a list when the pinned MolmoSpaces revision
107
+ or the asset packs change. A new list goes beside `right_view_blocked.json`, with its path constant in
108
+ `positronic_evals.molmo_spaces`, and `read_episode_list` reads it.
@@ -0,0 +1,97 @@
1
+ # positronic-evals
2
+
3
+ The definitions of the public evals, the labelers that read a recorded episode, the rules that score
4
+ a run, and MolmoSpaces' committed episode lists.
5
+
6
+ `positronic` runs these evals on your machine, `positronic-platform-client` names them on the wire,
7
+ and a service that scores a run reads them. Each of them installs this distribution. It depends on
8
+ `pydantic`, `pyarrow` and `positronic-eval-vocabulary`, and on no robotics stack and no client, so a
9
+ service installs it alone:
10
+
11
+ ```bash
12
+ uv add "positronic-evals @ git+https://github.com/Positronic-Robotics/positronic@<tag or commit>#subdirectory=evals"
13
+ ```
14
+
15
+ `positronic_evals.slug` carries an `IntEnum` on the wire as its lowercase name.
16
+
17
+ ## Public evals
18
+
19
+ `positronic_evals.evals` carries the definitions of the public evals. A definition pins what the
20
+ platform runs under the eval's name: the positronic eval config and its arguments, the trials, the
21
+ time limit of one run, its scoring, and the positronic commit that the platform runs the config at.
22
+ `public_eval(name)` returns one definition, and `PUBLIC_EVALS` names them all. The platform also
23
+ offers evals that it does not publish, and `public_eval` refuses their names.
24
+
25
+ `positronic eval run` with `--policy` takes a public eval's name as `--eval`, and runs that eval on
26
+ your machine. The run uses the positronic you installed. It warns when that positronic is not the
27
+ commit the definition names, because the two runs can then differ. A local run does not stop at the
28
+ time limit.
29
+
30
+ ## Labels and scores
31
+
32
+ `positronic_evals.labels` reads what happened in a recorded episode. A labeler reads one episode
33
+ directory and gives its `Label`: the episode's id and task, its end, and a list of timed events. An
34
+ event is a stage that the arm reached, a count of items delivered, a count of subtasks achieved, or
35
+ one of the three end events: `Success`, `Fail` and `Safety`. The earliest end event gives the
36
+ `outcome`. A label with no end event ran out of time. The label's other views, such as
37
+ `stage_seconds` and `placements`, are properties computed from its events. `Label.until(seconds)`
38
+ gives the label as it stood at that time.
39
+
40
+ `positronic_evals.scoring` states what a label is worth. An eval's `Scoring` names its labelers in
41
+ order of precedence, and its primary metric. `label(episode, scoring.labelers)` gives a pair: the
42
+ first labeler that labels the episode, and its label. It gives None when no labeler labels the
43
+ episode. `score(scoring, labels)` takes the label of each episode, or None. For the episode
44
+ directories `episodes` of one run:
45
+
46
+ ```python
47
+ from positronic_evals.evals import EvalRef, public_eval
48
+ from positronic_evals.labels import label
49
+ from positronic_evals.scoring import score
50
+
51
+ scoring = public_eval(EvalRef('molmo.franka_pick_mini')).scoring
52
+ picked = [label(episode, scoring.labelers) for episode in episodes]
53
+ scores = score(scoring, [None if given is None else given[1] for given in picked])
54
+ ```
55
+
56
+ `score` gives `Scores`, which holds three numbers. `primary` is the mean episode score, or the mean
57
+ of the task means where the metric sets `per_task`, and a board ranks on it. `episodes` counts every
58
+ episode, and `unscored` counts the episodes that no labeler labelled. `primary` does not count those
59
+ as failures. No file holds a score: a score is a function of the labels.
60
+
61
+ `PUBLIC_LABELERS` holds the labelers that this package publishes, `molmo` and `robolab`. A caller with
62
+ labelers of its own passes `label` a table that holds them beside `PUBLIC_LABELERS`. The MolmoSpaces
63
+ labeler takes the end of a trial from the episode's signal files: the time from the first sample to
64
+ the last, on the world clock. That time can exceed the trial by up to one control step.
65
+
66
+ The RoboLab labeler reads the end of a trial in the same way. Its task is the task id that RoboLab
67
+ names, and not the instruction. It also reads the `subtask` signal, which RoboLab records at each
68
+ step, and gives a `SubtaskAchieved` event, with RoboLab's score, for each change of the subtasks
69
+ completed. A change of the score alone is not an event. A RoboLab eval scores a success 1.0, and any
70
+ other episode the score at its last change of the count, or 0 where it completed no subtask. Where
71
+ RoboLab grades progress within a subtask, this score does not count that progress:
72
+
73
+ ```python
74
+ from positronic_evals.labels import ROBOLAB_LABELER
75
+ from positronic_evals.scoring import EpisodeScore, MeanEpisodeScore, Scoring
76
+
77
+ robolab = Scoring(
78
+ labelers=(ROBOLAB_LABELER,), primary=MeanEpisodeScore(episode_score=EpisodeScore(use_subtask_score=True))
79
+ )
80
+ ```
81
+
82
+ ## MolmoSpaces episode lists
83
+
84
+ `positronic_evals.molmo_spaces` holds the episode lists that an offline sweep builds, by benchmark
85
+ path below the asset packs' `benchmarks` directory. `RIGHT_VIEW_BLOCKED_PATH` lists the episodes where
86
+ scene geometry hides the start grasp site from the right shoulder camera. The MolmoSpaces sim server
87
+ leaves those episodes out of an open selection and refuses a pinned one. No public eval pins one.
88
+
89
+ ```python
90
+ from positronic_evals.molmo_spaces import RIGHT_VIEW_BLOCKED_PATH, read_episode_list
91
+
92
+ blocked = read_episode_list(RIGHT_VIEW_BLOCKED_PATH)
93
+ ```
94
+
95
+ A benchmark that a list does not name is unswept. Rebuild a list when the pinned MolmoSpaces revision
96
+ or the asset packs change. A new list goes beside `right_view_blocked.json`, with its path constant in
97
+ `positronic_evals.molmo_spaces`, and `read_episode_list` reads it.
File without changes
@@ -0,0 +1,107 @@
1
+ """The one name a caller picks a run by, and the definitions of the public evals.
2
+
3
+ A name is a task suite AND the embodiment that runs it, so there is no second axis to get wrong.
4
+ The platform owns the set, so a name that this package does not define still reaches the platform.
5
+
6
+ The platform holds the definitions of the evals it does not publish.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pydantic import BaseModel, ConfigDict, Field, GetCoreSchemaHandler, JsonValue, PositiveInt
12
+ from pydantic_core import core_schema
13
+
14
+ from positronic_evals.labels import MOLMO_LABELER
15
+ from positronic_evals.scoring import MeanEpisodeScore, Scoring
16
+
17
+
18
+ class EvalRef(str):
19
+ """The name of an eval the platform offers, as a validated `str`."""
20
+
21
+ __slots__ = ()
22
+
23
+ def __new__(cls, value: str) -> EvalRef:
24
+ if not value or any(c.isspace() for c in value):
25
+ raise ValueError(f'not an eval name: {value!r}')
26
+ return super().__new__(cls, value)
27
+
28
+ @classmethod
29
+ def __get_pydantic_core_schema__(cls, source: type, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema:
30
+ return core_schema.no_info_after_validator_function(cls, core_schema.str_schema())
31
+
32
+
33
+ _DEFINITION_CONFIG = ConfigDict(extra='forbid', frozen=True)
34
+
35
+
36
+ class EvalTask(BaseModel):
37
+ """One task of an eval, and its trials in run order."""
38
+
39
+ model_config = _DEFINITION_CONFIG
40
+
41
+ name: str = Field(min_length=1)
42
+ # Each trial's initial condition, in the keys of the config that runs it.
43
+ trials: list[dict[str, JsonValue]] = Field(min_length=1)
44
+
45
+
46
+ class EvalDefinition(BaseModel):
47
+ """What a run of a named eval runs."""
48
+
49
+ model_config = _DEFINITION_CONFIG
50
+
51
+ # The positronic eval config, as `positronic eval run --eval=` takes it.
52
+ config: str = Field(pattern=r'^\.[A-Za-z_][A-Za-z0-9_.]*$')
53
+ # The config's `--eval.<name>=` overrides, which expand to `tasks`.
54
+ args: dict[str, JsonValue]
55
+ tasks: list[EvalTask] = Field(min_length=1)
56
+ # The wall-clock limit of one platform run. A local run does not stop at it.
57
+ time_limit_s: PositiveInt
58
+ scoring: Scoring
59
+ # The positronic commit the platform runs the config at.
60
+ positronic_revision: str = Field(pattern=r'^[0-9a-f]{40}$')
61
+
62
+
63
+ # The MolmoSpaces evals: the config they name, its task and trial key as positronic spells them
64
+ # (`positronic/simulator/molmo_spaces/keys.py`), their scoring, and the positronic commit the platform's sim
65
+ # image holds. A run ranks on its success rate.
66
+ MOLMO_CONFIG = '.sim.molmo.benchmarks'
67
+ MOLMO_TASK = 'franka_pick_droid_mini'
68
+ MOLMO_EPISODE_INDEX_KEY = 'molmo.episode_index'
69
+ MOLMO_SCORING = Scoring(labelers=(MOLMO_LABELER,), primary=MeanEpisodeScore())
70
+ MOLMO_POSITRONIC_REVISION = 'a9e13e8a1fbe624ff62b49e25628b39859619986'
71
+
72
+
73
+ def _franka_pick_mini(episodes: list[int]) -> EvalDefinition:
74
+ """`episodes` of the one benchmark the MolmoSpaces evals run, once each."""
75
+ return EvalDefinition(
76
+ config=MOLMO_CONFIG,
77
+ # Each key must name a parameter of positronic's `benchmarks` config.
78
+ args={
79
+ 'suite': 'molmospaces-bench-v2',
80
+ 'scene_dataset': 'procthor-10k',
81
+ 'task_config': 'FrankaPickDroidMiniBench',
82
+ 'benchmark': 'FrankaPickDroidMiniBench_json_benchmark_20251231',
83
+ 'episodes': list(episodes),
84
+ 'trial_count': 1,
85
+ },
86
+ tasks=[EvalTask(name=MOLMO_TASK, trials=[{MOLMO_EPISODE_INDEX_KEY: i} for i in episodes])],
87
+ time_limit_s=7200,
88
+ scoring=MOLMO_SCORING,
89
+ positronic_revision=MOLMO_POSITRONIC_REVISION,
90
+ )
91
+
92
+
93
+ _PUBLIC_EVALS: dict[EvalRef, EvalDefinition] = {
94
+ # Episode 6 hides the start grasp site from the right shoulder camera; the sim server refuses it.
95
+ EvalRef('molmo.franka_pick_mini'): _franka_pick_mini([*range(6), *range(7, 21)]),
96
+ EvalRef('molmo.franka_pick_mini_smoke'): _franka_pick_mini([0, 1, 2, 3, 4]),
97
+ }
98
+
99
+ PUBLIC_EVALS: tuple[EvalRef, ...] = tuple(_PUBLIC_EVALS)
100
+
101
+
102
+ def public_eval(name: EvalRef) -> EvalDefinition:
103
+ """The definition of the public eval `name`, as a copy, or LookupError naming the public evals."""
104
+ definition = _PUBLIC_EVALS.get(name)
105
+ if definition is None:
106
+ raise LookupError(f'{name!r} is not a public eval; the public evals are {", ".join(PUBLIC_EVALS)}')
107
+ return definition.model_copy(deep=True)