sim2sim-onepass 0.1.0__py3-none-any.whl
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.
- sim2sim_onepass/__init__.py +3 -0
- sim2sim_onepass/cli.py +203 -0
- sim2sim_onepass/paths.py +44 -0
- sim2sim_onepass/resources/QUICKSTART.md +30 -0
- sim2sim_onepass/resources/README.md +19 -0
- sim2sim_onepass/resources/REPO_MAP.md +8 -0
- sim2sim_onepass/resources/RESULTS_SUMMARY.md +9 -0
- sim2sim_onepass/resources/VISUAL_INDEX.md +10 -0
- sim2sim_onepass/resources/__init__.py +1 -0
- sim2sim_onepass/utils/__init__.py +1 -0
- sim2sim_onepass/utils/docs.py +31 -0
- sim2sim_onepass/version.py +1 -0
- sim2sim_onepass/wrappers/__init__.py +1 -0
- sim2sim_onepass/wrappers/availability.py +51 -0
- sim2sim_onepass/wrappers/quick_sanity.py +162 -0
- sim2sim_onepass/wrappers/rollout.py +168 -0
- sim2sim_onepass/wrappers/state_project.py +52 -0
- sim2sim_onepass-0.1.0.dist-info/METADATA +156 -0
- sim2sim_onepass-0.1.0.dist-info/RECORD +23 -0
- sim2sim_onepass-0.1.0.dist-info/WHEEL +5 -0
- sim2sim_onepass-0.1.0.dist-info/entry_points.txt +2 -0
- sim2sim_onepass-0.1.0.dist-info/licenses/LICENSE +118 -0
- sim2sim_onepass-0.1.0.dist-info/top_level.txt +1 -0
sim2sim_onepass/cli.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from sim2sim_onepass.paths import get_repo_paths
|
|
9
|
+
from sim2sim_onepass.utils.docs import first_lines, load_doc, repo_file_summary
|
|
10
|
+
from sim2sim_onepass.version import __version__
|
|
11
|
+
from sim2sim_onepass.wrappers.availability import report_environment, simulator_layout_available
|
|
12
|
+
from sim2sim_onepass.wrappers.quick_sanity import resolve_default_paths, run_quick_sanity, run_quick_sanity_demo
|
|
13
|
+
from sim2sim_onepass.wrappers.rollout import run_rollout_check
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_json(payload: object) -> int:
|
|
17
|
+
print(json.dumps(payload, indent=2))
|
|
18
|
+
return 0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cmd_info(_: argparse.Namespace) -> int:
|
|
22
|
+
paths = get_repo_paths()
|
|
23
|
+
payload = {
|
|
24
|
+
"project": "Sim2Sim-OnePass",
|
|
25
|
+
"version": __version__,
|
|
26
|
+
"package_identity": "lightweight companion package",
|
|
27
|
+
"repo_root": str(paths.root),
|
|
28
|
+
"canonical_outputs": str(paths.canonical_pass),
|
|
29
|
+
"pypi": "https://pypi.org/project/sim2sim-onepass/",
|
|
30
|
+
"docs": {label: str(path) for label, path in repo_file_summary()},
|
|
31
|
+
"website_branch": "gh-pages",
|
|
32
|
+
"package_scope": [
|
|
33
|
+
"repo navigation",
|
|
34
|
+
"embedded docs access",
|
|
35
|
+
"environment checks",
|
|
36
|
+
"quick sanity checks on paired datasets",
|
|
37
|
+
"thin rollout-check wrapper",
|
|
38
|
+
"guarded simulator workflow wrappers",
|
|
39
|
+
],
|
|
40
|
+
}
|
|
41
|
+
return print_json(payload)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def cmd_doc(name: str) -> int:
|
|
45
|
+
print(first_lines(load_doc(name), limit=80))
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_docs(_: argparse.Namespace) -> int:
|
|
50
|
+
for label, path in repo_file_summary():
|
|
51
|
+
print(f"{label}: {path}")
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def cmd_check_env(_: argparse.Namespace) -> int:
|
|
56
|
+
return print_json(report_environment())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cmd_quick_sanity(args: argparse.Namespace) -> int:
|
|
60
|
+
paths = get_repo_paths()
|
|
61
|
+
if args.demo:
|
|
62
|
+
return print_json(run_quick_sanity_demo())
|
|
63
|
+
default_b, default_m = resolve_default_paths(paths.root)
|
|
64
|
+
if args.bullet and args.mujoco:
|
|
65
|
+
bullet = Path(args.bullet)
|
|
66
|
+
mujoco = Path(args.mujoco)
|
|
67
|
+
elif default_b.exists() and default_m.exists():
|
|
68
|
+
bullet = default_b
|
|
69
|
+
mujoco = default_m
|
|
70
|
+
else:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
"This command requires paired dataset inputs and does not run from package-only install by default.\n"
|
|
73
|
+
"Provide --bullet and --mujoco, or run `sim2sim-onepass quick-sanity --demo`."
|
|
74
|
+
)
|
|
75
|
+
return print_json(run_quick_sanity(bullet, mujoco))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def cmd_alignment_gate(_: argparse.Namespace) -> int:
|
|
79
|
+
if not simulator_layout_available():
|
|
80
|
+
print(
|
|
81
|
+
"This command requires the full curated Sim2Sim-OnePass repository layout.\n"
|
|
82
|
+
"It also requires simulator dependencies and public-repo workflow files.\n"
|
|
83
|
+
"This command is not available in lightweight package-only mode.",
|
|
84
|
+
file=sys.stderr,
|
|
85
|
+
)
|
|
86
|
+
return 2
|
|
87
|
+
print(
|
|
88
|
+
"This command requires the full curated Sim2Sim-OnePass repository layout and simulator dependencies.\n"
|
|
89
|
+
"Use the source workspace command preserved in QUICKSTART.md.",
|
|
90
|
+
file=sys.stderr,
|
|
91
|
+
)
|
|
92
|
+
return 2
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_alignment_report(_: argparse.Namespace) -> int:
|
|
96
|
+
print(
|
|
97
|
+
"This command requires the full curated Sim2Sim-OnePass repository layout.\n"
|
|
98
|
+
"It depends on simulator collection scripts, paired dataset generation, and workflow files.\n"
|
|
99
|
+
"This command is not available in lightweight package-only mode.",
|
|
100
|
+
file=sys.stderr,
|
|
101
|
+
)
|
|
102
|
+
return 2
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def cmd_rollout_check(args: argparse.Namespace) -> int:
|
|
106
|
+
result = run_rollout_check(
|
|
107
|
+
bullet=Path(args.bullet),
|
|
108
|
+
mujoco=Path(args.mujoco),
|
|
109
|
+
model_path=Path(args.model),
|
|
110
|
+
norm_path=Path(args.norm),
|
|
111
|
+
horizon=args.horizon,
|
|
112
|
+
nstart=args.nstart,
|
|
113
|
+
)
|
|
114
|
+
return print_json(result)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
118
|
+
parser = argparse.ArgumentParser(
|
|
119
|
+
prog="sim2sim-onepass",
|
|
120
|
+
description=(
|
|
121
|
+
"A lightweight companion package for the Sim2Sim-OnePass public research release.\n"
|
|
122
|
+
"It provides CLI access to documentation, results navigation, environment checks, and selected curated utilities."
|
|
123
|
+
),
|
|
124
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
125
|
+
epilog=(
|
|
126
|
+
"Command categories:\n"
|
|
127
|
+
" Standalone: info, quickstart, repo-map, results-summary, visual-index, docs, check-env\n"
|
|
128
|
+
" Dataset-dependent: quick-sanity, rollout-check\n"
|
|
129
|
+
" Full repo / simulator dependent: alignment-gate, alignment-report"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
133
|
+
|
|
134
|
+
sub.add_parser("info", help="[standalone] show package and repo overview")
|
|
135
|
+
sub.add_parser("quickstart", help="[standalone] print quickstart guidance")
|
|
136
|
+
sub.add_parser("repo-map", help="[standalone] print repo structure guidance")
|
|
137
|
+
sub.add_parser("results-summary", help="[standalone] print canonical result summary")
|
|
138
|
+
sub.add_parser("visual-index", help="[standalone] print visual evidence index")
|
|
139
|
+
sub.add_parser("docs", help="[standalone] list key packaged docs")
|
|
140
|
+
sub.add_parser("check-env", help="[standalone] report Python and optional dependency availability")
|
|
141
|
+
sub.add_parser(
|
|
142
|
+
"alignment-gate",
|
|
143
|
+
help="[full repo] guarded simulator workflow wrapper; not standalone",
|
|
144
|
+
description="Full repo / simulator dependent command.",
|
|
145
|
+
)
|
|
146
|
+
sub.add_parser(
|
|
147
|
+
"alignment-report",
|
|
148
|
+
help="[full repo] guarded report wrapper; not standalone",
|
|
149
|
+
description="Full repo / simulator dependent command.",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
quick = sub.add_parser(
|
|
153
|
+
"quick-sanity",
|
|
154
|
+
help="[dataset] inspect paired datasets or run tiny built-in demo",
|
|
155
|
+
description="Dataset-dependent command. Use --demo for the tiny standalone fixture.",
|
|
156
|
+
)
|
|
157
|
+
quick.add_argument("--bullet", default="")
|
|
158
|
+
quick.add_argument("--mujoco", default="")
|
|
159
|
+
quick.add_argument("--demo", action="store_true", help="run the tiny built-in synthetic paired fixture")
|
|
160
|
+
|
|
161
|
+
rollout = sub.add_parser(
|
|
162
|
+
"rollout-check",
|
|
163
|
+
help="[dataset+rollout extra] run rollout error check from paired data/model/norm",
|
|
164
|
+
description="Dataset-dependent command. Requires paired data, model, norm file, and the optional rollout extra.",
|
|
165
|
+
)
|
|
166
|
+
rollout.add_argument("--bullet", required=True)
|
|
167
|
+
rollout.add_argument("--mujoco", required=True)
|
|
168
|
+
rollout.add_argument("--model", required=True)
|
|
169
|
+
rollout.add_argument("--norm", required=True)
|
|
170
|
+
rollout.add_argument("--horizon", type=int, default=50)
|
|
171
|
+
rollout.add_argument("--nstart", type=int, default=50)
|
|
172
|
+
|
|
173
|
+
return parser
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main(argv: list[str] | None = None) -> int:
|
|
177
|
+
parser = build_parser()
|
|
178
|
+
args = parser.parse_args(argv)
|
|
179
|
+
mapping = {
|
|
180
|
+
"info": cmd_info,
|
|
181
|
+
"quickstart": lambda _: cmd_doc("QUICKSTART.md"),
|
|
182
|
+
"repo-map": lambda _: cmd_doc("REPO_MAP.md"),
|
|
183
|
+
"results-summary": lambda _: cmd_doc("RESULTS_SUMMARY.md"),
|
|
184
|
+
"visual-index": lambda _: cmd_doc("VISUAL_INDEX.md"),
|
|
185
|
+
"docs": cmd_docs,
|
|
186
|
+
"check-env": cmd_check_env,
|
|
187
|
+
"quick-sanity": cmd_quick_sanity,
|
|
188
|
+
"alignment-gate": cmd_alignment_gate,
|
|
189
|
+
"alignment-report": cmd_alignment_report,
|
|
190
|
+
"rollout-check": cmd_rollout_check,
|
|
191
|
+
}
|
|
192
|
+
try:
|
|
193
|
+
return mapping[args.command](args)
|
|
194
|
+
except FileNotFoundError as exc:
|
|
195
|
+
print(str(exc), file=sys.stderr)
|
|
196
|
+
return 2
|
|
197
|
+
except RuntimeError as exc:
|
|
198
|
+
print(str(exc), file=sys.stderr)
|
|
199
|
+
return 2
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
if __name__ == "__main__":
|
|
203
|
+
raise SystemExit(main())
|
sim2sim_onepass/paths.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_repo_root(start: Path | None = None) -> Path:
|
|
8
|
+
here = (start or Path(__file__)).resolve()
|
|
9
|
+
for candidate in [here, *here.parents]:
|
|
10
|
+
if (candidate / "README.md").exists() and (candidate / "RESULTS_SUMMARY.md").exists():
|
|
11
|
+
return candidate
|
|
12
|
+
return here.parents[2]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class RepoPaths:
|
|
17
|
+
root: Path
|
|
18
|
+
readme: Path
|
|
19
|
+
quickstart: Path
|
|
20
|
+
repo_map: Path
|
|
21
|
+
results_summary: Path
|
|
22
|
+
visual_index: Path
|
|
23
|
+
package_notes: Path
|
|
24
|
+
outputs: Path
|
|
25
|
+
canonical_pass: Path
|
|
26
|
+
scripts: Path
|
|
27
|
+
configs: Path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_repo_paths(start: Path | None = None) -> RepoPaths:
|
|
31
|
+
root = find_repo_root(start)
|
|
32
|
+
return RepoPaths(
|
|
33
|
+
root=root,
|
|
34
|
+
readme=root / "README.md",
|
|
35
|
+
quickstart=root / "QUICKSTART.md",
|
|
36
|
+
repo_map=root / "REPO_MAP.md",
|
|
37
|
+
results_summary=root / "RESULTS_SUMMARY.md",
|
|
38
|
+
visual_index=root / "VISUAL_INDEX.md",
|
|
39
|
+
package_notes=root / "PYPI_PACKAGE_NOTES.md",
|
|
40
|
+
outputs=root / "outputs",
|
|
41
|
+
canonical_pass=root / "outputs" / "canonical_pass",
|
|
42
|
+
scripts=root / "scripts",
|
|
43
|
+
configs=root / "configs",
|
|
44
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
Install editable from the repo:
|
|
4
|
+
|
|
5
|
+
```powershell
|
|
6
|
+
python -m pip install -e .
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Published PyPI install:
|
|
10
|
+
|
|
11
|
+
```powershell
|
|
12
|
+
python -m pip install sim2sim-onepass
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Package page:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
https://pypi.org/project/sim2sim-onepass/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Standalone-safe first commands:
|
|
22
|
+
|
|
23
|
+
```powershell
|
|
24
|
+
sim2sim-onepass info
|
|
25
|
+
sim2sim-onepass check-env
|
|
26
|
+
sim2sim-onepass quickstart
|
|
27
|
+
sim2sim-onepass quick-sanity --demo
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For the full canonical docs and media paths, use the source repository checkout.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Sim2Sim-OnePass Companion Package
|
|
2
|
+
|
|
3
|
+
This installed package is a lightweight companion package for the public Sim2Sim-OnePass research release.
|
|
4
|
+
|
|
5
|
+
It provides:
|
|
6
|
+
|
|
7
|
+
- installable CLI access
|
|
8
|
+
- embedded docs access
|
|
9
|
+
- results navigation
|
|
10
|
+
- environment checks
|
|
11
|
+
- selected lightweight utilities
|
|
12
|
+
- published package access via `https://pypi.org/project/sim2sim-onepass/`
|
|
13
|
+
|
|
14
|
+
It does not provide by default:
|
|
15
|
+
|
|
16
|
+
- full simulator environments
|
|
17
|
+
- full datasets
|
|
18
|
+
- full video/report artifacts
|
|
19
|
+
- complete standalone simulator reproduction from pip install alone
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Repo Map
|
|
2
|
+
|
|
3
|
+
This package ships a lightweight CLI and embedded markdown docs.
|
|
4
|
+
|
|
5
|
+
- standalone package code lives under `src/sim2sim_onepass/`
|
|
6
|
+
- canonical public docs stay in the source repository
|
|
7
|
+
- heavy outputs and media are intentionally not embedded in the wheel
|
|
8
|
+
- the website remains repo-based and is published separately
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Visual Index
|
|
2
|
+
|
|
3
|
+
The wheel keeps lightweight markdown only.
|
|
4
|
+
|
|
5
|
+
For the full visual proof bundle, use the source repository checkout and inspect:
|
|
6
|
+
|
|
7
|
+
- `VISUAL_INDEX.md`
|
|
8
|
+
- `outputs/canonical_pass/preview/`
|
|
9
|
+
- `outputs/canonical_pass/videos/`
|
|
10
|
+
- `outputs/canonical_pass/plots/`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Embedded fallback docs for installed package users."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility helpers for the public package."""
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from importlib import resources
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from sim2sim_onepass.paths import get_repo_paths
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_doc(name: str) -> str:
|
|
10
|
+
paths = get_repo_paths()
|
|
11
|
+
repo_candidate = paths.root / name
|
|
12
|
+
if repo_candidate.exists():
|
|
13
|
+
return repo_candidate.read_text(encoding="utf-8")
|
|
14
|
+
return resources.files("sim2sim_onepass.resources").joinpath(name).read_text(encoding="utf-8")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def first_lines(text: str, limit: int = 40) -> str:
|
|
18
|
+
lines = text.strip().splitlines()
|
|
19
|
+
return "\n".join(lines[:limit]).strip() + ("\n..." if len(lines) > limit else "")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def repo_file_summary() -> list[tuple[str, Path]]:
|
|
23
|
+
paths = get_repo_paths()
|
|
24
|
+
return [
|
|
25
|
+
("README", paths.readme),
|
|
26
|
+
("QUICKSTART", paths.quickstart),
|
|
27
|
+
("REPO_MAP", paths.repo_map),
|
|
28
|
+
("RESULTS_SUMMARY", paths.results_summary),
|
|
29
|
+
("VISUAL_INDEX", paths.visual_index),
|
|
30
|
+
("PYPI_PACKAGE_NOTES", paths.package_notes),
|
|
31
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Thin public wrappers for curated workflows."""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from sim2sim_onepass.paths import get_repo_paths
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def python_info() -> dict[str, str]:
|
|
11
|
+
return {
|
|
12
|
+
"python": sys.executable,
|
|
13
|
+
"version": sys.version.split()[0],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def module_available(name: str) -> bool:
|
|
18
|
+
return importlib.util.find_spec(name) is not None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def simulator_layout_available() -> bool:
|
|
22
|
+
paths = get_repo_paths()
|
|
23
|
+
required = [
|
|
24
|
+
paths.root / "pybullet_panda_gap",
|
|
25
|
+
paths.root / "mujoco_panda_gap",
|
|
26
|
+
paths.root / "plans",
|
|
27
|
+
]
|
|
28
|
+
return all(path.exists() for path in required)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def report_environment() -> dict[str, object]:
|
|
32
|
+
paths = get_repo_paths()
|
|
33
|
+
return {
|
|
34
|
+
"python": python_info(),
|
|
35
|
+
"repo_root": str(paths.root),
|
|
36
|
+
"repo_docs_present": all(
|
|
37
|
+
p.exists()
|
|
38
|
+
for p in [
|
|
39
|
+
paths.readme,
|
|
40
|
+
paths.quickstart,
|
|
41
|
+
paths.repo_map,
|
|
42
|
+
paths.results_summary,
|
|
43
|
+
paths.visual_index,
|
|
44
|
+
]
|
|
45
|
+
),
|
|
46
|
+
"numpy": module_available("numpy"),
|
|
47
|
+
"torch": module_available("torch"),
|
|
48
|
+
"yaml": module_available("yaml"),
|
|
49
|
+
"simulator_layout": simulator_layout_available(),
|
|
50
|
+
"canonical_outputs": paths.canonical_pass.exists(),
|
|
51
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def demo_fixture() -> tuple[dict[str, np.ndarray], dict[str, np.ndarray]]:
|
|
10
|
+
S = np.array(
|
|
11
|
+
[
|
|
12
|
+
np.linspace(0.0, 0.36, 37, dtype=np.float32),
|
|
13
|
+
np.linspace(0.1, 0.46, 37, dtype=np.float32),
|
|
14
|
+
np.linspace(0.2, 0.56, 37, dtype=np.float32),
|
|
15
|
+
],
|
|
16
|
+
dtype=np.float32,
|
|
17
|
+
)
|
|
18
|
+
A = np.array(
|
|
19
|
+
[
|
|
20
|
+
np.linspace(0.0, 0.06, 7, dtype=np.float32),
|
|
21
|
+
np.linspace(0.02, 0.08, 7, dtype=np.float32),
|
|
22
|
+
np.linspace(0.04, 0.10, 7, dtype=np.float32),
|
|
23
|
+
],
|
|
24
|
+
dtype=np.float32,
|
|
25
|
+
)
|
|
26
|
+
bullet = {"S": S, "A": A, "S_next": S + 0.01}
|
|
27
|
+
mujoco = {"S": S + 0.001, "A": A, "S_next": S + 0.0125}
|
|
28
|
+
return bullet, mujoco
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def resolve_default_paths(root: Path) -> Tuple[Path, Path]:
|
|
32
|
+
candidates = [
|
|
33
|
+
(root / "data" / "paired_bullet_seed0.npz", root / "data" / "paired_mujoco_seed0.npz"),
|
|
34
|
+
(root / "data_pybullet" / "paired_bullet_seed0.npz", root / "data_mujoco" / "paired_mujoco_seed0.npz"),
|
|
35
|
+
(
|
|
36
|
+
root / "outputs" / "canonical_pass" / "source_stress_metrics.json",
|
|
37
|
+
root / "outputs" / "canonical_pass" / "source_stress_metrics.json",
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
for b, m in candidates[:2]:
|
|
41
|
+
if b.exists() and m.exists():
|
|
42
|
+
return b, m
|
|
43
|
+
return candidates[1]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_npz(path: Path) -> tuple[np.lib.npyio.NpzFile, list[str]]:
|
|
47
|
+
data = np.load(path)
|
|
48
|
+
return data, list(data.keys())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def describe_array(x: np.ndarray) -> dict[str, object]:
|
|
52
|
+
x = np.asarray(x)
|
|
53
|
+
finite = x[np.isfinite(x)]
|
|
54
|
+
return {
|
|
55
|
+
"shape": tuple(x.shape),
|
|
56
|
+
"dtype": str(x.dtype),
|
|
57
|
+
"nan": int(np.isnan(x).sum()),
|
|
58
|
+
"inf": int(np.isinf(x).sum()),
|
|
59
|
+
"min": float(finite.min()) if finite.size else None,
|
|
60
|
+
"max": float(finite.max()) if finite.size else None,
|
|
61
|
+
"mean": float(finite.mean()) if finite.size else None,
|
|
62
|
+
"std": float(finite.std()) if finite.size else None,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def run_quick_sanity(bullet_path: Path, mujoco_path: Path) -> dict[str, object]:
|
|
67
|
+
if not bullet_path.exists():
|
|
68
|
+
raise FileNotFoundError(f"Bullet NPZ not found: {bullet_path}")
|
|
69
|
+
if not mujoco_path.exists():
|
|
70
|
+
raise FileNotFoundError(f"MuJoCo NPZ not found: {mujoco_path}")
|
|
71
|
+
|
|
72
|
+
b, bk = load_npz(bullet_path)
|
|
73
|
+
m, mk = load_npz(mujoco_path)
|
|
74
|
+
|
|
75
|
+
found = None
|
|
76
|
+
for s_key, a_key, sn_key in [("S", "A", "S_next"), ("obs", "act", "obs_next"), ("states", "actions", "next_states")]:
|
|
77
|
+
if s_key in b and a_key in b and sn_key in b and s_key in m and a_key in m and sn_key in m:
|
|
78
|
+
found = (s_key, a_key, sn_key)
|
|
79
|
+
break
|
|
80
|
+
if not found:
|
|
81
|
+
raise RuntimeError(f"Could not find canonical paired keys. Bullet keys={bk}; MuJoCo keys={mk}")
|
|
82
|
+
|
|
83
|
+
s_key, a_key, sn_key = found
|
|
84
|
+
Sb, Ab, Snb = b[s_key], b[a_key], b[sn_key]
|
|
85
|
+
Sm, Am, Snm = m[s_key], m[a_key], m[sn_key]
|
|
86
|
+
|
|
87
|
+
if Sb.shape != Sm.shape:
|
|
88
|
+
raise RuntimeError(f"State shape mismatch: {Sb.shape} vs {Sm.shape}")
|
|
89
|
+
if Ab.shape != Am.shape:
|
|
90
|
+
raise RuntimeError(f"Action shape mismatch: {Ab.shape} vs {Am.shape}")
|
|
91
|
+
if Snb.shape != Snm.shape:
|
|
92
|
+
raise RuntimeError(f"Next-state shape mismatch: {Snb.shape} vs {Snm.shape}")
|
|
93
|
+
|
|
94
|
+
drift_mean = np.abs(Sb.mean(axis=0) - Sm.mean(axis=0))
|
|
95
|
+
drift_std = np.abs(Sb.std(axis=0) - Sm.std(axis=0))
|
|
96
|
+
drift_score = drift_mean + drift_std
|
|
97
|
+
top = np.argsort(-drift_score)[:10]
|
|
98
|
+
err = np.linalg.norm(Snb - Snm, axis=1)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"mode": "dataset",
|
|
102
|
+
"keys": {"bullet": bk, "mujoco": mk, "selected": [s_key, a_key, sn_key]},
|
|
103
|
+
"bullet": {
|
|
104
|
+
s_key: describe_array(Sb),
|
|
105
|
+
a_key: describe_array(Ab),
|
|
106
|
+
sn_key: describe_array(Snb),
|
|
107
|
+
},
|
|
108
|
+
"mujoco": {
|
|
109
|
+
s_key: describe_array(Sm),
|
|
110
|
+
a_key: describe_array(Am),
|
|
111
|
+
sn_key: describe_array(Snm),
|
|
112
|
+
},
|
|
113
|
+
"one_step": {
|
|
114
|
+
"mean": float(err.mean()),
|
|
115
|
+
"median": float(np.median(err)),
|
|
116
|
+
"p95": float(np.quantile(err, 0.95)),
|
|
117
|
+
"max": float(err.max()),
|
|
118
|
+
},
|
|
119
|
+
"top_drift_dims": [
|
|
120
|
+
{
|
|
121
|
+
"dim": int(i),
|
|
122
|
+
"score": float(drift_score[i]),
|
|
123
|
+
"mean_gap": float(drift_mean[i]),
|
|
124
|
+
"std_gap": float(drift_std[i]),
|
|
125
|
+
}
|
|
126
|
+
for i in top
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def run_quick_sanity_demo() -> dict[str, object]:
|
|
132
|
+
b, m = demo_fixture()
|
|
133
|
+
Sb, Ab, Snb = b["S"], b["A"], b["S_next"]
|
|
134
|
+
Sm, Am, Snm = m["S"], m["A"], m["S_next"]
|
|
135
|
+
drift_mean = np.abs(Sb.mean(axis=0) - Sm.mean(axis=0))
|
|
136
|
+
drift_std = np.abs(Sb.std(axis=0) - Sm.std(axis=0))
|
|
137
|
+
drift_score = drift_mean + drift_std
|
|
138
|
+
top = np.argsort(-drift_score)[:10]
|
|
139
|
+
err = np.linalg.norm(Snb - Snm, axis=1)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
"mode": "demo",
|
|
143
|
+
"keys": {"bullet": ["S", "A", "S_next"], "mujoco": ["S", "A", "S_next"], "selected": ["S", "A", "S_next"]},
|
|
144
|
+
"bullet": {"S": describe_array(Sb), "A": describe_array(Ab), "S_next": describe_array(Snb)},
|
|
145
|
+
"mujoco": {"S": describe_array(Sm), "A": describe_array(Am), "S_next": describe_array(Snm)},
|
|
146
|
+
"one_step": {
|
|
147
|
+
"mean": float(err.mean()),
|
|
148
|
+
"median": float(np.median(err)),
|
|
149
|
+
"p95": float(np.quantile(err, 0.95)),
|
|
150
|
+
"max": float(err.max()),
|
|
151
|
+
},
|
|
152
|
+
"top_drift_dims": [
|
|
153
|
+
{
|
|
154
|
+
"dim": int(i),
|
|
155
|
+
"score": float(drift_score[i]),
|
|
156
|
+
"mean_gap": float(drift_mean[i]),
|
|
157
|
+
"std_gap": float(drift_std[i]),
|
|
158
|
+
}
|
|
159
|
+
for i in top
|
|
160
|
+
],
|
|
161
|
+
"note": "Built-in tiny synthetic paired fixture for package-only smoke testing.",
|
|
162
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from sim2sim_onepass.wrappers.state_project import project_state
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _require_torch():
|
|
12
|
+
try:
|
|
13
|
+
import torch
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
except ImportError as exc:
|
|
16
|
+
raise RuntimeError("rollout-check requires PyTorch. Install with `pip install sim2sim-onepass[rollout]`.") from exc
|
|
17
|
+
return torch, nn
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_npz(path: Path) -> dict[str, np.ndarray]:
|
|
21
|
+
data = np.load(path)
|
|
22
|
+
return {k: data[k] for k in data.files}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeltaMLP:
|
|
26
|
+
def __init__(self, nn, in_dim: int, out_dim: int) -> None:
|
|
27
|
+
self.net = nn.Sequential(
|
|
28
|
+
nn.Linear(in_dim, 256),
|
|
29
|
+
nn.ReLU(),
|
|
30
|
+
nn.Linear(256, 256),
|
|
31
|
+
nn.ReLU(),
|
|
32
|
+
nn.Linear(256, out_dim),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def __call__(self, x):
|
|
36
|
+
return self.net(x)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_model(nn, in_dim: int, out_dim: int):
|
|
40
|
+
class DeltaMoE(nn.Module):
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
super().__init__()
|
|
43
|
+
self.free = DeltaMLP(nn, in_dim, out_dim).net
|
|
44
|
+
self.contact = DeltaMLP(nn, in_dim, out_dim).net
|
|
45
|
+
self.gate = nn.Sequential(
|
|
46
|
+
nn.Linear(in_dim, 128),
|
|
47
|
+
nn.ReLU(),
|
|
48
|
+
nn.Linear(128, 1),
|
|
49
|
+
nn.Sigmoid(),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def forward(self, x):
|
|
53
|
+
gate = self.gate(x)
|
|
54
|
+
free_pred = self.free(x)
|
|
55
|
+
contact_pred = self.contact(x)
|
|
56
|
+
return contact_pred * gate + free_pred * (1.0 - gate)
|
|
57
|
+
|
|
58
|
+
return DeltaMoE()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def decide_units(pred: np.ndarray, delta_std: np.ndarray) -> str:
|
|
62
|
+
pred_std = pred.std(axis=0).mean()
|
|
63
|
+
delta_std_mean = delta_std.mean()
|
|
64
|
+
if delta_std_mean == 0:
|
|
65
|
+
return "ambiguous"
|
|
66
|
+
ratio = pred_std / delta_std_mean
|
|
67
|
+
if 0.3 <= ratio <= 3.0:
|
|
68
|
+
return "original"
|
|
69
|
+
if 0.03 <= ratio < 0.3:
|
|
70
|
+
return "normalized"
|
|
71
|
+
return "ambiguous"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def run_rollout_check(
|
|
75
|
+
bullet: Path,
|
|
76
|
+
mujoco: Path,
|
|
77
|
+
model_path: Path,
|
|
78
|
+
norm_path: Path,
|
|
79
|
+
horizon: int = 50,
|
|
80
|
+
nstart: int = 50,
|
|
81
|
+
) -> dict[str, float | str]:
|
|
82
|
+
torch, nn = _require_torch()
|
|
83
|
+
b = load_npz(bullet)
|
|
84
|
+
m = load_npz(mujoco)
|
|
85
|
+
for key in ("S", "A", "S_next", "episode_id", "t"):
|
|
86
|
+
if key not in b or key not in m:
|
|
87
|
+
raise RuntimeError(f"missing key {key}")
|
|
88
|
+
|
|
89
|
+
Sb = b["S"].astype(np.float32)
|
|
90
|
+
Ab = b["A"].astype(np.float32)
|
|
91
|
+
Snb = b["S_next"].astype(np.float32)
|
|
92
|
+
Snm = m["S_next"].astype(np.float32)
|
|
93
|
+
ep = b["episode_id"].astype(int)
|
|
94
|
+
t = b["t"].astype(int)
|
|
95
|
+
|
|
96
|
+
in_dim = Sb.shape[1] + Ab.shape[1]
|
|
97
|
+
out_dim = Snb.shape[1]
|
|
98
|
+
model = build_model(nn, in_dim, out_dim)
|
|
99
|
+
model.load_state_dict(torch.load(model_path, map_location="cpu"))
|
|
100
|
+
model.eval()
|
|
101
|
+
|
|
102
|
+
norm = json.loads(norm_path.read_text(encoding="utf-8"))
|
|
103
|
+
delta_mean = np.array(norm.get("delta_mean"), dtype=np.float32)
|
|
104
|
+
delta_std = np.array(norm.get("delta_std"), dtype=np.float32)
|
|
105
|
+
|
|
106
|
+
unique_eps = np.unique(ep)
|
|
107
|
+
rng = np.random.default_rng(0)
|
|
108
|
+
starts: list[int] = []
|
|
109
|
+
for e in unique_eps:
|
|
110
|
+
idx = np.where(ep == e)[0]
|
|
111
|
+
if idx.size < horizon + 1:
|
|
112
|
+
continue
|
|
113
|
+
order = np.argsort(t[idx])
|
|
114
|
+
idx = idx[order]
|
|
115
|
+
max_start = idx.size - horizon
|
|
116
|
+
if max_start <= 0:
|
|
117
|
+
continue
|
|
118
|
+
for _ in range(2):
|
|
119
|
+
starts.append(int(idx[rng.integers(0, max_start)]))
|
|
120
|
+
if not starts:
|
|
121
|
+
raise RuntimeError("no valid rollout starts")
|
|
122
|
+
rng.shuffle(starts)
|
|
123
|
+
starts = starts[:nstart]
|
|
124
|
+
|
|
125
|
+
sample_idx = np.array(starts[: min(len(starts), 10)])
|
|
126
|
+
X_sample = np.concatenate([Sb[sample_idx], Ab[sample_idx]], axis=1)
|
|
127
|
+
with torch.no_grad():
|
|
128
|
+
pred_sample = model(torch.from_numpy(X_sample).float()).numpy()
|
|
129
|
+
units = decide_units(pred_sample, delta_std)
|
|
130
|
+
if units == "ambiguous":
|
|
131
|
+
raise RuntimeError("cannot confidently determine model output units")
|
|
132
|
+
|
|
133
|
+
def pred_delta(x: np.ndarray) -> np.ndarray:
|
|
134
|
+
with torch.no_grad():
|
|
135
|
+
pred = model(torch.from_numpy(x).float()).numpy()
|
|
136
|
+
if units == "normalized":
|
|
137
|
+
return pred * delta_std + delta_mean
|
|
138
|
+
return pred
|
|
139
|
+
|
|
140
|
+
errs: list[float] = []
|
|
141
|
+
for s in starts:
|
|
142
|
+
e = ep[s]
|
|
143
|
+
idx = np.where(ep == e)[0]
|
|
144
|
+
order = np.argsort(t[idx])
|
|
145
|
+
idx = idx[order]
|
|
146
|
+
pos = int(np.where(idx == s)[0][0])
|
|
147
|
+
if pos + horizon > idx.size:
|
|
148
|
+
continue
|
|
149
|
+
for k in range(horizon):
|
|
150
|
+
i = idx[pos + k]
|
|
151
|
+
x = np.concatenate([Sb[i], Ab[i]], axis=0)[None, :]
|
|
152
|
+
delta = pred_delta(x)[0]
|
|
153
|
+
pred_state = project_state(Snb[i] + delta)
|
|
154
|
+
if not np.isfinite(pred_state).all():
|
|
155
|
+
raise RuntimeError(f"NaN/Inf at start={s} step={k}")
|
|
156
|
+
errs.append(float(np.linalg.norm(pred_state - Snm[i])))
|
|
157
|
+
|
|
158
|
+
if not errs:
|
|
159
|
+
raise RuntimeError("no rollout errors computed")
|
|
160
|
+
arr = np.asarray(errs, dtype=np.float32)
|
|
161
|
+
return {
|
|
162
|
+
"horizon": int(horizon),
|
|
163
|
+
"nstart": int(nstart),
|
|
164
|
+
"units": units,
|
|
165
|
+
"mean": float(arr.mean()),
|
|
166
|
+
"p95": float(np.quantile(arr, 0.95)),
|
|
167
|
+
"max": float(arr.max()),
|
|
168
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
STATE_DIM = 37
|
|
6
|
+
Q_SLICE = slice(0, 7)
|
|
7
|
+
DQ_SLICE = slice(7, 14)
|
|
8
|
+
EE_POS_SLICE = slice(14, 17)
|
|
9
|
+
EE_QUAT_SLICE = slice(17, 21)
|
|
10
|
+
OBJ_POS_SLICE = slice(21, 24)
|
|
11
|
+
OBJ_QUAT_SLICE = slice(24, 28)
|
|
12
|
+
OBJ_LINVEL_SLICE = slice(28, 31)
|
|
13
|
+
OBJ_ANGVEL_SLICE = slice(31, 34)
|
|
14
|
+
CONTACT_FLAG_IDX = 34
|
|
15
|
+
CONTACT_FORCE_IDX = 35
|
|
16
|
+
MIN_DIST_IDX = 36
|
|
17
|
+
VEL_LIMIT = 5.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _wrap_angles(x: np.ndarray) -> np.ndarray:
|
|
21
|
+
return (x + np.pi) % (2.0 * np.pi) - np.pi
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _renorm_quat(q: np.ndarray) -> np.ndarray:
|
|
25
|
+
norm = np.linalg.norm(q, axis=-1, keepdims=True)
|
|
26
|
+
safe = norm[..., 0] >= 1e-6
|
|
27
|
+
qn = np.zeros_like(q, dtype=np.float32)
|
|
28
|
+
qn[safe] = q[safe] / norm[safe]
|
|
29
|
+
qn[~safe] = np.array([0.0, 0.0, 0.0, 1.0], dtype=np.float32)
|
|
30
|
+
return qn
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def project_state(state: np.ndarray) -> np.ndarray:
|
|
34
|
+
state = np.asarray(state, dtype=np.float32)
|
|
35
|
+
if state.shape[-1] != STATE_DIM:
|
|
36
|
+
raise ValueError(f"Expected last dim {STATE_DIM}, got {state.shape}")
|
|
37
|
+
squeeze = state.ndim == 1
|
|
38
|
+
if squeeze:
|
|
39
|
+
state = state[None, :]
|
|
40
|
+
out = state.copy()
|
|
41
|
+
out[:, Q_SLICE] = _wrap_angles(out[:, Q_SLICE])
|
|
42
|
+
out[:, EE_QUAT_SLICE] = _renorm_quat(out[:, EE_QUAT_SLICE])
|
|
43
|
+
out[:, OBJ_QUAT_SLICE] = _renorm_quat(out[:, OBJ_QUAT_SLICE])
|
|
44
|
+
out[:, EE_POS_SLICE] = np.clip(out[:, EE_POS_SLICE], -2.0, 2.0)
|
|
45
|
+
out[:, OBJ_POS_SLICE] = np.clip(out[:, OBJ_POS_SLICE], -2.0, 2.0)
|
|
46
|
+
out[:, DQ_SLICE] = np.clip(out[:, DQ_SLICE], -VEL_LIMIT, VEL_LIMIT)
|
|
47
|
+
out[:, OBJ_LINVEL_SLICE] = np.clip(out[:, OBJ_LINVEL_SLICE], -VEL_LIMIT, VEL_LIMIT)
|
|
48
|
+
out[:, OBJ_ANGVEL_SLICE] = np.clip(out[:, OBJ_ANGVEL_SLICE], -VEL_LIMIT, VEL_LIMIT)
|
|
49
|
+
out[:, CONTACT_FLAG_IDX] = np.clip(out[:, CONTACT_FLAG_IDX], 0.0, 1.0)
|
|
50
|
+
out[:, CONTACT_FORCE_IDX] = np.clip(out[:, CONTACT_FORCE_IDX], 0.0, 50.0)
|
|
51
|
+
out[:, MIN_DIST_IDX] = np.clip(out[:, MIN_DIST_IDX], 0.0, 2.0)
|
|
52
|
+
return out[0] if squeeze else out
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sim2sim-onepass
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight companion package for the Sim2Sim-OnePass public research release.
|
|
5
|
+
Author: ANTHONY-OLEVESTER
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/ANTHONY-OLEVESTER/Robotics_sim-to-sim_OnePass
|
|
8
|
+
Project-URL: Repository, https://github.com/ANTHONY-OLEVESTER/Robotics_sim-to-sim_OnePass
|
|
9
|
+
Project-URL: Documentation, https://github.com/ANTHONY-OLEVESTER/Robotics_sim-to-sim_OnePass
|
|
10
|
+
Project-URL: Issues, https://github.com/ANTHONY-OLEVESTER/Robotics_sim-to-sim_OnePass/issues
|
|
11
|
+
Keywords: robotics,simulation,mujoco,pybullet,research
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Provides-Extra: rollout
|
|
24
|
+
Requires-Dist: torch>=2.0; extra == "rollout"
|
|
25
|
+
Provides-Extra: alignment
|
|
26
|
+
Requires-Dist: PyYAML>=6.0; extra == "alignment"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
29
|
+
Requires-Dist: twine>=5.1.1; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# Sim2Sim-OnePass Public Release
|
|
33
|
+
|
|
34
|
+
This curated layer is the fast path through the repository. It is designed so a new visitor can understand the claim, watch the evidence, inspect the canonical PASS artifacts, and run the shortest validation path without digging through the full development history.
|
|
35
|
+
|
|
36
|
+
## Companion Package
|
|
37
|
+
|
|
38
|
+
This branch adds a PyPI-ready package layer named `sim2sim-onepass`. It is a lightweight companion package for the public research release, not a standalone robotics simulator system. It provides an installable CLI, embedded docs access, results navigation, environment checks, and selected lightweight utilities around the curated repo.
|
|
39
|
+
|
|
40
|
+
Local editable install:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
python -m pip install -e .
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Published PyPI install:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
python -m pip install sim2sim-onepass
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
PyPI package page:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
https://pypi.org/project/sim2sim-onepass/
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
CLI entrypoint:
|
|
59
|
+
|
|
60
|
+
```powershell
|
|
61
|
+
sim2sim-onepass --help
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
What the package does provide:
|
|
65
|
+
|
|
66
|
+
- repo navigation and public documentation access
|
|
67
|
+
- embedded lightweight markdown docs
|
|
68
|
+
- environment checking
|
|
69
|
+
- quick sanity checks on paired datasets
|
|
70
|
+
- rollout-check wrapper for model + norm + paired data paths
|
|
71
|
+
- guarded simulator workflow commands with clear errors when the full simulator stack is not present
|
|
72
|
+
|
|
73
|
+
What the package does not provide by default:
|
|
74
|
+
|
|
75
|
+
- full simulator environments
|
|
76
|
+
- full datasets
|
|
77
|
+
- giant reports dumps
|
|
78
|
+
- replay videos and large binary outputs
|
|
79
|
+
- local environments and machine-specific folders
|
|
80
|
+
- the full simulator env trees and internal training workspace
|
|
81
|
+
|
|
82
|
+
## Command Capability Levels
|
|
83
|
+
|
|
84
|
+
| Command | Category | Requirements |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `info` | Standalone | package install only |
|
|
87
|
+
| `quickstart` | Standalone | package install only |
|
|
88
|
+
| `repo-map` | Standalone | package install only |
|
|
89
|
+
| `results-summary` | Standalone | package install only |
|
|
90
|
+
| `visual-index` | Standalone | package install only |
|
|
91
|
+
| `docs` | Standalone | package install only |
|
|
92
|
+
| `check-env` | Standalone | package install only |
|
|
93
|
+
| `quick-sanity` | Dataset-dependent | paired datasets, or `--demo` for the tiny built-in fixture |
|
|
94
|
+
| `rollout-check` | Dataset-dependent | paired datasets, model, norm file, and optional extra `sim2sim-onepass[rollout]` |
|
|
95
|
+
| `alignment-gate` | Full repo / simulator dependent | full curated repo layout plus simulator dependencies |
|
|
96
|
+
| `alignment-report` | Full repo / simulator dependent | full curated repo layout plus simulator dependencies and workflow files |
|
|
97
|
+
|
|
98
|
+
## Pick Your Route
|
|
99
|
+
|
|
100
|
+
| If you want to... | Open this |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| See the project in one screen | [RESULTS_SUMMARY.md](RESULTS_SUMMARY.md) |
|
|
103
|
+
| Watch the visual evidence first | [VISUAL_INDEX.md](VISUAL_INDEX.md) |
|
|
104
|
+
| Run the shortest validation path | [QUICKSTART.md](QUICKSTART.md) |
|
|
105
|
+
| Inspect the packaged PASS bundle | [outputs/canonical_pass/](outputs/canonical_pass/) |
|
|
106
|
+
| Understand how the repo is organized | [REPO_MAP.md](REPO_MAP.md) |
|
|
107
|
+
|
|
108
|
+
## See It Before You Read It
|
|
109
|
+
|
|
110
|
+
| Triptych preview | Rollout figure |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| [](outputs/canonical_pass/preview/triptych_frame0.png) | [](outputs/canonical_pass/plots/rollout_phys_p95.png) |
|
|
113
|
+
|
|
114
|
+
Open the full visual package here:
|
|
115
|
+
|
|
116
|
+
- [VISUAL_INDEX.md](VISUAL_INDEX.md)
|
|
117
|
+
- [outputs/canonical_pass/videos/compare_triptych.mp4](outputs/canonical_pass/videos/compare_triptych.mp4)
|
|
118
|
+
- [outputs/canonical_pass/report.md](outputs/canonical_pass/report.md)
|
|
119
|
+
|
|
120
|
+
## The Claim In Plain Terms
|
|
121
|
+
|
|
122
|
+
After enforcing deterministic cross-simulator alignment between PyBullet and MuJoCo, this repo learns a residual next-state correction that:
|
|
123
|
+
|
|
124
|
+
- reduces Bullet-to-MuJoCo one-step physical error,
|
|
125
|
+
- remains stable under long-horizon rollout checks,
|
|
126
|
+
- passes holdout and alignment gates,
|
|
127
|
+
- and can be inspected visually through replay videos, triptychs, and overlay plots.
|
|
128
|
+
|
|
129
|
+
## Canonical Story
|
|
130
|
+
|
|
131
|
+
1. Deterministic paired plans are executed in PyBullet and MuJoCo.
|
|
132
|
+
2. A strict alignment gate blocks training if reset state, timing, or first-step consistency drift.
|
|
133
|
+
3. A residual model predicts the MuJoCo-minus-Bullet next-state gap.
|
|
134
|
+
4. Hard-mode stress evaluation verifies one-step accuracy, holdouts, and rollout stability.
|
|
135
|
+
5. Behavioral acceptance exports replay videos, triptychs, and overlay plots for inspection.
|
|
136
|
+
|
|
137
|
+
## Canonical Evidence
|
|
138
|
+
|
|
139
|
+
- Quantitative anchor: packaged in `outputs/canonical_pass/source_stress_report.md` and `outputs/canonical_pass/source_stress_metrics.json`
|
|
140
|
+
- Visual anchor: packaged in `outputs/canonical_pass/source_behavioral_report.md` and the copied videos and preview images
|
|
141
|
+
- Public-facing copied bundle: [outputs/canonical_pass/](outputs/canonical_pass/)
|
|
142
|
+
- Provenance map: [configs/canonical_sources.json](configs/canonical_sources.json)
|
|
143
|
+
|
|
144
|
+
## Exact Quickstart
|
|
145
|
+
|
|
146
|
+
This public repo is optimized for inspection first. Open [QUICKSTART.md](QUICKSTART.md) for the shortest path through the packaged evidence and for command references preserved from the source workspace.
|
|
147
|
+
|
|
148
|
+
## What Lives Where
|
|
149
|
+
|
|
150
|
+
- [outputs/canonical_pass/](outputs/canonical_pass/) contains the reviewer-facing proof artifacts.
|
|
151
|
+
- [examples/canonical_commands.ps1](examples/canonical_commands.ps1) contains exact rerun commands.
|
|
152
|
+
- [docs/CANONICAL_SOURCES.md](docs/CANONICAL_SOURCES.md) records provenance.
|
|
153
|
+
- [docs/PUBLISHING.md](docs/PUBLISHING.md) defines what to ship in a public release.
|
|
154
|
+
- The interactive website is published from the `gh-pages` branch.
|
|
155
|
+
- Package implementation lives under `src/sim2sim_onepass/`.
|
|
156
|
+
- License is [Apache-2.0](LICENSE).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
sim2sim_onepass/__init__.py,sha256=v3x5oBkxwKJEZLv62QqSmP3iqNKLtZgrWZfH8eFzlQg,60
|
|
2
|
+
sim2sim_onepass/cli.py,sha256=XlXSmzxnQjrjSXkRmNuChKR-pPKnqZSXWGjyB6rlFAg,7671
|
|
3
|
+
sim2sim_onepass/paths.py,sha256=RexqsR1JtFlnXErnMrsz0rZ91tpmPkSTGJlRNrjbc1Q,1236
|
|
4
|
+
sim2sim_onepass/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
5
|
+
sim2sim_onepass/resources/QUICKSTART.md,sha256=_UtBSMQnReExxYay7wsczpcRpnx8GaQutU8WQZxO29w,488
|
|
6
|
+
sim2sim_onepass/resources/README.md,sha256=EAUFsHtI6WpYEmp5YZfDxhJ5S2v2ctT5mAY5Ggfri58,535
|
|
7
|
+
sim2sim_onepass/resources/REPO_MAP.md,sha256=2gL5G-3dQP3PwCpZouvAoxLiEvYUpVwTrbFmYs4A1-M,324
|
|
8
|
+
sim2sim_onepass/resources/RESULTS_SUMMARY.md,sha256=WMpT0yL6J6SEVR-mrl8OewJ884X8TYgXBSqogV5OeJ0,244
|
|
9
|
+
sim2sim_onepass/resources/VISUAL_INDEX.md,sha256=dYLu0mOQX2Gbf6qRcmgKBpE6ytwGJv_a_jObAYqwspA,268
|
|
10
|
+
sim2sim_onepass/resources/__init__.py,sha256=yzu-6U8H6kSaYEdsxuy0Ed-WrUfHxpnSJ1wAKQAhZF8,58
|
|
11
|
+
sim2sim_onepass/utils/__init__.py,sha256=kVHealcnpW7tQIPTax1nEOF_WPAMYSV8dg0P_LhzHu0,46
|
|
12
|
+
sim2sim_onepass/utils/docs.py,sha256=46PU5c5GMbPn_qdUOQs1w28TlAvHnCekDAUGwB8LYw8,978
|
|
13
|
+
sim2sim_onepass/wrappers/__init__.py,sha256=1QvoS4C-g77eI8c74Wur-JQ9qtUNOU87DjqKd2dY9iM,50
|
|
14
|
+
sim2sim_onepass/wrappers/availability.py,sha256=zfr1amaw6z4RW5yBb6W3w_hbk5bocSLJM68nuMM8w44,1329
|
|
15
|
+
sim2sim_onepass/wrappers/quick_sanity.py,sha256=nEdue9EtldtFYQTHkCNqc5DgLBhEPsoCiHuDg16p_hw,5776
|
|
16
|
+
sim2sim_onepass/wrappers/rollout.py,sha256=YMAfPyIA_m_1XPasCIHWg3X0ViadDfA8_w9FxB05rEI,5165
|
|
17
|
+
sim2sim_onepass/wrappers/state_project.py,sha256=mfevHUcpyc0KWrne0FhqsspMyJEkzIouIYPBYxs8xOM,1914
|
|
18
|
+
sim2sim_onepass-0.1.0.dist-info/licenses/LICENSE,sha256=lJ0vnK1Rqyj2IWRh9eU4g5hm7BA2Hl986eP9foo9jac,6131
|
|
19
|
+
sim2sim_onepass-0.1.0.dist-info/METADATA,sha256=fvd7nXH5rR_8_wgh0S0f1WymMvB7A5EPWUCiCXsa2OU,7094
|
|
20
|
+
sim2sim_onepass-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
21
|
+
sim2sim_onepass-0.1.0.dist-info/entry_points.txt,sha256=mYsgd-l4YxpXMa1Hj6dmz2aflkq47e15XlaKfTccaZI,61
|
|
22
|
+
sim2sim_onepass-0.1.0.dist-info/top_level.txt,sha256=Lr7UmBESV7svgOH8JVEsF2QjwVLtpvYUNYhCGo-0atA,16
|
|
23
|
+
sim2sim_onepass-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction, and
|
|
10
|
+
distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
13
|
+
owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
16
|
+
that control, are controlled by, or are under common control with that entity.
|
|
17
|
+
"Control" means (i) the power, direct or indirect, to cause the direction or
|
|
18
|
+
management of such entity, whether by contract or otherwise, or (ii) ownership
|
|
19
|
+
of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
|
|
20
|
+
ownership of such entity.
|
|
21
|
+
|
|
22
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
23
|
+
permissions granted by this License.
|
|
24
|
+
|
|
25
|
+
"Source" form shall mean the preferred form for making modifications, including
|
|
26
|
+
but not limited to software source code, documentation source, and configuration
|
|
27
|
+
files.
|
|
28
|
+
|
|
29
|
+
"Object" form shall mean any form resulting from mechanical transformation or
|
|
30
|
+
translation of a Source form, including but not limited to compiled object code,
|
|
31
|
+
generated documentation, and conversions to other media types.
|
|
32
|
+
|
|
33
|
+
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
|
34
|
+
available under the License, as indicated by a copyright notice that is included
|
|
35
|
+
in or attached to the work.
|
|
36
|
+
|
|
37
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
38
|
+
is based on (or derived from) the Work and for which the editorial revisions,
|
|
39
|
+
annotations, elaborations, or other modifications represent, as a whole, an
|
|
40
|
+
original work of authorship. For the purposes of this License, Derivative Works
|
|
41
|
+
shall not include works that remain separable from, or merely link (or bind by
|
|
42
|
+
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
43
|
+
|
|
44
|
+
"Contribution" shall mean any work of authorship, including the original version
|
|
45
|
+
of the Work and any modifications or additions to that Work or Derivative Works
|
|
46
|
+
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
47
|
+
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
48
|
+
on behalf of the copyright owner.
|
|
49
|
+
|
|
50
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
51
|
+
of whom a Contribution has been received by Licensor and subsequently
|
|
52
|
+
incorporated within the Work.
|
|
53
|
+
|
|
54
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this
|
|
55
|
+
License, each Contributor hereby grants to You a perpetual, worldwide,
|
|
56
|
+
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
|
57
|
+
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
|
58
|
+
sublicense, and distribute the Work and such Derivative Works in Source or
|
|
59
|
+
Object form.
|
|
60
|
+
|
|
61
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License,
|
|
62
|
+
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
63
|
+
no-charge, royalty-free, irrevocable (except as stated in this section) patent
|
|
64
|
+
license to make, have made, use, offer to sell, sell, import, and otherwise
|
|
65
|
+
transfer the Work.
|
|
66
|
+
|
|
67
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or
|
|
68
|
+
Derivative Works thereof in any medium, with or without modifications, and in
|
|
69
|
+
Source or Object form, provided that You meet the following conditions:
|
|
70
|
+
|
|
71
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of
|
|
72
|
+
this License; and
|
|
73
|
+
|
|
74
|
+
(b) You must cause any modified files to carry prominent notices stating that You
|
|
75
|
+
changed the files; and
|
|
76
|
+
|
|
77
|
+
(c) You must retain, in the Source form of any Derivative Works that You
|
|
78
|
+
distribute, all copyright, patent, trademark, and attribution notices from the
|
|
79
|
+
Source form of the Work, excluding those notices that do not pertain to any part
|
|
80
|
+
of the Derivative Works; and
|
|
81
|
+
|
|
82
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then
|
|
83
|
+
any Derivative Works that You distribute must include a readable copy of the
|
|
84
|
+
attribution notices contained within such NOTICE file, excluding those notices
|
|
85
|
+
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
86
|
+
following places: within a NOTICE text file distributed as part of the
|
|
87
|
+
Derivative Works; within the Source form or documentation, if provided along
|
|
88
|
+
with the Derivative Works; or within a display generated by the Derivative
|
|
89
|
+
Works, if and wherever such third-party notices normally appear.
|
|
90
|
+
|
|
91
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
|
92
|
+
Contribution intentionally submitted for inclusion in the Work by You to the
|
|
93
|
+
Licensor shall be under the terms and conditions of this License, without any
|
|
94
|
+
additional terms or conditions.
|
|
95
|
+
|
|
96
|
+
6. Trademarks. This License does not grant permission to use the trade names,
|
|
97
|
+
trademarks, service marks, or product names of the Licensor, except as required
|
|
98
|
+
for reasonable and customary use in describing the origin of the Work.
|
|
99
|
+
|
|
100
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
|
101
|
+
writing, Licensor provides the Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
102
|
+
CONDITIONS OF ANY KIND, either express or implied, including, without
|
|
103
|
+
limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT,
|
|
104
|
+
MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible
|
|
105
|
+
for determining the appropriateness of using or redistributing the Work.
|
|
106
|
+
|
|
107
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in
|
|
108
|
+
tort (including negligence), contract, or otherwise, unless required by
|
|
109
|
+
applicable law, shall any Contributor be liable to You for damages, including
|
|
110
|
+
any direct, indirect, special, incidental, or consequential damages of any
|
|
111
|
+
character arising as a result of this License or out of the use or inability to
|
|
112
|
+
use the Work.
|
|
113
|
+
|
|
114
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or
|
|
115
|
+
Derivative Works thereof, You may choose to offer, and charge a fee for,
|
|
116
|
+
acceptance of support, warranty, indemnity, or other liability obligations.
|
|
117
|
+
|
|
118
|
+
END OF TERMS AND CONDITIONS
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sim2sim_onepass
|