Skip to content

Fill KiteGeometry with SystemDefinition, its components and the YAML loader - #22

Open
1-Bort-1 wants to merge 6 commits into
mainfrom
agent/21-one-kite-system-definition-across-the-ki
Open

1-Bort-1 wants to merge 6 commits into
mainfrom
agent/21-one-kite-system-definition-across-the-ki

Conversation

@1-Bort-1

@1-Bort-1 1-Bort-1 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

TL;DR

SystemDefinition and the ten component types it holds, plus load_definition, the authoring YAML loader — copied out of SymbolicAWEModels, with the segment/winch model type parameters, the simulation state and the frame suffixes (_KA, _ENU, _CAD) changed on the way. The repository around it is the fleet's: BestieTemplate off KiteUtils' .copier-answers.yml, the three bin/ scripts, a tracked default manifest per CI Julia, REUSE, docs and a changelog.

This is one unit of duplication, not a second codepath

src/yaml_loader.jl, src/system_structure/types.jl, named_collection.jl and the resolution half of SAM's SystemStructure constructor now exist twice: there and here. That is deliberate and it is unit package of plans/topology_state_split_plan.md — unit sam deletes SAM's copies and has SystemStructure hold a SystemDefinition internally. Nothing in this repository reads SAM and nothing in SAM reads this yet, so until that unit lands the two do not have to be held in step; the definition wins wherever they have drifted by then.

The field sets are a second, shorter-lived piece of hand-writing: unit schema, next, generates them from awesIO's structure_schema.yml. What that unit will find is below, under "The field sets against the schema".

What a definition holds, and what it does not

The split is one question asked of every field: is this written every simulation step? Everything that is went, and with it the loader lines that restored it — a point's vel_w, a pulley's len/vel/sum_len, a tether's len, a winch's vel/set_value, a twist surface's twist/twist_vel, and the whole of load_body_state!. Point comes across with 24 fields rather than SAM's 34, and Body sheds its whole 6-DOF state block. The plan says Point should arrive with about ten; those 24 are exactly the list unit mirror names — name, references, pos_CAD, pos_undeformed_KA, anchor_KA, mass, area, drag coefficient, damping, type, flags — counted one field at a time, with "references" being ten _ref/_idx fields and "flags" three.

What is left is topology, CAD geometry, material and placement instructions, and the loader completes it: Route-2 tethers expand into points and segments, every name reference resolves to an index, rest lengths left unset come from the CAD geometry, densities left unset from the settings, and a body- or beam-anchored point gets its offset derived from pos_CAD.

The joint merge and the model parameters

elastic_joints and timoshenko_joints are one joints collection of Joint{M<:AbstractJointModel}, with ElasticJoint{S} carrying the four stiffnesses and TimoshenkoBeam{S} carrying EA, GA, GJ, EIy, EIz plus shear_coeff and rest_length. The two blocks still load from their own YAML names. The plan no longer asks for this merge — it says joints stay as #22 has them until unit schema replaces them with tubes — so it stays rather than being split back into two types that unit would delete.

unit_stiffness::Any is gone from both Segment and Tether, replaced by model::M<:AbstractSegmentModelLinearSpring or NonlinearSpring{F}. Winch gains the same treatment with AbstractWinchModel. Loading the 2-plate kite gives SystemDefinition{Segment{LinearSpring}, Tether{LinearSpring}, Winch{TorqueWinch}, Joint}, concrete through every collection whose members share a model: a segment's stiffness is read every step and an abstract field costs a dynamic dispatch each time.

The field sets against the schema

