Skip to content

Enh/tank geometry fluid density#72

Draft
aasitvora99 wants to merge 12 commits intomasterfrom
enh/tank-geometry-fluid-density
Draft

Enh/tank geometry fluid density#72
aasitvora99 wants to merge 12 commits intomasterfrom
enh/tank-geometry-fluid-density

Conversation

@aasitvora99
Copy link
Copy Markdown
Member

No description provided.

aasitvora99 and others added 12 commits April 20, 2026 12:40
Exposes structured drawing geometry that mirrors rocketpy.Rocket.draw(),
so clients can redraw a rocket using the same shape math rocketpy uses
without server-side rendering or duplicated geometry logic in the UI.

Response carries per-surface shape_x/shape_y arrays, body tube segments,
motor patch polygons (nozzle, chamber, grains, tanks, outline), rail
button positions, sensors, t=0 CG/CP, and drawing bounds. Coordinates
are already transformed into the draw frame rocketpy uses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align MotorModel and MotorTank with RocketPy's actual constructor
requirements so invalid motors are rejected at the API boundary with a
clear error rather than crashing deep inside RocketPy at simulate time.

- MotorModel.validate_dry_inertia_for_kind: SOLID / LIQUID / HYBRID
  motors in RocketPy require dry_inertia with no default. Only
  GenericMotor accepts (0, 0, 0). Reject the default tuple for every
  kind except GENERIC with a message the user can act on.
- MotorTank.discretize: change to Optional[int] = 100 to match the
  RocketPy Tank classes' default. Forms can now omit the field and still
  submit successfully.
- stub_motor_dump fixture: use dry_inertia=[0.1, 0.1, 0.1] so tests that
  override motor_kind to SOLID / LIQUID / HYBRID still pass the new
  validator without each having to add a dry_inertia override locally.
RocketPy's rocket.draw() does not draw a combustion chamber for
GenericMotor because _MotorPlots._generate_combustion_chamber reads
grain-only attributes (grain_initial_height, grain_outer_radius, etc.)
that GenericMotor lacks — it only emits a nozzle. Users who populate
chamber_radius / chamber_height / chamber_position then saw no chamber
in the jarvis playground.

Add a GenericMotor branch in RocketService._build_motor_geometry that
constructs an equivalent rectangular chamber patch from the chamber_*
fields. Vertex ordering mirrors _generate_combustion_chamber so the
patch flows through _generate_motor_region for outline assembly the
same way a SolidMotor chamber does.

Patch is emitted with role='chamber', flowing through the existing
drawingMotorSchema + GeometryRocket renderer without frontend changes.
…eneric kinds

Two related fixes surfaced during jarvis form refactor.

1. MotorModel.burn_time: float  →  Optional[float] = None.
   RocketPy's LiquidMotor / HybridMotor / SolidMotor all auto-detect
   burn_time from the thrust_source array span — forcing clients to
   supply it was wrong. GenericMotor still requires it; the service
   now raises a 422 at the API boundary when the GENERIC path receives
   None, instead of letting rocketpy error deeper in construction.

2. MotorService.from_motor_model: nozzle_position previously landed in
   motor_core only for the GenericMotor branch; Liquid / Hybrid / Solid
   silently ignored the user's value and took rocketpy's default of 0.
   Now forwarded via motor_core for every kind (conditionally, so a
   null still falls back to rocketpy's default). Removed the redundant
   nozzle_position kwarg on the GenericMotor constructor call.

3. Optional-forwarding convention: motor_core only carries burn_time /
   nozzle_position when the client actually supplied them, so rocketpy
   picks its own default otherwise instead of receiving None for
   number-typed args.

Verified against real rocketpy:
- LIQUID, burn_time=None  → LiquidMotor auto-detects burn window
- LIQUID, burn_time=2.0   → LiquidMotor builds with explicit window
- LIQUID, nozzle_position=0.3  →  LiquidMotor.nozzle_position == 0.3
- LIQUID, nozzle_position=None →  LiquidMotor.nozzle_position == 0
- GENERIC, burn_time=None → 422 'burn_time is required for generic motors.'

All 173 unit tests pass.
… guard

Paired API-side work for the jarvis tank/fluid migration (branch
feat/tank-fluid-schema-migration in jarvis-ts). Mirrors what that repo
now sends on the wire:

Schema (src/models/sub/tanks.py):
- MotorTank.geometry is a discriminated union on geometry_kind:
  * custom       → legacy piecewise (TankGeometry)
  * cylindrical  → CylindricalTank(radius, height, spherical_caps)
  * spherical    → SphericalTank(radius)
- TankFluids.density accepts float or List[(T_K, rho)] temperature
  samples; pressure dependence deferred.
- New validate_tank_kind_fields model_validator mirrors the
  validate_dry_inertia_for_kind pattern from motor.py — rejects
  payloads whose tank_kind omits required kind-specific fields at
  the API boundary with a kind-named 422 instead of letting rocketpy
  crash deeper in construction.
- discretize is now optional (defaults to 100).

Service (src/services/motor.py):
- _build_rocketpy_tank_geometry dispatches on geometry_kind to
  TankGeometry/CylindricalTank/SphericalTank.
- _build_rocketpy_fluid instantiates a real rocketpy.Fluid and wraps
  sampled density in a 1D Function-of-temperature callable; scalars
  pass through. (Eliminates the duck-typed Pydantic-into-rocketpy
  pattern that worked by accident.)

Inverse path (src/services/flight.py):
- _extract_fluid_density collapses Function-valued density back to a
  scalar at rocketpy's reference state (273.15 K, 101325 Pa) — lossy
  round-trip is documented; samples-roundtrip not supported in this
  iteration.
- Geometry inverse always emits the 'custom' segment list shape; all
  three rocketpy geometry subclasses expose a piecewise .geometry dict
  so one code path covers them uniformly.

Tests:
- test_motors_route.py: 8 new cases covering each geometry_kind,
  sampled density, invalid discriminator, and all four tank_kind guard
  paths (MASS / MASS_FLOW / LEVEL / ULLAGE missing sub-fields).
- tests/unit/test_services/test_motor_service.py: new suite exercising
  the adapter end-to-end against real rocketpy for each
  geometry×variant combination plus sampled density roundtrip.

Routes (src/routes/motor.py):
- POST /motors docstring gained an example payload showing the
  discriminated geometry union and sampled density shape.

Gitignore:
- Added .context and .pylint.d/ to keep local tooling artifacts out
  of the tree.

Full suite: 173/173 pass.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1425d2c-20e3-4d27-b4a7-1981f94071e4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enh/tank-geometry-fluid-density

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant