Skip to content

Commit 36381dc

Browse files
authored
ENH: pull v1.13 from RocketPy (#16)
1 parent e06285b commit 36381dc

190 files changed

Lines changed: 34117 additions & 3998 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/AGENTS.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# RocketPy Workspace Instructions
2+
3+
## Code Style
4+
- Use snake_case for variables, functions, methods, and modules. Use descriptive names.
5+
- Use PascalCase for classes and UPPER_SNAKE_CASE for constants.
6+
- Keep lines at 88 characters and follow PEP 8 unless existing code in the target file differs.
7+
- Run Ruff as the source of truth for formatting/import organization:
8+
- `make format`
9+
- `make lint`
10+
- Use NumPy-style docstrings for public classes, methods, and functions, including units.
11+
- In case of tooling drift between docs and config, prefer current repository tooling in `Makefile`
12+
and `pyproject.toml`.
13+
14+
## Architecture
15+
- RocketPy is a modular Python library; keep feature logic in the correct package boundary:
16+
- `rocketpy/simulation`: flight simulation and Monte Carlo orchestration.
17+
- `rocketpy/rocket`, `rocketpy/motors`, `rocketpy/environment`: domain models.
18+
- `rocketpy/mathutils`: numerical primitives and interpolation utilities.
19+
- `rocketpy/plots`, `rocketpy/prints`: output and visualization layers.
20+
- Prefer extending existing classes/patterns over introducing new top-level abstractions.
21+
- Preserve public API stability in `rocketpy/__init__.py` exports.
22+
23+
## Build and Test
24+
- Use Makefile targets for OS-agnostic workflows:
25+
- `make install`
26+
- `make pytest`
27+
- `make pytest-slow`
28+
- `make coverage`
29+
- `make coverage-report`
30+
- `make build-docs`
31+
- Before finishing code changes, run focused tests first, then broader relevant suites.
32+
- When running Python directly in this workspace, prefer the project virtual
33+
environment interpreter: `.venv/bin/python` on Unix/macOS or
34+
`.venv/Scripts/python.exe` on Windows.
35+
- Slow tests are explicitly marked with `@pytest.mark.slow` and are run with `make pytest-slow`.
36+
- For docs changes, check `make build-docs` output and resolve warnings/errors when practical.
37+
38+
## Development Workflow
39+
- Target pull requests to `develop` by default; `master` is the stable branch.
40+
- Use branch names in `type/description` format, such as:
41+
- `bug/<description>`
42+
- `doc/<description>`
43+
- `enh/<description>`
44+
- `mnt/<description>`
45+
- `tst/<description>`
46+
- Prefer rebasing feature branches on top of `develop` to keep history linear.
47+
- Keep commit and PR titles explicit and prefixed with project acronyms when possible:
48+
- `BUG`, `DOC`, `ENH`, `MNT`, `TST`, `BLD`, `REL`, `REV`, `STY`, `DEV`.
49+
50+
## Conventions
51+
- SI units are the default. Document units and coordinate-system references explicitly.
52+
- Position/reference-frame arguments are critical in this codebase. Be explicit about orientation
53+
(for example, `tail_to_nose`, `nozzle_to_combustion_chamber`).
54+
- Include unit tests for new behavior. Follow AAA structure and clear test names.
55+
- Use fixtures from `tests/fixtures`; if adding a new fixture module, update `tests/conftest.py`.
56+
- Use `pytest.approx` for floating-point checks where appropriate.
57+
- Use `@cached_property` for expensive computations when helpful, and be careful with stale-cache
58+
behavior when underlying mutable state changes.
59+
- Keep behavior backward compatible across the public API exported via `rocketpy/__init__.py`.
60+
- Prefer extending existing module patterns over creating new top-level package structure.
61+
62+
## Testing Taxonomy
63+
- Unit tests are mandatory for new behavior.
64+
- Unit tests in RocketPy can be sociable (real collaborators allowed) but should still be fast and
65+
method-focused.
66+
- Treat tests as integration tests when they are strongly I/O-oriented or broad across many methods,
67+
including `all_info()` convention cases.
68+
- Acceptance tests represent realistic user/flight scenarios and may compare simulation thresholds to
69+
known flight data.
70+
71+
## Documentation Links
72+
- Contributor workflow and setup: `docs/development/setting_up.rst`
73+
- Style and naming details: `docs/development/style_guide.rst`
74+
- Testing philosophy and structure: `docs/development/testing.rst`
75+
- API reference conventions: `docs/reference/index.rst`
76+
- Domain/physics background: `docs/technical/index.rst`
77+
78+
## Scoped Customizations
79+
- Simulation-specific rules: `.agents/skills/simulation-safety/SKILL.md`
80+
- Test-authoring rules: `.agents/skills/tests-python/SKILL.md`
81+
- RST/Sphinx documentation rules: `.agents/skills/sphinx-docs/SKILL.md`
82+
- Specialized review persona: `.agents/skills/rocketpy-reviewer/SKILL.md`
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
description: "Physics-safe RocketPy code review agent. Use for pull request review, unit consistency checks, coordinate-frame validation, cached-property risk detection, and regression-focused test-gap analysis."
3+
name: "RocketPy Reviewer"
4+
tools: [read, search]
5+
argument-hint: "Review these changes for physics correctness and regression risk: <scope or files>"
6+
user-invocable: true
7+
---
8+
You are a RocketPy-focused reviewer for physics safety and regression risk.
9+
10+
## Goals
11+
12+
- Detect behavioral regressions and numerical/physics risks before merge.
13+
- Validate unit consistency and coordinate/reference-frame correctness.
14+
- Identify stale-cache risks when `@cached_property` interacts with mutable state.
15+
- Check test coverage quality for changed behavior.
16+
- Verify alignment with RocketPy workflow and contributor conventions.
17+
18+
## Review Priorities
19+
20+
1. Correctness and safety issues (highest severity).
21+
2. Behavioral regressions and API compatibility.
22+
3. Numerical stability and tolerance correctness.
23+
4. Missing tests or weak assertions.
24+
5. Documentation mismatches affecting users.
25+
6. Workflow violations (test placement, branch/PR conventions, or missing validation evidence).
26+
27+
## RocketPy-Specific Checks
28+
29+
- SI units are explicit and consistent.
30+
- Orientation conventions are unambiguous (`tail_to_nose`, `nozzle_to_combustion_chamber`, etc.).
31+
- New/changed simulation logic does not silently invalidate cached values.
32+
- Floating-point assertions use `pytest.approx` where needed.
33+
- New fixtures are wired through `tests/conftest.py` when applicable.
34+
- Test type is appropriate for scope (`unit`, `integration`, `acceptance`) and `all_info()`-style tests
35+
are not misclassified.
36+
- New behavior includes at least one regression-oriented test and relevant edge-case checks.
37+
- For docs-affecting changes, references and paths remain valid and build warnings are addressed.
38+
- Tooling recommendations match current repository setup (prefer Makefile plus `pyproject.toml`
39+
settings when docs are outdated).
40+
41+
## Validation Expectations
42+
43+
- Prefer focused test runs first, then broader relevant suites.
44+
- Recommend `make format` and `make lint` when style/lint risks are present.
45+
- Recommend `make build-docs` when `.rst` files or API docs are changed.
46+
47+
## Output Format
48+
49+
Provide findings first, ordered by severity.
50+
For each finding include:
51+
- Severity: Critical, High, Medium, or Low
52+
- Location: file path and line
53+
- Why it matters: behavioral or physics risk
54+
- Suggested fix: concrete, minimal change
55+
56+
After findings, include:
57+
- Open questions or assumptions
58+
- Residual risks or testing gaps
59+
- Brief change summary
60+
- Suggested validation commands (only when useful)
61+
62+
If no findings are identified, state that explicitly and still report residual risks/testing gaps.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
description: "Use when editing rocketpy/simulation code, including Flight state updates, Monte Carlo orchestration, post-processing, or cached computations. Covers simulation state safety, unit/reference-frame clarity, and regression checks."
3+
name: "Simulation Safety"
4+
applyTo: "rocketpy/simulation/**/*.py"
5+
---
6+
# Simulation Safety Guidelines
7+
8+
- Keep simulation logic inside `rocketpy/simulation` and avoid leaking domain behavior that belongs in
9+
`rocketpy/rocket`, `rocketpy/motors`, or `rocketpy/environment`.
10+
- Preserve public API behavior and exported names used by `rocketpy/__init__.py`.
11+
- Prefer extending existing simulation components before creating new abstractions:
12+
- `flight.py`: simulation state, integration flow, and post-processing.
13+
- `monte_carlo.py`: orchestration and statistical execution workflows.
14+
- `flight_data_exporter.py` and `flight_data_importer.py`: persistence and interchange.
15+
- `flight_comparator.py`: comparative analysis outputs.
16+
- Be explicit with physical units and reference frames in new parameters, attributes, and docstrings.
17+
- For position/orientation-sensitive behavior, use explicit conventions (for example
18+
`tail_to_nose`, `nozzle_to_combustion_chamber`) and avoid implicit assumptions.
19+
- Treat state mutation carefully when cached values exist.
20+
- If changes can invalidate `@cached_property` values, either avoid post-computation mutation or
21+
explicitly invalidate affected caches in a controlled, documented way.
22+
- Keep numerical behavior deterministic unless stochastic behavior is intentional and documented.
23+
- For Monte Carlo and stochastic code paths, make randomness controllable and reproducible when tests
24+
rely on it.
25+
- Prefer vectorized NumPy operations for hot paths and avoid introducing Python loops in
26+
performance-critical sections without justification.
27+
- Guard against numerical edge cases (zero/near-zero denominators, interpolation limits, and boundary
28+
conditions).
29+
- Do not change default numerical tolerances or integration behavior without documenting motivation and
30+
validating regression impact.
31+
- Add focused regression tests for changed behavior, including edge cases and orientation-dependent
32+
behavior.
33+
- For floating-point expectations, use `pytest.approx` with meaningful tolerances.
34+
- Run focused tests first, then broader relevant tests (`make pytest` and `make pytest-slow` when
35+
applicable).
36+
37+
See:
38+
- `docs/development/testing.rst`
39+
- `docs/development/style_guide.rst`
40+
- `docs/development/setting_up.rst`
41+
- `docs/technical/index.rst`
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
description: "Use when writing or editing docs/**/*.rst. Covers Sphinx/reStructuredText conventions, cross-references, toctree hygiene, and RocketPy unit/reference-frame documentation requirements."
3+
name: "Sphinx RST Conventions"
4+
applyTo: "docs/**/*.rst"
5+
---
6+
# Sphinx and RST Guidelines
7+
8+
- Follow existing heading hierarchy and style in the target document.
9+
- Prefer linking to existing documentation pages instead of duplicating content.
10+
- Use Sphinx cross-references where appropriate (`:class:`, `:func:`, `:mod:`, `:doc:`, `:ref:`).
11+
- Keep API names and module paths consistent with current code exports.
12+
- Document physical units and coordinate/reference-frame conventions explicitly.
13+
- Include concise, practical examples when introducing new user-facing behavior.
14+
- Keep prose clear and technical; avoid marketing language in development/reference docs.
15+
- When adding a new page, update the relevant `toctree` so it appears in navigation.
16+
- Use RocketPy docs build workflow:
17+
- `make build-docs` from repository root for normal validation.
18+
- If stale artifacts appear, clean docs build outputs via `cd docs && make clean`, then rebuild.
19+
- Treat new Sphinx warnings/errors as issues to fix or explicitly call out in review notes.
20+
- Keep `docs/index.rst` section structure coherent with user, development, reference, technical, and
21+
examples navigation.
22+
- Do not edit Sphinx-generated scaffolding files unless explicitly requested:
23+
- `docs/Makefile`
24+
- `docs/make.bat`
25+
- For API docs, ensure references remain aligned with exported/public objects and current module paths.
26+
27+
See:
28+
- `docs/index.rst`
29+
- `docs/development/build_docs.rst`
30+
- `docs/development/style_guide.rst`
31+
- `docs/reference/index.rst`
32+
- `docs/technical/index.rst`
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
description: "Use when creating or editing pytest files in tests/. Enforces AAA structure, naming conventions, fixture usage, parameterization, slow-test marking, and numerical assertion practices for RocketPy."
3+
name: "RocketPy Pytest Standards"
4+
applyTo: "tests/**/*.py"
5+
---
6+
# RocketPy Test Authoring Guidelines
7+
8+
- Unit tests are mandatory for new behavior.
9+
- Follow AAA structure in each test: Arrange, Act, Assert.
10+
- Use descriptive test names matching project convention:
11+
- `test_methodname`
12+
- `test_methodname_stateundertest`
13+
- `test_methodname_expectedbehaviour`
14+
- Include docstrings that clearly state expected behavior and context.
15+
- Prefer parameterization for scenario matrices instead of duplicated tests.
16+
- Classify tests correctly:
17+
- `tests/unit`: fast, method-focused tests (sociable unit tests are acceptable in RocketPy).
18+
- `tests/integration`: broad multi-method/component interactions and strongly I/O-oriented cases.
19+
- `tests/acceptance`: realistic end-user/flight scenarios with threshold-based expectations.
20+
- By RocketPy convention, tests centered on `all_info()` behavior are integration tests.
21+
- Reuse fixtures from `tests/fixtures` whenever possible.
22+
- Keep fixture organization aligned with existing categories under `tests/fixtures`
23+
(environment, flight, motor, rockets, surfaces, units, etc.).
24+
- If you add a new fixture module, update `tests/conftest.py` so fixtures are discoverable.
25+
- Keep tests deterministic: set seeds when randomness is involved and avoid unstable external
26+
dependencies unless integration behavior explicitly requires them.
27+
- Use `pytest.approx` for floating-point comparisons with realistic tolerances.
28+
- Mark expensive tests with `@pytest.mark.slow` and ensure they can run under the project slow-test
29+
workflow.
30+
- Include at least one negative or edge-case assertion for new behaviors.
31+
- When adding a bug fix, include a regression test that fails before the fix and passes after it.
32+
33+
See:
34+
- `docs/development/testing.rst`
35+
- `docs/development/style_guide.rst`
36+
- `docs/development/setting_up.rst`

.claude/hooks/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Claude Code hooks
2+
3+
Project-level [Claude Code hooks](https://docs.claude.com/en/docs/claude-code/hooks)
4+
that keep Claude's edits lint-clean and stop lint failures from reaching CI. They
5+
are wired up in [`.claude/settings.json`](../settings.json) and run automatically
6+
for anyone using Claude Code in this repo.
7+
8+
Both hooks only run **ruff** (the fast linter/formatter, ~0.15s for the whole
9+
repo). Pylint is intentionally left to CI: on this codebase its per-file startup
10+
cost is seconds, too slow for an interactive guard. ruff is also what has actually
11+
been breaking the `Linters` job.
12+
13+
## Hooks
14+
15+
| Hook | Event | What it does |
16+
| --- | --- | --- |
17+
| [`ruff_on_edit.py`](ruff_on_edit.py) | `PostToolUse` on `Edit`/`Write`/`MultiEdit` | After Claude edits a `.py` file, runs `ruff format` then `ruff check --fix` on that one file. Surfaces any unfixable lint issues back to Claude. |
18+
| [`ruff_guard_push.py`](ruff_guard_push.py) | `PreToolUse` on `Bash` | Before a `git push`, runs `ruff check` + `ruff format --check` over the repo and **blocks the push** if either would fail (i.e. before it turns CI red). |
19+
20+
## Design guarantees
21+
22+
- **Cross-platform** (Windows / macOS / Linux): pure Python, no shell built-ins,
23+
no OS-specific paths. ruff is invoked as `<current python> -m ruff` so it
24+
resolves to the same environment the hook runs in.
25+
- **Never gets in the way**: if `ruff` is not installed, both hooks are a silent
26+
no-op. The push guard only acts on actual `git push` commands.
27+
28+
## Requirements
29+
30+
A working `python` on `PATH` (an activated virtualenv is the normal setup) with
31+
`ruff` installed: `pip install ruff` — already included in `pip install .[tests]`.
32+
33+
## Testing a hook manually
34+
35+
Pipe a sample event payload to a hook on stdin:
36+
37+
```bash
38+
printf '{"tool_input":{"file_path":"some_file.py"}}' | python .claude/hooks/ruff_on_edit.py
39+
printf '{"tool_input":{"command":"git push"}}' | python .claude/hooks/ruff_guard_push.py
40+
```

.claude/hooks/ruff_guard_push.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
"""Claude Code ``PreToolUse`` hook: block ``git push`` if ruff would fail in CI.
3+
4+
Runs before every ``Bash`` command. If the command is a ``git push``, it runs the
5+
two fast ruff steps from the Linters CI job (``ruff check .`` and
6+
``ruff format --check .``) over the repo. If either would fail, the push is
7+
blocked (exit code 2) and the failure is reported back to Claude so it gets fixed
8+
*before* it turns the CI red.
9+
10+
Only ruff runs here (both steps together are ~0.15s). Pylint is intentionally left
11+
to CI: on this repo pylint costs seconds-per-file of startup, which is too slow for
12+
an interactive guard. ruff is what has actually been breaking the Linters job.
13+
14+
Design notes
15+
------------
16+
* ruff is invoked as ``<current python> -m ruff`` (see ``ruff_on_edit.py``).
17+
* Cross-platform: pure Python, no shell built-ins, no OS-specific paths.
18+
* Silent no-op when the command is not a push, or when ruff is not installed.
19+
"""
20+
21+
import importlib.util
22+
import json
23+
import os
24+
import re
25+
import subprocess
26+
import sys
27+
28+
# Match ``git push`` as a real command (start of line or after a shell
29+
# separator), tolerating global flags like ``git -C . push``. Avoids most
30+
# false positives from the substring "push" appearing elsewhere.
31+
_GIT_PUSH = re.compile(r"(?:^|[\s;&|(`])git(?:\s+-\S+|\s+--\S+)*\s+push\b")
32+
33+
34+
def main() -> int:
35+
try:
36+
payload = json.load(sys.stdin)
37+
except (json.JSONDecodeError, ValueError):
38+
return 0
39+
40+
command = (payload.get("tool_input") or {}).get("command", "")
41+
if not isinstance(command, str) or not _GIT_PUSH.search(command):
42+
return 0
43+
44+
if importlib.util.find_spec("ruff") is None:
45+
return 0 # ruff not installed: don't block pushes.
46+
47+
# Target the project root explicitly. ``CLAUDE_PROJECT_DIR`` is set by Claude
48+
# Code to the native-format project root; passing it as a path argument (not
49+
# as the subprocess ``cwd``) keeps this robust across platforms/shells. Falls
50+
# back to ".", which resolves against the hook's working directory.
51+
target = os.environ.get("CLAUDE_PROJECT_DIR") or "."
52+
ruff = [sys.executable, "-m", "ruff"]
53+
check = subprocess.run(ruff + ["check", target], capture_output=True, text=True)
54+
fmt = subprocess.run(
55+
ruff + ["format", "--check", target], capture_output=True, text=True
56+
)
57+
if check.returncode == 0 and fmt.returncode == 0:
58+
return 0
59+
60+
out = [
61+
"[ruff] Push blocked — this would fail the Linters CI job. "
62+
"Fix the issues below, then push again.\n"
63+
]
64+
if check.returncode != 0:
65+
out.append("--- ruff check ---\n" + (check.stdout or "") + (check.stderr or ""))
66+
if fmt.returncode != 0:
67+
out.append(
68+
"--- ruff format --check . ---\n"
69+
+ (fmt.stdout or "")
70+
+ (fmt.stderr or "")
71+
+ "\nFix formatting with: python -m ruff format .\n"
72+
)
73+
sys.stderr.write("\n".join(out))
74+
return 2
75+
76+
77+
if __name__ == "__main__":
78+
raise SystemExit(main())

0 commit comments

Comments
 (0)