The columns here are SAM's authoring columns, because the test is that SAM's own files load. Checked one by one against structure_schema.yml on 1-Bart-1/awesIO#4's branch (1-Bart-1/awesIO@0784141), every block except the two joint blocks names at least one column differently, and a file written to the schema would not load today. None of that is guessed. It is SAM's spelling where the schema has since chosen another, and it is exactly what unit schema exists to settle. The lines that decide something there:

  • Spellings that differ: point_i/point_j for point_a/point_b, segment_i/segment_j for segment_a/segment_b, segment_idxs and tether_idxs for segments and tethers, twist_surfaces for stations, and pos and aero_model on a body for pos_cad and aero.
  • Units that differ: diameter_mm [mm] where the schema's diameter is [m]. Transform angles are degrees in the file and radians in the struct; the schema has no transforms.
  • Schema columns nothing reads: apparent_mass on bodies; model, gear_ratio and drum_radius on winches, which come from Settings instead; the metadata block.
  • One schema table, two here: the schema has one bodies table, and a wing is a body whose aero is not null. Here a wing is a row of a separate wings block, and SystemDefinition holds bodies and wings.
  • Columns here with no schema column: the simulation parameters (per-point damping and fix_* flags, pulley friction_epsilon, winch friction and inertia), the tether initial conditions, the wing's aero columns (drag_frac, group_points_moment, point area/drag_coeff), the frame-fitting z_ref_points/y_ref_points/origin, a body's orientation, and Transform as a whole. The schema carries no body orientation at all, so a body's inertia_principal has no frame to be read in.
  • rigidity is number | model_name; here it is a number or a callable. A string in a stiffness cell is not rejected: a joint builds an ElasticJoint{Union{Float64, String}} (checked in the session), and a segment hands any non-number to NonlinearSpring the same way. That is a validation gap, and closing it is the same decision as Decide how a nonlinear stiffness law is represented 1-Bart-1/awesIO#1.
Block by block
Schema block Here Differs
points Point body/wing read as body_idx/wing_idx; a schema BODY_STATIC point errors for want of body_idx
segments Segment{M} point_a/bpoint_i/j; diameter [m] ↔ diameter_mm
stations TwistSurface block name; moment_frac required there, defaults to 0 here
pulleys Pulley segment_a/bsegment_i/j
tethers Tether{M} segmentssegment_idxs; the schema forbids the start/end/n_segments expansion
winches Winch{M} tetherstether_idxs; model, gear_ratio, drum_radius unread
bodies Body, split over bodies + wings pospos_cad; aeroaero_model; apparent_mass unread; no orientation column there
elastic_joints Joint{ElasticJoint} none
timoshenko_joints Joint{TimoshenkoBeam} none
Transform excluded from the schema on purpose

What I could not bring across cleanly

Wing frames, mass distribution and ENU placement stayed in SAM. SAM's SystemStructure constructor does two jobs in one pass — resolve the definition, then set the model up. Only the first half is here. setup_wing_frame!, setup_aero!, distribute_mass_over_points!, finalize_particle_wing_mass!, reinit! and build_panel_twist_surface_map! are not, because each of them either needs VortexStepMethod or writes world-frame state. The visible consequence: a body whose frame is fitted from z_ref_points/y_ref_points/origin arrives with mass = 0, R_KA_to_principal = I, and its riding points keep anchor_KA = 0has_fitted_frame is where the loader stops, because there is no frame yet to express an anchor in. Unit sam keeps doing all of it; unit viewers will want the CAD→ENU placement and does not get it here.

Aero models are a Symbol, not a type parameter. The plan asks for a registry of name → constructor, as the loader already has for aero modes. But every one of SAM's six aero modes has exactly one field, a VSMEngine, which is simulation state — so by the plan's own test, do the field sets differ, there is nothing to parameterise, and the constructors would drag VortexStepMethod into a geometry package. Body.aero_model::Symbol records the name the file wrote (defaulting to :linearized for a rigid wing and :direct for a particle one, as SAM does), is_wing is aero_model !== :none, and SAM's parse_aero_mode maps it back at unit sam. The schema's aero column is a model name too, so this lines up with it.

