refractal 0.1.0a1__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 (98) hide show
  1. refractal-0.1.0a1/.gitignore +20 -0
  2. refractal-0.1.0a1/CHANGELOG.md +66 -0
  3. refractal-0.1.0a1/LICENSE +202 -0
  4. refractal-0.1.0a1/NOTICE +23 -0
  5. refractal-0.1.0a1/PKG-INFO +314 -0
  6. refractal-0.1.0a1/README.md +268 -0
  7. refractal-0.1.0a1/docs/README.md +68 -0
  8. refractal-0.1.0a1/docs/adapter-contract.md +109 -0
  9. refractal-0.1.0a1/docs/adapter-example.md +208 -0
  10. refractal-0.1.0a1/docs/backend-compose.md +220 -0
  11. refractal-0.1.0a1/docs/backend-vla-eval.md +166 -0
  12. refractal-0.1.0a1/docs/catalog-reference.md +251 -0
  13. refractal-0.1.0a1/docs/execution-mode.md +99 -0
  14. refractal-0.1.0a1/docs/getting-started.md +195 -0
  15. refractal-0.1.0a1/docs/perturbations/adapters.md +310 -0
  16. refractal-0.1.0a1/docs/perturbations/declaring.md +415 -0
  17. refractal-0.1.0a1/docs/perturbations/index.md +93 -0
  18. refractal-0.1.0a1/docs/perturbations/sweeps.md +173 -0
  19. refractal-0.1.0a1/docs/reading-a-comparison.md +159 -0
  20. refractal-0.1.0a1/docs/releasing.md +71 -0
  21. refractal-0.1.0a1/docs/writing-a-catalog.md +272 -0
  22. refractal-0.1.0a1/examples/catalog/assets/cube_bowl.xml +13 -0
  23. refractal-0.1.0a1/examples/catalog/assets/vial_rack.xml +14 -0
  24. refractal-0.1.0a1/examples/catalog/hardware.yaml +23 -0
  25. refractal-0.1.0a1/examples/catalog/run.yaml +19 -0
  26. refractal-0.1.0a1/examples/catalog/scenarios.yaml +64 -0
  27. refractal-0.1.0a1/examples/catalog/scenes.yaml +30 -0
  28. refractal-0.1.0a1/examples/catalog/tasks.yaml +29 -0
  29. refractal-0.1.0a1/examples/mjx-panda/assets/panda_pick_cube.xml +300 -0
  30. refractal-0.1.0a1/examples/mjx-panda/hardware.yaml +23 -0
  31. refractal-0.1.0a1/examples/mjx-panda/run.yaml +9 -0
  32. refractal-0.1.0a1/examples/mjx-panda/scenarios.yaml +20 -0
  33. refractal-0.1.0a1/examples/mjx-panda/scenes.yaml +62 -0
  34. refractal-0.1.0a1/examples/mjx-panda/tasks.yaml +7 -0
  35. refractal-0.1.0a1/pyproject.toml +83 -0
  36. refractal-0.1.0a1/src/refractal/__init__.py +109 -0
  37. refractal-0.1.0a1/src/refractal/build/__init__.py +479 -0
  38. refractal-0.1.0a1/src/refractal/cli.py +719 -0
  39. refractal-0.1.0a1/src/refractal/compare/__init__.py +66 -0
  40. refractal-0.1.0a1/src/refractal/compare/pairing.py +377 -0
  41. refractal-0.1.0a1/src/refractal/compare/stats.py +364 -0
  42. refractal-0.1.0a1/src/refractal/compare/variance.py +196 -0
  43. refractal-0.1.0a1/src/refractal/compare/verdict.py +589 -0
  44. refractal-0.1.0a1/src/refractal/example.py +98 -0
  45. refractal-0.1.0a1/src/refractal/execute/__init__.py +42 -0
  46. refractal-0.1.0a1/src/refractal/execute/fake.py +151 -0
  47. refractal-0.1.0a1/src/refractal/execute/harness.py +220 -0
  48. refractal-0.1.0a1/src/refractal/execute/local.py +163 -0
  49. refractal-0.1.0a1/src/refractal/execute/physics.py +126 -0
  50. refractal-0.1.0a1/src/refractal/execute/resources.py +122 -0
  51. refractal-0.1.0a1/src/refractal/execute/results.py +884 -0
  52. refractal-0.1.0a1/src/refractal/execute/vla_eval.py +940 -0
  53. refractal-0.1.0a1/src/refractal/execute/vla_eval_runner.py +694 -0
  54. refractal-0.1.0a1/src/refractal/expect.py +159 -0
  55. refractal-0.1.0a1/src/refractal/generators.py +24 -0
  56. refractal-0.1.0a1/src/refractal/init.py +236 -0
  57. refractal-0.1.0a1/src/refractal/perturbations/__init__.py +1056 -0
  58. refractal-0.1.0a1/src/refractal/predicates.py +34 -0
  59. refractal-0.1.0a1/src/refractal/promote.py +151 -0
  60. refractal-0.1.0a1/src/refractal/render/__init__.py +564 -0
  61. refractal-0.1.0a1/src/refractal/resolve/__init__.py +356 -0
  62. refractal-0.1.0a1/src/refractal/resolve/expand.py +432 -0
  63. refractal-0.1.0a1/src/refractal/resolve/fit.py +390 -0
  64. refractal-0.1.0a1/src/refractal/resolve/lock.py +314 -0
  65. refractal-0.1.0a1/src/refractal/schema/__init__.py +99 -0
  66. refractal-0.1.0a1/src/refractal/schema/canonical.py +212 -0
  67. refractal-0.1.0a1/src/refractal/schema/errors.py +57 -0
  68. refractal-0.1.0a1/src/refractal/schema/generators.py +258 -0
  69. refractal-0.1.0a1/src/refractal/schema/identity.py +428 -0
  70. refractal-0.1.0a1/src/refractal/schema/importstr.py +119 -0
  71. refractal-0.1.0a1/src/refractal/schema/loader.py +337 -0
  72. refractal-0.1.0a1/src/refractal/schema/models.py +767 -0
  73. refractal-0.1.0a1/src/refractal/schema/plan.py +349 -0
  74. refractal-0.1.0a1/src/refractal/sweep.py +180 -0
  75. refractal-0.1.0a1/tests/__init__.py +0 -0
  76. refractal-0.1.0a1/tests/fixture_filters.py +19 -0
  77. refractal-0.1.0a1/tests/test_boundaries.py +308 -0
  78. refractal-0.1.0a1/tests/test_bridge.py +723 -0
  79. refractal-0.1.0a1/tests/test_build.py +765 -0
  80. refractal-0.1.0a1/tests/test_canonical.py +142 -0
  81. refractal-0.1.0a1/tests/test_compare.py +1162 -0
  82. refractal-0.1.0a1/tests/test_encoding.py +123 -0
  83. refractal-0.1.0a1/tests/test_execute.py +934 -0
  84. refractal-0.1.0a1/tests/test_expect.py +186 -0
  85. refractal-0.1.0a1/tests/test_generators.py +177 -0
  86. refractal-0.1.0a1/tests/test_identity.py +309 -0
  87. refractal-0.1.0a1/tests/test_init.py +105 -0
  88. refractal-0.1.0a1/tests/test_loader.py +149 -0
  89. refractal-0.1.0a1/tests/test_models.py +259 -0
  90. refractal-0.1.0a1/tests/test_mutate.py +187 -0
  91. refractal-0.1.0a1/tests/test_perturbations.py +1235 -0
  92. refractal-0.1.0a1/tests/test_physics.py +119 -0
  93. refractal-0.1.0a1/tests/test_promote.py +210 -0
  94. refractal-0.1.0a1/tests/test_render.py +556 -0
  95. refractal-0.1.0a1/tests/test_resolve.py +1370 -0
  96. refractal-0.1.0a1/tests/test_rewrite.py +172 -0
  97. refractal-0.1.0a1/tests/test_sweep.py +129 -0
  98. refractal-0.1.0a1/tests/test_vla_eval_loop.py +1372 -0
