jjreif commited on
Commit
b3d14e3
·
1 Parent(s): 9cc9b1a

Deploy roverdevkit @ 2676a67

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +71 -0
  2. .gitattributes +0 -34
  3. .gitignore +123 -0
  4. Dockerfile +94 -0
  5. LICENSE +21 -0
  6. Makefile +89 -0
  7. README.md +22 -5
  8. data/README.md +47 -0
  9. data/analytical/.gitkeep +0 -0
  10. data/analytical/SCHEMA.md +201 -0
  11. data/mass_validation_set.csv +9 -0
  12. data/published_traverse_data.csv +3 -0
  13. data/rovers.yaml +202 -0
  14. data/soil_simulants.csv +11 -0
  15. data/validation/README.md +84 -0
  16. data/validation/raw/.gitkeep +0 -0
  17. data/validation/single_wheel_experiments.csv +28 -0
  18. data/validation/wong_layer3_reference.csv +8 -0
  19. deploy/huggingface/README.md +28 -0
  20. environment.yml +24 -0
  21. fig_system_architecture.png +0 -0
  22. models/README.md +23 -0
  23. models/surrogate_v9/quantile_bundles.joblib +3 -0
  24. pyproject.toml +120 -0
  25. reports/pareto_fronts/front_crater_rim_survey.csv +51 -0
  26. reports/pareto_fronts/front_crater_rim_survey.metadata.json +35 -0
  27. reports/pareto_fronts/front_equatorial_mare_traverse.csv +51 -0
  28. reports/pareto_fronts/front_equatorial_mare_traverse.metadata.json +35 -0
  29. reports/pareto_fronts/front_highland_slope_capability.csv +51 -0
  30. reports/pareto_fronts/front_highland_slope_capability.metadata.json +36 -0
  31. reports/pareto_fronts/front_polar_prospecting.csv +51 -0
  32. reports/pareto_fronts/front_polar_prospecting.metadata.json +35 -0
  33. reports/pareto_fronts/manifest.json +143 -0
  34. roverdevkit/__init__.py +24 -0
  35. roverdevkit/architecture.py +88 -0
  36. roverdevkit/drivetrain/__init__.py +28 -0
  37. roverdevkit/drivetrain/motor.py +325 -0
  38. roverdevkit/mass/__init__.py +38 -0
  39. roverdevkit/mass/parametric_mers.py +474 -0
  40. roverdevkit/mass/validation.py +251 -0
  41. roverdevkit/mission/__init__.py +22 -0
  42. roverdevkit/mission/capability.py +129 -0
  43. roverdevkit/mission/configs/cadre_polar_unit.yaml +41 -0
  44. roverdevkit/mission/configs/chandrayaan3_pragyan.yaml +34 -0
  45. roverdevkit/mission/configs/change4_yutu2_per_lunar_day.yaml +39 -0
  46. roverdevkit/mission/configs/crater_rim_micro.yaml +38 -0
  47. roverdevkit/mission/configs/crater_rim_survey.yaml +24 -0
  48. roverdevkit/mission/configs/equatorial_mare_traverse.yaml +29 -0
  49. roverdevkit/mission/configs/highland_micro.yaml +42 -0
  50. roverdevkit/mission/configs/highland_slope_capability.yaml +35 -0
.dockerignore ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Keep the Docker build context small. The build context is the
2
+ # repo root (see `webapp/Dockerfile`), so anything not needed at
3
+ # image-build time should be excluded here.
4
+
5
+ # VCS / IDE
6
+ .git
7
+ .gitignore
8
+ .github
9
+ .cursor
10
+ .vscode
11
+ .idea
12
+
13
+ # Python build / cache artifacts
14
+ __pycache__
15
+ *.pyc
16
+ *.pyo
17
+ *.pyd
18
+ *.egg-info
19
+ .pytest_cache
20
+ .mypy_cache
21
+ .ruff_cache
22
+ .tox
23
+ htmlcov
24
+ build
25
+ dist
26
+
27
+ # Conda / venv
28
+ .conda
29
+ .venv
30
+ venv
31
+ env
32
+
33
+ # Notebook outputs (notebooks themselves stay; outputs are large)
34
+ .ipynb_checkpoints
35
+
36
+ # Frontend build outputs (re-built inside the Dockerfile from source).
37
+ webapp/frontend/node_modules
38
+ webapp/frontend/dist
39
+ webapp/frontend/.cache
40
+
41
+ # Docker scaffolding (don't recurse the Dockerfile into itself).
42
+ **/Dockerfile
43
+ **/docker-compose*.yml
44
+ .dockerignore
45
+
46
+ # Local-only logs and reports
47
+ *.log
48
+ reports/**/*.log
49
+
50
+ # Datasets / artifacts that aren't needed at runtime. The runtime
51
+ # image only needs:
52
+ # - data/ (soils, scenarios, validation set)
53
+ # - models/surrogate_v9/quantile_bundles.joblib
54
+ # - reports/pareto_fronts/
55
+ # Heavy training-time parquets and superseded surrogate versions stay
56
+ # out so the image fits inside typical free-tier hosting limits.
57
+ data/analytical/
58
+ reports/baselines_*
59
+ reports/surrogate_v7_1/
60
+ reports/surrogate_v8/
61
+ reports/tuned_v7/
62
+ reports/tuned_v8/
63
+ reports/tuned_v9/
64
+ reports/rediscovery_loo_*/
65
+ reports/validation_*/
66
+ reports/week*/
67
+ reports/intervals_*/
68
+
69
+ # OS noise
70
+ .DS_Store
71
+ Thumbs.db
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
  *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
1
  *.joblib filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ # `lib/` and `lib64/` are anchored to the repo root so the Python venv