The frame suffixes are the ones set on 1-Bart-1/Agents#294: _KA for a body's own frame, _ENU for the world, _CAD for design geometry. Most of SAM's _w/_b fields — pos_w, vel_w, Q_b_to_w, aero_force_b, va_b — are written every step, so the state cull removed them rather than renaming them. What survived is renamed, fields and constructor keywords alike: pos_CAD on points and bodies, anchor_KA, com_offset_KA and pos_undeformed_KA on points and bodies, Transform.base_pos_ENU, and a body's rotation R_KA_to_CAD with the constructor keyword Q_KA_to_CAD. That last one is the CAD rotation, not the world one: SAM's YAML column Q_b_to_w is stored into what SAM calls R_b_to_c, and the new test "a body's Q_b_to_w column is its rotation into the CAD frame" pins it — a body turned 90° about z in the file gives that rotation, and a point riding it gets its anchor through it. By the same rule, which the issue did not list by name: Joint.anchor_a_KA/anchor_b_KA (each in its body's frame) and Body.R_KA_to_principal. Left alone: Point.beam_offset_b, which is in the beam's element frame rather than a body's, and so has no suffix in that vocabulary yet — unit schema replaces joints with tubes and decides it there. YAML column names are untouched throughout, including pos_cad, anchor_b, com_offset_b and Q_b_to_w, because the test is that SAM's own files still load. pos_undeformed_KA's docstring still says principal frame, as SAM's does; a wing's R_KA_to_principal is the identity, so for the one body it is read on they are the same frame.

TabulatedSpring is not here. The plan names it as a segment model; SAM has exactly two cases, a number and a callable F(ε), which are LinearSpring and NonlinearSpring. A spring backed by a curve file would need an interpolation dependency and a column nothing writes yet, and how the schema names one is 1-Bart-1/awesIO#1.

CascadedLengthWinch stayed in SAM. The YAML has no model column, so the loader only ever builds a TorqueWinch; a second model here would be a type nobody constructs. It subtypes AbstractWinchModel at unit sam like any custom model would.

SimFloat and KVec3 are defined here. KiteUtils has neither and SAM defines its own pair the same way, so there is no third place to depend on. Two spellings of Float64 is thin duplication, and it closes when SAM reexports ours.

A tether of explicit segments carries a NaN material. Its segments carry their own, but Tether{M} needs an M, so it holds LinearSpring(NaN, NaN). Said on the field; nothing reads it.

The deprecation paths did not come across — the removed-block errors for materials/elements/segment_properties, the SegmentType enum, the QUATERNION/REFINE aliases, the wing_type keyword. A package with no earlier version has nothing to countermand. Also dropped: the prn keyword and the @info line the loader printed per transform, and ignore_l0, which recomputes rest lengths from world positions and so needs the placement this package does not do.

The review on this pull request

Fixed in 9b40f03: the loader docstring named a joints block that does not exist, SystemDefinition claimed every collection is a type parameter, Body and Wing now link each other, and Wing's nested ref_pair closure is the module-level weighted_ref_pair. Fixed in ac6d33d, turned up by the schema check above: the README, the docs index and two docstrings said the types mirror the awesIO document one to one, which they do not yet.

Left as they are, with the reason:

  • pos_undeformed_KA stays const. KVec3 is an MVector, and SAM writes the field in place with .=, which works (checked in the session); anchor_KA is reassigned by the loader, so it is not const.
  • TwistSurface's x_airf/y_airf are not YAML columns in SAM either, so leaving them out of the loader is a faithful copy. Adding them is a column the schema does not have.
  • The 1.11 manifest is root-only and the 1.12 one covers the workspace, because [workspace] arrived in Julia 1.12; 1.11 ignores it.

Dependencies

KiteUtils (registered 0.12.2, for Settings, load_settings and get_data_path), YAML, StaticArrays (KVec3), LinearAlgebra (eigen and det for the inertia diagonalisation, norm/dot/cross for the beam element frame), and DocStringExtensions for $(TYPEDFIELDS), which is how the fleet writes field docs. Nothing else, and no [sources]: nothing here reads SysState, so KiteUtils #130's branch buys this unit nothing and would cost it a draft.

Repository shape

Generated with BestieTemplate from KiteUtils' .copier-answers.yml with the name, UUID, authors and copyright holder changed, then the fleet's additions that are not in the template: the REUSE files and the reuse-lint workflow, CompatHelper, dependabot, and bin/install, bin/update_default_manifests, bin/run_julia, bin/reuse_lint. Three deliberate differences from KiteUtils:

  • The CI matrix names 1.11 and 1.12, and there is a tracked .default for each. KiteUtils' matrix also names 1.10 but tracks no manifest for it, and a matrix cell with no tracked manifest is a cell that cannot install from one.
  • fail-fast: true, not the template's false (AGENTS.md §4.10).
  • @autodocs pages rather than hand-listed @docs blocks, so an added docstring cannot silently fall out of the built docs.