@@ -0,0 +1,20 @@
1
+ # Anchored to the repo root. An unanchored `build/` also matches
2
+ # src/refractal/build/, which silently kept the whole `refractal build` package
3
+ # out of git -- `git status` stayed clean, the tests passed against the working
4
+ # tree, and the wheel shipped without it. Anchor these.
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ .venv/
9
+ /build/
10
+ /dist/
11
+ /results/
12
+ /plan.json
13
+
14
+ # mkdocs build output. Regenerable from docs/ + mkdocs.yml, and when it was
15
+ # tracked it went stale: a wording fix in docs/ left the built HTML asserting
16
+ # the old text.
17
+ site/
18
+ .mutation-in-progress
19
+ .coverage.json
20
+ .coverage
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ What changed in each released version. Newest first.
4
+
5
+ This is not the commit history. The history records why a decision was made;
6
+ this records what a user gets in a version they can install. Different
7
+ audiences, and the history is the better read for the first question.
8
+
9
+ ## 0.1.0a1 (unreleased)
10
+
11
+ First alpha. Every stage is implemented and tested against real model servers
12
+ as well as the synthetic backend.
13
+
14
+ **`plan_id` may move between alpha versions.** Episode identity is a hash of
15
+ the catalog's content, and the fields that feed it are still settling: they
16
+ changed three times this month. Results written by `0.1.0a1` may therefore fail
17
+ to join with results from a later alpha, and `refractal compare` will refuse
18
+ the join rather than pool them, which is the intended behaviour and not a bug.
19
+ If you are about to spend GPU hours, this is the sentence to read first. The
20
+ `a` in the version is doing real work.
21
+
22
+ `plan_schema` is 3. It is deliberately not part of `plan_id`: a format bump
23
+ does not make an experiment a different experiment.
24
+
25
+ ### Added
26
+
27
+ - `refractal init`, write a working example catalog that runs end to end with
28
+ no GPU, no simulator and no checkpoints.
29
+ - `refractal build`, compute scene hashes, evaluate filters and record resource
30
+ shapes into `catalog/build.lock`.
31
+ - `refractal plan`, compile a catalog into `plan.json`. Runs with none of the
32
+ optional dependencies installed, which is enforced by a test rather than by
33
+ convention.
34
+ - `refractal run`, execute a plan. Backends: `local` (simulates outcomes, no
35
+ infrastructure), `vla-eval` (drives the harness against running model
36
+ servers), `compose` (one container per worker).
37
+ - `refractal render`, write a deployment description from a plan without Docker
38
+ or a cluster installed. Targets: `compose` and `k8s`.
39
+ - `refractal compare`, compare checkpoints in a results directory. Paired
40
+ McNemar on scenario-level outcomes, a clustered bootstrap that resamples
41
+ whole scenarios, Cochran's Q as a screen across three or more checkpoints,
42
+ and Holm correction across the whole contrast family.
43
+ - `refractal explain`, why two plans are different experiments.
44
+ - Perturbations: timed changes to the world, the observation or the action
45
+ during an episode, hashed into `scenario_hash` so a perturbed episode is a
46
+ different experiment from its unperturbed counterpart.
47
+ - Exit codes for CI: 0 no regression, 1 regression, 2 cannot be answered.
48
+ The third is never reported as the second.
49
+ - Optional extras: `execute`, `compare`, `video`, `vla-eval`, `all`, `test`.
50
+ Comparison needs no numerical stack.
51
+
52
+ ### Not built yet
53
+
54
+ Stated because a gap a user discovers is worse than one they were told about.
55
+
56
+ - **Trajectory quality metrics.** The `steps.parquet` schema is declared and
57
+ nothing writes it. Writing it against synthetic data would bake in guesses
58
+ about what a real adapter can record.
59
+ - **`refractal run --backend k8s`.** Kubernetes is reached through `render`:
60
+ Refractal writes one Job per worker and your cluster schedules them. Nothing
61
+ submits them, watches them or collects their exit codes.
62
+ - **Resource-shape probing.** `refractal build` carries hand-declared shapes
63
+ forward and records whether they were measured. A lint refuses a comment
64
+ claiming a measurement when nothing recorded one.
65
+ - **Partial credit.** `phase_outcomes` exists in the schema and the LIBERO
66
+ adapter never populates it, so success is binary.
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Nalin Raut
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,23 @@
1
+ Refractal
2
+ Copyright 2026 Nalin Raut
3
+
4
+ Licensed under the Apache License, Version 2.0. See LICENSE.
5
+
6
+ This product depends on software from the following projects.
7
+ No source from them is included in this distribution.
8
+
9
+ -------------------------------------------------------------------------------
10
+ vla-evaluation-harness
11
+ https://github.com/allenai/vla-evaluation-harness
12
+ Copyright The Allen Institute for Artificial Intelligence
13
+ Licensed under the Apache License, Version 2.0
14
+
15
+ Refractal's `--backend vla-eval` runs every episode through this harness. It
16
+ provides the model-server protocol, the benchmark adapters, the observation and
17
+ action specifications, and the episode loop. Refractal subclasses two of its
18
+ classes (`Orchestrator` and `EpisodeRecorder`) and adds no copies of its source.
19
+ -------------------------------------------------------------------------------
20
+
21
+ Benchmark suites and model servers loaded through the harness at run time are
22
+ the work of their own authors and carry their own licences. Refractal neither
23
+ distributes nor modifies them.
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.5
2
+ Name: refractal
3
+ Version: 0.1.0a1
4
+ Summary: Scene-coherent placement and paired comparison for policy evaluation
5
+ Project-URL: Homepage, https://github.com/nalinraut/refractal
6
+ Project-URL: Repository, https://github.com/nalinraut/refractal
7
+ Project-URL: Issues, https://github.com/nalinraut/refractal/issues
8
+ Author: Nalin Raut
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Keywords: benchmark,evaluation,reproducibility,robotics,simulation,statistics,vla
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: pydantic>=2.6
25
+ Requires-Dist: pyyaml>=6.0
26
+ Provides-Extra: all
27
+ Requires-Dist: fsspec>=2024.2.0; extra == 'all'
28
+ Requires-Dist: pillow>=10.0; extra == 'all'
29
+ Requires-Dist: pyarrow>=15.0; extra == 'all'
30
+ Provides-Extra: compare
31
+ Requires-Dist: fsspec>=2024.2.0; extra == 'compare'
32
+ Requires-Dist: pyarrow>=15.0; extra == 'compare'
33
+ Provides-Extra: execute
34
+ Requires-Dist: fsspec>=2024.2.0; extra == 'execute'
35
+ Requires-Dist: pyarrow>=15.0; extra == 'execute'
36
+ Provides-Extra: test
37
+ Requires-Dist: numpy>=1.24; extra == 'test'
38
+ Requires-Dist: pillow>=10.0; extra == 'test'
39
+ Provides-Extra: video
40
+ Requires-Dist: pillow>=10.0; extra == 'video'
41
+ Provides-Extra: vla-eval
42
+ Requires-Dist: fsspec>=2024.2.0; extra == 'vla-eval'
43
+ Requires-Dist: pyarrow>=15.0; extra == 'vla-eval'
44
+ Requires-Dist: vla-eval>=0.6.0; extra == 'vla-eval'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # Refractal
48
+
49
+ [![PyPI](https://img.shields.io/pypi/v/refractal)](https://pypi.org/project/refractal/)
50
+ [![Python](https://img.shields.io/pypi/pyversions/refractal)](https://pypi.org/project/refractal/)
51
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue)](https://github.com/nalinraut/refractal/blob/main/LICENSE)
52
+ [![CI](https://github.com/nalinraut/refractal/actions/workflows/ci.yml/badge.svg)](https://github.com/nalinraut/refractal/actions/workflows/ci.yml)
53
+
54
+ **A closed-loop evaluation compiler.**
55
+
56
+ Refractal turns a declared set of scenarios into a plan you can inspect before running,
57
+ executes it on your chosen backend with the policy acting in simulation, and tells you
58
+ whether the difference between two checkpoints is real.
59
+
60
+ Backends: local, Docker Compose, Kubernetes.
61
+
62
+ Run the same evaluation twice and know whether the difference is real.
63
+
64
+ You have a policy. You changed something. You want to know if the new version is
65
+ better.
66
+
67
+ Today you run a benchmark, get 78%, run the old one, get 74%, and guess. That
68
+ number hides everything that matters: *which* situations got better, whether four
69
+ points is real or noise, whether some of those "failures" were your container
70
+ crashing, and whether the new version got worse at something while getting better
71
+ overall.
72
+
73
+ Refractal turns "run a benchmark" into "run an experiment."
74
+
75
+ ```console
76
+ $ pip install "refractal[execute,compare]"
77
+ $ mkdir demo && cd demo
78
+ $ refractal init .
79
+ $ refractal plan catalog --hardware laptop -o plan.json
80
+ cube-bowl-v1 30 scenarios 360 episodes 2 worker(s) <=37 min
81
+ ------------------------------------------------------------------
82
+ 360 episodes across 1 scene(s), 2 worker(s), at most 37 min
83
+ that is an upper bound: every episode is costed at its full step limit,
84
+ and episodes that succeed finish sooner.
85
+ wrote plan.json (plan_schema 3, plan_id sha256:82b73f180a7a...)
86
+
87
+ $ refractal run plan.json -o results --catalog catalog
88
+ $ refractal compare results $(python -c "import json;print(json.load(open('plan.json'))['plan_id'])")
89
+ ```
90
+
91
+ About a minute end to end, with no GPU, no simulator and no checkpoints: the
92
+ example runs against a built-in fake benchmark, so the machinery is real and only
93
+ the robot is not.
94
+
95
+ **[Start here →](https://github.com/nalinraut/refractal/blob/main/docs/getting-started.md)**
96
+
97
+ ## Documentation
98
+
99
+ | | |
100
+ |---|---|
101
+ | [Getting started](https://github.com/nalinraut/refractal/blob/main/docs/getting-started.md) | Install to verdict, start to finish. |
102
+ | [Writing a catalog](https://github.com/nalinraut/refractal/blob/main/docs/writing-a-catalog.md) | Describing your own experiment, and which edits invalidate existing results. |
103
+ | [Catalog reference](https://github.com/nalinraut/refractal/blob/main/docs/catalog-reference.md) | Every file and every field, for looking things up. A key not listed there is rejected. |
104
+ | [Reading a comparison](https://github.com/nalinraut/refractal/blob/main/docs/reading-a-comparison.md) | Every number in the output and when not to trust it. |
105
+ | [Perturbations](https://github.com/nalinraut/refractal/blob/main/docs/perturbations/index.md) · [declaring](https://github.com/nalinraut/refractal/blob/main/docs/perturbations/declaring.md) · [adapters](https://github.com/nalinraut/refractal/blob/main/docs/perturbations/adapters.md) · [sweeps](https://github.com/nalinraut/refractal/blob/main/docs/perturbations/sweeps.md) | Changing the world mid-episode, when success rates alone cannot separate two checkpoints. |
106
+ | [Adapter contract](https://github.com/nalinraut/refractal/blob/main/docs/adapter-contract.md) · [example](https://github.com/nalinraut/refractal/blob/main/docs/adapter-example.md) | Connecting your own simulator. |
107
+ | [Against vla-eval](https://github.com/nalinraut/refractal/blob/main/docs/backend-vla-eval.md) · [In containers](https://github.com/nalinraut/refractal/blob/main/docs/backend-compose.md) | Running at scale. |
108
+ | [Execution modes](https://github.com/nalinraut/refractal/blob/main/docs/execution-mode.md) · [Releasing](https://github.com/nalinraut/refractal/blob/main/docs/releasing.md) | |
109
+
110
+ ## What it does
111
+
112
+ **Declares experiments, not benchmark invocations.** You describe scenes, tasks
113
+ and scenario grids in YAML. Refractal expands that into a concrete list of
114
+ episodes, each with a content-addressed identity, works out how many workers the
115
+ run needs and whether they fit in memory, and writes a plan you can read, diff and
116
+ commit before spending anything.
117
+
118
+ **Compares episode by episode, with statistics that fit the design.** The same
119
+ scenarios face every checkpoint, so the data is *paired*. Refractal reports the
120
+ full 2×2 per task, runs McNemar on scenario-level outcomes, and gates on a
121
+ clustered bootstrap that resamples whole scenarios, because five seeds at one
122
+ pose are one observation of that pose rather than five.
123
+
124
+ **Refuses rather than guessing.** A plan that oversubscribes VRAM is not emitted.
125
+ A comparison across two scene geometries is blocked, not reported. A worker
126
+ assignment the backend cannot execute is refused at planning time rather than
127
+ discovered forty minutes in.
128
+
129
+ ## Three things it gets right that aggregate scores cannot
130
+
131
+ **Identity is content, never position.** An episode is a hash of
132
+ `(scene, task, scenario, seed, checkpoint)`. Rename a task and results still
133
+ join; change what the task *means* and they correctly stop. Reordering a list
134
+ cannot silently change what an attempt was.
135
+
136
+ **Infra failures leave the denominator.** A crashed worker is its own column, not
137
+ a policy failure. And because dropping it breaks the pairing a paired test needs,
138
+ the seed is dropped from *every* checkpoint, with the loss reported rather than
139
+ absorbed.
140
+
141
+ **Multiplicity is corrected across the whole family.** The gate fires if any
142
+ contrast trips, so every contrast is one family. Three tasks at a nominal 5% is a
143
+ family-wise error rate near 14%; four checkpoints across three tasks is eighteen
144
+ contrasts and about 60%. Holm-adjusted, with the uncorrected number printed so
145
+ the correction does not read as pedantry.
146
+
147
+ ## Comparing more than two checkpoints
148
+
149
+ Two is the common case, not a special one. A single checkpoint is a comparison of
150
+ size one; three is a training sweep, and produces something a leaderboard cannot:
151
+
152
+ ```
153
+ scene-v1 / task-4 (119 scenarios)
154
+ rates: ckpt-46: 49.4% ckpt-47: 49.8% ckpt-48: 37.5% (baseline ckpt-46)
155
+ outcome patterns:
156
+ 35 none solve
157
+ 18 only ckpt-46, ckpt-47 <- what ckpt-48 lost
158
+ 18 all solve
159
+ 17 only ckpt-47
160
+ 3 only ckpt-48
161
+ Cochran Q (screen): Q=19.73 p=0.0005 (66 of 119 scenarios disagree)
162
+ ckpt-46 -> ckpt-47: +0.005 [-0.059, +0.066] p=0.8874 holm=1.0000 -> no change
163
+ ckpt-46 -> ckpt-48: -0.118 [-0.179, -0.059] p=0.0008 holm=0.0040 -> REGRESSED
164
+ ```
165
+
166
+ "Eighteen scenarios that ckpt-46 and ckpt-47 both solve and ckpt-48 lost" is a
167
+ hypothesis you can go replay. Three lost scenarios would be noise.
168
+
169
+ ## Exit codes
170
+
171
+ `refractal compare` is meant for CI:
172
+
173
+ | code | meaning |
174
+ |---|---|
175
+ | 0 | no regression |
176
+ | 1 | regression detected |
177
+ | 2 | **cannot be answered**: different scene geometry, duplicated episodes, or a change to the code that ran them |
178
+
179
+ The third is not a regression and is never reported as one. "The two runs used
180
+ different geometry" is a different sentence from "the policy got worse", and
181
+ conflating them teaches people to ignore the gate.
182
+
183
+ ## Architecture
184
+
185
+ Refractal is a compiler, not a runtime. A catalog goes in, a `plan.json` comes
186
+ out, and the plan executes. The schedule is decided once and recorded, which makes
187
+ placement part of the provenance rather than an accident of the day.
188
+
189
+ | package | does | needs infrastructure? |
190
+ |---|---|---|
191
+ | `refractal.schema` | catalog, validation, identity | no |
192
+ | `refractal.resolve` | catalog → `plan.json` | no |
193
+ | `refractal.build` | facts that need the engine → `build.lock` | yes |
194
+ | `refractal.execute` | runs a plan; three backends | yes |
195
+ | `refractal.perturbations` | timed changes to world, observation or action | no |
196
+ | `refractal.render` | plan → deployment file | no |
197
+ | `refractal.compare` | Parquet → verdict | no |
198
+
199
+ `refractal plan` runs on a laptop with no simulator, no GPU and no Docker. That is
200
+ enforced by a test rather than by convention: it is the property that lets you
201
+ inspect a plan and its cost before committing to it.
202
+
203
+ Results are Parquet at an fsspec URI, so `./results` and `s3://bucket/results` are
204
+ the same code path. Every write is atomic, which is what makes resume safe, and
205
+ resume works by episode identity rather than by a counter.
206
+
207
+ ## Backends
208
+
209
+ Two things are called a backend and it is worth keeping them apart. `refractal
210
+ run --backend X` executes a plan. `refractal render --target Y` writes a
211
+ deployment file that something else executes.
212
+
213
+ | `refractal run --backend` | |
214
+ |---|---|
215
+ | `local` | simulates outcomes; no infrastructure |
216
+ | `vla-eval` | drives the harness against running model servers |
217
+ | `compose` | one container per worker, over the same entrypoint |
218
+
219
+ | `refractal render --target` | |
220
+ |---|---|
221
+ | `compose` | a Compose file; `docker compose up` runs it |
222
+ | `k8s` | one Job per worker; `kubectl apply` runs it |
223
+
224
+ Kubernetes is reached through `render`, not through `run`: Refractal writes the
225
+ Jobs and your cluster schedules them. Both targets render **without Docker or a
226
+ cluster installed**, so you can read the file before running it.
227
+
228
+ ## Install
229
+
230
+ ```console
231
+ pip install refractal # plan and build
232
+ pip install "refractal[execute]" # + run
233
+ pip install "refractal[compare]" # + compare
234
+ pip install "refractal[vla-eval]" # + the harness backend
235
+ ```
236
+
237
+ Comparison needs no numerical stack: exact McNemar is a binomial tail, the
238
+ bootstrap is resampling, and Cochran's Q is a permutation test.
239
+
240
+ ## Status
241
+
242
+ Alpha. Every stage is implemented and tested, and the identity-bearing fields are
243
+ still able to move between versions, which is what the `a` in `0.1.0a1` is for.
244
+
245
+ Validated against real model servers as well as the synthetic backend: **15,490
246
+ episodes across 13 runs**, including a 600-episode comparison whose success rate
247
+ was predicted from an earlier run's data before it was run and came back within
248
+ two points.
249
+
250
+ The largest is a 2,700-episode comparison of three checkpoints across three
251
+ LIBERO suites, executed twice under one `plan_id` — once locally, once as one
252
+ container per worker:
253
+
254
+ | | wall clock | episode work | overall success |
255
+ |---|---|---|---|
256
+ | local, workers in sequence | 317 min | 314 min | 72.6% |
257
+ | compose, workers in parallel | **171 min** | 462 min | 72.0% |
258
+
259
+ Placement moved the wall clock by 1.86× and the result by 0.6 points. The two
260
+ placements agreed on **92.0%** of episodes — while re-running a *single*
261
+ placement against itself agreed on only **89.7%**, so two deployments differ
262
+ less than one deployment differs from its own rerun. That is the claim the
263
+ content-addressed identity exists to support, and it is measured rather than
264
+ asserted.
265
+
266
+ The 462 minutes of episode work against 314 is the honest other half:
267
+ parallelism bought real wall clock and was not free, because four workers
268
+ contend where one had the machine to itself.
269
+
270
+ ## What is not here yet
271
+
272
+ - **Trajectory quality metrics.** The `steps.parquet` schema is declared and
273
+ nothing writes it, deliberately: writing it against synthetic data would bake in
274
+ guesses about what a real adapter can record.
275
+ - **`--backend k8s`.** `refractal render --target k8s` writes the Jobs and they
276
+ run against the same `--worker` entrypoint Compose uses, but nothing supervises
277
+ them from inside Refractal: no `run` subcommand submits them, watches them, or
278
+ collects their exit codes. Rendered and applied, not driven.
279
+ - **Resource-shape probing.** `refractal build` carries hand-declared shapes
280
+ forward and records whether they were measured. A lint refuses a comment
281
+ claiming a measurement when nothing recorded one.
282
+
283
+ ## Built on vla-evaluation-harness
284
+
285
+ Refractal is a layer over
286
+ [**`allenai/vla-evaluation-harness`**](https://github.com/allenai/vla-evaluation-harness),
287
+ from the Allen Institute for AI, and does not fork it. The harness runs the
288
+ episodes: it owns the model-server protocol, the benchmark adapters, the
289
+ observation and action specs, and the episode loop. Refractal decides which
290
+ episodes to run, gives them content-addressed identities, and compares the
291
+ results.
292
+
293
+ Everything `--backend vla-eval` does is the harness doing it. Refractal subclasses
294
+ two of its classes and otherwise stays out of the way, which is why the
295
+ integration is a pinned dependency rather than a vendored copy, and why
296
+ `scripts/verify_harness_claims.py` exists: the assumptions Refractal makes about
297
+ someone else's code are checked rather than assumed.
298
+
299
+ The harness is Apache-2.0, as is Refractal. No code is copied from it.
300
+
301
+ If you use Refractal for published work, cite the harness as well. The evaluation
302
+ is theirs; the comparison is ours.
303
+
304
+ ## Acknowledgements
305
+
306
+ - [`allenai/vla-evaluation-harness`](https://github.com/allenai/vla-evaluation-harness)
307
+ (Allen Institute for AI, Apache-2.0), which runs every episode.
308
+ - The benchmark suites and model servers it wraps, each under its own licence and
309
+ each the work of its own authors.
310
+
311
+ ## Licence
312
+
313
+ Apache-2.0. See [LICENSE](https://github.com/nalinraut/refractal/blob/main/LICENSE)
314
+ and [NOTICE](https://github.com/nalinraut/refractal/blob/main/NOTICE).