14
+ # layout doesn't accidentally swallow `webapp/frontend/src/lib/`.
15
+ /lib/
16
+ /lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+ MANIFEST
25
+
26
+ # Virtual envs
27
+ .env
28
+ .env.*
29
+ !.env.example
30
+ .venv
31
+ env/
32
+ venv/
33
+ ENV/
34
+
35
+ # Testing / coverage
36
+ .tox/
37
+ .nox/
38
+ .coverage
39
+ .coverage.*
40
+ .cache
41
+ htmlcov/
42
+ .pytest_cache/
43
+ coverage.xml
44
+ *.cover
45
+
46
+ # Type checkers
47
+ .mypy_cache/
48
+ .pyre/
49
+ .pytype/
50
+ .ruff_cache/
51
+
52
+ # Jupyter
53
+ .ipynb_checkpoints/
54
+ *.ipynb_checkpoints
55
+
56
+ # IDE
57
+ .vscode/
58
+ .idea/
59
+ *.swp
60
+ *.swo
61
+
62
+ # macOS
63
+ .DS_Store
64
+
65
+ # Project data — large artifacts are not checked in.
66
+ # We keep the directory structure via .gitkeep files.
67
+ data/analytical/*
68
+ !data/analytical/.gitkeep
69
+ !data/analytical/SCHEMA.md
70
+ data/scm/*
71
+ !data/scm/.gitkeep
72
+ !data/scm/SCHEMA.md
73
+ data/validation/raw/*
74
+ !data/validation/raw/.gitkeep
75
+
76
+ # Generated reports / model artifacts.
77
+ # Top-level reports/ subdirectories are ignored by default; specific
78
+ # ship-with-repo artifacts are unignored below. The .joblib/.parquet
79
+ # globals still catch large binaries inside any unignored subtree.
80
+ reports/*
81
+ !reports/pareto_fronts/
82
+
83
+ # Shipped ML model artifacts (runtime bundle for webapp / scripts).
84
+ models/*
85
+ !models/README.md
86
+ !models/surrogate_v9/
87
+ models/surrogate_v9/*
88
+ !models/surrogate_v9/quantile_bundles.joblib
89
+ *.joblib
90
+ !models/surrogate_v9/quantile_bundles.joblib
91
+ *.parquet
92
+
93
+ # Internal planning notes
94
+ /project_*.md
95
+
96
+ # Paper drafting workspace and generated manuscript figures.
97
+ # The reproducible figure-generation scripts live under scripts/ and are tracked.
98
+ /paper/
99
+ /reports/figures/
100
+
101
+ # Logs
102
+ logs/
103
+ *.log
104
+
105
+ # Hugging Face Space deploy mirror (local clone of the Space repo,
106
+ # rebuilt on each `make deploy-space`).
107
+ .hf-space/
108
+
109
+ # Project-specific scratch
110
+ scratch/
111
+ tmp/
112
+ .tmp_*_venv/
113
+ /package-lock.json
114
+
115
+ # webapp webapp build / install artifacts
116
+ webapp/frontend/node_modules/
117
+ webapp/frontend/dist/
118
+ webapp/frontend/.vite/
119
+ webapp/frontend/coverage/
120
+ webapp/frontend/playwright-report/
121
+ webapp/frontend/test-results/
122
+ webapp/backend/.coverage
123
+ webapp/.env.local
Dockerfile ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # RoverDevKit webapp — multi-stage Dockerfile
2
+ #
3
+ # Stage 1 (`frontend-build`) builds the Vite production bundle with
4
+ # Node 20 LTS. Stage 2 (`runtime`) installs the Python package + the
5
+ # `[webapp]` extras on top of `python:3.12-slim`, copies the package
6
+ # source / on-disk artifacts / built frontend bundle in, and runs
7
+ # uvicorn as a non-root user.
8
+ #
9
+ # Build context expectation: this file is invoked from the repo
10
+ # root so it can reach `pyproject.toml`, `roverdevkit/`, `data/`,
11
+ # `models/`, `reports/`, and `webapp/` in one COPY plane:
12
+ #
13
+ # docker build -f webapp/Dockerfile -t roverdevkit/webapp:dev .
14
+ #
15
+ # The image bakes in:
16
+ # - the analytical Bekker-Wong mission evaluator,
17
+ # - the v9 quantile-XGB surrogate bundles
18
+ # (`models/surrogate_v9/quantile_bundles.joblib`),
19
+ # - the canonical Pareto fronts (`reports/pareto_fronts/`),
20
+ # - the built React frontend (`/app/static/`).
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Stage 1: build the frontend bundle
24
+ # ---------------------------------------------------------------------------
25
+
26
+ FROM node:20-bookworm-slim AS frontend-build
27
+
28
+ WORKDIR /build
29
+
30
+ # Install dependencies first so the npm cache is reusable across edits
31
+ # of `webapp/frontend/src/`. Lockfile copy + `npm ci` gives a
32
+ # reproducible install.
33
+ COPY webapp/frontend/package.json webapp/frontend/package-lock.json ./
34
+ RUN npm ci --no-audit --no-fund
35
+
36
+ COPY webapp/frontend/ ./
37
+ RUN npm run build
38
+
39
+ # ---------------------------------------------------------------------------
40
+ # Stage 2: runtime image
41
+ # ---------------------------------------------------------------------------
42
+
43
+ FROM python:3.12-slim-bookworm AS runtime
44
+
45
+ ENV PYTHONDONTWRITEBYTECODE=1 \
46
+ PYTHONUNBUFFERED=1 \
47
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
48
+ PIP_NO_CACHE_DIR=1 \
49
+ ROVERDEVKIT_STATIC_DIR=/app/static
50
+
51
+ # `libgomp1` is needed by xgboost on Linux for the OpenMP runtime.
52
+ RUN apt-get update \
53
+ && apt-get install -y --no-install-recommends libgomp1 \
54
+ && rm -rf /var/lib/apt/lists/*
55
+
56
+ WORKDIR /app
57
+
58
+ # Install Python dependencies first so a code-only edit doesn't bust
59
+ # the heavy ML wheel cache. Editable installs need the package
60
+ # source in the same layer; copy the build metadata here and the
61
+ # rest in the next layer.
62
+ COPY pyproject.toml README.md LICENSE ./
63
+ COPY roverdevkit/ ./roverdevkit/
64
+ RUN pip install --no-cache-dir ".[webapp]"
65
+
66
+ # Webapp backend + on-disk artifacts.
67
+ COPY webapp/backend/ ./webapp/backend/
68
+ COPY data/ ./data/
69
+ COPY models/ ./models/
70
+ COPY reports/ ./reports/
71
+
72
+ # Built frontend bundle from stage 1 → mounted at /app/static via
73
+ # the ROVERDEVKIT_STATIC_DIR env var above.
74
+ COPY --from=frontend-build /build/dist/ ./static/
75
+
76
+ # Run as non-root to satisfy the standard hosting-platform contract
77
+ # (Fly.io, HF Spaces, K8s pod security policies, etc.). UID 1000 is
78
+ # arbitrary; pick whatever your hosting environment prefers.
79
+ RUN useradd --create-home --uid 1000 roverdevkit \
80
+ && chown -R roverdevkit:roverdevkit /app
81
+ USER roverdevkit
82
+
83
+ EXPOSE 8000
84
+
85
+ # `--proxy-headers` lets the deployment reverse proxy (Fly's edge,
86
+ # HF Spaces' router, etc.) pass through the original client IP and
87
+ # scheme. `--forwarded-allow-ips='*'` is safe behind a trusted
88
+ # proxy and avoids 403s on the WebSocket / SSE probes some hosts
89
+ # use.
90
+ CMD ["uvicorn", "webapp.backend.main:app", \
91
+ "--host", "0.0.0.0", \
92
+ "--port", "8000", \
93
+ "--proxy-headers", \
94
+ "--forwarded-allow-ips=*"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Autonomous Mission Systems Lab, Duke University
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Makefile ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Top-level developer-experience targets.
2
+ #
3
+ # All targets are .PHONY; this Makefile is a typing shortcut, not a
4
+ # build system. The canonical build paths are still ``pytest``,
5
+ # ``uvicorn``, and ``npm`` invoked directly. Targets here just spell
6
+ # out the conventional invocation so a new contributor can boot the
7
+ # webapp with one command.
8
+ #
9
+ # Convention:
10
+ # make webapp-dev → boot backend on :8000 and frontend on :5173
11
+ # make webapp-backend → backend only
12
+ # make webapp-frontend → frontend only
13
+ # make webapp-test → backend pytest + frontend lint + frontend build
14
+ # make webapp-build → frontend production build only
15
+ # make pareto-fronts → (re)generate canonical Pareto fronts under reports/
16
+ # make figures → (re)render every manuscript figure under paper/figures/
17
+ # make optimizer-robustness → run multi-seed NSGA-II robustness sweep
18
+ # make deploy-space → push current HEAD to the Hugging Face Space (manual)
19
+ #
20
+ # Override ports with `UVICORN_PORT=8001 make webapp-backend`.
21
+ # Override the conda env used by python targets with `CONDA_ENV=other`.
22
+
23
+ .PHONY: webapp-dev webapp-backend webapp-frontend webapp-test webapp-build pareto-fronts optimizer-robustness architecture-crossover figures deploy-space
24
+
25
+ UVICORN_PORT ?= 8000
26
+ VITE_PORT ?= 5173
27
+ CONDA_ENV ?= roverdevkit
28
+
29
+ # Boot both servers in one command. `trap 'kill 0'` propagates Ctrl+C
30
+ # to every backgrounded child so the cleanup story stays sane on
31
+ # macOS GNU make 3.81 (Apple's bundled version) without `.ONESHELL`.
32
+ webapp-dev:
33
+ @echo ">> backend → http://localhost:$(UVICORN_PORT)"
34
+ @echo ">> frontend → http://localhost:$(VITE_PORT)"
35
+ @trap 'kill 0' INT TERM EXIT; \
36
+ uvicorn webapp.backend.main:app --reload --port $(UVICORN_PORT) & \
37
+ (cd webapp/frontend && npm run dev -- --port $(VITE_PORT)) & \
38
+ wait
39
+
40
+ webapp-backend:
41
+ uvicorn webapp.backend.main:app --reload --port $(UVICORN_PORT)
42
+
43
+ webapp-frontend:
44
+ cd webapp/frontend && npm run dev -- --port $(VITE_PORT)
45
+
46
+ webapp-test:
47
+ pytest webapp/backend/tests -q
48
+ cd webapp/frontend && npm run lint && npm run build
49
+
50
+ webapp-build:
51
+ cd webapp/frontend && npm run build
52
+
53
+ # Regenerate the canonical evaluator-driven Pareto fronts that ship with
54
+ # the repo. The Pareto Explorer tab in the webapp loads these via
55
+ # `/pareto/fronts`, so a fresh clone gets a working explorer without
56
+ # anyone running NSGA-II live. Re-run after editing scenario configs.
57
+ # Defaults (50 pop × 60 gens, ~4 min
58
+ # total for all four scenarios) are tuned for offline use; pass extra
59
+ # args via SCRIPT_ARGS.
60
+ pareto-fronts:
61
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/generate_pareto_fronts.py $(SCRIPT_ARGS)
62
+
63
+ optimizer-robustness:
64
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/run_optimizer_robustness.py $(SCRIPT_ARGS)
65
+
66
+ architecture-crossover:
67
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/run_architecture_obstacle_crossover.py $(SCRIPT_ARGS)
68
+
69
+ # Re-render every manuscript figure from the committed artifacts under
70
+ # reports/ into paper/figures/ (the directory main.tex reads). Each figure
71
+ # has a dedicated scripts/make_*_figure.py regenerator (no notebook), so this
72
+ # target is the single one-command rebuild of all paper figures. Run
73
+ # `make pareto-fronts` first if the fronts changed.
74
+ figures:
75
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_pareto_fronts_figure.py
76
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_rediscovery_distance_figure.py
77
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_rediscovery_overlay_figure.py
78
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_peak_solar_figure.py
79
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_terramechanics_experiment_figure.py
80
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_terramechanics_sensitivity_figure.py
81
+ conda run -n $(CONDA_ENV) --no-capture-output python scripts/make_architecture_obstacle_crossover_figure.py
82
+
83
+ # Manually deploy the webapp to the dedicated Hugging Face Space (Docker
84
+ # SDK). Pushes the current committed HEAD; it does NOT run on git push.
85
+ # Requires HF_SPACE_REMOTE to point at the Space git URL and git-lfs to
86
+ # be installed (the ~26 MB surrogate bundle exceeds HF's plain-git limit).
87
+ # See scripts/deploy_hf_space.sh for the full contract.
88
+ deploy-space:
89
+ bash scripts/deploy_hf_space.sh
README.md CHANGED
@@ -1,11 +1,28 @@
1
  ---
2
- title: Roverdevkit
3
- emoji: 📉
4
- colorFrom: yellow
5
- colorTo: purple
6
  sdk: docker
 
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: RoverDevKit
3
+ emoji: 🛰️
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 8000
8
  pinned: false
9
  license: mit
10
  ---
11
 
12
+ # RoverDevKit hosted demo
13
+
14
+ Interactive tradespace explorer for conceptual design of lunar micro-rovers:
15
+ physics-based mission evaluator, calibrated surrogate predictions, parametric
16
+ sweeps, NSGA-II multi-objective optimization, and SHAP-style design
17
+ explanations.
18
+
19
+ - Source code: <https://github.com/Autonomous-Mission-Systems-Lab/roverdevkit>
20
+ - Paper preprint: <https://arxiv.org/abs/2606.21755>
21
+
22
+ This Space runs the single-container build from
23
+ [`webapp/Dockerfile`](https://github.com/Autonomous-Mission-Systems-Lab/roverdevkit/blob/main/webapp/Dockerfile):
24
+ one `uvicorn` process serves the FastAPI backend and the React single-page app
25
+ from the same origin on port 8000.
26
+
27
+ > This README (with its Spaces front matter) is generated for the hosted demo
28
+ > by `scripts/deploy_hf_space.sh` and is not the repository's main README.
data/README.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Data
2
+
3
+ Small, curated datasets and citations live here. Large generated datasets
4
+ (LHS samples) are git-ignored — see `.gitignore`.
5
+
6
+ ## Rover data
7
+
8
+ Three consumers describe the same set of rovers for verification, each holding
9
+ *purpose-specific* values, all reconciled against one canonical facts file:
10
+
11
+ - `rovers.yaml` — **canonical published-facts reference** (single source of
12
+ truth). Holds only published/citable facts (mass, wheels, grousers, landing
13
+ latitude, traverse/peak-solar/thermal truth, ...) with **per-field
14
+ provenance** (`value` + `provenance` ∈ {published, derived, imputed} +
15
+ `source`). Loaded by `roverdevkit/validation/rover_facts.py`. It deliberately
16
+ excludes modeling-derived quantities (chassis mass, torque anchor, panel
17
+ efficiency, thermal architecture, scenario duty cycles) that legitimately
18
+ differ per consumer. `tests/test_rover_facts.py` enforces that the consumers
19
+ below agree with the `published`/`derived` facts here, so the sources cannot
20
+ silently drift.
21
+ - `mass_validation_set.csv` — published-rover mass and subsystem inputs used
22
+ by `roverdevkit/mass/validation.py` to check the bottom-up mass model.
23
+ Source/provenance details live in each row's `citation` and `imputation_notes`.
24
+ - `published_traverse_data.csv` — flown-rover traverse, peak-solar, thermal,
25
+ and mission-duration truth data used by `roverdevkit/validation/rover_comparison.py`.
26
+ Source details live in each row's `citation` and `notes`.
27
+ - `roverdevkit/validation/rover_registry.py` (code, not data) — executable
28
+ design vectors + scenarios + thermal/panel architecture consumed by the
29
+ evaluator, rediscovery, surrogate sanity check, and webapp.
30
+
31
+ ## Other files
32
+ - `soil_simulants.csv` — Bekker parameters (n, k_c, k_phi, cohesion,
33
+ friction angle) for common lunar soil simulants: FJS-1, JSC-1A, GRC-1,
34
+ plus Apollo regolith estimates.
35
+ - `validation/` — single-wheel testbed data digitized from published
36
+ papers (Ding 2011, Iizuka & Kubota 2011, Wong's datasets). Used as
37
+ held-out data to sanity-check the evaluator — never used for training.
38
+ - `analytical/` — generated LHS samples from the analytical evaluator.
39
+ Git-ignored except for schema documentation.
40
+
41
+ ## Citation discipline
42
+
43
+ Every curated data row must carry a citation or provenance note. Prefer the
44
+ canonical `rovers.yaml` per-field provenance for published rover facts; use the
45
+ dedicated `citation` column where present; otherwise document sources and
46
+ imputations in `notes` / `imputation_notes`. If you can't cite it, don't fit
47
+ on it.
data/analytical/.gitkeep ADDED
File without changes
data/analytical/SCHEMA.md ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Analytical Dataset Column Schema
2
+
3
+ Produced by `roverdevkit.surrogate.dataset.build_dataset` from
4
+ `LHSSample`s generated by `roverdevkit.surrogate.sampling.generate_samples`.
5
+ Each row is **one** `(design, scenario, soil)` triple evaluated by
6
+ `roverdevkit.mission.evaluator.evaluate_verbose`, flattened into a
7
+ single Parquet row.
8
+
9
+ - **Schema version:** `v9` (see `dataset.SCHEMA_VERSION`). Scientific
10
+ payload is an explicit mission requirement, carried by the
11
+ scenario-side inputs `scenario_payload_mass_kg` and
12
+ `scenario_payload_power_w`. Payload mass enters the total vehicle
13
+ mass as a line item outside the AIAA S-120A dry-mass growth margin
14
+ (`m_total = m_dry + m_margin + m_payload`); payload power adds to the
15
+ continuous ops-time electrical load (alongside avionics) and to the
16
+ hot-case thermal dissipation. Both are sampled uniform and
17
+ family-agnostic (`payload_mass_kg` in `[0, 30]`,
18
+ `payload_power_w` in `[0, 30]`) so the entire webapp Mission-Inputs
19
+ slider range is in-distribution. Payload lives on `MissionScenario`
20
+ (a requirement set by the mission), not on `DesignVector` (a variable
21
+ the designer trades); the design vector is 11-D. A per-call override
22
+ on `evaluate` / `/evaluate` / `/predict` lets callers substitute a
23
+ specific payload.
24
+ - **Fidelity level (this file):** `analytical` — the Bekker-Wong
25
+ terramechanics path solved inside `traverse_sim.run_traverse`.
26
+ - **Canonical filename:** `lhs_v9.parquet` — the current training set,
27
+ 40k rows at 10k × 4 scenario families. Pilot (`lhs_pilot.parquet`)
28
+ and challenge (`challenge_v1.parquet`) files are generated on demand
29
+ from `scripts/build_dataset.py`; only the canonical training set is
30
+ treated as a tracked artifact.
31
+
32
+ Dataset-level metadata is written to the Parquet file's schema footer;
33
+ use `read_parquet_metadata(path)` to recover it (seed, sampler version,
34
+ scenario families, val/test fractions, UTC build timestamp, evaluator
35
+ version, free-form notes).
36
+
37
+ ## Column groups
38
+
39
+ Prefix conventions:
40
+
41
+ - `design_*` — inputs from the 11-D `DesignVector`.
42
+ - `scenario_*` — inputs from the `MissionScenario`, plus the sampler's
43
+ jittered Bekker soil parameters (`scenario_soil_*`).
44
+ - `stat_*` — aggregate statistics (mean / p95 / max / final) reduced
45
+ from the per-step `TraverseLog` time series.
46
+ - Unprefixed columns with physical units (e.g. `range_km`) — targets
47
+ from `MissionMetrics`.
48
+ - Otherwise — dataset metadata.
49
+
50
+ ### Dataset metadata (5 columns)
51
+
52
+ | Column | dtype | Description |
53
+ | --- | --- | --- |
54
+ | `sample_index` | int64 | Monotonic row id from the sampler. Stable across re-runs with the same seed. |
55
+ | `split` | category | `train` / `val` / `test`, assigned at sample time with a deterministic RNG independent of row ordering. |
56
+ | `stratum_id` | int | `0 = 4-wheel`, `1 = 6-wheel`. Matches `design_n_wheels`. |
57
+ | `fidelity` | category | `analytical` for this file — the Bekker-Wong terramechanics path. No separate fidelity tier is shipped. |
58
+ | `status` | category | `ok` if evaluator succeeded, else the exception class name (e.g. `ValueError`). Numeric target columns are NaN on non-`ok` rows; boolean targets are `False`. |
59
+
60
+ ### Design vector (11 columns)
61
+
62
+ All `design_*` columns mirror the `DesignVector` pydantic schema.
63
+
64
+ | Column | dtype | Range | Description |
65
+ | --- | --- | --- | --- |
66
+ | `design_wheel_radius_m` | float64 | [0.05, 0.20] | Wheel radius R |
67
+ | `design_wheel_width_m` | float64 | [0.03, 0.20] | Wheel width W |
68
+ | `design_grouser_height_m` | float64 | [0.0, 0.020] | Grouser height |
69
+ | `design_grouser_count` | int64 | [0, 24] | Number of grousers per wheel |
70
+ | `design_n_wheels` | int64 | {4, 6} | Wheel count (kept in sync with architecture) |
71
+ | `design_mobility_architecture` | category | `rigid_4wheel`, `rocker_bogie_6wheel` | Primary mobility-architecture trade in the evaluator/optimizer. Not yet present in the shipped `lhs_v9.parquet` surrogate training set; the surrogate still keys off `design_n_wheels` until the dataset is rebuilt. |
72
+ | `design_chassis_mass_kg` | float64 | [0.5, 50.0] | Dry chassis mass (structural chassis only) |
73
+ | `design_wheelbase_m` | float64 | [0.3, 1.2] | Wheelbase |
74
+ | `design_solar_area_m2` | float64 | [0.1, 1.5] | Solar array area |
75
+ | `design_battery_capacity_wh` | float64 | [5.0, 500.0] | Usable battery energy |
76
+ | `design_avionics_power_w` | float64 | [5.0, 40.0] | Continuous avionics draw |
77
+ | `design_peak_wheel_torque_nm` | float64 | [0.05, 20.0] | Per-wheel hub torque capacity. Cruise speed is derived inside the evaluator from torque + slip + power balance (see `roverdevkit/drivetrain/motor.py::cruise_speed`). LHS is log-uniform around a per-row anchor rather than uniform on these bounds — see `roverdevkit/surrogate/sampling.py::_peak_wheel_torque_anchor_for_row`. |
78
+
79
+ ### Scenario inputs (18 columns)
80
+
81
+ Family-fixed columns (`scenario_family`, `scenario_terrain_class`,
82
+ `scenario_soil_simulant`, `scenario_sun_geometry`,
83
+ `scenario_traverse_distance_m`) take one of four canonical values per
84
+ family. The remaining columns are jittered per sample.
85
+
86
+ | Column | dtype | Notes |
87
+ | --- | --- | --- |
88
+ | `scenario_family` | category | One of `equatorial_mare_traverse`, `polar_prospecting`, `highland_slope_capability`, `crater_rim_survey`. Use for per-scenario accuracy breakdown. |
89
+ | `scenario_name` | category | Mirrors `scenario_family` in this dataset (validation-only scenarios live elsewhere). |
90
+ | `scenario_latitude_deg` | float64 | Family-specific range; see `sampling.FAMILIES`. |
91
+ | `scenario_traverse_distance_m` | float64 | Family-fixed, non-binding — deliberately above the energy-/duty-limited reach so `range_km` stays a continuous signal instead of saturating at a distance cap. |
92
+ | `scenario_terrain_class` | category | `mare_nominal`, `mare_loose`, `highland_dense`, `polar_regolith`. |
93
+ | `scenario_soil_simulant` | category | Family nominal; the *actual* Bekker numbers used by the evaluator are the `scenario_soil_*` columns below. |
94
+ | `scenario_mission_duration_earth_days` | float64 | Family-specific range. |
95
+ | `scenario_max_slope_deg` | float64 | Family-specific range. |
96
+ | `scenario_operational_duty_cycle` | float64 | Drive duty cycle the rover would actually run on the ground — sets `δ_eff = clamp(δ_ops, 0, 0.6)` in the traverse loop. Sampled per row uniform on `[0, 0.6]` independently of family, so the surrogate keys off it as a true continuous input. The per-family default is kept on `ScenarioFamily` for canonical YAML / UI initial slider position. |
97
+ | `scenario_sun_geometry` | category | `continuous` / `diurnal` / `polar_intermittent`. |
98
+ | `scenario_soil_n` | float64 | Bekker sinkage exponent, jitter bounds [0.8, 1.2]. |
99
+ | `scenario_soil_k_c` | float64 | Cohesive modulus, [0.5, 2.0] kN/m^(n+1). |
100
+ | `scenario_soil_k_phi` | float64 | Frictional modulus, [400, 1200] kN/m^(n+2). |
101
+ | `scenario_soil_cohesion_kpa` | float64 | Soil cohesion, [0.1, 1.0] kPa. |
102
+ | `scenario_soil_friction_angle_deg` | float64 | Internal friction angle, [30, 50]°. |
103
+ | `scenario_soil_shear_modulus_k_m` | float64 | Janosi-Hanamoto K, [0.010, 0.025] m. |
104
+ | `scenario_payload_mass_kg` | float64 | Scientific-payload mass (mission requirement). Per-row LHS feature uniform on `[0, 30]` independently of family. Added to total vehicle mass as a line item outside the dry-mass growth margin; the per-scenario default is kept on the YAML / `ScenarioFamily` for canonical webapp slider position. |
105
+ | `scenario_payload_power_w` | float64 | Scientific-payload continuous ops-time power (mission requirement). Per-row LHS feature uniform on `[0, 30]`. Added to the continuous electrical load (alongside avionics) in the traverse budget and to the hot-case thermal dissipation. |
106
+ | `scenario_required_obstacle_height_m` | float64 | Minimum traversable obstacle/step height (m). Defaults to 0 on the canonical smooth-regolith scenarios. Evaluator-only today: obstacle metrics (`obstacle_capability_m`, `obstacle_margin_m`) are computed from `mobility_architecture` and wheel radius; the surrogate does not yet predict them. |
107
+
108
+ ### Mission-metric targets (8 columns)
109
+
110
+ Mirror `MissionMetrics` fields. `range_km` and `energy_margin_raw_pct`
111
+ are the primary regression targets (no saturation); `*_pct` and the
112
+ boolean flag are secondary reporting/classification targets.
113
+ `thermal_survival` is **not** in this group: the evaluator still
114
+ computes it as a diagnostic, but the mass model treats RHU power and
115
+ MLI quality as free, so it reduces to a near-trivial gate with no real
116
+ design trade-off and the surrogate does not consume or predict it.
117
+
118
+ | Column | dtype | Notes |
119
+ | --- | --- | --- |
120
+ | `range_km` | float64 | Energy-feasible mission range. `run_traverse` applies an in-traverse throttle that drops effective duty when the battery floors and load exceeds solar. |
121
+ | `energy_margin_pct` | float64 | Clipped 0-100, SOC-based reporting metric. |
122
+ | `energy_margin_raw_pct` | float64 | Unclipped mission-integrated `(E_in - E_out)/E_out × 100`; primary surrogate target. |
123
+ | `slope_capability_deg` | float64 | Max climbable slope on this soil. |
124
+ | `total_mass_kg` | float64 | Mass-model output. |
125
+ | `peak_motor_torque_nm` | float64 | Observed peak wheel torque during traverse. |
126
+ | `sinkage_max_m` | float64 | Observed peak sinkage during traverse. |
127
+ | `stalled` | bool | Single feasibility classifier target (1 = infeasible). Captures whether the rover failed the slip-balance solve at any traverse step (Brent solver could not find a slip that satisfied force balance under the available drawbar pull and torque envelope). |
128
+
129
+ Evaluator-only architecture metrics (present on live `/evaluate` and optimizer outputs, not in the shipped `lhs_v9.parquet` targets):
130
+
131
+ | Column | dtype | Notes |
132
+ | --- | --- | --- |
133
+ | `obstacle_capability_m` | float64 | Estimated max traversable obstacle height from architecture proxy ($h_{\mathrm{obs}} = k_{\mathrm{arch}} R$). |
134
+ | `obstacle_margin_m` | float64 | Capability minus `scenario_required_obstacle_height_m`. |
135
+ | `obstacle_requirement_met` | bool | Whether `obstacle_margin_m \ge 0`. |
136
+ | `architecture_mass_kg` | float64 | Rocker-bogie suspension/linkage mass charged in the bottom-up mass model. |
137
+
138
+ ### Traverse-log aggregate statistics (≥24 columns)
139
+
140
+ Reduced from the per-step `TraverseLog` time series. Used to measure
141
+ where the surrogate needs to be accurate (peak-load versus
142
+ steady-state regimes) and as auxiliary diagnostics for the baselines.
143
+
144
+ Numeric aggregates (mean / p95 / max over the whole traverse, absolute
145
+ value for signed quantities like slip and torque):
146
+
147
+ - `stat_power_in_{mean,p95,max}_w` — solar input power.
148
+ - `stat_power_out_{mean,p95,max}_w` — total electrical draw (mobility + avionics).
149
+ - `stat_mobility_power_{mean,p95,max}_w` — mobility subsystem draw alone.
150
+ - `stat_slip_{mean,p95,max}` — wheel slip magnitude in [0, ~0.95].
151
+ - `stat_sinkage_{mean,p95}_m` — peak is already `sinkage_max_m` above.
152
+ - `stat_wheel_torque_{mean,p95}_nm` — peak is already `peak_motor_torque_nm` above.
153
+ - `stat_sun_elevation_{mean,max}_deg` — degrees above horizon.
154
+ - `stat_soc_final` / `stat_soc_min` — end-of-mission and deepest SOC.
155
+
156
+ Boolean end-of-run flags:
157
+
158
+ - `stat_rover_stalled` — Brent slip solve failed at some step.
159
+ - `stat_battery_floored` — SOC hit the 15% DoD floor during the run.
160
+ - `stat_reached_distance` — the rover reached the scenario's (non-binding) distance budget.
161
+
162
+ Categorical:
163
+
164
+ - `stat_terminated_reason` — a free-form short string from the sim layer
165
+ (e.g. `"mission_duration"`, `"evaluator_error"`). Use as a
166
+ post-hoc diagnostic; not suitable as a model input.
167
+
168
+ ## Layer-1 registry sanity scope
169
+
170
+ `roverdevkit.surrogate.baselines.predict_for_registry_rovers` produces
171
+ a `registry_sanity.csv` artifact with one row per `(rover, algorithm,
172
+ target)` tuple plus an `is_primary` boolean. The split is enforced
173
+ by the `LAYER1_PRIMARY_TARGETS` / `LAYER1_DIAGNOSTIC_TARGETS`
174
+ constants in `roverdevkit/surrogate/baselines.py`:
175
+
176
+ - **Primary (is_primary=True):** `total_mass_kg`,
177
+ `slope_capability_deg`, `stalled`. Design-axis metrics where the LHS
178
+ bounds put every flown / design-target rover inside the surrogate's
179
+ training support. Treated as the main registry sanity set.
180
+ - **Diagnostic (is_primary=False):** `range_km`,
181
+ `energy_margin_raw_pct`. Both are scenario-OOD for the registry:
182
+ Pragyan ≈ 100 m, Yutu-2 ≈ 25 m / lunar day, MoonRanger and
183
+ Rashid-1 ≈ 1 km published mission distances against LHS family
184
+ budgets of 20–80 km (intentionally non-binding so `range_km` stays
185
+ a continuous training signal). The relative error for these
186
+ targets is dominated by the absolute-scale mismatch and reflects
187
+ scenario-OOD rather than a surrogate-calibration failure. Reported
188
+ for transparency only.
189
+
190
+ Slope MAPE on Pragyan and MoonRanger runs elevated relative to mass:
191
+ published rover slope-capability specs come from real-rover-specific
192
+ design choices the analytical Bekker-Wong kernel's feature space cannot
193
+ fully resolve.
194
+
195
+ ## Column count sanity
196
+
197
+ Metadata (5) + design (11) + scenario (18) + metrics (8) + stats (≥24)
198
+ = ≥66 columns at `SCHEMA_VERSION = v9`. Future versions appending,
199
+ removing, or re-binding columns — *or* changing the LHS support so a
200
+ surrogate trained on one version would be OOD on the next — *must* bump
201
+ `SCHEMA_VERSION` so downstream code can detect a mismatch.
data/mass_validation_set.csv ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ rover_name,mass_total_kg,wheel_radius_m,wheel_width_m,n_wheels,chassis_mass_kg,solar_area_m2,battery_capacity_wh,avionics_power_w,grouser_height_m,grouser_count,payload_mass_kg,in_class,citation,imputation_notes
2
+ Rashid,10.0,0.10,0.08,4,2.0,0.4,50.0,10.0,0.015,14,1.5,true,"Hurrell et al. 2025 Space Science Reviews 221:37; Els et al. LPSC 2021 #1905; MBRSC Emirates Lunar Mission materials","wheel_width, grouser_height_m, and grouser_count updated to Hurrell et al. 2025 flight-wheel values; chassis is structural-only (35%-of-m_total ROT bucket minus the science payload); battery + avionics scaled from mass class. payload_mass_kg ~ 1.5 kg = 4 cameras + microscopic imager + Langmuir probe per Emirates Lunar Mission (Rashid) instrument list (schema v9: payload is a separate mission requirement, no longer folded into chassis)"
3
+ Sojourner,10.6,0.065,0.08,6,2.0,0.22,40.0,10.0,0.010,12,1.5,true,"Wilcox & Nguyen 1998; NASA Mars Pathfinder / Sojourner rover mission materials","chassis structural-only ~ 33%-of-m_total per Wilcox & Nguyen 1998 minus payload; grouser_count estimated at 12; Mars rover used as lunar-micro proxy (gravity correction pending real-rover validation). payload_mass_kg ~ 1.5 kg = APXS (~0.55 kg) + 3 cameras + electronics"
4
+ CADRE-unit,2.0,0.08,0.04,4,0.5,0.1,10.0,5.0,0.0,0,0.3,false,"Rothenbuchner et al. 2023 IEEE Aerospace #2300; NASA/JPL CADRE project materials","In the design-space class after the 2026-05-27 schema floor widening (chassis 0.5 kg / torque 0.05 Nm / battery 5 Wh), but OUT of the bottom-up mass model's calibration regime — MassModelParams specific-mass constants are calibrated to 5-50 kg micro-rovers, and at 2 kg the fixed-cost terms (4 x motor_base 0.15 kg + avionics_base 0.3 kg + ...) over-predict total mass by ~100%. Specs per Rothenbuchner 2023 IEEE Aerospace + NASA/JPL CADRE press. chassis structural-only ~ 40%-of-m_total ROT minus payload; no grousers (smooth wire-spoke rims). payload_mass_kg ~ 0.3 kg = stereo-camera + small comms-ranging payload"
5
+ Resilience-Tenacious,5.0,0.06,0.04,4,1.7,0.15,25.0,8.0,0.005,12,0.3,true,"ispace HAKUTO-R Mission 2 mission overview and press materials; ispace Mission 2 updates","In-class after the 2026-05-27 ultra-micro floor widening. Specs per iSpace HAKUTO-R M2 mission overview; chassis structural-only ~ 40%-of-m_total ROT minus payload; small grousers visible in iSpace press imagery. payload_mass_kg ~ 0.3 kg = HD camera payload"
6
+ ExoMy,8.0,0.055,0.06,6,3.0,0.10,30.0,10.0,0.008,12,0.0,true,"ESA ExoMy open-hardware documentation","Earth educational rover; chassis = 37.5% of m_total; specs approximate per ESA ExoMy docs. payload_mass_kg = 0 (educational platform, no dedicated science instrument)"
7
+ Pragyan,26.0,0.085,0.07,6,6.5,0.5,60.0,20.0,0.008,12,3.5,true,"ISRO Chandrayaan-3 press materials; Chandrayaan-3 Pragyan instrument-suite materials","wheel_width, solar_area, battery, avionics all scaled from class and mission duration (6 lunar hours); chassis structural-only ~ 38%-of-m_total ROT minus payload. payload_mass_kg ~ 3.5 kg = APXS + LIBS spectrometers per Chandrayaan-3 Pragyan instrument suite"
8
+ Yutu-2,135.0,0.15,0.15,6,45.0,1.3,130.0,40.0,0.0,0,25.0,false,"Di et al. 2020 Icarus; Ding et al. 2022 Acta Astronautica; Chang'e-4 / Yutu-2 payload manifest","out-of-class (medium, >50 kg ceiling); chassis structural-only = published-derived bucket minus the ~25 kg science payload. payload_mass_kg ~ 25 kg = Lunar Penetrating Radar + VNIS + APXS + panoramic/navigation cameras per Chang'e-4 Yutu-2 payload manifest; solar deployable 2-wing"
9
+ MARSOKHOD-proto,70.0,0.17,0.13,6,24.0,0.5,100.0,30.0,0.015,12,8.0,false,"Kemurjian et al. 1993","out-of-class (medium); chassis structural-only ~ 46%-of-m_total ROT minus payload; specs per Kemurjian et al. 1993. payload_mass_kg ~ 8 kg = instrument mast + manipulator/sampling proto payload"
data/published_traverse_data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ rover_name,scenario_name,traverse_m_published,traverse_m_low,traverse_m_high,peak_solar_power_w_published,peak_solar_power_w_low,peak_solar_power_w_high,thermal_survival_published,mission_duration_published_days,citation,notes
2
+ Pragyan,chandrayaan3_pragyan,101.4,80.0,140.0,50.0,40.0,70.0,false,10.0,"ISRO Chandrayaan-3 mission updates (Aug-Sep 2023); Nature SR 14:24178 (2024)","Traverse over Lunar Day 1 only. Rover did NOT survive lunar night (no RHUs); thermal_survival_published=false matches the sim's full-mission hot+cold steady-state check. traverse_m_published is the in-mission total."
3
+ Yutu-2,change4_yutu2_per_lunar_day,25.0,10.0,60.0,135.0,110.0,160.0,true,5.0,"Di et al. 2020 Icarus; Ding et al. 2022 Acta Astronautica; CNSA dispatches","Per-lunar-day drive distance, first ~2 years. Yutu-2 carries Pu-238 RHUs for lunar-night survival (surviving 60+ lunar days as of 2025); thermal_survival_published=true conditional on registry's RHU-carrying architecture."
data/rovers.yaml ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Canonical published-facts reference for the rovers used in verification.
2
+ #
3
+ # Purpose
4
+ # -------
5
+ # Single source of truth for the *published, citable facts* about each real
6
+ # (or reference) rover. The mass-model validation set
7
+ # (``data/mass_validation_set.csv``), the flown-rover truth table
8
+ # (``data/published_traverse_data.csv``), and the executable design registry
9
+ # (``roverdevkit/validation/rover_registry.py``) all describe these same
10
+ # rovers; this file is where the underlying facts live so the three consumers
11
+ # cannot silently drift apart. ``tests/test_rover_facts.py`` enforces that the
12
+ # consumers agree with the published/derived facts recorded here.
13
+ #
14
+ # Facts vs modeling values
15
+ # ------------------------
16
+ # This file holds ONLY published facts. It deliberately does NOT hold
17
+ # modeling-derived quantities that legitimately differ per consumer:
18
+ # - chassis_mass_kg (back-solved differently by the mass model and
19
+ # the evaluator registry)
20
+ # - avionics_power_w (steady-state vs peak, model-specific)
21
+ # - peak_wheel_torque_nm (sizing anchor)
22
+ # - panel_efficiency / panel_dust_factor / thermal architecture
23
+ # - scenario duty cycles, slopes, soil simulants
24
+ # Those stay in their respective consumers.
25
+ #
26
+ # Per-field provenance
27
+ # --------------------
28
+ # Every leaf is ``{value, provenance, source}``:
29
+ # - provenance: published -> taken directly from the cited source
30
+ # derived -> computed from other published values
31
+ # imputed -> estimated (class heritage / back-solve); the
32
+ # source field explains the basis
33
+ # Only ``published`` and ``derived`` fields are consistency-enforced against
34
+ # the consumers; ``imputed`` fields may differ per model.
35
+ #
36
+ # Latitude sign convention: positive = lunar north, negative = lunar south.
37
+
38
+ schema_version: 1
39
+
40
+ rovers:
41
+ - name: Pragyan
42
+ aliases: []
43
+ status: { value: flown, provenance: published, source: "ISRO Chandrayaan-3 mission updates (Aug-Sep 2023)" }
44
+ agency: { value: ISRO, provenance: published, source: "ISRO Chandrayaan-3 press materials" }
45
+ launch_year: { value: 2023, provenance: published, source: "ISRO Chandrayaan-3 (launch Jul 2023, landing 23 Aug 2023)" }
46
+ landing_latitude_deg: { value: -69.4, provenance: published, source: "ISRO Chandrayaan-3 press materials (~69.37 S, Shiv Shakti point)" }
47
+ landing_site: { value: "South polar highlands", provenance: published, source: "ISRO Chandrayaan-3 press materials" }
48
+ mass_total_kg: { value: 26.0, provenance: published, source: "ISRO Chandrayaan-3 press kit" }
49
+ n_wheels: { value: 6, provenance: published, source: "ISRO Chandrayaan-3 press kit" }
50
+ wheel_radius_m: { value: 0.085, provenance: published, source: "ISRO Chandrayaan-3 press materials (~170 mm wheel dia.)" }
51
+ wheel_width_m: { value: 0.07, provenance: imputed, source: "scaled from 6-wheel geometry (not published)" }
52
+ grouser_height_m: { value: 0.008, provenance: imputed, source: "class heritage (Yutu/Rashid-style 8 mm grousers)" }
53
+ grouser_count: { value: 12, provenance: imputed, source: "class heritage" }
54
+ wheelbase_m: { value: 0.5, provenance: imputed, source: "estimated from published rover imagery" }
55
+ solar_area_m2: { value: 0.5, provenance: imputed, source: "power-budget back-solve (single lunar-day ops)" }
56
+ battery_capacity_wh: { value: 60.0, provenance: imputed, source: "scaled from mass class and mission duration" }
57
+ payload_mass_kg: { value: 3.5, provenance: published, source: "Chandrayaan-3 Pragyan instrument suite (APXS + LIBS)" }
58
+ truth:
59
+ scenario_name: { value: chandrayaan3_pragyan, provenance: derived, source: "validation scenario key" }
60
+ traverse_m: { value: 101.4, low: 80.0, high: 140.0, provenance: published, source: "ISRO Chandrayaan-3 mission updates (Aug-Sep 2023); Nature SR 14:24178 (2024)" }
61
+ peak_solar_power_w: { value: 50.0, low: 40.0, high: 70.0, provenance: published, source: "ISRO Chandrayaan-3 power-system reporting" }
62
+ thermal_survival: { value: false, provenance: published, source: "No RHUs; rover did not survive lunar night" }
63
+ mission_duration_days: { value: 10.0, provenance: published, source: "Lunar Day 1 operations only" }
64
+
65
+ - name: Yutu-2
66
+ aliases: []
67
+ status: { value: flown, provenance: published, source: "CNSA Chang'e-4 mission dispatches" }
68
+ agency: { value: CNSA, provenance: published, source: "CNSA Chang'e-4 program" }
69
+ launch_year: { value: 2018, provenance: published, source: "Chang'e-4 launched 7 Dec 2018 (landing 3 Jan 2019)" }
70
+ landing_latitude_deg: { value: -45.5, provenance: published, source: "Di et al. 2020 Icarus (45.44 S, Von Karman crater, SPA basin)" }
71
+ landing_site: { value: "Von Karman crater, South Pole-Aitken basin (farside)", provenance: published, source: "Di et al. 2020 Icarus" }
72
+ mass_total_kg: { value: 135.0, provenance: published, source: "Di et al. 2020 Icarus; Ding et al. 2022 Acta Astronautica" }
73
+ n_wheels: { value: 6, provenance: published, source: "Di et al. 2020 Icarus" }
74
+ wheel_radius_m: { value: 0.15, provenance: published, source: "Di et al. 2020 Icarus (~300 mm wheel dia.)" }
75
+ wheel_width_m: { value: 0.15, provenance: published, source: "Di et al. 2020 Icarus" }
76
+ grouser_height_m: { value: 0.012, provenance: imputed, source: "Yutu-class grousered wheels (estimated from imagery)" }
77
+ grouser_count: { value: 18, provenance: imputed, source: "estimated from published wheel imagery" }
78
+ wheelbase_m: { value: 1.0, provenance: imputed, source: "estimated from published rover imagery" }
79
+ solar_area_m2: { value: 1.3, provenance: imputed, source: "two-wing deployable array (estimated)" }
80
+ battery_capacity_wh: { value: 130.0, provenance: imputed, source: "Li-ion pack (estimated from class)" }
81
+ payload_mass_kg: { value: 25.0, provenance: published, source: "Chang'e-4 Yutu-2 payload manifest (LPR + VNIS + APXS + cameras)" }
82
+ truth:
83
+ scenario_name: { value: change4_yutu2_per_lunar_day, provenance: derived, source: "validation scenario key" }
84
+ traverse_m: { value: 25.0, low: 10.0, high: 60.0, provenance: published, source: "Di et al. 2020 Icarus; CNSA dispatches (per-lunar-day drive)" }
85
+ peak_solar_power_w: { value: 135.0, low: 110.0, high: 160.0, provenance: published, source: "Chang'e-4 Yutu-2 power-system reporting" }
86
+ thermal_survival: { value: true, provenance: published, source: "Pu-238 RHUs; survived 60+ lunar days" }
87
+ mission_duration_days: { value: 5.0, provenance: imputed, source: "active-ops window per lunar day (not full 14 days)" }
88
+
89
+ - name: Rashid
90
+ aliases: [Rashid-1]
91
+ status: { value: lost_on_landing, provenance: published, source: "Lost on Hakuto-R Mission 1 lander failure (Apr 2023)" }
92
+ agency: { value: MBRSC/UAE, provenance: published, source: "MBRSC Emirates Lunar Mission" }
93
+ launch_year: { value: 2022, provenance: published, source: "Launched Dec 2022 on Hakuto-R Mission 1" }
94
+ landing_latitude_deg: { value: 47.5, provenance: published, source: "MBRSC Emirates Lunar Mission (Atlas Crater, 47.5 N 44.4 E, Mare Frigoris)" }
95
+ landing_site: { value: "Atlas Crater, Mare Frigoris", provenance: published, source: "MBRSC Emirates Lunar Mission" }
96
+ mass_total_kg: { value: 10.0, provenance: published, source: "Hurrell et al. 2025 Space Science Reviews 221:37" }
97
+ n_wheels: { value: 4, provenance: published, source: "Hurrell et al. 2025 SSR 221:37" }
98
+ wheel_radius_m: { value: 0.10, provenance: published, source: "Hurrell et al. 2025 SSR 221:37 (radius 100 mm)" }
99
+ wheel_width_m: { value: 0.08, provenance: published, source: "Hurrell et al. 2025 SSR 221:37 (width 80 mm)" }
100
+ grouser_height_m: { value: 0.015, provenance: published, source: "Hurrell et al. 2025 SSR 221:37 (15 mm flight grouser)" }
101
+ grouser_count: { value: 14, provenance: published, source: "Hurrell et al. 2025 SSR 221:37" }
102
+ wheelbase_m: { value: 0.50, provenance: published, source: "Els et al. LPSC 2021 #1905 (footprint 0.535 x 0.539 m)" }
103
+ solar_area_m2: { value: 0.25, provenance: imputed, source: "power-budget back-solve (0.5 x 0.5 m chassis)" }
104
+ battery_capacity_wh: { value: 50.0, provenance: imputed, source: "class-typical for 10 kg rover" }
105
+ payload_mass_kg: { value: 1.5, provenance: published, source: "Els et al. LPSC 2021 #1905 (2 cameras + CAM-M + CAM-T + Langmuir probes)" }
106
+
107
+ - name: Tenacious
108
+ aliases: [Resilience-Tenacious]
109
+ status: { value: lost_on_landing, provenance: published, source: "Resilience lander hard landing (Jun 2025)" }
110
+ agency: { value: ispace, provenance: published, source: "ispace HAKUTO-R Mission 2" }
111
+ launch_year: { value: 2025, provenance: published, source: "Launched 15 Jan 2025 on Falcon 9" }
112
+ landing_latitude_deg: { value: 60.5, provenance: published, source: "ispace Mission 2 target (Mare Frigoris, 60.5 N 4.6 W)" }
113
+ landing_site: { value: "Mare Frigoris", provenance: published, source: "ispace Mission 2 landing-zone announcement" }
114
+ mass_total_kg: { value: 5.0, provenance: published, source: "ispace HAKUTO-R Mission 2 mission overview" }
115
+ n_wheels: { value: 4, provenance: published, source: "ispace HAKUTO-R Mission 2 mission overview" }
116
+ wheel_radius_m: { value: 0.06, provenance: imputed, source: "estimated from ispace press imagery (scaled from Rashid by mass)" }
117
+ wheel_width_m: { value: 0.04, provenance: imputed, source: "estimated from ispace press imagery" }
118
+ grouser_height_m: { value: 0.005, provenance: imputed, source: "small grousers visible in ispace imagery" }
119
+ grouser_count: { value: 12, provenance: imputed, source: "class-typical 12-tooth pattern" }
120
+ wheelbase_m: { value: 0.30, provenance: imputed, source: "small-chassis class typical" }
121
+ solar_area_m2: { value: 0.15, provenance: imputed, source: "small body-mounted array (estimated)" }
122
+ battery_capacity_wh: { value: 25.0, provenance: imputed, source: "class-typical for 5 kg day-1 demo rover" }
123
+ payload_mass_kg: { value: 0.3, provenance: published, source: "ispace Mission 2 (HD camera + scoop sample demo)" }
124
+
125
+ - name: CADRE-unit
126
+ aliases: []
127
+ status: { value: design_target, provenance: published, source: "NASA/JPL CADRE (per-unit; no published surface-mission report at registry snapshot)" }
128
+ agency: { value: NASA/JPL, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300" }
129
+ launch_year: { value: 2025, provenance: imputed, source: "2024-2025 launch / deployment window (NASA/JPL CADRE)" }
130
+ landing_latitude_deg: { value: -85.0, provenance: imputed, source: "lunar south polar region target (design scenario)" }
131
+ landing_site: { value: "Lunar south pole region", provenance: published, source: "NASA/JPL CADRE project materials" }
132
+ mass_total_kg: { value: 2.0, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300 (per-unit ~2 kg)" }
133
+ n_wheels: { value: 4, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300" }
134
+ wheel_radius_m: { value: 0.08, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300" }
135
+ wheel_width_m: { value: 0.04, provenance: imputed, source: "class-typical aspect ratio for ultra-micro wire-spoke wheel" }
136
+ grouser_height_m: { value: 0.0, provenance: published, source: "smooth wire-spoke rims (JPL flotilla imagery)" }
137
+ grouser_count: { value: 0, provenance: published, source: "smooth wire-spoke rims (JPL flotilla imagery)" }
138
+ wheelbase_m: { value: 0.30, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300" }
139
+ solar_area_m2: { value: 0.10, provenance: published, source: "Rothenbuchner et al. 2023 IEEE Aerospace #2300 (small body-mounted array)" }
140
+ battery_capacity_wh: { value: 10.0, provenance: imputed, source: "power-budget back-solve (short coordinated drives)" }
141
+ payload_mass_kg: { value: 0.3, provenance: imputed, source: "stereo camera + small comms-ranging payload (estimated)" }
142
+
143
+ - name: MoonRanger
144
+ aliases: []
145
+ status: { value: design_target, provenance: published, source: "CMU/Astrobotic (in development)" }
146
+ agency: { value: CMU/Astrobotic, provenance: published, source: "Kumar et al. i-SAIRAS 2020 #5068" }
147
+ landing_latitude_deg: { value: -85.0, provenance: imputed, source: "south-polar demo target (design scenario)" }
148
+ landing_site: { value: "Lunar south pole region", provenance: published, source: "Kumar et al. i-SAIRAS 2020 #5068" }
149
+ mass_total_kg: { value: 13.0, provenance: published, source: "Kumar et al. i-SAIRAS 2020 #5068 (13 kg full-up)" }
150
+ n_wheels: { value: 4, provenance: published, source: "Kumar et al. i-SAIRAS 2020 #5068" }
151
+ wheel_radius_m: { value: 0.10, provenance: imputed, source: "class-match to Rashid-1" }
152
+ wheel_width_m: { value: 0.08, provenance: imputed, source: "class-match to Rashid-1" }
153
+ grouser_height_m: { value: 0.012, provenance: imputed, source: "class-typical for ~0.10 m radius lunar wheel" }
154
+ grouser_count: { value: 12, provenance: imputed, source: "class-typical" }
155
+ wheelbase_m: { value: 0.40, provenance: imputed, source: "body length ~0.65 m minus wheel diameter" }
156
+ solar_area_m2: { value: 0.30, provenance: imputed, source: "polar power-budget back-solve" }
157
+ battery_capacity_wh: { value: 100.0, provenance: imputed, source: "class-typical for 13 kg polar rover" }
158
+
159
+ - name: Sojourner
160
+ aliases: []
161
+ status: { value: flown_mars, provenance: published, source: "NASA Mars Pathfinder / Sojourner (Mars-gravity proxy, not lunar)" }
162
+ agency: { value: NASA/JPL, provenance: published, source: "Wilcox & Nguyen 1998" }
163
+ launch_year: { value: 1996, provenance: published, source: "Mars Pathfinder launched Dec 1996, landed Jul 1997" }
164
+ mass_total_kg: { value: 10.6, provenance: published, source: "Wilcox & Nguyen 1998" }
165
+ n_wheels: { value: 6, provenance: published, source: "Wilcox & Nguyen 1998" }
166
+ wheel_radius_m: { value: 0.065, provenance: published, source: "Wilcox & Nguyen 1998" }
167
+ wheel_width_m: { value: 0.08, provenance: imputed, source: "estimated" }
168
+ grouser_height_m: { value: 0.010, provenance: imputed, source: "estimated" }
169
+ grouser_count: { value: 12, provenance: imputed, source: "estimated" }
170
+ solar_area_m2: { value: 0.22, provenance: published, source: "Mars Pathfinder / Sojourner solar panel" }
171
+ battery_capacity_wh: { value: 40.0, provenance: imputed, source: "primary battery (estimated)" }
172
+ payload_mass_kg: { value: 1.5, provenance: published, source: "APXS (~0.55 kg) + 3 cameras + electronics" }
173
+
174
+ - name: ExoMy
175
+ aliases: []
176
+ status: { value: educational_concept, provenance: published, source: "ESA ExoMy open-hardware documentation" }
177
+ agency: { value: ESA/ESTEC, provenance: published, source: "ESA ExoMy open-hardware documentation" }
178
+ launch_year: { value: 2020, provenance: published, source: "ESA ExoMy open-hardware release" }
179
+ mass_total_kg: { value: 8.0, provenance: imputed, source: "approximate per ESA ExoMy docs (educational platform)" }
180
+ n_wheels: { value: 6, provenance: published, source: "ESA ExoMy open-hardware documentation" }
181
+ wheel_radius_m: { value: 0.055, provenance: published, source: "ESA ExoMy open-hardware documentation" }
182
+ wheel_width_m: { value: 0.06, provenance: imputed, source: "approximate per ESA ExoMy docs" }
183
+ grouser_height_m: { value: 0.008, provenance: imputed, source: "approximate per ESA ExoMy docs" }
184
+ grouser_count: { value: 12, provenance: imputed, source: "approximate per ESA ExoMy docs" }
185
+ solar_area_m2: { value: 0.10, provenance: imputed, source: "nominal (educational platform)" }
186
+ battery_capacity_wh: { value: 30.0, provenance: imputed, source: "nominal (educational platform)" }
187
+ payload_mass_kg: { value: 0.0, provenance: published, source: "educational platform, no dedicated science instrument" }
188
+
189
+ - name: MARSOKHOD-proto
190
+ aliases: []
191
+ status: { value: prototype, provenance: published, source: "Kemurjian et al. 1993 (historical reference)" }
192
+ agency: { value: IKI, provenance: published, source: "Kemurjian et al. 1993" }
193
+ launch_year: { value: 1992, provenance: published, source: "Kemurjian et al. 1993 (prototype)" }
194
+ mass_total_kg: { value: 70.0, provenance: published, source: "Kemurjian et al. 1993" }
195
+ n_wheels: { value: 6, provenance: published, source: "Kemurjian et al. 1993" }
196
+ wheel_radius_m: { value: 0.17, provenance: published, source: "Kemurjian et al. 1993" }
197
+ wheel_width_m: { value: 0.13, provenance: published, source: "Kemurjian et al. 1993" }
198
+ grouser_height_m: { value: 0.015, provenance: imputed, source: "estimated from prototype specs" }
199
+ grouser_count: { value: 12, provenance: imputed, source: "estimated" }
200
+ solar_area_m2: { value: 0.5, provenance: imputed, source: "estimated" }
201
+ battery_capacity_wh: { value: 100.0, provenance: imputed, source: "estimated" }
202
+ payload_mass_kg: { value: 8.0, provenance: imputed, source: "instrument mast + manipulator/sampling proto payload (estimated)" }
data/soil_simulants.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ simulant,n,k_c_kN_per_m_n_plus_1,k_phi_kN_per_m_n_plus_2,cohesion_kPa,friction_angle_deg,density_kg_per_m3,citation,notes
2
+ FJS-1,1.0,1.37,820.0,0.2,38.0,1500,"Kanamori et al. 1998",Lunar highland simulant (Shimizu).
3
+ JSC-1A,1.0,1.4,820.0,1.0,45.0,1600,"Zeng et al. 2010, J Aerospace Engineering",Most widely used lunar mare simulant.
4
+ GRC-1,0.8,0.7,505.0,0.25,38.0,1620,"Oravec et al. 2010, JTerramechanics",Lunar regolith simulant (NASA Glenn).
5
+ GRC-3,0.9,1.0,700.0,0.4,42.0,1700,"He et al. 2013, J Aerospace Engineering",
6
+ Apollo_regolith_nominal,1.0,1.4,820.0,0.17,46.0,1660,"Heiken et al. 1991 (Lunar Sourcebook ch. 9)",Best-estimate nominal regolith; use for baseline runs.
7
+ Apollo_regolith_loose,1.0,0.5,400.0,0.1,30.0,1400,"Heiken et al. 1991, bounds",Soft / worst-case for slope studies.
8
+ Apollo_regolith_dense,1.2,2.0,1200.0,0.5,50.0,1900,"Heiken et al. 1991, bounds",Compacted / best-case.
9
+ Ding2011_planetary_simulant,1.10,15.6,2407.4,0.25,31.9,1605,"Ding et al. 2011, J. Terramechanics 48(1):27-45, Table 2","Planetary soil simulant used in Ding 2011 single-wheel tests; k_c/k_phi reported in kPa equal kN units here; Janosi K=9.7-13.1 mm (use ~0.011 m)."
10
+ KLS-1,1.2594,-44.0554,3581.8106,1.716,40.6,1600,"shear C/phi: Wang & Han 2016 J. Korean Geotech. Soc. 32(11) Table 2; pressure-sinkage n/k_c/k_phi: Lim et al. 2021 J. Astron. Space Sci. 38(4):237 bevameter fit","Korean lunar simulant; cohesion 1.716 kPa, friction 40.6 deg (direct shear, Wang & Han 2016). Bekker pressure-sinkage n=1.2594, k_c=-44.06, k_phi=3581.8 from a KICT/Pai Chai bevameter (Lim et al. 2021, Wong 1980 least-squares fit over three plate sizes); negative k_c is a normal least-squares fit artifact, k_eff = k_c/b + k_phi stays strongly positive. RD ~60%."
11
+ Hurrell2025_FJS1,1.0,1.37,820.0,2.4,38.0,1740,"Hurrell et al. 2025 Table 2 (cohesion 2.4 kPa via Ozaki et al. 2023; angle of repose 38 deg); n/k_c/k_phi proxied from catalogue FJS-1 (Kanamori 1998)","FJS-1 as characterised in Hurrell 2025 Rashid-1 tests, loose (~25% rel. density, bulk 1623-2100). Cohesion 2.4 kPa is far above the dry-catalogue FJS-1 value (0.2); pressure-sinkage params not reported -> FJS-1 proxy."
data/validation/README.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation data
2
+
3
+ Single-wheel testbed data digitised from published papers. Used **only** to
4
+ validate the evaluator; never used for training the surrogate.
5
+
6
+ ## Active reference grids
7
+
8
+ - `wong_layer3_reference.csv` — Layer-3 BW-vs-published reference grid
9
+ exercised by `tests/test_terramechanics.py::test_layer3_published_reference_grid`.
10
+ Each row is one (wheel, soil, vertical load, slip) operating point with
11
+ per-quantity `[lo, hi]` tolerance bands. Three row kinds:
12
+
13
+ 1. **`characterisation`** — Wong (2008) §4.2-style worked-example
14
+ fixture (JSC-1A canonical Bekker parameters; R=0.10 m, b=0.06 m;
15
+ 50 N at slip ∈ {0.05, 0.20, 0.50}). Bounds pinned at the BW
16
+ kernel's verified outputs to within ±5 % so the test guards
17
+ against unintended kernel drift while staying inside the
18
+ ±15-30 % BW model-form band reported in the literature.
19
+ 2. **`published_rover_class`** — Apollo nominal regolith × a smooth
20
+ Pragyan-class wheel (R=0.135 m, b=0.10 m, W=70.2 N) and a
21
+ grousered Yutu-2-class wheel (R=0.165 m, b=0.150 m, h_g=0.012 m,
22
+ N_g=14, W=36.4 N). Bounds sized at the published Bekker-Wong
23
+ model-form error (Ishigami 2007; Ding et al. 2011).
24
+ 3. **`closed_form_limit`** — kernel regression checks at analytic
25
+ limits (e.g. smooth wheel with N_g>0 ⇒ grouser lift factor ≡ 1;
26
+ bounds pinned at the v1 kernel output, not digitised experiments).
27
+
28
+ Appending rows is additive — the test reads the CSV with
29
+ `csv.DictReader` and parametrises one case per row.
30
+
31
+ - `single_wheel_experiments.csv` — **experiment-vs-model worksheet** for the
32
+ experimental anchor of Layer 3. Each row is one measured single-wheel
33
+ operating point: wheel geometry, vertical load, slip, the soil simulant
34
+ name (Bekker parameters resolved from `../soil_simulants.csv`), and the
35
+ Janosi-Hanamoto shear modulus `soil_shear_modulus_k_m`. The
36
+ `meas_drawbar_pull_n` / `meas_sinkage_m` / `meas_torque_nm` columns hold
37
+ point measurements traced from the source figures.
38
+
39
+ Consumed by `roverdevkit.validation.terramechanics_experiment`
40
+ (`compare_to_experiment`, `summarise`) and
41
+ `tests/test_terramechanics_experiment.py`. The harness runs the
42
+ analytical Bekker-Wong kernel at every operating point and reports
43
+ residuals + percentage errors against the measured columns.
44
+ `scripts/make_terramechanics_experiment_figure.py` renders the
45
+ terramechanics-experiment figure
46
+ (`reports/figures/fig_terramechanics_experiment.png`) from it.
47
+
48
+ ## Sources
49
+
50
+ - **Ding et al. 2011**, *J. Terramechanics* 48(1):27-45 — rigid single-wheel
51
+ slip/sinkage/drawbar-pull experiments (R=135/157 mm, b=110/165 mm, lugs
52
+ 0-15 mm, loads 30/80/150 N, slip 0-0.6). Digitised from Fig. 8/9 (Wh3
53
+ family: Wh34 smooth + Wh32 grousered at 80 N; soil Bekker params from
54
+ Table 2 → `Ding2011_planetary_simulant` in `../soil_simulants.csv`). BW
55
+ reproduces drawbar pull within the literature model-form band (~27 %
56
+ median |error|).
57
+ - **Wang & Han 2016**, *J. Korean Geotech. Soc.* 32(11):97-108 (open access) —
58
+ KLS-1 single-wheel testbed (R=85 mm, b=80 mm, 59 N), smooth vs grousered
59
+ (h=10 mm), slip 0.1-0.5. Digitised from Fig. 14. The paper publishes only
60
+ shear strength (Table 2: C=1.716 kPa, φ=40.6°); the pressure-sinkage moduli
61
+ in `KLS-1` (`../soil_simulants.csv`) are KLS-1's own bevameter-measured Bekker
62
+ values (Lim et al. 2021, *J. Astron. Space Sci.* 38(4):237; n=1.2594,
63
+ k_c=-44.06, k_phi=3581.8). This is a deliberate **stress case at the edge of
64
+ the rigid-wheel kernel's regime**: the smallest/most-lightly-loaded wheel on a
65
+ firm, dense, fines-rich simulant that barely sinks (1-14 mm), so BW's
66
+ force-balance sinkage solve over-predicts DP and sinkage (~135 % / ~385 %
67
+ median |error|) and cannot capture the measured DP collapse at s≈0.5.
68
+ - **Hurrell et al. 2025**, *Space Sci. Rev.* 221 art. 37 (open access, CC-BY) —
69
+ Rashid-1 micro-rover wheel (R=100 mm, b=80 mm, 14 grousers h=20 mm, 24.5 N)
70
+ on FJS-1, slip 0.1-0.5. Digitised from Figs. 5/6:
71
+ `meas_drawbar_pull_n` = traction coefficient F_x/F_z (Fig. 5) × 24.5 N;
72
+ `meas_sinkage_m` from Fig. 6; torque not reported. Soil = `Hurrell2025_FJS1`
73
+ (`../soil_simulants.csv`): cohesion 2.4 kPa (Ozaki et al. 2023) and AoR 38°,
74
+ pressure-sinkage proxied from catalogue FJS-1. **Most application-relevant
75
+ case** (in-scope micro-rover wheel + load); BW lands within band on both DP
76
+ (~24 % median) and sinkage (~28 %).
77
+ - **Iizuka & Kubota 2011** — grousered-wheel experiments motivating the
78
+ arc-density grouser correction (not digitised into this grid; empirical
79
+ grousered-wheel checks live in `single_wheel_experiments.csv`).
80
+ - **Wong** — datasets from *Theory of Ground Vehicles* (4th ed.) ch. 4.
81
+
82
+ Keep raw digitised traces in `raw/` (git-ignored, re-downloadable from the
83
+ papers); curated, checked-in reference rows live in
84
+ `wong_layer3_reference.csv` / `single_wheel_experiments.csv` at this level.
data/validation/raw/.gitkeep ADDED
File without changes
data/validation/single_wheel_experiments.csv ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ source,case_id,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,soil_simulant,soil_shear_modulus_k_m,vertical_load_n,slip,meas_drawbar_pull_n,meas_sinkage_m,meas_torque_nm,status,citation,notes
2
+ ding2011,ding2011_smooth_w80_s00,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.0,-6.5,0.005,0.0,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel Wh34 (R=157.35 mm, b=165 mm, h=0); DP/torque/sinkage vs slip from Fig 9 h=0 curve; soil Bekker params from paper Table 2."
3
+ ding2011,ding2011_smooth_w80_s10,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.1,11.0,0.0055,3.0,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel R=157.35 mm, b=165 mm."
4
+ ding2011,ding2011_smooth_w80_s20,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.2,14.5,0.0075,4.2,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel R=157.35 mm, b=165 mm."
5
+ ding2011,ding2011_smooth_w80_s30,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.3,15.0,0.010,4.8,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel R=157.35 mm, b=165 mm."
6
+ ding2011,ding2011_smooth_w80_s40,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.4,15.0,0.0125,5.0,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel R=157.35 mm, b=165 mm."
7
+ ding2011,ding2011_smooth_w80_s60,0.15735,0.165,0.0,0,Ding2011_planetary_simulant,0.011,80.0,0.6,15.0,0.0175,5.3,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Smooth rigid wheel R=157.35 mm, b=165 mm."
8
+ ding2011,ding2011_lug10_w80_s00,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.0,-6.5,0.0045,0.0,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
9
+ ding2011,ding2011_lug10_w80_s10,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.1,14.0,0.005,3.5,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
10
+ ding2011,ding2011_lug10_w80_s20,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.2,20.0,0.0075,5.0,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
11
+ ding2011,ding2011_lug10_w80_s30,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.3,22.0,0.0105,5.8,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
12
+ ding2011,ding2011_lug10_w80_s40,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.4,23.0,0.014,6.3,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
13
+ ding2011,ding2011_lug10_w80_s60,0.15735,0.165,0.010,30,Ding2011_planetary_simulant,0.011,80.0,0.6,24.5,0.027,7.2,digitised_approx,"Ding et al. 2011, J. Terramechanics 48(1):27-45, single-wheel testbed (HIT)","Lugged rigid wheel R=157.35 mm, b=165 mm, lug height 10 mm, 30 lugs (Wh32)."
14
+ wang_han_2016_kls1,kls1_smooth_w59_s30,0.085,0.080,0.0,0,KLS-1,0.018,58.86,0.3,2.0,0.0045,1.9,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Smooth wheel d=170 mm, b=80 mm; KLS-1 simulant (PSD-matched to JSC-1/FJS-1), relative density 60%, load 6 kg, 10 mm/s. Measured DP/torque/sinkage in paper Fig. 14."
15
+ wang_han_2016_kls1,kls1_smooth_w59_s10,0.085,0.080,0.0,0,KLS-1,0.018,58.86,0.1,-0.5,0.001,1.3,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Smooth wheel d=170 mm, b=80 mm; KLS-1 simulant."
16
+ wang_han_2016_kls1,kls1_smooth_w59_s20,0.085,0.080,0.0,0,KLS-1,0.018,58.86,0.2,1.0,0.0043,1.55,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Smooth wheel d=170 mm, b=80 mm; KLS-1 simulant."
17
+ wang_han_2016_kls1,kls1_smooth_w59_s40,0.085,0.080,0.0,0,KLS-1,0.018,58.86,0.4,2.5,0.0057,2.05,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Smooth wheel d=170 mm, b=80 mm; KLS-1 simulant."
18
+ wang_han_2016_kls1,kls1_smooth_w59_s50,0.085,0.080,0.0,0,KLS-1,0.018,58.86,0.5,0.5,0.008,2.05,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Smooth wheel d=170 mm, b=80 mm; KLS-1 simulant."
19
+ wang_han_2016_kls1,kls1_lug10_w59_s30,0.085,0.080,0.010,16,KLS-1,0.018,58.86,0.3,10.0,0.0078,3.05,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Grousered wheel d=170 mm, b=80 mm, 16 grousers (10 mm, 36 deg spacing, 3 mm thick); KLS-1."
20
+ wang_han_2016_kls1,kls1_lug10_w59_s10,0.085,0.080,0.010,16,KLS-1,0.018,58.86,0.1,7.7,0.0022,2.85,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Grousered wheel d=170 mm, b=80 mm, 16 grousers; KLS-1."
21
+ wang_han_2016_kls1,kls1_lug10_w59_s20,0.085,0.080,0.010,16,KLS-1,0.018,58.86,0.2,9.0,0.0043,2.95,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Grousered wheel d=170 mm, b=80 mm, 16 grousers; KLS-1."
22
+ wang_han_2016_kls1,kls1_lug10_w59_s40,0.085,0.080,0.010,16,KLS-1,0.018,58.86,0.4,12.7,0.0096,3.1,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Grousered wheel d=170 mm, b=80 mm, 16 grousers; KLS-1."
23
+ wang_han_2016_kls1,kls1_lug10_w59_s50,0.085,0.080,0.010,16,KLS-1,0.018,58.86,0.5,6.4,0.0135,2.85,digitised_approx,"Wang & Han 2016, J. Korean Geotech. Soc. 32(11):97-108 (open access), KICT single-wheel testbed","Grousered wheel d=170 mm, b=80 mm, 16 grousers; KLS-1."
24
+ hurrell2025_rashid1,rashid1_fjs1_w24_s10,0.100,0.080,0.020,14,Hurrell2025_FJS1,0.018,24.5,0.1,8.1,0.006,,digitised_approx,"Hurrell et al. 2025, Space Sci. Rev. 221 art. 37 (open access, CC-BY), single-wheel testbed + DEM, Rashid-1 wheel","Rashid-1 micro-rover wheel R=100 mm, b=80 mm, 14 grousers (20 mm), load 24.5 N (Earth), FJS-1 loose (~25% rel. density). Source reports traction coefficient mu = DP/W (Fig 5) and dynamic sinkage (Fig 6); meas_drawbar_pull_n = mu * 24.5 N. FJS-1 measured AoR 38 deg matches catalogue phi."
25
+ hurrell2025_rashid1,rashid1_fjs1_w24_s20,0.100,0.080,0.020,14,Hurrell2025_FJS1,0.018,24.5,0.2,8.6,0.0095,,digitised_approx,"Hurrell et al. 2025, Space Sci. Rev. 221 art. 37 (open access, CC-BY), single-wheel testbed + DEM, Rashid-1 wheel","Rashid-1 wheel; meas_drawbar_pull_n = mu * 24.5 N from Fig 5."
26
+ hurrell2025_rashid1,rashid1_fjs1_w24_s30,0.100,0.080,0.020,14,Hurrell2025_FJS1,0.018,24.5,0.3,8.8,0.011,,digitised_approx,"Hurrell et al. 2025, Space Sci. Rev. 221 art. 37 (open access, CC-BY), single-wheel testbed + DEM, Rashid-1 wheel","Rashid-1 wheel; meas_drawbar_pull_n = mu * 24.5 N from Fig 5."
27
+ hurrell2025_rashid1,rashid1_fjs1_w24_s40,0.100,0.080,0.020,14,Hurrell2025_FJS1,0.018,24.5,0.4,9.6,0.016,,digitised_approx,"Hurrell et al. 2025, Space Sci. Rev. 221 art. 37 (open access, CC-BY), single-wheel testbed + DEM, Rashid-1 wheel","Rashid-1 wheel; meas_drawbar_pull_n = mu * 24.5 N from Fig 5."
28
+ hurrell2025_rashid1,rashid1_fjs1_w24_s50,0.100,0.080,0.020,14,Hurrell2025_FJS1,0.018,24.5,0.5,10.5,0.0195,,digitised_approx,"Hurrell et al. 2025, Space Sci. Rev. 221 art. 37 (open access, CC-BY), single-wheel testbed + DEM, Rashid-1 wheel","Rashid-1 wheel; meas_drawbar_pull_n = mu * 24.5 N from Fig 5."
data/validation/wong_layer3_reference.csv ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ case_id,kind,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,soil_n,soil_k_c_kN,soil_k_phi_kN,soil_c_kPa,soil_phi_deg,vertical_load_n,slip,exp_drawbar_pull_n_lo,exp_drawbar_pull_n_hi,exp_sinkage_m_lo,exp_sinkage_m_hi,exp_torque_nm_lo,exp_torque_nm_hi,exp_dp_over_w_lo,exp_dp_over_w_hi,citation
2
+ wong_2008_ch4_fixture_s0_05,characterisation,0.10,0.06,0.0,0,1.0,1.4,820.0,1.0,45.0,50.0,0.05,-0.5,1.5,0.026,0.029,1.65,1.86,-0.01,0.03,Wong 2008 §4.2 worked-example fixture (JSC-1A canonical Bekker params); pinned at kernel v1.
3
+ wong_2008_ch4_fixture_s0_20,characterisation,0.10,0.06,0.0,0,1.0,1.4,820.0,1.0,45.0,50.0,0.20,5.5,7.7,0.026,0.029,2.30,2.55,0.10,0.16,Wong 2008 §4.2 worked-example fixture (JSC-1A canonical Bekker params); pinned at kernel v1.
4
+ wong_2008_ch4_fixture_s0_50,characterisation,0.10,0.06,0.0,0,1.0,1.4,820.0,1.0,45.0,50.0,0.50,12.5,14.9,0.026,0.029,3.05,3.30,0.24,0.31,Wong 2008 §4.2 worked-example fixture (JSC-1A canonical Bekker params); pinned at kernel v1.
5
+ pragyan_class_smooth_s0_20,published_rover_class,0.135,0.10,0.0,0,1.0,1.4,820.0,0.17,46.0,70.2,0.20,4.5,18.5,0.012,0.030,2.5,5.5,0.05,0.30,Apollo nominal regolith (Heiken et al. 1991) × Pragyan-class smooth wheel; DP/W and sinkage bounded by ±25% Bekker-Wong model-form error (Ishigami 2007; Ding 2011).
6
+ pragyan_class_smooth_s0_50,published_rover_class,0.135,0.10,0.0,0,1.0,1.4,820.0,0.17,46.0,70.2,0.50,15.0,30.0,0.012,0.030,4.5,7.0,0.20,0.45,Apollo nominal regolith × Pragyan-class smooth wheel at high slip; lunar-rover testbed (Ding 2011 Fig 8) reports DP/W in this band on JSC-1A / FJS-1.
7
+ yutu2_class_grousered_s0_20,published_rover_class,0.165,0.150,0.012,14,1.0,1.4,820.0,0.17,46.0,36.4,0.20,3.0,12.0,0.006,0.020,1.5,3.5,0.08,0.30,Apollo nominal regolith × Yutu-2-class grousered wheel at lunar per-wheel load 36.4 N (135 kg / 6 wheels / 1.62 m·s⁻²).
8
+ grouser_lift_unity_smooth_s0_60,closed_form_limit,0.10,0.10,0.0,14,1.0,1.4,820.0,0.17,46.0,40.0,0.6,8.7,12.7,0.012,0.020,2.05,2.65,0.20,0.32,Smooth-wheel limit (h_g=0 N_g=14): grouser shear factor collapses to unity; BW kernel regression check at slip=0.6; bounds pinned at v1 kernel output ±10%.
deploy/huggingface/README.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: RoverDevKit
3
+ emoji: 🛰️
4
+ colorFrom: gray
5
+ colorTo: blue
6
+ sdk: docker
7
+ app_port: 8000
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
+ # RoverDevKit — hosted demo
13
+
14
+ Interactive tradespace explorer for conceptual design of lunar micro-rovers:
15
+ physics-based mission evaluator, calibrated surrogate predictions, parametric
16
+ sweeps, NSGA-II multi-objective optimization, and SHAP-style design
17
+ explanations.
18
+
19
+ - Source code: <https://github.com/Autonomous-Mission-Systems-Lab/roverdevkit>
20
+ - Paper preprint: <https://arxiv.org/abs/2606.21755>
21
+
22
+ This Space runs the single-container build from
23
+ [`webapp/Dockerfile`](https://github.com/Autonomous-Mission-Systems-Lab/roverdevkit/blob/main/webapp/Dockerfile):
24
+ one `uvicorn` process serves the FastAPI backend and the React single-page app
25
+ from the same origin on port 8000.
26
+
27
+ > This README (with its Spaces front matter) is generated for the hosted demo
28
+ > by `scripts/deploy_hf_space.sh` and is not the repository's main README.
environment.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: roverdevkit
2
+ channels:
3
+ - conda-forge
4
+ dependencies:
5
+ - python=3.12
6
+
7
+ - pip
8
+ - numpy>=1.26
9
+ - scipy>=1.11
10
+ - pandas>=2.1
11
+ - scikit-learn>=1.4
12
+ - matplotlib>=3.8
13
+ - plotly>=5.18
14
+ - jupyterlab>=4.0
15
+ - pyyaml>=6.0
16
+ - tqdm>=4.66
17
+ - pip:
18
+ - xgboost>=2.0
19
+ - pymoo>=0.6.1
20
+ - shap>=0.44
21
+ - optuna>=3.5
22
+ - pydantic>=2.5
23
+ - torch>=2.2
24
+ - -e .
fig_system_architecture.png ADDED
models/README.md ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Models
2
+
3
+ Shipped trained surrogate bundles used at runtime by the web app and
4
+ validation scripts.
5
+
6
+ | Path | Purpose |
7
+ | --- | --- |
8
+ | `surrogate_v9/quantile_bundles.joblib` | v9 quantile-XGB heads (calibrated 90% PIs) for the Current Design and Explain Design tabs |
9
+
10
+ Training-time metrics (`coverage.csv`, `median_sanity.csv`, etc.) are
11
+ written to `reports/surrogate_v9/` when you re-fit. A full calibration
12
+ run via `scripts/calibrate_intervals.py` publishes the runtime bundle
13
+ here automatically:
14
+
15
+ ```bash
16
+ python scripts/calibrate_intervals.py \
17
+ --dataset data/analytical/lhs_v9.parquet \
18
+ --tuned-params reports/tuned_v9/tuned_best_params.json
19
+ ```
20
+
21
+ Use `--no-publish-bundle` on smoke runs so partial calibrations do not
22
+ overwrite the shipped model. Override the runtime path with
23
+ `ROVERDEVKIT_QUANTILE_BUNDLES` if needed.
models/surrogate_v9/quantile_bundles.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3ef01d20ce66ecc641df60287b71c8fee24ef16f56e11e880cfd849de80b4ec2
3
+ size 26786100
pyproject.toml ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "roverdevkit"
7
+ version = "0.1.0"
8
+ description = "ML-accelerated co-design of mobility and power subsystems for lunar micro-rovers"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Autonomous Mission Systems Lab, Duke University" },
14
+ ]
15
+ keywords = [
16
+ "lunar",
17
+ "rover",
18
+ "terramechanics",
19
+ "tradespace",
20
+ "surrogate",
21
+ "multi-objective-optimization",
22
+ "space-systems",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 2 - Pre-Alpha",
26
+ "Intended Audience :: Science/Research",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Topic :: Scientific/Engineering",
32
+ ]
33
+ dependencies = [
34
+ "numpy>=1.26",
35
+ "scipy>=1.11",
36
+ "pandas>=2.1",
37
+ "scikit-learn>=1.4",
38
+ "xgboost>=2.0",
39
+ "pymoo>=0.6.1",
40
+ "shap>=0.44",
41
+ "optuna>=3.5",
42
+ "matplotlib>=3.8",
43
+ "plotly>=5.18",
44
+ "pydantic>=2.5",
45
+ "pyyaml>=6.0",
46
+ "tqdm>=4.66",
47
+ "pyarrow>=14",
48
+ ]
49
+
50
+ [project.optional-dependencies]
51
+ torch = ["torch>=2.2"]
52
+ webapp = [
53
+ # Browser-based tradespace tool backend.
54
+ # Frontend toolchain (Node 20 LTS + npm) is managed separately under webapp/frontend/.
55
+ "fastapi>=0.115",
56
+ "uvicorn[standard]>=0.30",
57
+ "sse-starlette>=2.1",
58
+ "httpx>=0.27",
59
+ "python-multipart>=0.0.9",
60
+ ]
61
+ dev = [
62
+ "pytest>=8.0",
63
+ "pytest-cov>=4.1",
64
+ "ruff>=0.3",
65
+ "mypy>=1.8",
66
+ "jupyterlab>=4.0",
67
+ "nbstripout>=0.7",
68
+ ]
69
+
70
+ [project.urls]
71
+ Repository = "https://github.com/Autonomous-Mission-Systems-Lab/roverdevkit"
72
+
73
+ [tool.setuptools.packages.find]
74
+ include = ["roverdevkit*"]
75
+
76
+ [tool.setuptools.package-data]
77
+ roverdevkit = ["py.typed"]
78
+
79
+ [tool.ruff]
80
+ line-length = 100
81
+ target-version = "py311"
82
+
83
+ [tool.ruff.lint]
84
+ select = ["E", "F", "W", "I", "N", "UP", "B", "SIM", "NPY"]
85
+ ignore = ["E501"]
86
+
87
+ [tool.ruff.lint.per-file-ignores]
88
+ # Allow the conventional sklearn naming (`X_train`, `Y_pred`, `X_feas`)
89
+ # in surrogate ML code; lowercase variants would obscure the standard
90
+ # convention.
91
+ "roverdevkit/surrogate/baselines.py" = ["N806"]
92
+ "roverdevkit/surrogate/tuning.py" = ["N803", "N806"]
93
+ "roverdevkit/surrogate/uncertainty.py" = ["N803", "N806"]
94
+ "scripts/tune_baselines.py" = ["N806"]
95
+ "scripts/calibrate_intervals.py" = ["N806"]
96
+ "tests/test_surrogate_tuning.py" = ["N806"]
97
+ "tests/test_surrogate_uncertainty.py" = ["N803", "N806"]
98
+ "webapp/backend/services/predict.py" = ["N803"]
99
+ "webapp/backend/routes/predict.py" = ["N806"]
100
+
101
+ [tool.ruff.format]
102
+ quote-style = "double"
103
+
104
+ [tool.mypy]
105
+ python_version = "3.11"
106
+ ignore_missing_imports = true
107
+ warn_unused_ignores = true
108
+ warn_redundant_casts = true
109
+
110
+ [tool.pytest.ini_options]
111
+ testpaths = ["tests", "webapp/backend/tests"]
112
+ # Repo root is added to sys.path so `import webapp.backend...` works
113
+ # without installing the webapp package. The webapp is a deployment
114
+ # artifact, not a Python distribution; it lives next to roverdevkit.
115
+ pythonpath = ["."]
116
+ addopts = "-ra --strict-markers"
117
+ markers = [
118
+ "slow: slow tests (>10s)",
119
+ "integration: full mission evaluator integration tests",
120
+ ]
reports/pareto_fronts/front_crater_rim_survey.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ scenario_name,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,n_wheels,chassis_mass_kg,wheelbase_m,solar_area_m2,battery_capacity_wh,avionics_power_w,peak_wheel_torque_nm,range_km,energy_margin_raw_pct,slope_capability_deg,total_mass_kg,backend_used
2
+ crater_rim_survey,0.19996949336018777,0.03015591370191417,0.01999947745771378,24,4,0.5013295982314507,1.09239005542805,0.11074041652767178,6.5998976103874725,5.040316501944113,0.6816791635367268,25.0,64.75573128709165,32.39190332913843,9.147868153565007,evaluator
3
+ crater_rim_survey,0.19996475949140272,0.030046460113463763,0.01999810016708055,24,4,3.084264550682021,1.1412822471359005,0.10287281342060557,63.50697853914775,12.012126079302831,18.536282533125284,0.38006527396986056,0.5755216671889363,34.16605030168889,23.4666655556521,evaluator
4
+ crater_rim_survey,0.1999691605068453,0.03021341809861096,0.01999950890415926,24,4,32.0557930112859,0.8287957171902023,0.11064993132036935,38.423390020782065,5.807522040762394,5.084523565790135,25.0,17.447123653924336,34.78795237028887,54.900464902228116,evaluator
5
+ crater_rim_survey,0.19997541938513752,0.030210883832046093,0.01999480533631987,24,4,3.0067251103422388,1.0916940373390942,0.19029184457796658,7.862853303258829,26.050290075535422,4.484651162830488,6.1745592153218825,1.199240928328565,33.578144184942836,16.345451077945384,evaluator
6
+ crater_rim_survey,0.19999371313820274,0.0302101785843682,0.01999480533631987,24,4,4.9453598721917835,1.0040727343588634,0.20220891672271504,8.974146954879252,26.050290075535422,2.4390552811342747,25.0,3.8715615973877266,33.74026656337317,17.923333136178734,evaluator
7
+ crater_rim_survey,0.19992738362360304,0.030025907534493097,0.01999978200123614,24,4,11.625840702592752,1.1087973059317935,0.11801969071068683,78.35665817016414,12.375143296187218,17.724103777357353,13.51014492492579,5.296665042347408,34.60310527329037,34.89089614577678,evaluator
8
+ crater_rim_survey,0.199994593821754,0.030208110186515022,0.019998098535041933,24,4,4.200760509028835,0.9285303718390917,0.20287489830844252,194.93850095281107,26.58273228047366,4.567260654470145,22.156521162164832,3.07028830576107,33.939104232184405,20.21577098356393,evaluator
9
+ crater_rim_survey,0.1998949871214314,0.030226446195385434,0.019998440791125394,24,4,1.9444277499154028,1.138623951995295,0.11602906162333988,99.32372189273153,12.012126079302831,17.600191822065792,22.669379612387523,5.341743231664687,34.056770062505,21.86877883276512,evaluator
10
+ crater_rim_survey,0.1999857781965795,0.030219707500303873,0.019991902488848946,24,4,16.480098047845054,1.181846667401259,0.19734518866266185,17.888723619848363,25.758354168212,19.25454786667237,9.11510996273768,3.2350798403546905,34.72718815417143,42.83802367757407,evaluator
11
+ crater_rim_survey,0.19990870607176364,0.030005903631146944,0.019999782098889494,24,4,0.9780012293651135,1.1410654699732552,0.11602334313964643,13.263494286868664,13.572908448135099,19.376224060699947,6.521841063123421,2.062807291834961,33.97992105908711,20.635616391079257,evaluator
12
+ crater_rim_survey,0.19993754795265384,0.0302188258001639,0.019998828755911417,24,4,22.42907010712495,1.1331715897292536,0.19364059998966796,165.14780294750207,24.44552701676996,17.593253842836596,10.888515997861626,3.9840929686706517,34.78355858313113,51.59678768563304,evaluator
13
+ crater_rim_survey,0.19985603014556594,0.03022083184623974,0.019998809843019164,24,4,16.480098047845054,1.189590994985081,0.19754238550794442,98.18252251724451,25.218683255432175,19.033484831644756,12.003761067672333,3.8936653900425657,34.73564667948465,43.591329784582555,evaluator
14
+ crater_rim_survey,0.19998543176775221,0.030219707500303873,0.019991902488848946,24,4,16.90346789491681,1.181846667401259,0.19714559757258543,242.43737347732795,24.579601308626025,19.25454786667237,14.871717077408523,4.319745338819463,34.75464950864102,45.879642837033,evaluator
15
+ crater_rim_survey,0.19998288464547984,0.030212343262724605,0.019999872897597346,24,4,13.120265914448098,1.0921353308095618,0.20010149768516522,196.85947401952347,26.799532361513545,18.25226270422459,7.366092274882523,2.205636770687478,34.692170283016345,39.82978643388382,evaluator
16
+ crater_rim_survey,0.19992738362360304,0.030025907534493097,0.01999978200123614,24,4,13.850086445341361,1.1087973059317935,0.11801969071068683,77.56309429824228,12.375143296187218,17.724103777357353,12.386592773948287,5.296665042347408,34.66368605040657,37.908650738201494,evaluator
17
+ crater_rim_survey,0.1999998267581842,0.030224287458665244,0.019999888079936544,24,4,10.395015241185963,0.5447001379848206,0.11943090236622705,74.18031107617836,13.637844175820277,18.54953477077168,7.259576895060765,3.0249930938135954,34.571049672416926,33.72289459807108,evaluator
18
+ crater_rim_survey,0.19993106729572208,0.030025691595464102,0.01999981531032702,24,4,12.580518359002717,1.1046593742150552,0.12034701160859249,166.01080285732803,12.531714451309602,6.343881666037599,17.907045407026544,5.236896013614218,34.49780007091426,31.008123757677893,evaluator
19
+ crater_rim_survey,0.1999798793520878,0.03021613116781923,0.0199999007669067,24,4,0.7170518856952199,1.0916759879026448,0.12484608667360336,99.8841197552043,13.5702146463674,1.0098186363034296,25.0,5.309325282243869,32.85342043543148,11.310479876609676,evaluator
20
+ crater_rim_survey,0.19985603014556594,0.03022149988453427,0.019999783704246595,24,4,8.305982797643265,1.0063461590300977,0.1868290719919364,10.326856203447273,25.31491204627705,4.946013755270927,5.5710001803315725,1.4532239938067297,34.175827137340384,23.77378567037766,evaluator
21
+ crater_rim_survey,0.19996049402745086,0.030208672999496103,0.019998100204768634,24,4,3.449709457090443,1.1411500115891193,0.1156456209596583,200.49366664226574,12.012126079302831,18.877736200412205,19.84266616314643,4.705616618897112,34.28266042917022,25.75783322180167,evaluator
22
+ crater_rim_survey,0.19993969042263834,0.03021469824534104,0.01999947745771378,24,4,0.5013295982314507,1.1172947378577829,0.11074041652767178,68.54734302984869,5.040316501944113,0.6816791635367268,25.0,62.375091566859,32.55374098678155,9.854151422276866,evaluator
23
+ crater_rim_survey,0.19999371313820274,0.0302101785843682,0.019194677829063513,24,4,4.9453598721917835,1.0040727343588634,0.20220891672271504,8.974146954879252,26.050290075535422,2.4390552811342747,25.0,3.8715615973877266,33.42462260831035,17.906281275047576,evaluator
24
+ crater_rim_survey,0.19998288464547984,0.030212343262724605,0.019998098497353848,24,4,12.755009848307738,1.125500238982686,0.20034875217783665,196.78548849672512,26.799532361513545,17.85871112839597,7.733793012515015,2.255067532533772,34.680572893213636,39.11849233156485,evaluator
25
+ crater_rim_survey,0.19994659170934925,0.030225759225313672,0.019998474775986135,24,4,25.912964987324678,1.10935292429397,0.1212039704796571,6.548536080033266,12.959459668519187,2.4113643308563706,9.579072666008036,5.346733853199638,34.75173490655878,45.247952962638074,evaluator
26
+ crater_rim_survey,0.19989408551282212,0.030130993945979984,0.01999512793426261,24,4,6.629521847447889,0.7022225480133527,0.11816213322028937,62.621261457790624,12.737361809298234,17.30504052755663,14.215431729860589,4.646482139873441,34.371917413004155,27.71734901517685,evaluator
27
+ crater_rim_survey,0.19999828858438273,0.0301619482864442,0.01999991361391298,24,4,9.627334404382893,1.1721434753302948,0.12531789325058865,83.01067504182704,12.631405267602263,17.443294853148082,21.51054102614195,6.9337647081009885,34.528933692574675,32.12346883580072,evaluator
28
+ crater_rim_survey,0.1952066074556897,0.030208110186515022,0.019998098535041933,24,4,4.200760509028835,0.9285303718390917,0.24093368671451512,194.93850095281107,26.58273228047366,4.567260654470145,25.0,18.138874465159706,33.877416054371125,20.305673832827242,evaluator
29
+ crater_rim_survey,0.19997541938513752,0.030210600032861874,0.01999480533631987,24,4,0.8243151514801373,1.0916940373390942,0.12153592409393749,27.21004465383342,13.998247884409631,4.484651162830488,19.420976887332465,3.057372979463376,33.06697485822573,12.541076632781099,evaluator
30
+ crater_rim_survey,0.19999831477043908,0.030025601417783404,0.01999981499921321,24,4,12.566677732068761,1.1087973059317935,0.1194711049138735,74.31940606349654,11.733229145668608,17.73531146515037,18.63591192103689,7.090258141531174,34.63024752334344,36.09332952315394,evaluator
31
+ crater_rim_survey,0.19992738362360304,0.03003854921672812,0.01999978200123614,24,4,0.8940862451343436,1.1087973059317935,0.11801969071068683,77.56309429824228,13.95056805818217,4.430691369415018,10.063761931249484,1.8449038774055322,33.174612863955836,13.150317195886718,evaluator
32
+ crater_rim_survey,0.1999998267581842,0.030044158833755413,0.019999888079936544,24,4,6.521319554271298,0.5447001379848206,0.11943090236622705,74.18031107617836,13.637844175820277,18.54953477077168,8.672461738129524,3.0249930938135954,34.40747426368394,28.439171314791846,evaluator
33
+ crater_rim_survey,0.19996915094368273,0.03021124529636843,0.019999851980371464,24,4,1.4181397371099358,1.0108223196481498,0.1868290719919364,9.336019400593369,13.36001115638058,4.946013755270927,25.0,54.93100001380934,33.228393196922866,13.576392378046279,evaluator
34
+ crater_rim_survey,0.19998346056624744,0.030219711599665647,0.019999783704246595,24,4,11.513336098021568,1.0063461590300977,0.1868290719919364,10.326856203447273,25.31491204627705,5.180452589423311,4.653545848554822,1.4532239938067297,34.3945159669859,28.266892399852406,evaluator
35
+ crater_rim_survey,0.19996672942558297,0.030025908200769946,0.019997587275357605,24,4,12.826042313649488,1.1091042442285663,0.12155244879304188,98.3672578987437,14.793448257903915,18.101714223744423,2.0146495953268406,1.231207327039812,34.64936222079686,37.1334283234286,evaluator
36
+ crater_rim_survey,0.1999972589053633,0.030161993909901784,0.019997720223840953,24,4,27.43230645259139,0.47923049257521577,0.12079950503702129,182.5597417006075,5.847749380278069,2.876114908955059,25.0,15.367343631821818,34.77608578257913,49.07517029752097,evaluator
37
+ crater_rim_survey,0.1998503628815009,0.030211453382063477,0.01999399066062191,24,4,2.875649776832269,1.1055497642968386,0.1902836920482329,7.276332106252866,26.460483867131103,2.134751949511693,1.166746511598552,0.5918528066324912,33.40571628318376,14.908205440137719,evaluator
38
+ crater_rim_survey,0.1999453360698079,0.0302188144758805,0.019998614778199197,24,4,1.234189460708043,0.5027562960276755,0.12090449753720407,6.534935760589103,12.895912713147577,2.7952935952768327,25.0,5.624175762146862,32.95356172911064,11.868073539972091,evaluator
39
+ crater_rim_survey,0.19997505983140038,0.03001131130467081,0.019997593544333843,24,4,1.203028996067899,1.0948232403200435,0.11689308183718175,10.155484377260683,14.225659955951887,0.6694089854888206,2.916745322871573,0.8335843719001543,32.76137243310877,10.772338411573678,evaluator
40
+ crater_rim_survey,0.19996049402745086,0.030208672999496103,0.01999810016708055,24,4,3.084264550682021,1.1412822471359005,0.1156456209596583,200.41968111946736,12.012126079302831,18.536282533125284,20.40567580139513,4.705616618897112,34.24821431636473,25.07383673635179,evaluator
41
+ crater_rim_survey,0.1998935869093835,0.03011469097731069,0.019998792800966832,24,4,3.061583897423999,0.7232072925051825,0.1766777774406931,62.39608448419539,24.16924103447909,18.34887408862155,1.0080703174964334,0.6544225831419768,34.215579343120076,24.403572460978662,evaluator
42
+ crater_rim_survey,0.19995895850001952,0.030153428142367652,0.019991570018008036,24,4,8.083429534986386,0.5974179640374865,0.19851611329649577,194.37858518803256,25.411008777392365,1.0320005391055362,23.61486792806271,3.631400060266274,34.159167623367615,23.469895643478004,evaluator
43
+ crater_rim_survey,0.19994681067429088,0.030215425614192753,0.01999950890415926,24,4,32.14367050914465,0.8544890547835011,0.12123970470959237,5.720429072348487,6.266049326748184,4.605741185152444,25.0,18.935775344242035,34.78753528061201,54.45576419541737,evaluator
44
+ crater_rim_survey,0.19997541938513752,0.030214197093595848,0.01999480533631987,24,4,1.256673362201843,1.0937561032542502,0.11689308183718175,10.155484377260683,5.232187376949527,0.6766584919613376,25.0,68.54435966398616,32.63941387224673,10.251274243061342,evaluator
45
+ crater_rim_survey,0.1999302078193984,0.030025907534493097,0.01999978200123614,24,4,13.850086445341361,1.0116393663165382,0.1823339515046686,9.995989262321892,12.374546590079678,4.996055706599398,25.0,41.57925240511967,34.479072326151964,30.433088358004877,evaluator
46
+ crater_rim_survey,0.19982567550391447,0.0302188258001639,0.019998828755911417,24,4,22.42907010712495,1.1324298177762144,0.19284659557305986,82.37016760409517,24.44552701676996,17.593253842836596,10.23490561531125,3.969267927575843,34.779199243095405,50.65446313371426,evaluator
47
+ crater_rim_survey,0.19996915094368273,0.030212334662223245,0.019999873541036467,24,4,1.4181397371099358,1.0092034250635393,0.19933257379635824,183.34599492662016,27.24985783649888,15.777620264646071,7.604706374755984,1.5021196705386648,34.09622340578168,22.433203067983463,evaluator
48
+ crater_rim_survey,0.19991035441242636,0.03021404045288451,0.019998974136648747,24,4,1.0849572306739068,0.6907519778873832,0.17788556163701721,61.65024271909051,5.077339227965677,0.6816791635367268,25.0,152.9869710662552,32.753633480346835,10.800787092009811,evaluator
49
+ crater_rim_survey,0.19994604561822632,0.030225759225313672,0.019999135459863464,24,4,1.652882421929199,1.1077730078299493,0.12142218628500301,6.548536080033266,12.959459668519187,2.2233068460516243,25.0,5.569270383645587,32.99927610913862,12.133221098937998,evaluator
50
+ crater_rim_survey,0.1999715897464899,0.03000295868683525,0.019999771924460973,24,4,12.849906392433654,1.1043183840231139,0.12154572699696423,78.35665817016414,12.38654337768794,17.516181134539423,16.460037623691274,6.347591308681268,34.637646236539005,36.454983088793135,evaluator
51
+ crater_rim_survey,0.19999828858438273,0.030224148010901272,0.01999991361391298,24,4,9.627334404382893,1.1721434753302948,0.12531789325058865,77.42892437103957,12.631405267602263,18.54953477077168,21.138985125584643,6.9337647081009885,34.54275427379168,32.66660145616334,evaluator
reports/pareto_fronts/front_crater_rim_survey.metadata.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scenario_name": "crater_rim_survey",
3
+ "backend": "evaluator",
4
+ "dataset_version": "v9",
5
+ "objectives": [
6
+ {
7
+ "target": "range_km",
8
+ "direction": "max"
9
+ },
10
+ {
11
+ "target": "total_mass_kg",
12
+ "direction": "min"
13
+ },
14
+ {
15
+ "target": "slope_capability_deg",
16
+ "direction": "max"
17
+ }
18
+ ],
19
+ "constraints": [
20
+ {
21
+ "target": "range_km",
22
+ "sense": "min",
23
+ "value": 0.1
24
+ }
25
+ ],
26
+ "traverse_distance_m": 25000.0,
27
+ "population_size": 50,
28
+ "generations": 60,
29
+ "seed": 12,
30
+ "panel_tilt_deg": 0.0,
31
+ "panel_azimuth_deg": 180.0,
32
+ "elapsed_s": 32.589310958981514,
33
+ "pareto_size": 50,
34
+ "front_csv": "reports/pareto_fronts/front_crater_rim_survey.csv"
35
+ }
reports/pareto_fronts/front_equatorial_mare_traverse.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ scenario_name,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,n_wheels,chassis_mass_kg,wheelbase_m,solar_area_m2,battery_capacity_wh,avionics_power_w,peak_wheel_torque_nm,range_km,energy_margin_raw_pct,slope_capability_deg,total_mass_kg,backend_used
2
+ equatorial_mare_traverse,0.17966028565146944,0.03039956760018241,0.008182229457302601,24,4,0.5380833748056679,0.4335806040369062,0.10165876619272624,8.997804783460904,5.356359047610721,0.3781790734474563,80.0,92.96340914451272,26.93473844847004,9.64490746552987,evaluator
3
+ equatorial_mare_traverse,0.19986128212195706,0.030292096427161217,0.019997073043771345,24,4,11.214649678956356,0.34573992106280527,0.150995512269261,29.873149349758204,29.34811017392639,11.920699578672703,0.8848064761851875,0.6385829045982598,34.54441144533367,32.907370502148154,evaluator
4
+ equatorial_mare_traverse,0.19997745599465494,0.030590948424177592,0.019999805514851414,24,4,25.768558209445658,0.9994731630133085,0.7597096755482708,11.240839905422256,12.554063797915965,14.310524706339239,80.0,438.32027744159217,34.788542594127875,54.75163640658672,evaluator
5
+ equatorial_mare_traverse,0.19367225817890393,0.03021385817426154,0.010130029878649197,24,4,0.5187433614190687,1.0181926341630547,0.10201064455895326,8.477782035487206,5.59173017600839,0.36078609602362616,80.0,88.1452358263669,28.342398331472445,9.76758401051974,evaluator
6
+ equatorial_mare_traverse,0.1935713769877956,0.03040622812539698,0.01867371181419051,22,4,0.533038056847511,1.0183338488033367,0.10210209288863026,9.038212012542584,5.583683998672508,0.36035876550515233,80.0,87.94761332050398,31.324903037607154,9.95353244985581,evaluator
7
+ equatorial_mare_traverse,0.1998128499767734,0.030055752455620845,0.019994827108702255,24,4,8.33812087873973,0.38532716546010376,0.1428739063393388,39.11452770746371,26.873862672526183,2.795460172362963,20.650998542349807,1.3882667214240019,34.18822447351292,23.918055580181665,evaluator
8
+ equatorial_mare_traverse,0.19986175521736724,0.03016560484862971,0.019972577000892826,24,4,12.101834660177074,0.32716952855232856,0.15074980107502692,487.5667435484656,26.593798194207228,16.232700999904782,57.976983664098285,2.592284850127033,34.70215365019747,41.45454780240283,evaluator
9
+ equatorial_mare_traverse,0.19994534055113247,0.030078855288681147,0.01999702870514257,24,4,25.54485632887412,1.006443364482119,0.10109684633712794,45.527385180641474,12.85157524204105,11.83971534500576,76.19583434819337,8.90737310012355,34.782453445946786,51.235195937509374,evaluator
10
+ equatorial_mare_traverse,0.19985376968358456,0.03007090038344987,0.019999763814230978,24,4,21.933767522829896,1.0708468804465745,0.1469879834079606,41.06803028889759,26.794861772185044,11.785105991640807,25.44096133600444,2.5325356307348654,34.766461825720114,47.3445123239427,evaluator
11
+ equatorial_mare_traverse,0.19999962921381634,0.0300427481864885,0.019993947750907595,24,4,15.388759224984664,0.9753088129443631,0.14831272139917662,10.275876201402788,26.55630181292349,11.939116010436774,40.91578906554164,3.298506593369386,34.666510080167996,38.16012698013008,evaluator
12
+ equatorial_mare_traverse,0.199857502461715,0.030285266221320408,0.019994931130989862,24,4,15.932169081616458,0.32660313476078584,0.7838793632444141,29.873149349758204,26.484977529325235,16.68279419246426,80.0,319.3303484555587,34.73613333061206,43.87679063334305,evaluator
13
+ equatorial_mare_traverse,0.19985807538289835,0.0300174549688136,0.019999194343062642,24,4,11.188699621510814,1.0494095592429245,0.14831272139917662,10.351990827401869,26.74077236547027,11.939116010436774,44.550540873027416,3.0736281681181556,34.53998009839563,32.455308099351626,evaluator
14
+ equatorial_mare_traverse,0.19985805142217997,0.030016756711533872,0.019995683385887503,24,4,15.744376191482553,1.0497694789002703,0.14831272139917662,477.8318325909422,27.189950740722384,15.664920217815917,29.85045207379336,1.7838777610712244,34.756872876820495,46.0143634495506,evaluator
15
+ equatorial_mare_traverse,0.19966907349277724,0.030039508757413364,0.01999307247729388,24,4,5.952954658210377,1.046062952056694,0.7578526886638339,478.38118535142047,13.201823888582929,2.6987140796568543,80.0,594.5632250208669,34.32961057797897,26.760520715195494,evaluator
16
+ equatorial_mare_traverse,0.1998515298002952,0.0302929146926154,0.019972577000892826,24,4,11.395558294570193,0.33527835326818295,0.14756155855198724,487.5667435484656,26.593798194207228,3.5619149115492164,53.39832356576964,2.085782404713479,34.55285139075893,33.5943089195924,evaluator
17
+ equatorial_mare_traverse,0.19974641540312468,0.030016789354282942,0.019995420244159165,24,4,9.356944952815827,1.0494095592429245,0.10360897055444673,14.680370919293011,12.792421677714248,13.057195999487226,80.0,10.167063802212015,34.443264949324245,29.518162795418633,evaluator
18
+ equatorial_mare_traverse,0.1935713769877956,0.030407530595858958,0.01934035775008197,24,4,0.5380833748056679,0.43422100296046434,0.10210209288863026,8.997804783460904,5.356335286024761,0.3781774863620438,80.0,91.12535658613218,32.22545628703486,10.001935148524822,evaluator
19
+ equatorial_mare_traverse,0.19358329655127376,0.03023763610346199,0.018572231014876296,24,4,0.5187433614190687,0.4491942308023737,0.10211767263101934,13.26248663503225,5.3643309468763105,0.378603229795105,80.0,90.9213139053419,31.93074417292202,9.997205457773376,evaluator
20
+ equatorial_mare_traverse,0.19997745599465494,0.03059269339327144,0.01999999882566245,24,4,25.768558209445658,1.0669904404357518,0.1817144976105955,11.240839905422256,12.554063797915965,13.779011643152165,80.0,43.835072948737704,34.785832143539004,52.49610788743395,evaluator
21
+ equatorial_mare_traverse,0.1935713769877956,0.03040622812539698,0.01934035775008197,22,4,0.5380833748056679,1.0183338488033367,0.10210209288863026,9.038212012542584,5.583683998672508,0.36035876550515233,80.0,87.89109854365401,31.576335059089573,9.973505879649348,evaluator
22
+ equatorial_mare_traverse,0.19966355239944847,0.0300446395164878,0.01999706064619622,24,4,5.952954658210377,1.041114969501095,0.1616174409348035,347.9614005277324,9.628382727098286,1.7640764459199434,80.0,85.59157557395393,34.102303446003596,22.501680950979612,evaluator
23
+ equatorial_mare_traverse,0.19985805142217997,0.03004037491195512,0.01999480882345419,24,4,12.349119305999292,1.0494095592429245,0.14834306164880967,10.011124389013247,26.488776858237145,15.659920068685839,44.96685131068038,3.388938297129835,34.624664334196964,36.040290827306876,evaluator
24
+ equatorial_mare_traverse,0.1933746459726256,0.03004001228993762,0.019986651538182895,24,4,0.5258967446130819,0.43422100296046434,0.10263326713930987,8.997804783460904,9.210509800400638,0.3781774863620438,80.0,47.29375568445724,32.5521141515573,10.236996823155255,evaluator
25
+ equatorial_mare_traverse,0.19994534055113247,0.03009193665409114,0.01999702870514257,24,4,6.903528773962815,0.9249366376279518,0.14702488780463763,45.68867788372448,12.85157524204105,3.2116874804077393,80.0,52.486537897478435,34.02581809580667,21.330645694424845,evaluator
26
+ equatorial_mare_traverse,0.19985847646075183,0.030016862079877728,0.019999995135702837,24,4,22.268500110648358,1.0734554446022797,0.14954803139300665,27.889456251127328,26.368472273951667,14.12003253841739,39.024241876707414,3.718717667055279,34.774705221345975,48.89754263948475,evaluator
27
+ equatorial_mare_traverse,0.19985807538289835,0.030016862079877728,0.019999194343062642,24,4,22.17579647692005,1.071451762183981,0.14954803139300665,8.799792375475512,26.74077236547027,14.12437886704558,32.956877518960795,3.3935299592864703,34.77299254617588,48.5825915873951,evaluator
28
+ equatorial_mare_traverse,0.19966119219019107,0.030044952808417978,0.01999706064619622,24,4,5.403081896575908,1.0412539193344732,0.1616174409348035,347.9614005277324,9.009317858790915,14.48442140465549,80.0,76.97735443086296,34.40891539926693,28.63523322358909,evaluator
29
+ equatorial_mare_traverse,0.19985805142217997,0.03001066404786041,0.019995683385887503,24,4,11.149556205329999,1.0494095592429245,0.14831272139917662,10.029767241614959,26.485506975304858,15.522273300235906,47.29892440150306,3.3850587930751184,34.58761364785491,34.33086196577899,evaluator
30
+ equatorial_mare_traverse,0.19985805142217997,0.030016676144910225,0.019995683385887503,24,4,16.028392890908183,0.9866980087447234,0.14831272139917662,10.011124389013247,26.488776858237145,15.664920217815917,38.994433231808735,3.381057911897182,34.708431071059,41.04805313014547,evaluator
31
+ equatorial_mare_traverse,0.1999583524211712,0.03033349152216959,0.01997018794400092,24,4,1.5398664290166044,1.014365608181424,0.15665320545287995,22.474954156559676,12.357787895460008,2.423970111799072,80.0,79.65846754167418,33.17686877614042,13.355073968528412,evaluator
32
+ equatorial_mare_traverse,0.1996695373567177,0.03006438152406502,0.01999307247729388,24,4,20.704781058302164,1.0452252084015694,0.14888821859776286,27.822697889754295,23.924731614151607,13.307073411160319,78.1415940892455,6.403448719515403,34.7533500308139,46.15941792774388,evaluator
33
+ equatorial_mare_traverse,0.19985807538289835,0.030015052311930544,0.01999542011268096,24,4,11.33756413877837,1.0494095592429245,0.14950151610999843,10.419531591569601,26.74077236547027,11.939116010436774,50.22323995266236,3.3815452819831853,34.54432297953303,32.66244793189408,evaluator
34
+ equatorial_mare_traverse,0.19964258777339933,0.030017418025845753,0.019998128751297695,24,4,13.233399834840787,1.0057904820957306,0.1451896681354153,28.050395950957103,26.150351986524512,15.318515395696526,37.04935403657662,2.885176801765543,34.646433001143485,37.22531901960305,evaluator
35
+ equatorial_mare_traverse,0.1999583524211712,0.0303339560382081,0.019999157594776645,24,4,0.5052566360827521,1.0396428840727354,0.16066278288365138,105.73909290015905,11.560411277399552,1.7693457716409773,80.0,91.93898557713827,33.05337390049307,12.495106123858172,evaluator
36
+ equatorial_mare_traverse,0.1999583524211712,0.030334607269122595,0.01999934290297604,24,4,0.5052566360827521,1.0396428840727354,0.7990788670969965,14.189059764706762,12.07537528311951,2.2724620517884784,80.0,801.6293705232293,33.272525778296924,13.937763452882253,evaluator
37
+ equatorial_mare_traverse,0.19971784842981258,0.030069158917998006,0.019995426062630075,24,4,11.368867547401816,1.0733692156293864,0.1498497541055819,11.757891612603204,26.68860759970813,14.20621993487694,51.21778072068148,3.5115851675165755,34.57471115815943,33.954456375216,evaluator
38
+ equatorial_mare_traverse,0.19997745599465494,0.030590948424177592,0.019999655485740328,24,4,1.2134545901091442,0.9985238224071419,0.1534391523521108,69.86899513361027,14.446645859539778,13.714484039162045,80.0,50.41660485632228,33.88376697711659,19.743693551648757,evaluator
39
+ equatorial_mare_traverse,0.19986838755162878,0.030039428190985724,0.01999307247729388,24,4,6.19588465451847,0.9829914819011443,0.15618450898803787,15.054508849986405,12.50065528735485,3.5520298360630997,80.0,66.20414614087082,33.94268454381091,20.20850212889211,evaluator
40
+ equatorial_mare_traverse,0.19994517613869897,0.030078855288681147,0.019999185848925443,24,4,25.218122418225008,1.04008329455604,0.10109684633712794,43.55701368606013,12.084463481396364,11.83971534500576,80.0,10.06993741825928,34.7819662394364,50.71608255386488,evaluator
41
+ equatorial_mare_traverse,0.19971470186150325,0.03003032999949951,0.019993081924397287,24,4,1.5682617332417674,1.0161668869061726,0.7309395961765106,386.93665335424157,9.334591124246263,1.1953343132698755,80.0,787.8628237372915,33.805659765506434,18.58358926501375,evaluator
42
+ equatorial_mare_traverse,0.19985691687481746,0.030016862079877728,0.019999194343062642,24,4,22.268500110648358,1.0667418817876002,0.14954803139300665,11.706344655679462,12.507685826610984,2.9051434016329756,80.0,32.48341338633476,34.717480946137556,41.666420023837205,evaluator
43
+ equatorial_mare_traverse,0.19989261816031834,0.030228370637315677,0.01999282288537965,24,4,1.5130409196645478,0.36766328183001656,0.10166392998974953,14.556523449331053,5.629633137351526,0.45723081822562905,80.0,81.10513612391884,32.8849907650051,11.506089083621763,evaluator
44
+ equatorial_mare_traverse,0.19969642451926337,0.03007090038344987,0.01999312655720344,24,4,21.262562159833283,1.0775949505575504,0.11875173698299846,27.65841864031782,12.690783798287889,13.215654674918527,80.0,13.357246541063084,34.75264824096287,46.00061079081805,evaluator
45
+ equatorial_mare_traverse,0.19985847646075183,0.03008347199737673,0.019999194343062642,24,4,22.268500110648358,1.0040060037648284,0.15005532889257034,7.0360279573693845,26.689341095544233,11.810728156931358,36.132833036869016,3.607056393338314,34.766720746235855,47.43218711507268,evaluator
46
+ equatorial_mare_traverse,0.19974641540312468,0.030016789354282942,0.019995420244159165,24,4,9.356944952815827,0.42977946305513054,0.140338279518992,15.652043507364441,12.7070141634327,12.169287616284453,80.0,37.66186001797165,34.43042214376875,29.165017253248095,evaluator
47
+ equatorial_mare_traverse,0.19985847646075183,0.030016862079877728,0.019999194343062642,24,4,22.268500110648358,1.0732663860256937,0.14954803139300665,11.706344655679462,26.689341095544233,14.12003253841739,33.75142447865225,3.4339290014019213,34.77368328020459,48.735841111783905,evaluator
48
+ equatorial_mare_traverse,0.19985805142217997,0.03001066404786041,0.019995683385887503,24,4,11.08574900800416,1.0494095592429245,0.1483076308151734,10.029767241614959,26.485506975304858,15.522273300235906,47.402902808659036,3.383736433686447,34.585516337064426,34.24401581349122,evaluator
49
+ equatorial_mare_traverse,0.19966907349277724,0.030296207050194892,0.019985003378199175,24,4,0.5157781225544853,1.039298098202913,0.1616174409348035,342.7997905737702,9.334591124246263,2.8189427606079764,80.0,106.25698034063112,33.481767291000224,15.615513453156433,evaluator
50
+ equatorial_mare_traverse,0.19999962921381634,0.030406004506259292,0.019993947750907595,24,4,0.6682030309261924,0.9762472994028375,0.14831272139917662,9.034927626034252,26.55630181292349,11.939116010436774,80.0,3.298506593369386,33.75048980231638,18.139323022562667,evaluator
51
+ equatorial_mare_traverse,0.19986838755162878,0.030039508757413364,0.01999307247729388,24,4,5.952954658210377,1.046062952056694,0.15618450898803787,478.38118535142047,13.201823888582929,3.5520298360630997,80.0,48.6842218318719,34.25684578398426,25.179760555145204,evaluator
reports/pareto_fronts/front_equatorial_mare_traverse.metadata.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scenario_name": "equatorial_mare_traverse",
3
+ "backend": "evaluator",
4
+ "dataset_version": "v9",
5
+ "objectives": [
6
+ {
7
+ "target": "range_km",
8
+ "direction": "max"
9
+ },
10
+ {
11
+ "target": "total_mass_kg",
12
+ "direction": "min"
13
+ },
14
+ {
15
+ "target": "slope_capability_deg",
16
+ "direction": "max"
17
+ }
18
+ ],
19
+ "constraints": [
20
+ {
21
+ "target": "range_km",
22
+ "sense": "min",
23
+ "value": 0.1
24
+ }
25
+ ],
26
+ "traverse_distance_m": 80000.0,
27
+ "population_size": 50,
28
+ "generations": 60,
29
+ "seed": 13,
30
+ "panel_tilt_deg": 20.2,
31
+ "panel_azimuth_deg": 180.0,
32
+ "elapsed_s": 36.020679499953985,
33
+ "pareto_size": 50,
34
+ "front_csv": "reports/pareto_fronts/front_equatorial_mare_traverse.csv"
35
+ }
reports/pareto_fronts/front_highland_slope_capability.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ scenario_name,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,n_wheels,chassis_mass_kg,wheelbase_m,solar_area_m2,battery_capacity_wh,avionics_power_w,peak_wheel_torque_nm,range_km,energy_margin_raw_pct,slope_capability_deg,total_mass_kg,backend_used
2
+ highland_slope_capability,0.19999848849450302,0.030191849818629372,0.019503901234723633,24,4,0.5041814195273064,1.0339532362824977,0.18372809307203547,8.814768033990617,6.187941532527164,0.38264783264486746,48.169395364533685,285.38902015497695,19.358220439857405,8.332642941714546,evaluator
3
+ highland_slope_capability,0.19999784996301712,0.03825311376785551,0.01999807835169149,24,4,16.563387617513204,0.6591162728323452,0.374322644299933,242.77327514869162,9.960242557868893,2.5411140296122454,55.7123721743145,339.7824377276585,18.827464701217636,35.484425937053835,evaluator
4
+ highland_slope_capability,0.1999835438154197,0.03006780849686012,0.019999190913774154,24,4,0.5144281370934042,0.9468944568649806,0.3294369457766206,28.79990079853653,5.038143744898282,0.4754272153806184,52.541118043484495,665.3410489894707,19.526195803530094,9.043077395342834,evaluator
5
+ highland_slope_capability,0.19999835074990116,0.03044576666813941,0.01996011148891477,24,4,0.5041814195273064,1.0339532362824977,0.22461747600265922,67.99541052473444,5.036891183253616,0.3822386329974194,53.4576626275889,420.4578331795287,19.514393389934455,9.091502146875467,evaluator
6
+ highland_slope_capability,0.19998894837078124,0.03825311376785551,0.019998911527639163,24,4,15.356978994368717,0.6898335099413058,0.3286533656886073,236.63576080190361,5.508166295790697,1.4611285794677664,55.68940981812972,429.98378692054825,18.939054446700876,32.72693115224429,evaluator
7
+ highland_slope_capability,0.1999980045662266,0.031419879565108576,0.01999905400639963,24,4,12.36039479388276,0.9910410313276625,0.5922682236436743,236.63576080190361,9.902513664984559,2.2624638446300454,55.68903883747236,630.7762588539654,18.79651831977162,29.810948175599876,evaluator
8
+ highland_slope_capability,0.19996676140645783,0.0303384365525574,0.019991725260512457,24,4,0.5343027210561805,1.0239956207421692,0.2576369066630436,7.0074241260819115,5.105693858486826,0.46156729304200167,50.66701298662335,500.383525641933,19.523301460621557,8.594111000729022,evaluator
9
+ highland_slope_capability,0.1999980045662266,0.030538715527596853,0.01999905400639963,24,4,10.740125190250282,0.9910410313276625,0.5934680611965933,118.82840807769378,5.367391643685564,2.2624638446300454,55.655143237460656,945.9138338194754,18.951477536268168,25.905012798255246,evaluator
10
+ highland_slope_capability,0.19998916381541815,0.03044701010866504,0.01999946602876711,24,4,9.077770582133347,0.6709527621176541,0.8203114198329903,98.7530892529269,7.570862699148428,1.5962605279388438,55.60368344035622,1149.2191955652554,19.04225606029462,23.96786726815359,evaluator
11
+ highland_slope_capability,0.19857796534203842,0.030336490967733706,0.019987001874135026,24,4,0.5349910769796431,1.049227061847042,0.1908922885738742,5.705487679018481,5.151443096881851,0.46156729304200167,48.90737423527232,345.6119512671572,19.493589822633645,8.344569065563995,evaluator
12
+ highland_slope_capability,0.19999347496273354,0.03033043899949897,0.0199984185258365,24,4,4.980024417813447,0.623631608542796,0.7614646892287258,130.99452458737494,9.841438785384224,0.7594368566183693,55.290707488010575,955.308606031738,19.294965679979192,18.248055911381513,evaluator
13
+ highland_slope_capability,0.19999835074990116,0.03044576666813941,0.01999922235492463,24,4,0.5041814195273064,1.0339532362824977,0.3004569963923524,67.37948345580715,5.0369012412591605,0.3822386329974194,53.92521720648769,592.9600848489374,19.527268438617007,9.343364275048636,evaluator
14
+ highland_slope_capability,0.19993732733121755,0.030207138164324628,0.019987778068108725,24,4,0.5102718385157966,1.0221756910863005,0.302769991301532,10.445476161261759,5.185173397816671,0.46174803876281845,51.51909029689274,596.6423096064752,19.521615876686496,8.750083551891723,evaluator
15
+ highland_slope_capability,0.19996649876855524,0.030340425107676827,0.019503901234723633,24,4,0.5032973318270852,0.9447639155933812,0.2249674392538346,11.586887742807587,5.039055563382784,0.42069323538181075,50.044271582844274,429.3362027988452,19.358387410514634,8.455619432424527,evaluator
16
+ highland_slope_capability,0.19999835074990116,0.0304458708888056,0.01987247008493646,24,4,0.5041814195273064,1.0002117848272727,0.22461747600265922,14.285697236313268,5.036891183253616,0.3822386329974194,50.36390115979733,428.49102132151734,19.483455332288596,8.480558833243343,evaluator
17
+ highland_slope_capability,0.19996651173370084,0.03021808125803219,0.019998949931617296,24,4,0.5170130538330862,0.9468944568649806,0.29991927731405765,28.981142249400037,5.038143744898282,0.46051078696912895,52.25980019909972,598.1559640983224,19.526242660986977,8.950308334567735,evaluator
18
+ highland_slope_capability,0.1999291589241574,0.03000266064576263,0.01999807494502127,24,4,0.5170130538330862,1.0253577807898524,0.5087841584659651,28.981142249400037,5.038143744898282,0.9784760955537699,54.07347321808152,1062.7549604399037,19.520472930391573,9.92765517119426,evaluator
19
+ highland_slope_capability,0.1999939206924001,0.030493804802949578,0.019963921423231987,24,4,0.6358529057733895,0.668307388853824,0.6263552904405267,30.150383184154038,5.736997111214297,0.881425768432651,54.17150521235519,1220.2894440556825,19.507012230811625,10.530932966531829,evaluator
20
+ highland_slope_capability,0.19997527218483024,0.03030670638658543,0.0199996928919978,24,4,0.5871179443968604,0.8595883333536966,0.7816897306387341,128.37097130958628,8.167194461947945,2.175455036975377,54.665974063280686,1191.123169533209,19.47348893238036,12.964337575988248,evaluator
21
+ highland_slope_capability,0.199928310537739,0.030504621232126005,0.01999637573881543,24,4,3.5277885049333144,0.6926966270706141,0.5123919074047064,184.37673627662755,9.714433321415187,0.8748561329973845,55.05377972608475,631.8990272117136,19.3788747002025,16.095446112151297,evaluator
22
+ highland_slope_capability,0.19999835074990116,0.03044576666813941,0.01996011148891477,24,4,0.5041814195273064,0.9879669107502445,0.23330198548584838,67.99541052473444,5.154176815224884,0.3822386329974194,53.58614590274415,433.30357235332366,19.514372772015683,9.129026962536603,evaluator
23
+ highland_slope_capability,0.19996651173370084,0.03021808125803219,0.019996459388944107,24,4,0.5170130538330862,0.9781113097640339,0.29991927731405765,25.051634732743803,5.038143744898282,0.4617300956398033,51.92774710815905,599.1101168097003,19.5253739411876,8.906358322514944,evaluator
24
+ highland_slope_capability,0.19999800471187312,0.036957077491068316,0.0199977099302605,24,4,14.971442267880622,1.0329313216405829,0.3655008466601977,242.77327514869162,10.377888979922377,2.362663500472295,55.7021067261856,328.10277912517216,18.876365191575548,33.13016599547098,evaluator
25
+ highland_slope_capability,0.19998887972505716,0.03005930583668712,0.01999871079513833,24,4,5.771287224310623,1.0328274643887883,0.7036599749071026,129.7733760739555,8.758444076052633,1.476551439030848,55.38941132728368,936.172087943247,19.238651794636237,19.41226687835707,evaluator
26
+ highland_slope_capability,0.19998353702611302,0.030308149877947544,0.019997562963086364,24,4,1.6053186315392285,0.8071377877336818,0.7815868758436406,128.37097130958628,5.158058190856068,2.0873952633707273,54.836640176474155,1557.8959047555377,19.441976688723503,14.097003173172332,evaluator
27
+ highland_slope_capability,0.19999848849450302,0.030191849818629372,0.019503901234723633,24,4,0.5041814195273064,1.0339532362824977,0.22461747600265922,8.814768033990617,5.047349979907862,0.38264783264486746,49.719661766773115,428.82775138027114,19.358495047584135,8.394142773204312,evaluator
28
+ highland_slope_capability,0.19999745834160243,0.03006780849686012,0.01999670041992606,24,4,0.51348118320767,0.9468944568649806,0.22176515741688801,28.893266252190507,5.038143744898282,0.4766465240512927,51.11324921421623,419.5173565807165,19.525291713931367,8.677273459681214,evaluator
29
+ highland_slope_capability,0.19998353702611302,0.030308345502431264,0.01998770551512448,24,4,1.6062777934791779,0.8071377877336818,0.24596890590024587,128.37097130958628,5.158058190856068,0.5082680005961928,54.38297670618054,442.86436041020113,19.50239511500091,11.416388219948868,evaluator
30
+ highland_slope_capability,0.1999781396463912,0.030358115056512508,0.019999109660634652,24,4,8.176696793366695,0.9904428645363171,0.5959882860653999,130.2591787940024,5.379269833954202,2.2891797131848306,55.549544336112504,997.8699869828811,19.105658049305937,22.55765407229187,evaluator
31
+ highland_slope_capability,0.19999800471187312,0.030228340098058765,0.0199977099302605,24,4,10.516107483090398,1.0329313216405829,0.3655008466601977,242.77327514869162,10.377888979922377,2.362663500472295,55.66734274278879,351.71320493572864,18.902584723177814,26.6042233874943,evaluator
32
+ highland_slope_capability,0.19994192485228526,0.030336490967733706,0.019987001874135026,24,4,0.5349910769796431,1.0780475789850192,0.1908922885738742,5.705487679018481,5.070224403084438,0.46156729304200167,49.36312167198297,349.31667051467883,19.519961249568663,8.350364106724653,evaluator
33
+ highland_slope_capability,0.19856307657550615,0.030336490967733706,0.019997472965599603,24,4,0.5354748516662735,1.049227061847042,0.1908922885738742,5.68996773928722,5.151443096881851,0.46156729304200167,48.911641890313284,345.61134810073025,19.496841424645645,8.345151896998253,evaluator
34
+ highland_slope_capability,0.19999543959069935,0.030340726161044117,0.0199984185258365,24,4,0.533949883608984,0.6237522676033832,0.3211604273875452,10.791734151218982,5.122928281843378,0.4558279557766652,51.75119968606228,642.6096162878026,19.526840332499923,8.85123943377463,evaluator
35
+ highland_slope_capability,0.1999981476353354,0.030540996999403512,0.01999989758981069,24,4,12.89242534048866,1.0373736648140948,0.6041050134894371,102.44540365139258,5.059494632245982,2.245347548902042,55.685478191774,952.4097347741846,18.813652349705723,28.654176830259534,evaluator
36
+ highland_slope_capability,0.1999939206924001,0.030326044348926662,0.01999904997927752,24,4,2.803346545165649,0.6786221979522151,0.5908315689347444,135.36130308306215,7.582774222162994,1.6265654819804531,54.969096937592276,897.1579430910749,19.412611301713174,15.073088708702349,evaluator
37
+ highland_slope_capability,0.1999978400239634,0.038571337760413285,0.01999807835169149,24,4,16.11930059359123,1.0238416435753446,0.3207361417040406,242.77327514869162,6.088445310801983,2.5411140296122454,55.70709218523391,385.61471002122244,18.881151799565583,34.45627651523765,evaluator
38
+ highland_slope_capability,0.19996651173370084,0.03021808125803219,0.019959175892984946,24,4,0.5170130538330862,0.9468944568649806,0.22407975692449164,29.597070904134604,5.038133686892737,0.46051078696912895,51.118516819766725,424.7572410811491,19.512436389260017,8.698438369437387,evaluator
39
+ highland_slope_capability,0.1999869200252456,0.030332454319610888,0.019987001874135026,24,4,0.5349917718898918,1.0572011117715903,0.28752431434374337,311.7655352736915,6.4377447888494395,0.461232268609313,54.534924362605906,454.0034647377526,19.486415813752977,12.242787549857905,evaluator
40
+ highland_slope_capability,0.19997916056074375,0.03003972252071095,0.01999870664668745,24,4,5.767170306923489,0.8562849486849129,0.8078213001684713,138.5839005427178,7.659564102610683,2.7454229866017448,55.45415960633406,1171.9593414606932,19.19080934696055,20.475409312533728,evaluator
41
+ highland_slope_capability,0.1999980045662266,0.03054852252415752,0.019999295036762593,24,4,12.321995380153112,1.0312710673667833,0.6041050134894371,67.6904849735462,5.846595886623601,2.2247493887586836,55.67804865692019,902.818908962967,18.870947038311783,27.52666113628217,evaluator
42
+ highland_slope_capability,0.19998443712294817,0.03007674973345048,0.019995969361615892,24,4,3.5277885049333144,0.6926966270706141,0.5123919074047064,184.37673627662755,9.572160226445103,1.6199958466432522,55.13328882784802,636.00268253602,19.357659790399588,16.462372733033614,evaluator
43
+ highland_slope_capability,0.19998208145704682,0.030046789427068916,0.0199975480624849,24,4,0.5052931892563582,1.0053979007364457,0.19108112434974953,11.586887742807587,5.384414665979895,0.4621760686413018,49.57696317742857,334.23974845829474,19.524039781043754,8.379618419414982,evaluator
44
+ highland_slope_capability,0.19999834354304713,0.03044576666813941,0.019998994512111404,24,4,0.5041814195273064,1.0094228373816636,0.22540900480359904,240.85983073782552,5.036891183253616,0.4486276825492864,54.32523028163832,406.34581219739584,19.51291253023718,11.091449395391257,evaluator
45
+ highland_slope_capability,0.19997916056074375,0.03000742723386108,0.01999870664668745,24,4,5.767170306923489,0.8562849486849129,0.8078213001684713,138.5839005427178,7.659564102610683,4.671706812630349,55.51174178343114,1156.6338936287964,19.14182543021922,21.521701460536985,evaluator
46
+ highland_slope_capability,0.19999905408124183,0.030324789394730385,0.019998898692395094,24,4,6.104304315336263,0.9853129435691355,0.4613875789647047,255.81077317855085,8.655632413641351,2.362663500472295,55.47996471673478,573.0784561511423,19.17827922157098,20.964183704263785,evaluator
47
+ highland_slope_capability,0.1999655383938775,0.030218546909578682,0.0199984185258365,24,4,0.6264642390070607,0.9579454593986867,0.7614646892287258,131.01131049474748,9.841438785384224,0.7594368566183693,54.55834279556259,1019.9112385289977,19.487096072340748,12.315989409784025,evaluator
48
+ highland_slope_capability,0.19998353702611302,0.030308149877947544,0.01999754734592868,24,4,3.524729087508154,0.6879446531470726,0.7815868758436406,128.37097130958628,9.332512492728128,2.0873952633707273,55.177037069107755,1035.0931509967347,19.343336164796618,16.992966458450915,evaluator
49
+ highland_slope_capability,0.19999381837386723,0.0303111307044308,0.019999059183707233,24,4,2.465652916778426,1.0328274643887883,0.7036599749071026,129.75692862237716,7.55605068428146,1.4825867551879464,54.94214987169634,1093.2150622670245,19.4194944544425,14.852627959894514,evaluator
50
+ highland_slope_capability,0.1999980045662266,0.03140911008483446,0.019998575166492994,24,4,12.36039479388276,1.0258612695151046,0.5771143345702514,235.27356285379977,5.887982194369248,2.349280438557093,55.68803001665316,832.3426245504427,18.81044920434116,29.517302749642887,evaluator
51
+ highland_slope_capability,0.1999939206924001,0.030326044348926662,0.01999904997927752,24,4,4.415257370390839,0.6719854017060696,0.5908315689347444,133.66128840663598,7.477177731452813,1.8673372991981627,55.21771177379648,878.4824434094327,19.32996270717057,17.371170923558136,evaluator
reports/pareto_fronts/front_highland_slope_capability.metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scenario_name": "highland_slope_capability",
3
+ "backend": "evaluator",
4
+ "dataset_version": "v9",
5
+ "objectives": [
6
+ {
7
+ "target": "range_km",
8
+ "direction": "max"
9
+ },
10
+ {
11
+ "target": "total_mass_kg",
12
+ "direction": "min"
13
+ }
14
+ ],
15
+ "constraints": [
16
+ {
17
+ "target": "range_km",
18
+ "sense": "min",
19
+ "value": 0.1
20
+ },
21
+ {
22
+ "target": "slope_capability_deg",
23
+ "sense": "min",
24
+ "value": 15.0
25
+ }
26
+ ],
27
+ "traverse_distance_m": 120000.0,
28
+ "population_size": 50,
29
+ "generations": 60,
30
+ "seed": 14,
31
+ "panel_tilt_deg": 10.0,
32
+ "panel_azimuth_deg": 180.0,
33
+ "elapsed_s": 32.234466542024165,
34
+ "pareto_size": 50,
35
+ "front_csv": "reports/pareto_fronts/front_highland_slope_capability.csv"
36
+ }
reports/pareto_fronts/front_polar_prospecting.csv ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ scenario_name,wheel_radius_m,wheel_width_m,grouser_height_m,grouser_count,n_wheels,chassis_mass_kg,wheelbase_m,solar_area_m2,battery_capacity_wh,avionics_power_w,peak_wheel_torque_nm,range_km,energy_margin_raw_pct,slope_capability_deg,total_mass_kg,backend_used
2
+ polar_prospecting,0.19919232794559236,0.030016591051623878,0.019990618981594786,24,4,0.5245647253151042,0.5777976990573055,0.1923841802694848,71.66244047597367,5.098419236121115,0.730534243248785,30.0,54.670057128503935,33.01131914634105,12.209435410538148,evaluator
3
+ polar_prospecting,0.19971354003635836,0.030031478124273002,0.019992538887191644,24,4,14.067866682032632,0.6300021197272216,0.9858373638390915,188.23068988993924,37.37118138147521,19.2564842070138,30.0,127.49607323889296,34.75919697608552,46.94560084957061,evaluator
4
+ polar_prospecting,0.19970771015410305,0.03003654393706933,0.019992828042426577,24,4,8.871644856597744,0.6501104625959102,0.1812961201616805,185.82452300778394,11.262816109080399,2.334193635017587,3.749273378613546,0.8211066678378433,34.30083629900911,26.12299903996457,evaluator
5
+ polar_prospecting,0.19969889992196832,0.030585410546369755,0.019990565424487853,24,4,1.467746055786895,0.8698011431246389,0.18008603492148076,181.7623584365482,10.77418747538477,1.348962419524673,26.95211425437558,1.8943793797212167,33.450500163904536,15.465673704878702,evaluator
6
+ polar_prospecting,0.19919169278313936,0.030009602880722554,0.01999051351690844,24,4,1.323644885693695,0.6501104625959102,0.1801079033477888,269.65623896668046,11.082568950877992,2.3153604267328483,8.97773251654592,0.9680434013468255,33.62195786692515,16.769636756688715,evaluator
7
+ polar_prospecting,0.19969828769849654,0.030037092988265048,0.01999262919559681,24,4,14.842213854303811,0.8561204451358481,0.18008072645499432,185.24884923401976,10.323116416374006,18.729078889500872,18.545517257596693,3.258512703749742,34.72790853527953,43.09716929107084,evaluator
8
+ polar_prospecting,0.199216012911625,0.030112790938319266,0.019990720056646854,24,4,10.293030331406156,0.597935097826652,0.18009632045605373,188.90857525241023,10.32306628540407,1.745380210924138,29.381683369553954,3.256477161988716,34.3602313524069,27.704814029526375,evaluator
9
+ polar_prospecting,0.19919231892943542,0.030016591051623878,0.019990618971466582,24,4,0.7242088338470322,0.6359612441577706,0.1923841802694848,70.17452071048244,5.000299837486238,1.1714028302428345,30.0,55.680999663946054,33.09149561498588,12.697535574415827,evaluator
10
+ polar_prospecting,0.1991820931899969,0.030033972455268967,0.01999051351690844,24,4,12.557319998782047,0.663867761633789,0.18062311767339015,90.05615176350128,10.797527512413792,19.09620536992501,10.780298872064122,2.033631423136979,34.66841573436856,39.13785783678,evaluator
11
+ polar_prospecting,0.1998248974535649,0.030023031502208163,0.019990509253292325,24,4,3.5601440102148887,0.6371607077103119,0.18010912330198486,271.17442345946097,10.774315968286377,2.309743585301252,21.79841540839599,1.8624176969569861,33.91062218820201,19.812376805377593,evaluator
12
+ polar_prospecting,0.19923370061287007,0.03057358193216254,0.019990565298911456,24,4,0.7838632539813191,0.6889349058321569,0.18008603492148076,175.1666085842235,10.804775509697677,2.311094294086544,26.00734728238401,1.802470688107471,33.38460250460101,14.981334473374908,evaluator
13
+ polar_prospecting,0.19984311950575043,0.03012545640550927,0.019990735705662836,24,4,0.592158022459309,0.8559118089620925,0.18104986027579853,151.15446825261247,11.089465215561395,2.161871862298133,15.791336692551665,1.2609716679418796,33.338208422684936,14.3638680859347,evaluator
14
+ polar_prospecting,0.19970620375382786,0.030117433703774926,0.019995840331820253,24,4,0.693563850275746,0.6300017310998679,1.0395865293026232,73.52749899971654,37.57761993550009,2.3061321100647265,30.0,142.7507705146114,33.78765157366135,18.42151926266851,evaluator
15
+ polar_prospecting,0.19971342159322497,0.03003141446717941,0.019990818687721637,24,4,14.842213854303811,0.8553155443174655,0.18101550133420746,91.28333066825262,10.832640508375349,3.023967896209503,12.829624156275093,2.0512468281424163,34.56276809163969,33.520538060100954,evaluator
16
+ polar_prospecting,0.19919200218401562,0.030009602880722554,0.01999051351690844,24,4,4.986770174530046,0.6501444516575399,0.18108523775983357,269.65623896668046,11.082568950877992,2.3100360449115844,11.261430600791165,1.2650248519609526,34.04539401554572,21.754846914316456,evaluator
17
+ polar_prospecting,0.1996826595825958,0.030024133112202346,0.019996887081204465,24,4,14.842213854303811,0.6383236758679929,0.18008072645499432,151.82782253558352,10.299709196374497,4.3458006749572204,23.36287591838696,3.3595732573415944,34.598138176949355,34.88654713667193,evaluator
18
+ polar_prospecting,0.199216012911625,0.030112790938319266,0.019990720056646854,24,4,13.018372832705357,0.6492007757015484,0.18108523775983357,188.90857525241023,10.322239522410483,1.9852875870850326,28.596457848710774,3.5745064640916895,34.49765676066792,31.5473545319996,evaluator
19
+ polar_prospecting,0.19970964309217526,0.030041672471052834,0.019990731183361816,24,4,0.5431872460855484,0.6382007508613962,0.19173297576958848,152.4453499246995,5.124626233367495,0.8608044717195027,30.0,53.43064668482829,33.17954961526147,13.227306664991975,evaluator
20
+ polar_prospecting,0.199216012911625,0.03002244094012891,0.019990720056646854,24,4,5.168385777641856,0.6010437728322404,0.18009483915048427,178.49000619271686,10.32306628540407,1.745380210924138,30.0,3.2701551509087876,33.96352914786638,20.606847308695514,evaluator
21
+ polar_prospecting,0.19971342159322497,0.03003141446717941,0.01999262919559681,24,4,14.842213854303811,0.8561204451358481,0.18101550133420746,91.28333066825262,10.33883720152938,18.774779226573507,19.976903230047636,3.5943786056836196,34.71698418603195,42.06045966264026,evaluator
22
+ polar_prospecting,0.19919169278313936,0.03032334080128116,0.019990707366512185,24,4,12.641080917085677,0.6380666739211333,0.1806389722138684,33.110743075016565,10.805423924484087,2.2647429404066326,13.851322599369476,2.047831161986665,34.42150424405044,29.464902600055886,evaluator
23
+ polar_prospecting,0.19919169278313936,0.03032334080128116,0.019992517874387356,24,4,12.641080917085677,0.6380666739211333,0.1806389722138684,33.110743075016565,10.31162061763812,16.359085657751898,21.777895592908685,3.628389048333979,34.6286370999423,37.10317558000583,evaluator
24
+ polar_prospecting,0.19971354003635836,0.030316677784041127,0.019990722837143537,24,4,13.882434463441065,0.6300021197272216,0.9953029992348574,188.59397268192757,11.163937878342878,19.172420848403647,30.0,425.01296648805777,34.741220039228416,44.920255728911776,evaluator
25
+ polar_prospecting,0.199207320872731,0.030009602880722554,0.01999567448098733,24,4,0.9922545958377467,0.6382106524946465,0.1810734315152468,269.9661803758157,11.082568950877992,1.2725604708653215,15.55656604475901,1.2614460626376405,33.51198034388965,15.75810101675818,evaluator
26
+ polar_prospecting,0.19969938045502555,0.030041401601036216,0.019993218817141938,24,4,2.3322065408719563,0.6382007508613962,0.18008975112694933,152.4453499246995,11.056264168750458,4.133988091610977,9.288796294475821,1.0576101305107886,33.733029618267274,17.807391568879922,evaluator
27
+ polar_prospecting,0.19969828769849654,0.030040208928755033,0.019992635705520803,24,4,14.896967110082741,0.6642067351034092,0.18101550133420746,187.84283210132344,10.33309655444955,2.2647395988049093,25.82207237490948,3.5195129270500787,34.58111838740275,34.24329761133178,evaluator
28
+ polar_prospecting,0.19969828769849654,0.030037799998642003,0.01999074257851328,24,4,1.0433078212993552,0.6382007508613962,0.18008072645499432,153.19600555880967,10.81435781743055,4.147223640360962,23.47662526457484,1.781617828652949,33.54915887766028,16.05237595404288,evaluator
29
+ polar_prospecting,0.19970580017381542,0.030021342390539496,0.019990501546816763,24,4,4.300580763559699,0.6382010609369723,0.1818221561818048,193.82500881997433,11.253477694915782,1.1425957344529216,8.120573380536879,1.010136110821265,33.8707513876737,19.344854164646932,evaluator
30
+ polar_prospecting,0.19921056267964507,0.03001664974850319,0.01999051351690844,24,4,0.6832479485833767,0.6466378610037072,0.18130612734587512,78.92393369303892,11.262816109080399,1.9050523067827831,7.057469374422291,0.8356309158425392,33.21875923306655,13.528920123445811,evaluator
31
+ polar_prospecting,0.1997423726295979,0.030342246478011245,0.01999070336654011,24,4,6.7151539237418945,0.6380656151952937,0.18103019426311123,187.77771958611524,11.079291894569536,2.3191458109663836,10.129176609064636,1.2772973847641533,34.13234392262149,23.210270658691815,evaluator
32
+ polar_prospecting,0.19919169550492882,0.03011109689677441,0.01999051932866138,24,4,0.7397820859310764,0.6357297093144441,0.18102005989133635,91.41388194268204,11.082568950877992,2.26466645401143,16.076015171765846,1.287534899234842,33.27121966208532,13.936320175736316,evaluator
33
+ polar_prospecting,0.19969828769849654,0.03003805865823989,0.019990876019607102,24,4,9.997772513467199,0.6382562573361807,0.1810357740408716,149.9290022645877,11.089970197872585,4.984734609564864,7.745589060870799,1.2550419513269724,34.40890292471523,28.67846000686004,evaluator
34
+ polar_prospecting,0.19969828769849654,0.030347255481982666,0.019990735705662836,24,4,6.459805775371338,0.6382295061522196,0.1810357740408716,153.19600555880967,11.071835214684608,4.984734609564864,9.990315959128605,1.309100011294337,34.17330602922869,23.921060216786827,evaluator
35
+ polar_prospecting,0.19969828769849654,0.030037799998642003,0.01999074257851328,24,4,1.0433078212993552,0.6382007508613962,0.18008072645499432,153.19600555880967,10.805510176833634,4.147223640360962,23.96069743167954,1.8084006170285987,33.5490909050214,16.051773960576668,evaluator
36
+ polar_prospecting,0.19918615405504972,0.0303180114820083,0.019990698772475005,24,4,12.522247281187582,0.8355313917708629,0.18061081198933926,90.0551555342406,11.092555044625428,4.523901747273542,5.623519034250771,1.1259121359817825,34.48045538942091,31.197677616876717,evaluator
37
+ polar_prospecting,0.19970771015410305,0.030025478664323025,0.019992828042426577,24,4,8.871644856597744,0.6566882229209831,0.18062227095212882,88.28824432179823,10.784840240168444,2.334193635017587,17.495393152950268,2.0763689780380408,34.24473678525024,24.981363047038222,evaluator
38
+ polar_prospecting,0.1991788491685881,0.0303180114820083,0.01999051351690844,24,4,12.522247281187582,0.8355313917708629,0.18108523775983357,90.05615176350128,11.08830959795782,17.13317369096819,5.788401626072359,1.2909697320170777,34.6453658320887,38.0624283974201,evaluator
39
+ polar_prospecting,0.19919169278313936,0.03032334080128116,0.01999051351690844,24,4,12.641080917085677,0.6382010146305537,0.1806389722138684,91.53038088584388,10.812678060426776,2.2647429404066326,13.74609746785503,1.9920109180790118,34.44494036425441,30.127870717553176,evaluator
40
+ polar_prospecting,0.19920025879584385,0.030021707592354115,0.019993249643178113,24,4,4.963958690681447,0.8671175701590538,0.18173585996256186,90.59992255543604,10.324772606903702,1.4246735109996465,30.0,4.257672997946969,33.84930198362448,19.162995013296914,evaluator
41
+ polar_prospecting,0.19971724913733735,0.030012344873700036,0.019990520100549043,24,4,5.392629099188158,0.6382069263600166,0.18182501744003532,255.53492383987805,11.073059738076124,2.267315612662372,14.62964042805728,1.521462249664447,34.07787281949579,22.130121490094968,evaluator
42
+ polar_prospecting,0.19968816914944046,0.030013949457193278,0.019990618093049712,24,4,12.518413388701799,0.6431167243375115,0.18103893463203158,191.97501577503158,11.108313724751072,19.098021451833734,5.15602251283128,1.1926895837922364,34.69334279274973,40.26700720284735,evaluator
43
+ polar_prospecting,0.1991804527304546,0.03032334080128116,0.01999064234698691,24,4,8.532289880472714,0.6350248940340801,0.1806389722138684,91.53038088584388,10.798895952291817,2.2647429404066326,17.44558260605576,2.0344997648971805,34.2008907227876,24.535599641685433,evaluator
44
+ polar_prospecting,0.19918068199339525,0.030112006932992777,0.019992569131667207,24,4,7.258213434276835,0.6359214096122451,0.18102005989133635,91.53038088584388,11.076411718074075,2.2646950079213135,9.979696859160402,1.3062571351491186,34.109925404538444,22.80753449135939,evaluator
45
+ polar_prospecting,0.19919169278313936,0.030007924104700524,0.01999051351690844,24,4,1.781211073585153,0.6488340882918129,0.18102039842257645,269.1478548579722,11.082198355435638,2.263194985285352,13.82217855365255,1.2464336539181715,33.68281301153504,17.361096981928323,evaluator
46
+ polar_prospecting,0.19983652206335265,0.03009226090981498,0.019990497338213217,24,4,9.295856270217218,0.8732409135576399,0.18014318686335734,188.04436679526464,10.383908728976367,19.731562358024053,20.946847476129147,3.0913682016552033,34.623465202564915,36.13629289865456,evaluator
47
+ polar_prospecting,0.19971354003635836,0.030321622012370587,0.019995742757704105,24,4,13.389854957847453,0.6300021197272216,0.8576032079254274,205.11483895644412,36.98401333960562,19.2564842070138,30.0,99.74147067803864,34.750031056463136,45.77184803961729,evaluator
48
+ polar_prospecting,0.19919194118674907,0.03011262534427953,0.019991244508670545,24,4,13.945246008858074,0.6440080158382542,0.1800898641456323,178.00692364998406,11.08370047934501,2.2676882740527025,4.276994827206549,0.9723116713745091,34.536104025973025,32.88696015872462,evaluator
49
+ polar_prospecting,0.19970446607791795,0.030309854326076822,0.019992828042426577,24,4,8.836572139003389,0.828351853058061,0.18108439103857224,88.28824432179823,11.075622325712475,1.789899491787514,9.446858964729273,1.3312973122931218,34.21761609567688,24.678245283639615,evaluator
50
+ polar_prospecting,0.19969828769849654,0.030040208928755033,0.019992635705520803,24,4,9.426662825400259,0.6642067351034092,0.18101550133420746,187.91901578532259,10.37185588643266,2.2647395988049093,30.0,3.40063739586232,34.33173716873356,26.80280864866055,evaluator
51
+ polar_prospecting,0.1998248974535649,0.030023031502208163,0.019990620313485127,24,4,4.708175253080844,0.6382010609369723,0.17990633382425247,177.56171898074092,10.774315968286377,2.309743585301252,19.594354785924622,1.8376627457466181,33.94987627053331,20.31236211426689,evaluator
reports/pareto_fronts/front_polar_prospecting.metadata.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "scenario_name": "polar_prospecting",
3
+ "backend": "evaluator",
4
+ "dataset_version": "v9",
5
+ "objectives": [
6
+ {
7
+ "target": "range_km",
8
+ "direction": "max"
9
+ },
10
+ {
11
+ "target": "total_mass_kg",
12
+ "direction": "min"
13
+ },
14
+ {
15
+ "target": "slope_capability_deg",
16
+ "direction": "max"
17
+ }
18
+ ],
19
+ "constraints": [
20
+ {
21
+ "target": "range_km",
22
+ "sense": "min",
23
+ "value": 0.1
24
+ }
25
+ ],
26
+ "traverse_distance_m": 30000.0,
27
+ "population_size": 50,
28
+ "generations": 60,
29
+ "seed": 15,
30
+ "panel_tilt_deg": 80.0,
31
+ "panel_azimuth_deg": 0.0,
32
+ "elapsed_s": 41.1206740840571,
33
+ "pareto_size": 50,
34
+ "front_csv": "reports/pareto_fronts/front_polar_prospecting.csv"
35
+ }
reports/pareto_fronts/manifest.json ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "scenario_name": "crater_rim_survey",
4
+ "backend": "evaluator",
5
+ "dataset_version": "v9",
6
+ "objectives": [
7
+ {
8
+ "target": "range_km",
9
+ "direction": "max"
10
+ },
11
+ {
12
+ "target": "total_mass_kg",
13
+ "direction": "min"
14
+ },
15
+ {
16
+ "target": "slope_capability_deg",
17
+ "direction": "max"
18
+ }
19
+ ],
20
+ "constraints": [
21
+ {
22
+ "target": "range_km",
23
+ "sense": "min",
24
+ "value": 0.1
25
+ }
26
+ ],
27
+ "traverse_distance_m": 25000.0,
28
+ "population_size": 50,
29
+ "generations": 60,
30
+ "seed": 12,
31
+ "panel_tilt_deg": 0.0,
32
+ "panel_azimuth_deg": 180.0,
33
+ "elapsed_s": 32.589310958981514,
34
+ "pareto_size": 50,
35
+ "front_csv": "reports/pareto_fronts/front_crater_rim_survey.csv"
36
+ },
37
+ {
38
+ "scenario_name": "equatorial_mare_traverse",
39
+ "backend": "evaluator",
40
+ "dataset_version": "v9",
41
+ "objectives": [
42
+ {
43
+ "target": "range_km",
44
+ "direction": "max"
45
+ },
46
+ {
47
+ "target": "total_mass_kg",
48
+ "direction": "min"
49
+ },
50
+ {
51
+ "target": "slope_capability_deg",
52
+ "direction": "max"
53
+ }
54
+ ],
55
+ "constraints": [
56
+ {
57
+ "target": "range_km",
58
+ "sense": "min",
59
+ "value": 0.1
60
+ }
61
+ ],
62
+ "traverse_distance_m": 80000.0,
63
+ "population_size": 50,
64
+ "generations": 60,
65
+ "seed": 13,
66
+ "panel_tilt_deg": 20.2,
67
+ "panel_azimuth_deg": 180.0,
68
+ "elapsed_s": 36.020679499953985,
69
+ "pareto_size": 50,
70
+ "front_csv": "reports/pareto_fronts/front_equatorial_mare_traverse.csv"
71
+ },
72
+ {
73
+ "scenario_name": "highland_slope_capability",
74
+ "backend": "evaluator",
75
+ "dataset_version": "v9",
76
+ "objectives": [
77
+ {
78
+ "target": "range_km",
79
+ "direction": "max"
80
+ },
81
+ {
82
+ "target": "total_mass_kg",
83
+ "direction": "min"
84
+ }
85
+ ],
86
+ "constraints": [
87
+ {
88
+ "target": "range_km",
89
+ "sense": "min",
90
+ "value": 0.1
91
+ },
92
+ {
93
+ "target": "slope_capability_deg",
94
+ "sense": "min",
95
+ "value": 15.0
96
+ }
97
+ ],
98
+ "traverse_distance_m": 120000.0,
99
+ "population_size": 50,
100
+ "generations": 60,
101
+ "seed": 14,
102
+ "panel_tilt_deg": 10.0,
103
+ "panel_azimuth_deg": 180.0,
104
+ "elapsed_s": 32.234466542024165,
105
+ "pareto_size": 50,
106
+ "front_csv": "reports/pareto_fronts/front_highland_slope_capability.csv"
107
+ },
108
+ {
109
+ "scenario_name": "polar_prospecting",
110
+ "backend": "evaluator",
111
+ "dataset_version": "v9",
112
+ "objectives": [
113
+ {
114
+ "target": "range_km",
115
+ "direction": "max"
116
+ },
117
+ {
118
+ "target": "total_mass_kg",
119
+ "direction": "min"
120
+ },
121
+ {
122
+ "target": "slope_capability_deg",
123
+ "direction": "max"
124
+ }
125
+ ],
126
+ "constraints": [
127
+ {
128
+ "target": "range_km",
129
+ "sense": "min",
130
+ "value": 0.1
131
+ }
132
+ ],
133
+ "traverse_distance_m": 30000.0,
134
+ "population_size": 50,
135
+ "generations": 60,
136
+ "seed": 15,
137
+ "panel_tilt_deg": 80.0,
138
+ "panel_azimuth_deg": 0.0,
139
+ "elapsed_s": 41.1206740840571,
140
+ "pareto_size": 50,
141
+ "front_csv": "reports/pareto_fronts/front_polar_prospecting.csv"
142
+ }
143
+ ]
roverdevkit/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RoverDevKit — ML-accelerated co-design of lunar micro-rover mobility and power.
2
+
3
+ Top-level package. Most public API lives in submodules:
4
+
5
+ - :mod:`roverdevkit.schema` — shared dataclasses for design vectors, scenarios,
6
+ and mission metrics.
7
+ - :mod:`roverdevkit.terramechanics` — Bekker-Wong analytical terramechanics.
8
+ - :mod:`roverdevkit.power` — solar, battery, and thermal survival sub-models.
9
+ - :mod:`roverdevkit.mass` — parametric mass-estimating relationships.
10
+ - :mod:`roverdevkit.mission` — top-level mission evaluator, scenarios,
11
+ time-stepped traverse simulator.
12
+ - :mod:`roverdevkit.surrogate` — training, models, feature engineering, UQ.
13
+ - :mod:`roverdevkit.tradespace` — sweeps, NSGA-II optimization, SHAP rules.
14
+ - :mod:`roverdevkit.validation` — rover rediscovery, experimental comparison,
15
+ error budget.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from roverdevkit.architecture import MobilityArchitecture
21
+
22
+ __version__ = "0.1.0"
23
+
24
+ __all__ = ["MobilityArchitecture", "__version__"]
roverdevkit/architecture.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Mobility-architecture proxy for obstacle negotiation and suspension mass.
2
+
3
+ This is an *architecture-level* model, not a kinematic rocker-bogie simulation.
4
+ ``mobility_architecture`` selects between a four-wheel rigid/skid-steer proxy
5
+ and a six-wheel rocker-bogie proxy. Obstacle capability scales with wheel
6
+ radius through literature-motivated step-height factors; rocker-bogie carries
7
+ an explicit suspension mass penalty in the bottom-up mass model.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+ from typing import Literal
14
+
15
+ MobilityArchitecture = Literal["rigid_4wheel", "rocker_bogie_6wheel"]
16
+
17
+ OBSTACLE_CAPABILITY_FACTOR: dict[MobilityArchitecture, float] = {
18
+ "rigid_4wheel": 0.5,
19
+ "rocker_bogie_6wheel": 1.25,
20
+ }
21
+ """Max traversable obstacle height as a fraction of wheel radius R.
22
+
23
+ Conservative proxies for conceptual design: rigid four-wheel layouts are
24
+ limited to roughly half a wheel diameter; passive rocker-bogie suspension
25
+ can negotiate obstacles on the order of one wheel radius (MER/MSL class).
26
+ """
27
+
28
+
29
+ def wheel_count_for_architecture(architecture: MobilityArchitecture) -> int:
30
+ """Return the drive-wheel count implied by ``architecture``."""
31
+ return 6 if architecture == "rocker_bogie_6wheel" else 4
32
+
33
+
34
+ def architecture_for_wheel_count(n_wheels: int) -> MobilityArchitecture:
35
+ """Map legacy ``n_wheels`` values to the closest architecture label."""
36
+ if n_wheels == 6:
37
+ return "rocker_bogie_6wheel"
38
+ if n_wheels == 4:
39
+ return "rigid_4wheel"
40
+ raise ValueError(f"n_wheels must be 4 or 6 (got {n_wheels}).")
41
+
42
+
43
+ def obstacle_capability_m(
44
+ architecture: MobilityArchitecture,
45
+ wheel_radius_m: float,
46
+ ) -> float:
47
+ """Estimated max traversable obstacle height, m."""
48
+ if wheel_radius_m <= 0.0:
49
+ raise ValueError("wheel_radius_m must be positive.")
50
+ return OBSTACLE_CAPABILITY_FACTOR[architecture] * wheel_radius_m
51
+
52
+
53
+ def obstacle_margin_m(
54
+ capability_m: float,
55
+ required_obstacle_height_m: float,
56
+ ) -> float:
57
+ """Capability minus the scenario requirement (m)."""
58
+ return capability_m - required_obstacle_height_m
59
+
60
+
61
+ def obstacle_requirement_met(
62
+ capability_m: float,
63
+ required_obstacle_height_m: float,
64
+ ) -> bool:
65
+ return capability_m + 1e-12 >= required_obstacle_height_m
66
+
67
+
68
+ @dataclass(frozen=True)
69
+ class ArchitectureParams:
70
+ """Mass penalty coefficients for the rocker-bogie proxy."""
71
+
72
+ rocker_bogie_fixed_mass_kg: float = 0.5
73
+ rocker_bogie_chassis_fraction: float = 0.08
74
+
75
+
76
+ def architecture_suspension_mass_kg(
77
+ architecture: MobilityArchitecture,
78
+ chassis_mass_kg: float,
79
+ *,
80
+ params: ArchitectureParams | None = None,
81
+ ) -> float:
82
+ """Suspension / linkage mass charged to rocker-bogie architectures only."""
83
+ if architecture == "rigid_4wheel":
84
+ return 0.0
85
+ p = params or ArchitectureParams()
86
+ if chassis_mass_kg <= 0.0:
87
+ raise ValueError("chassis_mass_kg must be positive.")
88
+ return p.rocker_bogie_fixed_mass_kg + p.rocker_bogie_chassis_fraction * chassis_mass_kg
roverdevkit/drivetrain/__init__.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drivetrain modelling.
2
+
3
+ This package owns the motor + gearbox torque-speed envelope and the
4
+ helpers that derive cruise speed inside the mission evaluator. See
5
+ :mod:`roverdevkit.drivetrain.motor` for the public API.
6
+ """
7
+
8
+ from roverdevkit.drivetrain.motor import (
9
+ DEFAULT_DRIVETRAIN_EFFICIENCY,
10
+ OMEGA_NO_LOAD_HUB_RAD_S,
11
+ CruiseResult,
12
+ cruise_speed,
13
+ effective_duty_cycle,
14
+ energy_balance_v_cruise,
15
+ kinematic_envelope_v_max,
16
+ sizing_peak_torque_anchor_nm,
17
+ )
18
+
19
+ __all__ = [
20
+ "DEFAULT_DRIVETRAIN_EFFICIENCY",
21
+ "OMEGA_NO_LOAD_HUB_RAD_S",
22
+ "CruiseResult",
23
+ "cruise_speed",
24
+ "effective_duty_cycle",
25
+ "energy_balance_v_cruise",
26
+ "kinematic_envelope_v_max",
27
+ "sizing_peak_torque_anchor_nm",
28
+ ]
roverdevkit/drivetrain/motor.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drivetrain torque-speed envelope and cruise-speed derivation.
2
+
3
+ Cruise speed is derived inside the evaluator rather than supplied as a
4
+ design input; see ``data/analytical/SCHEMA.md`` for the current schema.
5
+ Drive duty cycle is a single per-scenario ``operational_duty_cycle``:
6
+ the only role of a separate designed duty cycle was to upper-bound
7
+ ``δ_eff``, which a user can equivalently express by lowering
8
+ ``operational_duty_cycle``.
9
+
10
+ The pre-schema-v7 design vector exposed ``nominal_speed_mps`` and
11
+ ``drive_duty_cycle`` as free design *inputs* and gated mobility on an
12
+ implicit, mass-derived torque ceiling inside the mass model. That made
13
+ ``range_km`` close to a tautology of two design knobs and let the
14
+ optimiser pick rover speeds the drivetrain could not actually sustain
15
+ on the scenario soil and slope. This module implements the v6 fix:
16
+
17
+ 1. **Slip-balance** is solved once by the traverse simulator (already
18
+ loop-invariant under the current scenario schema). It returns the
19
+ per-wheel hub torque demand ``T_req`` and equilibrium slip ``s_eq``
20
+ needed to develop the drawbar pull required to climb the scenario's
21
+ worst-case slope plus rolling resistance.
22
+ 2. **Stall gate** (binary): ``stalled = T_req > peak_wheel_torque_nm``
23
+ or the slip solver failed (no slip in the bracket achieves the
24
+ required DP). Replaces the old implicit "even at slip 0.95 we can't
25
+ develop DP" gate with an explicit, design-controlled torque ceiling.
26
+ 3. **Energy-balance steady-state cruise speed** ``v_eb``: the speed at
27
+ which avionics + ``δ_eff × P_mobility(v) ≤ P_solar_avg``. Solving
28
+ the equality gives a closed-form ``v_eb`` (no iteration). The
29
+ ``δ_eff`` cancels in the achievable-range product
30
+ ``v_eb × δ_eff × time``, so range in the energy-binding regime is
31
+ independent of duty cycle (only kinematic-bound regimes feel it).
32
+ 4. **Kinematic envelope** ``v_kin = ω_no_load × R × (1 - s_eq)``. A
33
+ hygiene cap reflecting the conservative drivetrain archetype
34
+ assumption: motor + gearbox combined deliver ``peak_wheel_torque_nm``
35
+ at any hub speed up to ``ω_no_load_hub`` (5 rad/s ≈ 48 rpm). For
36
+ ``R ∈ [0.05, 0.20] m`` and ``s ∈ [0, 0.3]`` this gate is an upper
37
+ bound rarely binding in the energy-binding regime where lunar
38
+ micro-rovers live; we expect to see it fire on < 1 % of LHS samples.
39
+ 5. **Cruise speed** ``v_cruise = 0`` if stalled, else ``min(v_eb, v_kin)``.
40
+
41
+ API surface (importable from :mod:`roverdevkit.drivetrain`):
42
+
43
+ - :data:`OMEGA_NO_LOAD_HUB_RAD_S` — module-level constant for (4).
44
+ - :data:`DEFAULT_DRIVETRAIN_EFFICIENCY` — combined motor + gearbox
45
+ efficiency. Mirrors :data:`roverdevkit.mission.traverse_sim.DEFAULT_MOTOR_EFFICIENCY`.
46
+ - :func:`effective_duty_cycle` — clamp ``δ_ops`` into ``[0, 1]``
47
+ (kept as a thin helper for symmetry with the v6 API; the previous
48
+ ``min(δ_des, δ_ops)`` semantics collapsed in v7 when
49
+ ``designed_duty_cycle`` was removed from the design vector).
50
+ - :func:`kinematic_envelope_v_max` — step (4) closed form.
51
+ - :func:`energy_balance_v_cruise` — step (3) closed form.
52
+ - :func:`cruise_speed` — composes (2)–(5) into a :class:`CruiseResult`.
53
+ - :func:`sizing_peak_torque_anchor_nm` — pre-v6 implicit torque ceiling,
54
+ retained as the LHS prior anchor for the v6 dataset rebuild (so
55
+ ``peak_wheel_torque_nm`` samples cluster around physically plausible
56
+ values for the rest of the design vector).
57
+ """
58
+
59
+ from __future__ import annotations
60
+
61
+ from dataclasses import dataclass
62
+
63
+ OMEGA_NO_LOAD_HUB_RAD_S: float = 5.0
64
+ """No-load hub angular speed of the constant-peak-torque drivetrain
65
+ archetype, in rad/s. Approximately 48 rpm at the wheel hub.
66
+
67
+ The micro-rover regime sits well inside this envelope: at ``R = 0.10 m``
68
+ and ``s = 0.10`` the kinematic cap is ``5 × 0.10 × 0.90 = 0.45 m/s``,
69
+ roughly an order of magnitude above any lunar-day average cruise. The
70
+ constant is exposed at module level (not promoted to a design variable)
71
+ because doing so would force the user to think in motor-internal terms
72
+ the rest of the design vector deliberately abstracts away. Revisit if
73
+ LHS sampling shows > 1 % of cells clamping at ``v_kin_max``."""
74
+
75
+ DEFAULT_DRIVETRAIN_EFFICIENCY: float = 0.8
76
+ """Combined motor + gearbox efficiency, dimensionless. Mirrors
77
+ :data:`roverdevkit.mission.traverse_sim.DEFAULT_MOTOR_EFFICIENCY` so
78
+ that the cruise-speed solve and the per-step mobility power use the
79
+ same number; keeping a separate copy here would risk silent drift.
80
+ Value calibrated against Maxon EC-i + GP series datasheets (BLDC +
81
+ planetary gearbox at nominal load)."""
82
+
83
+
84
+ def effective_duty_cycle(operational_duty_cycle: float) -> float:
85
+ """Return ``δ_eff = clamp(δ_ops, [0, 1])``.
86
+
87
+ Schema v7 collapsed the v6 ``min(δ_des, δ_ops)`` semantics into a
88
+ single per-scenario duty cycle: the design-side ``designed_duty_cycle``
89
+ field was removed from :class:`~roverdevkit.schema.DesignVector`
90
+ after it turned out to do no engineering work in the mass model.
91
+ This helper is retained as a thin wrapper so callers stay readable
92
+ and so the [0, 1] clamp lives in exactly one place.
93
+ """
94
+ if operational_duty_cycle < 0.0:
95
+ raise ValueError(
96
+ "operational_duty_cycle must be non-negative "
97
+ f"(got {operational_duty_cycle})."
98
+ )
99
+ return min(1.0, operational_duty_cycle)
100
+
101
+
102
+ def kinematic_envelope_v_max(
103
+ omega_no_load_hub_rad_s: float,
104
+ wheel_radius_m: float,
105
+ slip_eq: float,
106
+ ) -> float:
107
+ """Kinematic cruise-speed cap from the constant-peak-torque envelope.
108
+
109
+ ``v_kin = ω_no_load × R × (1 - s_eq)``. The slip term reduces the
110
+ forward speed for a given hub speed: a wheel spinning at ω with
111
+ equilibrium slip ``s`` advances at ``ω × R × (1 - s)``.
112
+ """
113
+ if wheel_radius_m <= 0.0:
114
+ raise ValueError(f"wheel_radius_m must be positive (got {wheel_radius_m}).")
115
+ if omega_no_load_hub_rad_s <= 0.0:
116
+ raise ValueError(
117
+ f"omega_no_load_hub_rad_s must be positive (got {omega_no_load_hub_rad_s})."
118
+ )
119
+ return omega_no_load_hub_rad_s * wheel_radius_m * max(0.0, 1.0 - slip_eq)
120
+
121
+
122
+ def energy_balance_v_cruise(
123
+ *,
124
+ p_solar_avg_w: float,
125
+ p_avionics_w: float,
126
+ wheel_radius_m: float,
127
+ slip_eq: float,
128
+ motor_efficiency: float,
129
+ delta_eff: float,
130
+ n_wheels: int,
131
+ t_req_per_wheel_nm: float,
132
+ ) -> float:
133
+ """Closed-form energy-balance cruise speed.
134
+
135
+ Solves ``δ_eff × P_mobility(v) + P_avionics = P_solar_avg`` for ``v``.
136
+ With ``ω = v / (R × (1 - s_eq))`` and per-wheel mechanical power
137
+ ``T_req × ω``, the per-wheel electrical draw at efficiency η is
138
+ ``T_req × ω / η``; total mobility power is ``n_wheels`` times that.
139
+ Algebraic solve:
140
+
141
+ v_eb = (P_solar_avg - P_avionics) × R × (1 - s_eq) × η_motor
142
+ / (δ_eff × n_wheels × T_req)
143
+
144
+ Returns 0.0 when net solar headroom is non-positive (rover cannot
145
+ even sustain avionics let alone mobility). Returns ``inf`` when
146
+ ``T_req`` is effectively zero (flat ground, smooth wheels) so that
147
+ callers compose with ``min(v_eb, v_kin_max)`` cleanly.
148
+ """
149
+ if delta_eff < 0.0 or n_wheels <= 0 or wheel_radius_m <= 0.0:
150
+ raise ValueError(
151
+ "delta_eff must be >= 0, n_wheels and wheel_radius_m must be "
152
+ f"positive (got delta_eff={delta_eff}, n_wheels={n_wheels}, "
153
+ f"wheel_radius_m={wheel_radius_m})."
154
+ )
155
+ if motor_efficiency <= 0.0:
156
+ raise ValueError(f"motor_efficiency must be positive (got {motor_efficiency}).")
157
+
158
+ p_net_avail = p_solar_avg_w - p_avionics_w
159
+ if p_net_avail <= 0.0:
160
+ return 0.0
161
+
162
+ # Effectively-zero torque demand: any speed is energy-feasible, so
163
+ # delegate the binding constraint to the kinematic cap.
164
+ if t_req_per_wheel_nm <= 1e-9:
165
+ return float("inf")
166
+
167
+ # delta_eff = 0 means the rover doesn't drive at all; the loop-side
168
+ # multiplier (dx_per_step ∝ δ_eff) zeroes out forward progress
169
+ # regardless of v_eb, but we'd divide by zero here. Return inf so
170
+ # the kinematic cap dominates and the caller gets a finite v_cruise.
171
+ if delta_eff <= 1e-12:
172
+ return float("inf")
173
+
174
+ factor = wheel_radius_m * max(1e-6, 1.0 - slip_eq) * motor_efficiency
175
+ return p_net_avail * factor / (delta_eff * n_wheels * t_req_per_wheel_nm)
176
+
177
+
178
+ @dataclass(frozen=True)
179
+ class CruiseResult:
180
+ """Output of :func:`cruise_speed`.
181
+
182
+ Attributes
183
+ ----------
184
+ stalled
185
+ ``True`` iff the slip solver could not develop the required
186
+ drawbar pull, or the per-wheel torque demand exceeds the
187
+ design's ``peak_wheel_torque_nm``. When ``True``, ``v_cruise_mps``
188
+ is forced to 0.
189
+ v_cruise_mps
190
+ Final cruise speed used by the time loop, m/s.
191
+ v_eb_mps
192
+ Energy-balance solve output, m/s. Stored for diagnostics; can
193
+ be larger than ``v_cruise_mps`` when the kinematic cap binds.
194
+ ``inf`` is possible when ``T_req`` is effectively zero (flat
195
+ ground, smooth wheels).
196
+ v_kin_max_mps
197
+ Kinematic envelope cap, m/s.
198
+ kinematic_clamped
199
+ ``True`` iff ``v_eb`` exceeded ``v_kin_max`` and the cap bound.
200
+ Tracking this lets the LHS dataset builder verify the design
201
+ doc's "< 1 % of cells clamp" assumption.
202
+ delta_eff
203
+ Effective duty cycle the time loop should use.
204
+ """
205
+
206
+ stalled: bool
207
+ v_cruise_mps: float
208
+ v_eb_mps: float
209
+ v_kin_max_mps: float
210
+ kinematic_clamped: bool
211
+ delta_eff: float
212
+
213
+
214
+ def cruise_speed(
215
+ *,
216
+ peak_wheel_torque_nm: float,
217
+ t_req_per_wheel_nm: float,
218
+ slip_eq: float,
219
+ slip_solver_failed: bool,
220
+ p_solar_avg_w: float,
221
+ p_avionics_w: float,
222
+ wheel_radius_m: float,
223
+ motor_efficiency: float,
224
+ delta_eff: float,
225
+ n_wheels: int,
226
+ omega_no_load_hub_rad_s: float = OMEGA_NO_LOAD_HUB_RAD_S,
227
+ ) -> CruiseResult:
228
+ """Compose the stall gate, energy-balance solve, and kinematic cap.
229
+
230
+ See module docstring for the physics. This is the canonical entry
231
+ point that :mod:`roverdevkit.mission.traverse_sim` calls on the
232
+ pre-loop wheel-force solve; tests hit it directly.
233
+ """
234
+ if peak_wheel_torque_nm <= 0.0:
235
+ raise ValueError(f"peak_wheel_torque_nm must be positive (got {peak_wheel_torque_nm}).")
236
+
237
+ v_kin_max = kinematic_envelope_v_max(
238
+ omega_no_load_hub_rad_s, wheel_radius_m, slip_eq
239
+ )
240
+
241
+ stalled = bool(
242
+ slip_solver_failed
243
+ or t_req_per_wheel_nm > peak_wheel_torque_nm + 1e-9
244
+ )
245
+ if stalled:
246
+ return CruiseResult(
247
+ stalled=True,
248
+ v_cruise_mps=0.0,
249
+ v_eb_mps=0.0,
250
+ v_kin_max_mps=v_kin_max,
251
+ kinematic_clamped=False,
252
+ delta_eff=delta_eff,
253
+ )
254
+
255
+ v_eb = energy_balance_v_cruise(
256
+ p_solar_avg_w=p_solar_avg_w,
257
+ p_avionics_w=p_avionics_w,
258
+ wheel_radius_m=wheel_radius_m,
259
+ slip_eq=slip_eq,
260
+ motor_efficiency=motor_efficiency,
261
+ delta_eff=delta_eff,
262
+ n_wheels=n_wheels,
263
+ t_req_per_wheel_nm=t_req_per_wheel_nm,
264
+ )
265
+
266
+ if v_eb >= v_kin_max:
267
+ return CruiseResult(
268
+ stalled=False,
269
+ v_cruise_mps=v_kin_max,
270
+ v_eb_mps=v_eb,
271
+ v_kin_max_mps=v_kin_max,
272
+ kinematic_clamped=True,
273
+ delta_eff=delta_eff,
274
+ )
275
+ return CruiseResult(
276
+ stalled=False,
277
+ v_cruise_mps=max(0.0, v_eb),
278
+ v_eb_mps=v_eb,
279
+ v_kin_max_mps=v_kin_max,
280
+ kinematic_clamped=False,
281
+ delta_eff=delta_eff,
282
+ )
283
+
284
+
285
+ # ---------------------------------------------------------------------------
286
+ # Pre-v6 implicit torque ceiling (LHS prior anchor only)
287
+ # ---------------------------------------------------------------------------
288
+
289
+
290
+ def sizing_peak_torque_anchor_nm(
291
+ *,
292
+ total_mass_kg: float,
293
+ wheel_radius_m: float,
294
+ n_wheels: int,
295
+ motor_sizing_safety_factor: float = 2.0,
296
+ motor_peak_friction_coef: float = 0.7,
297
+ gravity_m_per_s2: float = 1.625,
298
+ ) -> float:
299
+ """Pre-v6 implicit per-wheel torque ceiling, retained as an LHS anchor.
300
+
301
+ ``T_anchor = sf × μ × (m × g / N) × R``. In v5 the mass model
302
+ sized motor mass against this ceiling; in v6 ``peak_wheel_torque_nm``
303
+ is a first-class design variable, but the LHS sampler still draws
304
+ around this value (multiplied by a log-uniform tail) so the
305
+ surrogate spends data on physically realisable torque sizings.
306
+
307
+ Defaults match
308
+ :class:`roverdevkit.mass.parametric_mers.MassModelParams`. Live
309
+ here (rather than in mass) because the runtime mass model no
310
+ longer computes it; this function exists *only* for the
311
+ LHS prior in :mod:`roverdevkit.surrogate.sampling`.
312
+ """
313
+ if total_mass_kg <= 0.0 or wheel_radius_m <= 0.0 or n_wheels <= 0:
314
+ raise ValueError(
315
+ "total_mass_kg, wheel_radius_m, n_wheels must be positive "
316
+ f"(got total_mass_kg={total_mass_kg}, "
317
+ f"wheel_radius_m={wheel_radius_m}, n_wheels={n_wheels})."
318
+ )
319
+ weight_per_wheel_n = total_mass_kg * gravity_m_per_s2 / n_wheels
320
+ return (
321
+ motor_sizing_safety_factor
322
+ * motor_peak_friction_coef
323
+ * weight_per_wheel_n
324
+ * wheel_radius_m
325
+ )
roverdevkit/mass/__init__.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bottom-up parametric mass model for lunar micro-rovers.
2
+
3
+ See :mod:`.parametric_mers` for the :func:`estimate_mass` top-level
4
+ function and the :class:`MassModelParams` constants bag. See
5
+ :mod:`.validation` for the published-rover cross-check. The design choice
6
+ to go bottom-up (instead of fitting per-subsystem MERs on n~8 published
7
+ rovers) is documented inline in the validation helpers.
8
+ """
9
+
10
+ from roverdevkit.mass.parametric_mers import (
11
+ MassBreakdown,
12
+ MassModelParams,
13
+ estimate_mass,
14
+ estimate_mass_from_design,
15
+ )
16
+ from roverdevkit.mass.validation import (
17
+ RoverValidationResult,
18
+ RoverValidationRow,
19
+ ValidationSummary,
20
+ format_report,
21
+ load_validation_set,
22
+ predict_row,
23
+ validate_against_published_rovers,
24
+ )
25
+
26
+ __all__ = [
27
+ "MassBreakdown",
28
+ "MassModelParams",
29
+ "RoverValidationResult",
30
+ "RoverValidationRow",
31
+ "ValidationSummary",
32
+ "estimate_mass",
33
+ "estimate_mass_from_design",
34
+ "format_report",
35
+ "load_validation_set",
36
+ "predict_row",
37
+ "validate_against_published_rovers",
38
+ ]
roverdevkit/mass/parametric_mers.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bottom-up parametric mass model for lunar micro-rovers.
2
+
3
+ Approach
4
+ --------
5
+ Each subsystem mass
6
+ is computed from a **physics-grounded specific mass or a standard
7
+ spacecraft-sizing fraction** with a cited source. The rows in
8
+ ``data/mass_validation_set.csv`` are then used as a **validation set**
9
+ (see :mod:`roverdevkit.mass.validation`) - "does the bottom-up model
10
+ reproduce total mass within ~30 % for each real rover?".
11
+
12
+ The model is deliberately transparent: every coefficient is exposed as a
13
+ field of :class:`MassModelParams` so it can be overridden for sensitivity
14
+ studies from the surrogate / tradespace layer. Default values are chosen
15
+ from published space-hardware sources; see each field's docstring for the
16
+ citation.
17
+
18
+ Subsystem accounting (SMAD Ch. 11, Table 11-43 convention)::
19
+
20
+ m_subsystems = m_chassis + m_wheels + m_motors + m_solar + m_battery + m_avionics
21
+ m_harness = f_harness * m_subsystems
22
+ m_thermal = f_thermal * (m_subsystems + m_harness)
23
+ m_dry = m_subsystems + m_harness + m_thermal
24
+ m_margin = f_margin * m_dry
25
+ m_total = m_dry + m_margin + m_payload
26
+
27
+ Payload mass (schema v9). Scientific payload is a *mission
28
+ requirement* carried on :class:`roverdevkit.schema.MissionScenario`,
29
+ not a design variable. It enters the total as a top-level line item
30
+ **after** the AIAA S-120A dry-mass growth margin (``m_payload`` is a
31
+ known, specified mass, so the bus growth allowance does not apply to
32
+ it). This matches standard aerospace mass-budget practice (payload is
33
+ tracked separately from bus dry mass) and lets the bottom-up model
34
+ reproduce full-up published rover mass — e.g. Yutu-2's ~25 kg science
35
+ payload no longer has to be hidden inside ``chassis_mass_kg``.
36
+
37
+ Motor mass (schema v6, v6 schema update). The motor subsystem mass is now
38
+ computed directly from the design's
39
+ :attr:`roverdevkit.schema.DesignVector.peak_wheel_torque_nm` (a true
40
+ input), so the pre-v6 fixed-point loop over total mass is gone — this
41
+ function is now strictly bottom-up and converges in a single pass.
42
+ The pre-v6 implicit mass-derived torque ceiling lives on in
43
+ :func:`roverdevkit.drivetrain.motor.sizing_peak_torque_anchor_nm` only
44
+ as the LHS prior anchor.
45
+
46
+ Primary references
47
+ ------------------
48
+ Larson, W. J. & Wertz, J. R. *Space Mission Analysis and Design (SMAD)*,
49
+ 3rd ed., Microcosm/Springer, 1999.
50
+ Ch. 11 Table 11-43 - subsystem mass fractions.
51
+ Ch. 16 - C&DH MERs.
52
+
53
+ Larson, W. J. & Pranke, L. K. *Human Spaceflight: Mission Analysis and
54
+ Design*, McGraw-Hill, 2000. Surface-system sizing.
55
+
56
+ AIAA S-120A-2015 *Mass Properties Control for Space Systems*, dry-mass
57
+ growth allowances.
58
+ """
59
+
60
+ from __future__ import annotations
61
+
62
+ import math
63
+ from dataclasses import dataclass, field
64
+
65
+ from roverdevkit.architecture import (
66
+ ArchitectureParams,
67
+ MobilityArchitecture,
68
+ architecture_suspension_mass_kg,
69
+ )
70
+ from roverdevkit.schema import DesignVector
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Model parameters
74
+ # ---------------------------------------------------------------------------
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class MassModelParams:
79
+ """Specific-mass constants and sizing fractions for the bottom-up model.
80
+
81
+ All values are exposed so the tradespace layer can sweep them for
82
+ sensitivity analysis. Defaults are cited in-field.
83
+ """
84
+
85
+ # -- Wheels -------------------------------------------------------------
86
+ wheel_structural_area_density_kg_per_m2: float = 8.0
87
+ """Mass per unit of wheel-side area (2*pi*R*W), kg/m^2.
88
+
89
+ Covers rim, hub, spokes, and fastener hardware for aluminium/CFRP rigid
90
+ wheels in the 0.05-0.25 m radius class. Default chosen for the
91
+ micro-rover mass class where thin-gauge aluminium or composite wheels
92
+ dominate. Tune upward toward 15 kg/m^2 for MER/MSL-style stiff-rim wheels.
93
+ """
94
+
95
+ grouser_plate_thickness_m: float = 0.002
96
+ """Grouser-plate thickness, m. 2 mm aluminium is typical for
97
+ micro-rover traction fins (Bauer et al., i-SAIRAS 2005; MER grouser
98
+ geometry scaled to micro-rover class)."""
99
+
100
+ grouser_material_density_kg_per_m3: float = 2700.0
101
+ """Grouser plate material density, kg/m^3. Default = 6061-T6 Al."""
102
+
103
+ # -- Motors and drives -------------------------------------------------
104
+ motor_base_mass_kg: float = 0.15
105
+ """Irreducible motor + gearbox housing mass per wheel, kg.
106
+ Floor for small brushless motors (~20-50 W) paired with a compact
107
+ planetary or harmonic-drive reducer. Maxon EC-i 32 + GP 32 reaches
108
+ ~0.12 kg; we round up to 0.15 kg to cover space-qualified bearings,
109
+ shaft seals, and a flight-heritage connector."""
110
+
111
+ motor_specific_torque_kg_per_nm: float = 0.10
112
+ """Mass per unit of peak output (post-gearbox) torque, kg/(N*m).
113
+
114
+ Calibrated against vendor catalogues: Maxon EC-i 32 + GP 32 AR
115
+ planetary (100:1) = 0.325 kg at 4 N*m peak output -> 0.08 kg/(N*m);
116
+ Maxon EC-i 40 + GP 52 (80:1) = 1.15 kg at ~20 N*m peak output ->
117
+ 0.06 kg/(N*m). We use 0.10 kg/(N*m) as a slightly conservative
118
+ centre of the 0.06-0.12 kg/(N*m) range. Applies to the output
119
+ torque; the motor itself produces a small fraction of this after
120
+ the gear reduction."""
121
+
122
+ motor_peak_friction_coef: float = 0.7
123
+ """Peak tractive friction coefficient — schema v6 dead parameter.
124
+
125
+ Pre-v6 the mass model sized motor torque internally from this
126
+ coefficient and the rover's lunar weight. v6 makes
127
+ :attr:`roverdevkit.schema.DesignVector.peak_wheel_torque_nm` a
128
+ first-class design input, and this coefficient survives only as a
129
+ default in
130
+ :func:`roverdevkit.drivetrain.motor.sizing_peak_torque_anchor_nm`
131
+ (the LHS prior anchor for the v6 dataset rebuild). Kept on
132
+ :class:`MassModelParams` so existing callers / pickled fixtures
133
+ don't break; remove on the next mass-model bump."""
134
+
135
+ motor_sizing_safety_factor: float = 2.0
136
+ """Schema v6 dead parameter — see :attr:`motor_peak_friction_coef`."""
137
+
138
+ # -- Solar panels ------------------------------------------------------
139
+ solar_specific_area_mass_kg_per_m2: float = 2.5
140
+ """Areal mass density of a rigid body-mounted GaAs triple-junction solar
141
+ panel including CFRP substrate and cell-to-substrate bond, kg/m^2.
142
+ SMAD Table 11-43 gives 2.0-5.0 for body-mounted rigid panels;
143
+ Spectrolab/AzurSpace datasheets for UTJ/ZTJ cells on a thin CFRP
144
+ panel land near the lower bound."""
145
+
146
+ # -- Battery -----------------------------------------------------------
147
+ battery_pack_specific_energy_wh_per_kg: float = 120.0
148
+ """Pack-level specific energy, Wh/kg. Li-ion cell-level ~200 Wh/kg
149
+ multiplied by a ~0.6 pack-integration factor (BMS, casing, harness,
150
+ thermal pads). NASA Glenn Battery Research Center tech reports;
151
+ SMAD Ch. 11 secondary-battery table."""
152
+
153
+ # -- Avionics and C&DH -------------------------------------------------
154
+ avionics_base_mass_kg: float = 0.3
155
+ """Floor mass for the smallest flyable avionics box, kg.
156
+ Captures enclosure, backplane, and one CPU card. SMAD Ch. 16
157
+ CDH MER lower bound."""
158
+
159
+ avionics_specific_mass_kg_per_w: float = 0.05
160
+ """Additional kg of structure / heat-sink per W of continuous avionics
161
+ power dissipation. Derived from rule-of-thumb PCB-and-chassis thermal
162
+ sizing at ~0.05 kg/W (SMAD Ch. 16)."""
163
+
164
+ # -- Housekeeping fractions -------------------------------------------
165
+ harness_fraction: float = 0.08
166
+ """Harness mass as a fraction of the summed subsystem mass, SMAD
167
+ Table 11-43 mid-range (6-10 %)."""
168
+
169
+ thermal_fraction: float = 0.05
170
+ """Thermal-control (MLI, heaters, straps) mass as a fraction of
171
+ (subsystems + harness). SMAD Table 11-43 small-spacecraft mid-range
172
+ (4-7 %)."""
173
+
174
+ margin_fraction: float = 0.20
175
+ """Dry-mass growth allowance (margin) as a fraction of dry mass.
176
+ AIAA S-120A-2015 recommends 20 % at PDR maturity, dropping toward
177
+ launch. Tradespace-level work uses the PDR number."""
178
+
179
+ rocker_bogie_fixed_mass_kg: float = 0.5
180
+ """Fixed rocker-bogie linkage / differential mass, kg."""
181
+
182
+ rocker_bogie_chassis_fraction: float = 0.08
183
+ """Additional rocker-bogie suspension mass as a fraction of chassis mass."""
184
+
185
+ # -- Environment -------------------------------------------------------
186
+ gravity_moon_m_per_s2: float = 1.625
187
+ """Surface gravity at the lunar equator, m/s^2."""
188
+
189
+
190
+ # ---------------------------------------------------------------------------
191
+ # Breakdown container
192
+ # ---------------------------------------------------------------------------
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class MassBreakdown:
197
+ """Subsystem mass breakdown in kg. Sum of fields equals ``total_kg``."""
198
+
199
+ chassis_kg: float
200
+ wheels_kg: float
201
+ motors_and_drives_kg: float
202
+ solar_panels_kg: float
203
+ battery_kg: float
204
+ avionics_kg: float
205
+ harness_kg: float
206
+ thermal_kg: float
207
+ margin_kg: float
208
+ architecture_kg: float = 0.0
209
+ payload_kg: float = 0.0
210
+ """Scientific-payload mass, kg (schema v9).
211
+
212
+ A mission requirement carried on
213
+ :class:`roverdevkit.schema.MissionScenario`, added to the total
214
+ *outside* the dry-mass growth margin. Defaults to 0.0 so pre-v9
215
+ callers (and the mass model's own subsystem-only sweeps) are
216
+ unaffected."""
217
+ n_iterations: int = field(default=0, compare=False)
218
+ """Number of fixed-point iterations taken to converge motor mass.
219
+
220
+ Schema v6 (v6 schema update): always 1 — motor mass is a direct function
221
+ of :attr:`roverdevkit.schema.DesignVector.peak_wheel_torque_nm` so
222
+ the model converges in one pass. Field retained for back-compat
223
+ with pre-v6 fixtures and the validation harness."""
224
+
225
+ @property
226
+ def total_kg(self) -> float:
227
+ return (
228
+ self.chassis_kg
229
+ + self.wheels_kg
230
+ + self.motors_and_drives_kg
231
+ + self.solar_panels_kg
232
+ + self.battery_kg
233
+ + self.avionics_kg
234
+ + self.harness_kg
235
+ + self.thermal_kg
236
+ + self.margin_kg
237
+ + self.architecture_kg
238
+ + self.payload_kg
239
+ )
240
+
241
+ @property
242
+ def dry_kg(self) -> float:
243
+ """Bus dry mass: excludes both the growth margin and the payload."""
244
+ return self.total_kg - self.margin_kg - self.payload_kg
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # Per-subsystem helpers
249
+ # ---------------------------------------------------------------------------
250
+
251
+
252
+ def _wheels_mass(
253
+ wheel_radius_m: float,
254
+ wheel_width_m: float,
255
+ grouser_height_m: float,
256
+ grouser_count: int,
257
+ n_wheels: int,
258
+ params: MassModelParams,
259
+ ) -> float:
260
+ """Rim-and-hub + grouser mass for all drive wheels.
261
+
262
+ Structural term: ``rho_wheel_area * (2 * pi * R * W) * n_wheels``, where
263
+ the side-area factor captures the dominant scaling of a rim-and-hub
264
+ wheel with a thin cylindrical skin (calibrated to lunar-wheel
265
+ hardware, not derived from first-principles shell theory).
266
+
267
+ Grouser term: each grouser is modelled as a thin rectangular aluminium
268
+ plate of dimensions ``W x h_g x t``; mass is
269
+ ``N_g * W * h_g * t * rho_Al``.
270
+ """
271
+ if wheel_radius_m <= 0.0 or wheel_width_m <= 0.0 or n_wheels <= 0:
272
+ raise ValueError("wheel_radius_m, wheel_width_m and n_wheels must be positive.")
273
+ if grouser_height_m < 0.0 or grouser_count < 0:
274
+ raise ValueError("grouser_height_m and grouser_count must be non-negative.")
275
+
276
+ side_area_m2 = 2.0 * math.pi * wheel_radius_m * wheel_width_m
277
+ structural_kg = params.wheel_structural_area_density_kg_per_m2 * side_area_m2
278
+
279
+ grouser_volume_m3 = (
280
+ grouser_count * wheel_width_m * grouser_height_m * params.grouser_plate_thickness_m
281
+ )
282
+ grouser_kg = grouser_volume_m3 * params.grouser_material_density_kg_per_m3
283
+
284
+ return n_wheels * (structural_kg + grouser_kg)
285
+
286
+
287
+ def _motors_mass(
288
+ n_wheels: int,
289
+ peak_wheel_torque_nm: float,
290
+ params: MassModelParams,
291
+ ) -> float:
292
+ """Drive-motor + gearbox mass sized from the peak-wheel torque.
293
+
294
+ Schema v6 (v6 schema update): ``peak_wheel_torque_nm`` is now a direct
295
+ design input rather than something derived from the vehicle's
296
+ lunar weight inside the mass model. Per-motor mass remains
297
+ ``m_0 + k_tau * tau_peak``; total summed over ``n_wheels``. The
298
+ pre-v6 mass-derived ceiling lives on in
299
+ :func:`roverdevkit.drivetrain.motor.sizing_peak_torque_anchor_nm`
300
+ only as an LHS prior anchor.
301
+ """
302
+ if peak_wheel_torque_nm < 0.0:
303
+ raise ValueError("peak_wheel_torque_nm must be non-negative.")
304
+ if n_wheels <= 0:
305
+ raise ValueError("n_wheels must be positive.")
306
+
307
+ per_motor_kg = (
308
+ params.motor_base_mass_kg
309
+ + params.motor_specific_torque_kg_per_nm * peak_wheel_torque_nm
310
+ )
311
+ return n_wheels * per_motor_kg
312
+
313
+
314
+ def _solar_panels_mass(solar_area_m2: float, params: MassModelParams) -> float:
315
+ if solar_area_m2 < 0.0:
316
+ raise ValueError("solar_area_m2 must be non-negative.")
317
+ return params.solar_specific_area_mass_kg_per_m2 * solar_area_m2
318
+
319
+
320
+ def _battery_mass(battery_capacity_wh: float, params: MassModelParams) -> float:
321
+ if battery_capacity_wh < 0.0:
322
+ raise ValueError("battery_capacity_wh must be non-negative.")
323
+ return battery_capacity_wh / params.battery_pack_specific_energy_wh_per_kg
324
+
325
+
326
+ def _avionics_mass(avionics_power_w: float, params: MassModelParams) -> float:
327
+ if avionics_power_w < 0.0:
328
+ raise ValueError("avionics_power_w must be non-negative.")
329
+ return params.avionics_base_mass_kg + params.avionics_specific_mass_kg_per_w * avionics_power_w
330
+
331
+
332
+ # ---------------------------------------------------------------------------
333
+ # Top-level entry point
334
+ # ---------------------------------------------------------------------------
335
+
336
+
337
+ def estimate_mass(
338
+ *,
339
+ wheel_radius_m: float,
340
+ wheel_width_m: float,
341
+ n_wheels: int,
342
+ chassis_mass_kg: float,
343
+ solar_area_m2: float,
344
+ battery_capacity_wh: float,
345
+ avionics_power_w: float,
346
+ peak_wheel_torque_nm: float,
347
+ grouser_height_m: float = 0.0,
348
+ grouser_count: int = 0,
349
+ payload_mass_kg: float = 0.0,
350
+ mobility_architecture: MobilityArchitecture = "rigid_4wheel",
351
+ params: MassModelParams | None = None,
352
+ ) -> MassBreakdown:
353
+ """Assemble a bottom-up subsystem mass breakdown for a rover design.
354
+
355
+ Schema v6 (v6 schema update). All subsystems are load-independent now
356
+ that ``peak_wheel_torque_nm`` is a true design input — the pre-v6
357
+ fixed-point iteration over total mass is gone, and this function
358
+ converges in a single pass. The ``n_iterations`` field on the
359
+ returned :class:`MassBreakdown` is kept for backward compatibility
360
+ but is always 1 in v6.
361
+
362
+ The keyword-only signature matches the design-variable names on
363
+ :class:`roverdevkit.schema.DesignVector`. See
364
+ :func:`estimate_mass_from_design` for a convenience wrapper.
365
+
366
+ Parameters
367
+ ----------
368
+ wheel_radius_m, wheel_width_m
369
+ Wheel geometry, m.
370
+ n_wheels
371
+ Drive-wheel count (4 or 6 per :class:`DesignVector`).
372
+ chassis_mass_kg
373
+ Dry chassis structural mass, kg. A design-variable input.
374
+ solar_area_m2, battery_capacity_wh, avionics_power_w
375
+ Power-subsystem design variables.
376
+ peak_wheel_torque_nm
377
+ Peak per-wheel hub torque the drivetrain delivers, Nm. Sizes
378
+ motor mass directly via ``m_0 + k_tau * tau_peak``.
379
+ grouser_height_m, grouser_count
380
+ Grouser geometry, m and count. Defaults to 0.
381
+ payload_mass_kg
382
+ Scientific-payload mass, kg (schema v9). A mission requirement
383
+ from :attr:`roverdevkit.schema.MissionScenario.payload_mass_kg`.
384
+ Added to the total *after* the dry-mass growth margin (payload
385
+ is a known mass, not grown). Defaults to 0.0.
386
+ params
387
+ :class:`MassModelParams` override; defaults to the module defaults.
388
+
389
+ Returns
390
+ -------
391
+ MassBreakdown
392
+ Subsystem masses summing to the total vehicle mass.
393
+
394
+ Raises
395
+ ------
396
+ ValueError
397
+ On any non-physical input (negative masses, non-positive
398
+ geometry).
399
+ """
400
+ params = params or MassModelParams()
401
+
402
+ if payload_mass_kg < 0.0:
403
+ raise ValueError("payload_mass_kg must be non-negative.")
404
+
405
+ m_chassis = chassis_mass_kg
406
+ if m_chassis <= 0.0:
407
+ raise ValueError("chassis_mass_kg must be positive.")
408
+ m_wheels = _wheels_mass(
409
+ wheel_radius_m, wheel_width_m, grouser_height_m, grouser_count, n_wheels, params
410
+ )
411
+ m_solar = _solar_panels_mass(solar_area_m2, params)
412
+ m_battery = _battery_mass(battery_capacity_wh, params)
413
+ m_avionics = _avionics_mass(avionics_power_w, params)
414
+ m_motors = _motors_mass(n_wheels, peak_wheel_torque_nm, params)
415
+
416
+ m_subsystems = m_chassis + m_wheels + m_motors + m_solar + m_battery + m_avionics
417
+ m_architecture = architecture_suspension_mass_kg(
418
+ mobility_architecture,
419
+ m_chassis,
420
+ params=ArchitectureParams(
421
+ rocker_bogie_fixed_mass_kg=params.rocker_bogie_fixed_mass_kg,
422
+ rocker_bogie_chassis_fraction=params.rocker_bogie_chassis_fraction,
423
+ ),
424
+ )
425
+ m_subsystems += m_architecture
426
+ m_harness = params.harness_fraction * m_subsystems
427
+ m_thermal = params.thermal_fraction * (m_subsystems + m_harness)
428
+ m_dry = m_subsystems + m_harness + m_thermal
429
+ m_margin = params.margin_fraction * m_dry
430
+
431
+ return MassBreakdown(
432
+ chassis_kg=m_chassis,
433
+ wheels_kg=m_wheels,
434
+ motors_and_drives_kg=m_motors,
435
+ solar_panels_kg=m_solar,
436
+ battery_kg=m_battery,
437
+ avionics_kg=m_avionics,
438
+ harness_kg=m_harness,
439
+ thermal_kg=m_thermal,
440
+ margin_kg=m_margin,
441
+ architecture_kg=m_architecture,
442
+ payload_kg=payload_mass_kg,
443
+ n_iterations=1,
444
+ )
445
+
446
+
447
+ def estimate_mass_from_design(
448
+ design: DesignVector,
449
+ params: MassModelParams | None = None,
450
+ *,
451
+ payload_mass_kg: float = 0.0,
452
+ ) -> MassBreakdown:
453
+ """Convenience wrapper that unpacks a :class:`DesignVector`.
454
+
455
+ ``payload_mass_kg`` (schema v9) is a mission requirement that lives
456
+ on :class:`roverdevkit.schema.MissionScenario`, not on the design
457
+ vector, so it is passed in explicitly by the evaluator. Defaults to
458
+ 0.0 for callers that only need the bus mass.
459
+ """
460
+ return estimate_mass(
461
+ wheel_radius_m=design.wheel_radius_m,
462
+ wheel_width_m=design.wheel_width_m,
463
+ n_wheels=design.n_wheels,
464
+ chassis_mass_kg=design.chassis_mass_kg,
465
+ solar_area_m2=design.solar_area_m2,
466
+ battery_capacity_wh=design.battery_capacity_wh,
467
+ avionics_power_w=design.avionics_power_w,
468
+ peak_wheel_torque_nm=design.peak_wheel_torque_nm,
469
+ grouser_height_m=design.grouser_height_m,
470
+ grouser_count=design.grouser_count,
471
+ payload_mass_kg=payload_mass_kg,
472
+ mobility_architecture=design.mobility_architecture,
473
+ params=params,
474
+ )
roverdevkit/mass/validation.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Cross-check the bottom-up mass model against published rover total masses.
2
+
3
+ The validation set lives in ``data/mass_validation_set.csv``. Each row is a
4
+ best-effort full design vector for a published rover, with an
5
+ ``imputation_notes`` column documenting every field that was not directly
6
+ published and how it was estimated.
7
+
8
+ The ``in_class`` flag (True/False) marks whether the rover is inside the
9
+ bottom-up mass model's specific-mass calibration regime (5-50 kg
10
+ lunar micro-rovers). At sub-5-kg total mass the bottom-up
11
+ model's fixed-cost terms (per-wheel motor base mass, avionics base
12
+ mass, harness / thermal / margin fractions) come to dominate, and the
13
+ model systematically over-predicts total mass relative to ultra-micro
14
+ hardware which uses mass-optimised custom motors and avionics that
15
+ the SMAD/AIAA/vendor-catalogue specific-mass constants do not reflect.
16
+ Updating the constants for the ultra-micro regime would invalidate
17
+ the model's calibration on the 5-50 kg class, so we keep the
18
+ calibration unchanged and explicitly mark sub-5-kg rovers as
19
+ ``in_class=False``.
20
+
21
+ The primary validation statistic is **median absolute percent error on
22
+ in-class rovers**; the target is <= 30 % (plan §8). Out-of-regime
23
+ rovers (CADRE at 2 kg, Yutu-2 at 135 kg, etc.) are reported alongside
24
+ but excluded from the primary statistic.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import csv
30
+ from dataclasses import dataclass
31
+ from pathlib import Path
32
+ from statistics import mean, median
33
+
34
+ from roverdevkit.architecture import architecture_for_wheel_count
35
+ from roverdevkit.drivetrain.motor import sizing_peak_torque_anchor_nm
36
+ from roverdevkit.mass.parametric_mers import (
37
+ MassBreakdown,
38
+ MassModelParams,
39
+ estimate_mass,
40
+ )
41
+
42
+ DEFAULT_VALIDATION_CSV: Path = (
43
+ Path(__file__).resolve().parents[2] / "data" / "mass_validation_set.csv"
44
+ )
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class RoverValidationRow:
49
+ """One row of the validation set: a published rover plus imputations.
50
+
51
+ ``in_class`` marks whether the rover sits inside the bottom-up
52
+ mass model's specific-mass calibration regime (5-50 kg lunar
53
+ micro-rovers). See the
54
+ module docstring for why the two diverged on 2026-05-27.
55
+ """
56
+
57
+ rover_name: str
58
+ mass_total_kg: float
59
+ wheel_radius_m: float
60
+ wheel_width_m: float
61
+ n_wheels: int
62
+ chassis_mass_kg: float
63
+ solar_area_m2: float
64
+ battery_capacity_wh: float
65
+ avionics_power_w: float
66
+ grouser_height_m: float
67
+ grouser_count: int
68
+ payload_mass_kg: float
69
+ """Scientific-payload mass, kg.
70
+
71
+ Separated out of the back-solved ``chassis_mass_kg`` bucket so the
72
+ bottom-up model sizes only the *bus* and adds payload as a flat,
73
+ ungrown line item — matching how payload enters the live evaluator.
74
+ See ``data/mass_validation_set.csv`` ``citation`` and
75
+ ``imputation_notes`` for the per-rover literature source."""
76
+ in_class: bool
77
+ citation: str
78
+ imputation_notes: str
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class RoverValidationResult:
83
+ """Outcome of running the bottom-up mass model on one rover.
84
+
85
+ ``in_class`` mirrors :class:`RoverValidationRow.in_class`: True iff
86
+ the rover sits inside the mass-model calibration regime.
87
+ """
88
+
89
+ rover_name: str
90
+ in_class: bool
91
+ mass_published_kg: float
92
+ mass_predicted_kg: float
93
+ breakdown: MassBreakdown
94
+
95
+ @property
96
+ def absolute_error_kg(self) -> float:
97
+ return self.mass_predicted_kg - self.mass_published_kg
98
+
99
+ @property
100
+ def percent_error(self) -> float:
101
+ return 100.0 * self.absolute_error_kg / self.mass_published_kg
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class ValidationSummary:
106
+ """Aggregate statistics over a batch of validation rows."""
107
+
108
+ n_total: int
109
+ n_in_class: int
110
+ median_abs_percent_error_in_class: float
111
+ mean_abs_percent_error_in_class: float
112
+ worst_in_class: RoverValidationResult
113
+ per_rover: tuple[RoverValidationResult, ...]
114
+
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Loading
118
+ # ---------------------------------------------------------------------------
119
+
120
+
121
+ def _parse_bool(value: str) -> bool:
122
+ v = value.strip().lower()
123
+ if v in ("true", "1", "yes", "y"):
124
+ return True
125
+ if v in ("false", "0", "no", "n"):
126
+ return False
127
+ raise ValueError(f"unparseable boolean: {value!r}")
128
+
129
+
130
+ def load_validation_set(csv_path: Path | str | None = None) -> list[RoverValidationRow]:
131
+ """Read ``data/mass_validation_set.csv`` into a list of dataclasses."""
132
+ path = Path(csv_path) if csv_path else DEFAULT_VALIDATION_CSV
133
+ rows: list[RoverValidationRow] = []
134
+ with path.open() as f:
135
+ reader = csv.DictReader(f)
136
+ for row in reader:
137
+ rows.append(
138
+ RoverValidationRow(
139
+ rover_name=row["rover_name"],
140
+ mass_total_kg=float(row["mass_total_kg"]),
141
+ wheel_radius_m=float(row["wheel_radius_m"]),
142
+ wheel_width_m=float(row["wheel_width_m"]),
143
+ n_wheels=int(row["n_wheels"]),
144
+ chassis_mass_kg=float(row["chassis_mass_kg"]),
145
+ solar_area_m2=float(row["solar_area_m2"]),
146
+ battery_capacity_wh=float(row["battery_capacity_wh"]),
147
+ avionics_power_w=float(row["avionics_power_w"]),
148
+ grouser_height_m=float(row["grouser_height_m"]),
149
+ grouser_count=int(row["grouser_count"]),
150
+ payload_mass_kg=float(row.get("payload_mass_kg", 0.0) or 0.0),
151
+ in_class=_parse_bool(row["in_class"]),
152
+ citation=row.get("citation", ""),
153
+ imputation_notes=row["imputation_notes"],
154
+ )
155
+ )
156
+ return rows
157
+
158
+
159
+ # ---------------------------------------------------------------------------
160
+ # Running the comparison
161
+ # ---------------------------------------------------------------------------
162
+
163
+
164
+ def predict_row(
165
+ row: RoverValidationRow,
166
+ params: MassModelParams | None = None,
167
+ ) -> RoverValidationResult:
168
+ """Run ``estimate_mass`` on a single validation row.
169
+ """
170
+ peak_wheel_torque_nm = sizing_peak_torque_anchor_nm(
171
+ total_mass_kg=row.mass_total_kg,
172
+ wheel_radius_m=row.wheel_radius_m,
173
+ n_wheels=row.n_wheels,
174
+ )
175
+ breakdown = estimate_mass(
176
+ wheel_radius_m=row.wheel_radius_m,
177
+ wheel_width_m=row.wheel_width_m,
178
+ n_wheels=row.n_wheels,
179
+ chassis_mass_kg=row.chassis_mass_kg,
180
+ solar_area_m2=row.solar_area_m2,
181
+ battery_capacity_wh=row.battery_capacity_wh,
182
+ avionics_power_w=row.avionics_power_w,
183
+ peak_wheel_torque_nm=peak_wheel_torque_nm,
184
+ grouser_height_m=row.grouser_height_m,
185
+ grouser_count=row.grouser_count,
186
+ payload_mass_kg=row.payload_mass_kg,
187
+ mobility_architecture=architecture_for_wheel_count(row.n_wheels),
188
+ params=params,
189
+ )
190
+ return RoverValidationResult(
191
+ rover_name=row.rover_name,
192
+ in_class=row.in_class,
193
+ mass_published_kg=row.mass_total_kg,
194
+ mass_predicted_kg=breakdown.total_kg,
195
+ breakdown=breakdown,
196
+ )
197
+
198
+
199
+ def validate_against_published_rovers(
200
+ csv_path: Path | str | None = None,
201
+ params: MassModelParams | None = None,
202
+ ) -> ValidationSummary:
203
+ """Run the bottom-up mass model on the full validation set and summarise.
204
+
205
+ The primary statistic returned is the median absolute percent error on
206
+ in-class (5-50 kg) rovers. Out-of-class rovers (nano, medium, large)
207
+ are included in ``per_rover`` but excluded from the in-class
208
+ statistics, reflecting the 5-50 kg calibration range of the specific
209
+ mass constants in :class:`MassModelParams`.
210
+ """
211
+ rows = load_validation_set(csv_path)
212
+ results = tuple(predict_row(r, params=params) for r in rows)
213
+
214
+ in_class_results = [r for r in results if r.in_class]
215
+ if not in_class_results:
216
+ raise ValueError("Validation set contains no in-class rovers.")
217
+
218
+ in_class_abs_errors = [abs(r.percent_error) for r in in_class_results]
219
+ worst = max(in_class_results, key=lambda r: abs(r.percent_error))
220
+
221
+ return ValidationSummary(
222
+ n_total=len(results),
223
+ n_in_class=len(in_class_results),
224
+ median_abs_percent_error_in_class=float(median(in_class_abs_errors)),
225
+ mean_abs_percent_error_in_class=float(mean(in_class_abs_errors)),
226
+ worst_in_class=worst,
227
+ per_rover=results,
228
+ )
229
+
230
+
231
+ def format_report(summary: ValidationSummary) -> str:
232
+ """Human-readable table for notebooks and reports."""
233
+ lines = [
234
+ "Rover in_class published (kg) predicted (kg) err %",
235
+ "-" * 73,
236
+ ]
237
+ for r in summary.per_rover:
238
+ flag = "yes" if r.in_class else "no "
239
+ lines.append(
240
+ f"{r.rover_name:20s} {flag:>8s} {r.mass_published_kg:14.2f} "
241
+ f"{r.mass_predicted_kg:14.2f} {r.percent_error:+7.1f}"
242
+ )
243
+ lines.append("-" * 73)
244
+ lines.append(
245
+ f"Aggregates on in-class rovers (n={summary.n_in_class}): "
246
+ f"median |err| = {summary.median_abs_percent_error_in_class:.1f} %, "
247
+ f"mean |err| = {summary.mean_abs_percent_error_in_class:.1f} %, "
248
+ f"worst = {summary.worst_in_class.rover_name} "
249
+ f"({summary.worst_in_class.percent_error:+.1f} %)."
250
+ )
251
+ return "\n".join(lines)
roverdevkit/mission/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Mission evaluator: the primary artifact of the project.
2
+
3
+ - :mod:`.evaluator` — top-level ``evaluate(design, scenario) → metrics``.
4
+ - :mod:`.scenarios` — the four canonical mission scenarios as configs.
5
+ - :mod:`.traverse_sim` — time-stepped traverse loop integrating
6
+ terramechanics, power, battery, mass, and thermal.
7
+ - :mod:`.capability` — static mobility capability metrics (max slope).
8
+ """
9
+
10
+ from roverdevkit.mission.capability import max_climbable_slope_deg
11
+ from roverdevkit.mission.evaluator import evaluate
12
+ from roverdevkit.mission.scenarios import list_scenarios, load_scenario
13
+ from roverdevkit.mission.traverse_sim import TraverseLog, run_traverse
14
+
15
+ __all__ = [
16
+ "TraverseLog",
17
+ "evaluate",
18
+ "list_scenarios",
19
+ "load_scenario",
20
+ "max_climbable_slope_deg",
21
+ "run_traverse",
22
+ ]
roverdevkit/mission/capability.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Derived mobility capabilities computed from the Bekker-Wong model.
2
+
3
+ Right now this contains one thing: the **maximum climbable slope** for a
4
+ rover design on a given soil. This is separate from the traverse sim
5
+ because it's a static capability metric (not time-resolved) and it
6
+ populates ``MissionMetrics.slope_capability_deg`` for the canonical mission scenarios
7
+ scenario 3).
8
+
9
+ Approach
10
+ --------
11
+ For a rover traversing a slope of inclination theta, at steady speed:
12
+
13
+ Tractive force required per wheel = m*g*sin(theta) / n_wheels
14
+ Normal load per wheel = m*g*cos(theta) / n_wheels
15
+
16
+ The Bekker-Wong model's ``drawbar_pull_n`` is the *net* horizontal force
17
+ a single wheel delivers beyond its own motion resistance, so DP must
18
+ balance the gradient term alone. At each candidate slope we evaluate
19
+ :func:`single_wheel_forces` at a reference high-slip point
20
+ (``max_slip``) to get the maximum available DP and look for the slope
21
+ at which available DP equals required DP.
22
+
23
+ We cap the search at 35 deg because (a) the :class:`MissionScenario`
24
+ schema allows ``max_slope_deg <= 35`` and (b) at that slope rover
25
+ stability (tip-over) starts to dominate over traction, which this model
26
+ ignores.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import math
32
+
33
+ from scipy.optimize import brentq
34
+
35
+ from roverdevkit.terramechanics.bekker_wong import (
36
+ SoilParameters,
37
+ WheelGeometry,
38
+ single_wheel_forces,
39
+ )
40
+
41
+ DEFAULT_LUNAR_GRAVITY_M_PER_S2: float = 1.625
42
+ """Reference lunar gravity; matches :data:`MassModelParams.gravity_moon_m_per_s2`."""
43
+
44
+ DEFAULT_MAX_SLIP_FOR_CAPABILITY: float = 0.6
45
+ """Reference high-slip operating point for max-DP (Wong 2008 §4.2)."""
46
+
47
+ SLOPE_SEARCH_UPPER_DEG: float = 35.0
48
+ """Upper bound of the brentq search, matching the scenario schema cap."""
49
+
50
+
51
+ def _dp_balance_residual(
52
+ slope_deg: float,
53
+ *,
54
+ wheel: WheelGeometry,
55
+ soil: SoilParameters,
56
+ total_mass_kg: float,
57
+ n_wheels: int,
58
+ gravity_m_per_s2: float,
59
+ max_slip: float,
60
+ ) -> float:
61
+ """Available minus required drawbar pull per wheel (in N).
62
+
63
+ Positive = rover can climb this slope with margin to spare;
64
+ negative = unclimbable.
65
+ """
66
+ theta = math.radians(slope_deg)
67
+ weight_n = total_mass_kg * gravity_m_per_s2
68
+ load_per_wheel_n = weight_n * math.cos(theta) / n_wheels
69
+ required_dp_n = weight_n * math.sin(theta) / n_wheels
70
+
71
+ forces = single_wheel_forces(wheel, soil, load_per_wheel_n, slip=max_slip)
72
+ return forces.drawbar_pull_n - required_dp_n
73
+
74
+
75
+ def max_climbable_slope_deg(
76
+ wheel: WheelGeometry,
77
+ soil: SoilParameters,
78
+ total_mass_kg: float,
79
+ n_wheels: int,
80
+ *,
81
+ gravity_m_per_s2: float = DEFAULT_LUNAR_GRAVITY_M_PER_S2,
82
+ max_slip: float = DEFAULT_MAX_SLIP_FOR_CAPABILITY,
83
+ ) -> float:
84
+ """Largest slope (deg) this design can climb on this soil.
85
+
86
+ Parameters
87
+ ----------
88
+ wheel, soil
89
+ Bekker-Wong geometry and soil parameters.
90
+ total_mass_kg
91
+ Vehicle mass (from the mass model).
92
+ n_wheels
93
+ Number of driven wheels.
94
+ gravity_m_per_s2
95
+ Surface gravity, default lunar.
96
+ max_slip
97
+ Slip ratio at which to evaluate max available DP. 0.6 is the
98
+ conventional choice for short-duration peak pull (Wong 2008).
99
+
100
+ Returns
101
+ -------
102
+ float
103
+ Slope in degrees, in ``[0, 35]``. Returns 35 if the rover can
104
+ climb at least that steep (the schema cap). Returns 0 if the
105
+ rover cannot move on flat ground.
106
+ """
107
+ if total_mass_kg <= 0.0 or n_wheels <= 0:
108
+ raise ValueError("total_mass_kg and n_wheels must be positive.")
109
+
110
+ def residual(slope_deg: float) -> float:
111
+ return _dp_balance_residual(
112
+ slope_deg,
113
+ wheel=wheel,
114
+ soil=soil,
115
+ total_mass_kg=total_mass_kg,
116
+ n_wheels=n_wheels,
117
+ gravity_m_per_s2=gravity_m_per_s2,
118
+ max_slip=max_slip,
119
+ )
120
+
121
+ if residual(0.0) <= 0.0:
122
+ # Cannot move on flat ground (e.g. wheel is buried); return 0.
123
+ return 0.0
124
+
125
+ if residual(SLOPE_SEARCH_UPPER_DEG) >= 0.0:
126
+ # Rover can climb at least the schema cap.
127
+ return SLOPE_SEARCH_UPPER_DEG
128
+
129
+ return float(brentq(residual, 0.0, SLOPE_SEARCH_UPPER_DEG, xtol=1e-3, rtol=1e-4))
roverdevkit/mission/configs/cadre_polar_unit.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation scenario — NASA JPL CADRE flotilla single unit.
2
+ # Not a canonical tradespace scenario; used only by
3
+ # roverdevkit/validation/rover_registry.py for the design-target
4
+ # Layer-1 surrogate sanity check (CADRE is a multi-rover technology
5
+ # demonstration; this scenario describes one unit operating as a
6
+ # member of the flotilla).
7
+ #
8
+ # Source: Rothenbuchner et al. 2023 IEEE Aerospace #2300 "Cooperative
9
+ # Autonomous Distributed Robotic Exploration (CADRE)"; NASA/JPL CADRE
10
+ # project page; CADRE flotilla press materials (2024-2025 launch and
11
+ # deployment window onto a Commercial Lunar Payload Services lander).
12
+ # Each CADRE unit is a ~2 kg 4-wheel rover designed for multi-rover
13
+ # coordination demonstrations and Nokia/Bell Labs LTE communications
14
+ # trials at the lunar south pole region. As of the registry's snapshot
15
+ # the rovers had launched but ground-truth surface operations data was
16
+ # still propagating; entry is treated as `is_flown=False` design-target
17
+ # until the published surface-mission report is available.
18
+ #
19
+ # `traverse_distance_m` is non-binding: CADRE's mission demonstration
20
+ # is choreography over short distances (tens of metres), not range.
21
+ # Set as a soft cap an order of magnitude above the demonstration
22
+ # target.
23
+ name: cadre_polar_unit
24
+ latitude_deg: -85.0
25
+ traverse_distance_m: 200.0
26
+ terrain_class: polar_regolith
27
+ soil_simulant: Apollo_regolith_loose
28
+ mission_duration_earth_days: 14.0
29
+ max_slope_deg: 8.0
30
+ sun_geometry: polar_intermittent
31
+ # δ_ops calibrated for a coordinated-demonstration flotilla rover:
32
+ # slow ground-ops cadence, frequent stop-look-rendezvous moves rather
33
+ # than continuous drive. Class-typical for sub-class polar micro-rovers
34
+ # (between Pragyan 0.008 and MoonRanger 0.20).
35
+ operational_duty_cycle: 0.05
36
+ # Schema v9: CADRE per-unit payload = stereo-camera + LTE comms-ranging
37
+ # experiment payload, ~0.3 kg. payload_power_w held at 0 for this
38
+ # validation scenario (experiment payload off during the short
39
+ # coordinated drive moves).
40
+ payload_mass_kg: 0.3
41
+ payload_power_w: 0.0
roverdevkit/mission/configs/chandrayaan3_pragyan.yaml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation scenario — Chandrayaan-3 Pragyan mission (ISRO, 2023).
2
+ # Not a canonical tradespace scenario; used only by
3
+ # roverdevkit/validation/rover_registry.py for the real-rover
4
+ # cross-check.
5
+ #
6
+ # Source: ISRO Chandrayaan-3 press materials (landing 23 Aug 2023) and
7
+ # Scientific Reports (Nature) 14:24178 (2024) for landing coordinates
8
+ # 69.37 S, 32.35 E. Pragyan operated through a single lunar day
9
+ # (~14 Earth days) and failed to reawaken after lunar night. Published
10
+ # in-mission traverse distance ~101.4 m over ~10 active Earth days.
11
+ name: chandrayaan3_pragyan
12
+ latitude_deg: -69.4
13
+ traverse_distance_m: 500.0 # soft cap; published actual ~101 m
14
+ terrain_class: polar_regolith
15
+ soil_simulant: Apollo_regolith_loose
16
+ mission_duration_earth_days: 14.0 # one lunar day, hot case
17
+ max_slope_deg: 5.0 # typical-ops mean; Pragyan stayed on near-flat terrain
18
+
19
+ sun_geometry: polar_intermittent
20
+ # Schema v6 (v6 schema update): δ_ops calibrated to Pragyan's published
21
+ # 101 m / ~10 active Earth days at ~6.6 cm/s nominal v_cruise.
22
+ # Implied δ_ops = (101/(10*86400)) / 0.066 ≈ 0.0018; we use 0.008
23
+ # (the design-doc historical-conservative anchor) which sits
24
+ # between Pragyan's own ops (~0.002) and a more aspirational
25
+ # polar concept. Used only by the registry-rover validation gate.
26
+ operational_duty_cycle: 0.008
27
+ # Schema v9: Pragyan science payload = APXS + LIBS spectrometers,
28
+ # ~3.5 kg (Chandrayaan-3 instrument suite). payload_power_w held at 0
29
+ # for this validation scenario: the published traverse / peak-solar /
30
+ # thermal truth was measured during mobility windows with the
31
+ # instruments powered down, so the mobility-validation gate sees mass
32
+ # (always carried) but not instrument standby draw.
33
+ payload_mass_kg: 3.5
34
+ payload_power_w: 0.0
roverdevkit/mission/configs/change4_yutu2_per_lunar_day.yaml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Validation scenario — Chang'e-4 Yutu-2 single-lunar-day drive window.
2
+ # Not a canonical tradespace scenario; used only by
3
+ # roverdevkit/validation/rover_registry.py for the real-rover validation real-rover
4
+ # cross-check.
5
+ #
6
+ # Source: Di et al. 2020, Icarus; Ding et al. 2022, Acta Astronautica;
7
+ # CNSA mission dispatches. Yutu-2 landed 2019-01-03 at 45.5 S on the
8
+ # lunar far side (Von Karman crater) and has driven ~1.6 km cumulatively
9
+ # over 60+ lunar days. Published per-lunar-day drive distances from
10
+ # the first year range ~20-30 m; we treat that as the truth number the
11
+ # model must reproduce for *one* lunar-day active window.
12
+ #
13
+ # Rationale for the reduced mission_duration_earth_days: Yutu-2's drive
14
+ # operations are concentrated into a few Earth-day activity window per
15
+ # lunar day (not all 14). By setting the sim window to 5 days of active
16
+ # ops we approximate the drive schedule without needing a proper
17
+ # hibernation model (that is v2 work per traverse_sim.py docstring).
18
+ name: change4_yutu2_per_lunar_day
19
+ latitude_deg: -45.5
20
+ traverse_distance_m: 200.0
21
+ terrain_class: mare_nominal
22
+ soil_simulant: Apollo_regolith_nominal
23
+ mission_duration_earth_days: 5.0
24
+ max_slope_deg: 5.0 # typical-ops mean across Yutu-2's Von Karman traverse
25
+ sun_geometry: diurnal
26
+ # Schema v6 (v6 schema update): δ_ops calibrated to Yutu-2's
27
+ # historical-conservative ops (~0.001-0.0014 implied from total
28
+ # ~1.6 km / 60+ lunar days). Used only by the registry-rover
29
+ # validation gate; see data/analytical/SCHEMA.md.
30
+ operational_duty_cycle: 0.001
31
+ # Schema v9: Yutu-2 science payload = Lunar Penetrating Radar + VNIS +
32
+ # APXS + panoramic/navigation cameras, ~25 kg (Chang'e-4 payload
33
+ # manifest). payload_power_w held at 0 for this validation scenario:
34
+ # the published per-lunar-day drive distance was achieved with science
35
+ # instruments off during the short drive windows, so the
36
+ # mobility-validation gate (and especially the hot-case thermal check)
37
+ # sees payload mass but not instrument power.
38
+ payload_mass_kg: 25.0
39
+ payload_power_w: 0.0
roverdevkit/mission/configs/crater_rim_micro.yaml ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Class-generic crater-rim micro-rover scenario (rediscovery library).
2
+ #
3
+ # Used only by the Layer-5 rediscovery harness
4
+ # (`roverdevkit.validation.rover_rediscovery`). NOT a canonical
5
+ # tradespace scenario (the canonical crater-rim scenario lives in
6
+ # `crater_rim_survey.yaml` and is returned by `list_scenarios()`);
7
+ # this scenario is excluded from `list_scenarios()` and exposed by
8
+ # `list_class_generic_micro_scenarios()` instead.
9
+ #
10
+ # Leakage controls (why this exists as a separate file)
11
+ # -----------------------------------------------------
12
+ # The canonical `crater_rim_survey.yaml` pins
13
+ # `operational_duty_cycle: 0.20`, calibrated against MER-A / MER-B
14
+ # daily averages on uneven terrain. No registry rover is operating on
15
+ # this scenario family today, so the leakage risk is currently latent
16
+ # — but the class-generic library still pins δ_ops to a flat 0.10
17
+ # across all four scenarios for symmetry.
18
+ #
19
+ # Everything else (latitude, traverse-distance non-binding budget,
20
+ # duration, max_slope, sun_geometry, terrain_class, soil_simulant)
21
+ # inherits from the canonical scenario because those are environmental
22
+ # facts the rover does not choose.
23
+ name: crater_rim_micro
24
+ latitude_deg: 0.0
25
+ traverse_distance_m: 25000.0
26
+ terrain_class: mare_nominal
27
+ soil_simulant: Apollo_regolith_nominal
28
+ mission_duration_earth_days: 5.0
29
+ max_slope_deg: 18.0
30
+ sun_geometry: diurnal
31
+ operational_duty_cycle: 0.10
32
+ # Schema v9: payload left at 0 (class-neutral). The rediscovery harness
33
+ # forwards each target rover's published payload as a per-call override
34
+ # to both the rover re-evaluation and every NSGA-II candidate, so the
35
+ # scenario itself carries no per-rover payload label (same leakage
36
+ # logic as the flat δ_ops anchor above).
37
+ payload_mass_kg: 0.0
38
+ payload_power_w: 0.0
roverdevkit/mission/configs/crater_rim_survey.yaml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Scenario 4 — crater rim survey, short traverse, lots of slope changes.
2
+ # Canonical crater-rim traverse scenario; optimizer objective is energy-optimal traverse.
3
+ #
4
+ # `traverse_distance_m` is non-binding (see equatorial_mare_traverse).
5
+ # 25 km is just below the 5-day theoretical reach at max speed / duty
6
+ # (~25.9 km); this keeps range energy-/duty-bound across the LHS sweep.
7
+ name: crater_rim_survey
8
+ latitude_deg: 0.0
9
+ traverse_distance_m: 25000.0
10
+ terrain_class: mare_nominal
11
+ soil_simulant: Apollo_regolith_nominal
12
+ mission_duration_earth_days: 5.0
13
+ max_slope_deg: 18.0
14
+ sun_geometry: diurnal
15
+ # Schema v6 (v6 schema update): per-scenario default ops duty cycle.
16
+ # Anchored on MER-A/B daily averages (~0.15-0.20 in typical drive
17
+ # sols on uneven terrain). See data/analytical/SCHEMA.md.
18
+ operational_duty_cycle: 0.20
19
+ # Schema v9: scientific-payload mission requirement. Crater-rim survey
20
+ # carries a mid-weight imaging + spectroscopy suite (~4 kg / 5 W).
21
+ # Surrogate LHS samples payload independently; webapp / evaluator
22
+ # default.
23
+ payload_mass_kg: 4.0
24
+ payload_power_w: 5.0
roverdevkit/mission/configs/equatorial_mare_traverse.yaml ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Scenario 1 — Apollo-17-like equatorial mare traverse, 14-day mission.
2
+ # Canonical Apollo-17-like equatorial mare traverse scenario.
3
+ #
4
+ # `traverse_distance_m` is set as a *non-binding budget* rather than a
5
+ # short mission assignment: 80 km is just above the 14-day theoretical
6
+ # reach at max speed (0.10 m/s) and max duty cycle (0.6), i.e.
7
+ # 14 * 86400 * 0.10 * 0.6 ≈ 72.6 km. This ensures the surrogate-training LHS sweep
8
+ # sees a range signal that responds to speed/duty/energy instead of
9
+ # saturating at an assigned distance cap.
10
+ name: equatorial_mare_traverse
11
+ latitude_deg: 20.2 # Apollo 17 landing-site latitude
12
+ traverse_distance_m: 80000.0
13
+ terrain_class: mare_nominal
14
+ soil_simulant: Apollo_regolith_nominal
15
+ mission_duration_earth_days: 14.0
16
+ max_slope_deg: 15.0
17
+ sun_geometry: diurnal
18
+ # Schema v6 (v6 schema update): per-scenario default ops duty cycle.
19
+ # Calibrated against Apollo-17 LRV (~0.5 manned EVA duty) and
20
+ # unmanned long-traverse references (~0.30). See
21
+ # the per-scenario calibration rationale.
22
+ operational_duty_cycle: 0.30
23
+ # Schema v9: scientific-payload mission requirement. Class-typical
24
+ # default for a mare science traverse (cameras + spectrometer +
25
+ # sample tools, ~5 kg / 5 W); the webapp Mission-Inputs panel and the
26
+ # evaluator use this unless the caller passes an override, and the
27
+ # surrogate LHS samples payload independently of this default.
28
+ payload_mass_kg: 5.0
29
+ payload_power_w: 5.0
roverdevkit/mission/configs/highland_micro.yaml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Class-generic highland micro-rover scenario (rediscovery library).
2
+ #
3
+ # Used only by the Layer-5 rediscovery harness
4
+ # (`roverdevkit.validation.rover_rediscovery`). NOT a canonical
5
+ # tradespace scenario (the canonical highland scenario lives in
6
+ # `highland_slope_capability.yaml` and is returned by
7
+ # `list_scenarios()`); this scenario is excluded from
8
+ # `list_scenarios()` and exposed by
9
+ # `list_class_generic_micro_scenarios()` instead.
10
+ #
11
+ # Leakage controls (why this exists as a separate file)
12
+ # -----------------------------------------------------
13
+ # The canonical `highland_slope_capability.yaml` pins
14
+ # `operational_duty_cycle: 0.15`, calibrated against "slope-focused
15
+ # missions are systematically slower than nominal terrain missions".
16
+ # No registry rover is operating on highland_dense terrain today, so
17
+ # the leakage risk is currently latent rather than active — but the
18
+ # class-generic library still pins δ_ops to a flat 0.10 across all
19
+ # four scenarios so the leakage-control story is symmetric rather than
20
+ # "we pinned the three scenarios where we have flown rovers but kept
21
+ # the fourth at its calibrated value."
22
+ #
23
+ # Everything else (latitude, traverse-distance non-binding budget,
24
+ # duration, max_slope, sun_geometry, terrain_class, soil_simulant)
25
+ # inherits from the canonical scenario because those are environmental
26
+ # facts the rover does not choose.
27
+ name: highland_micro
28
+ latitude_deg: 10.0
29
+ traverse_distance_m: 20000.0
30
+ terrain_class: highland_dense
31
+ soil_simulant: Apollo_regolith_loose
32
+ mission_duration_earth_days: 7.0
33
+ max_slope_deg: 25.0
34
+ sun_geometry: diurnal
35
+ operational_duty_cycle: 0.10
36
+ # Schema v9: payload left at 0 (class-neutral). The rediscovery harness
37
+ # forwards each target rover's published payload as a per-call override
38
+ # to both the rover re-evaluation and every NSGA-II candidate, so the
39
+ # scenario itself carries no per-rover payload label (same leakage
40
+ # logic as the flat δ_ops anchor above).
41
+ payload_mass_kg: 0.0
42
+ payload_power_w: 0.0
roverdevkit/mission/configs/highland_slope_capability.yaml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Scenario 3 — highland slope capability on loose regolith.
2
+ # Canonical highland slope-capability scenario; optimizer objective is usually min-mass
3
+ # subject to slope-capability ≥ max_slope_deg.
4
+ #
5
+ # On this scenario the slope-capability constraint dominates: the focus
6
+ # is whether a design can climb a sustained loose-regolith grade at all,
7
+ # not how far it travels. Treat ``range_km`` here as a feasibility floor
8
+ # (range > 0 means the rover can move) rather than an optimization objective.
9
+ #
10
+ # Target slope is 15°. This sits inside the validated Bekker-Wong slope
11
+ # envelope on loose Apollo regolith (the strongest micro-rover design in
12
+ # the search space tops out at ~19.6° under the analytical kernel), so the
13
+ # scenario yields a meaningful Pareto front of designs that genuinely clear
14
+ # the grade. A steeper 25° target is infeasible for every design in the
15
+ # space under pure BW physics (loose regolith friction limit), and would
16
+ # produce an empty front.
17
+ name: highland_slope_capability
18
+ latitude_deg: 10.0
19
+ traverse_distance_m: 20000.0
20
+ terrain_class: highland_dense
21
+ soil_simulant: Apollo_regolith_loose # worst-case soil for slope climbing
22
+ mission_duration_earth_days: 7.0
23
+ max_slope_deg: 15.0
24
+ sun_geometry: diurnal
25
+ # Schema v6 (v6 schema update): per-scenario default ops duty cycle.
26
+ # Slope-focused missions are systematically slower than nominal
27
+ # terrain missions (more careful traverse planning). See
28
+ # the per-scenario calibration rationale.
29
+ operational_duty_cycle: 0.15
30
+ # Schema v9: scientific-payload mission requirement. A slope-capability
31
+ # demonstrator carries a lighter payload (stereo cameras + IMU science,
32
+ # ~3 kg / 3 W) so mobility margin goes to the climb. Surrogate LHS
33
+ # samples payload independently; this is the webapp / evaluator default.
34
+ payload_mass_kg: 3.0
35
+ payload_power_w: 3.0