The fixture is SAM's data/2plate_kite minus aero_geometry.yaml, vsm_settings.yaml and polars/: those describe the aerodynamic solve, which this package does not do, and nothing here reads them.

Verification

  • Reproduced first: n/a — new package. Red before green was against my own unfinished code; the failure worth naming is Unknown point name: nothing, from a tether of explicit segments naming no start point, caught by the test written for that path (test/test-yaml-loader.jl, "a tether of explicit segments takes their endpoints").
  • Frame suffixes: test/test-joints.jl "a body's Q_b_to_w column is its rotation into the CAD frame" red on ac6d33d (FieldError: type Body has no field R_KA_to_CAD), green on 2d9d167 (juliaserver). The pos_CAD rename likewise: test/test-components.jl red on 5977e25 (UndefKeywordError: keyword argument pos_cad not assigned), green on 2d9d167.
  • test/runtests.jl on 2d9d167: 218 pass, 0 fail, 0 error on Julia 1.12.7 (juliaserver); clean on 1.11 in an earlier round, not re-run.
  • Local CI mirror (agent ci-local, Pkg.test() on Julia 1.12.7, fail-fast) on 2d9d167: PASS in under a minute, 218 tests, exit 0.
  • GitHub CI on 2d9d167: Test on PRs (1.12, ubuntu), Documentation and reuse-lint all PASS. The 1.11 and macOS/Windows cells run on main only.
  • Docs build clean on 2d9d167 (docs/make.jl, no missing_docs, no broken cross-references) · up to date with origin/main (6f7a0a7).
  • REUSE lint — not run locally: reuse is not installed on this box. The reuse-lint workflow passed on 2d9d167.
  • jetls check — not run; jetls is not installed on this box.
  • Benchmark: n/a — no hot path, nothing claimed faster.
  • Risk: the boundary in "what I could not bring across cleanly", first item. A body whose frame is fitted from reference points arrives with zero mass and an identity frame, and unit sam has to keep fitting it. If that line is in the wrong place, it is cheaper to move now than after the five units that build on this.

Scope

+6409 / −10 across 61 files on an empty repository, of which about 2780 lines are src/, 1572 the two tracked manifests, 585 tests, 344 the fixture, docs and the two licence texts most of the rest. The src/ total replaces roughly 3500 lines of SAM: the loader is 873 against 1093, types.jl 724 against 1406, and NamedCollection 85 against 180 — most of that from letting AbstractVector supply the iteration and indexing SAM hand-wrote. The last two commits, 5977e25 and 2d9d167, are +113 / −94: the frame-suffix renames (one-for-one lines) plus the 18-line test and the docs' frames section. Unit package of plans/topology_state_split_plan.md, tracked in 1-Bart-1/Agents#294; schema, document, sam, kitemodels and viewers build on this.

Closes #21 · task KiteGeometry.jl-21

1-Bort-1 and others added 2 commits September 15, 2026 00:02
The system definition of the plan's unit `package`: `SystemDefinition` over
`NamedCollection`s of `Point`, `TwistSurface`, `Segment`, `Pulley`, `Tether`,
`Winch`, `Body`, `Joint` and `Transform`, and `load_definition` reading one
from an authoring YAML file.

Copied from SymbolicAWEModels' `src/yaml_loader.jl`, `system_structure/types.jl`,
`named_collection.jl` and the definition half of its `SystemStructure`
constructor. SAM keeps its own until unit `sam` deletes it.

Changed on the way across: the joint merge (one `Joint{M}` over `ElasticJoint`
and `TimoshenkoBeam`), the segment and winch model type parameters that take
`unit_stiffness::Any` out, the frame suffixes, and the state cull — a
definition holds nothing that is written per simulation step.

Also the repository shape: BestieTemplate off KiteUtils' `.copier-answers.yml`,
the three `bin/` scripts, a tracked default manifest per CI Julia, REUSE, the
docs and the changelog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XRzDnVRsMUvGW2psaAgtFC
`Pkg.test()` runs in `test/Project.toml`'s own environment, where the
`using LinearAlgebra` that `test/test-components.jl` needs for `Diagonal`
and `det` was not declared. It loaded under `--project=.` and failed under
`Pkg.test()`, which is what CI runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XRzDnVRsMUvGW2psaAgtFC
@1-Bort-1

1-Bort-1 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Local full suite: PASS (0 min, Julia 1.12.7, one cell of the matrix)

@1-Bort-1 1-Bort-1 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review (advisory)

Verdict: REQUEST CHANGES · 0 inline, 0 off the diff

Good

  • Every exported symbol carries a """ docstring and the docs pages are @autodocs (docs/src/types.md, functions.md, internals.md), so a new export cannot silently fall out of the built docs — checked make.jl leaves checkdocs at its erroring default.
  • The state cull is consistent: read every struct in src/types.jl and src/rigid_body.jl — no vel, len, force, quaternion or sum_len field survives anywhere, so the package really does hold only topology, CAD geometry and material.
  • Type parameterisation is real, not decorative: narrow re-types a homogeneous collection and test/test-joints.jl:30 asserts eltype(beams.joints) == Joint{TimoshenkoBeam{SimFloat}} while a mixed set falls back to Joint.
  • Tests name the behaviour they protect and cover both fixture wings, the generated-tether chain, the explicit-segment tether, variable expansion, the joint blocks and the inertia diagonalisation, plus three error paths (Unknown point name, requires body_a and body_b, No points or bodies).
  • test/runtests.jl copies the fixture into mktempdir() before set_data_path, so no test can write into the tracked data/2plate_kite.
  • Repo hygiene holds: docs/build/ is untracked, Manifest-v1.*.toml ignored while only .default is tracked, REUSE headers on every source file, and no line in src/ or test/ exceeds 92 characters (grepped).

Not good

  • src/yaml_loader.jl:11 lists a joints top-level block; the loader actually reads elastic_joints and timoshenko_joints (docs/src/yaml.md gets this right).
  • src/system_definition.jl:13 claims "Every collection is a type parameter"; four of the eleven are.
  • TwistSurface's x_airf/y_airf keywords are absent from load_yaml_twist_surfaces' kwargs list, so chord and y_airf are unreachable from a YAML file.
  • The two tracked manifests differ in shape: the 1.12 default is workspace-wide (contains KiteGeometry and Documenter), the 1.11 default is root-only — bin/install therefore gives a different environment on the two pinned Julias.
  • Wing builds a Body, so it lands on the "Exported functions" page; a reader looking for a Wing type will not find one.
  • Point.anchor_b is mutable while the sibling pos_undeformed_b is const, although both are filled by the downstream simulator.

claude, rubric CLEAN_CODE.md. A different lab from the implementer
on purpose: a reviewer sharing its blind spots would not flag its mistakes.

1-Bort-1 and others added 2 commits September 16, 2026 15:51
`load_definition` named a `joints` block the loader does not read; it reads
`elastic_joints` and `timoshenko_joints`. `SystemDefinition` said every
collection is a type parameter; four are. `Body` and `Wing` now point at each
other, since `Wing` builds a `Body` and there is no `Wing` type to find.

`Wing`'s nested `ref_pair` closure becomes the module-level
`weighted_ref_pair`. No behaviour changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README, the docs index and two docstrings said every collection mirrors a
table of the awesIO structure document. Checked column by column against
`structure_schema.yml` it does not: every block but the two joint blocks names
at least one column differently. The field sets are SymbolicAWEModels' authoring
columns, written by hand; the schema is the format they are to follow, and
generating them from it is the next unit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

1-Bort-1 and others added 2 commits September 16, 2026 18:13
A body's own frame is KA and the world ENU, so the body-frame offsets
become anchor_KA, com_offset_KA, pos_undeformed_KA and the joint's
anchor_a_KA / anchor_b_KA, a body's rotation into the design frame
R_KA_to_CAD (keyword Q_KA_to_CAD), and the transform's base position
base_pos_ENU. YAML column names are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pos_cad is pos_CAD on Point and Body, field and keyword, matching
R_KA_to_CAD. The YAML column stays pos_cad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

One kite system definition across the Kite packages: KiteGeometry.jl: the system definition

2 participants