|
| 1 | +# GXBeam.jl Style Reference |
| 2 | + |
| 3 | +Patterns extracted from GXBeam.jl v0.3.1 source (`~/.julia/packages/GXBeam/525aD/src/`). |
| 4 | +Use these as the target style for WATT.jl refactoring. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## Struct Conventions |
| 9 | + |
| 10 | +### Parametric structs — always use `{TF}` for floating point type |
| 11 | + |
| 12 | +```julia |
| 13 | +struct PointState{TF} |
| 14 | + u::SVector{3, TF} |
| 15 | + theta::SVector{3, TF} |
| 16 | + F::SVector{3, TF} |
| 17 | + M::SVector{3, TF} |
| 18 | +end |
| 19 | + |
| 20 | +struct Element{TF} |
| 21 | + L::TF |
| 22 | + x::SVector{3, TF} |
| 23 | + compliance::SMatrix{6,6,TF,36} |
| 24 | + mass::SMatrix{6,6,TF,36} |
| 25 | +end |
| 26 | +``` |
| 27 | + |
| 28 | +- Type parameter is always named `TF` (floating point type) |
| 29 | +- Additional container type parameters use `TV`, `TM`, `TP`, `TC`, `TE` etc. |
| 30 | +- Fully-constrained signatures for complex structs: |
| 31 | + ```julia |
| 32 | + struct Assembly{TF, TP<:AbstractVector{<:AbstractVector{TF}}, |
| 33 | + TC<:AbstractVector{<:Integer}, TE<:AbstractVector{Element{TF}}} |
| 34 | + ``` |
| 35 | +- Small fixed-size arrays: `SVector` / `SMatrix` from StaticArrays.jl (avoids heap allocation) |
| 36 | +- Large/variable arrays: `AbstractVector{TF}` (allows both `Vector` and sparse/offset arrays) |
| 37 | + |
| 38 | +### Immutable vs mutable |
| 39 | + |
| 40 | +- **Immutable** (`struct`): pure data containers — state outputs, element properties, boundary conditions, load structs |
| 41 | +- **Mutable** (`mutable struct`): system state that is updated in-place during solve — `StaticSystem`, `DynamicSystem` |
| 42 | + |
| 43 | +The key distinction: if you only mutate array *contents* (not array references), immutable is fine. Use `mutable struct` only when field references themselves are reassigned. |
| 44 | + |
| 45 | +### Always define `Base.eltype` |
| 46 | + |
| 47 | +```julia |
| 48 | +Base.eltype(::Element{TF}) where TF = TF |
| 49 | +Base.eltype(::Type{Element{TF}}) where TF = TF # both instance and type forms |
| 50 | +``` |
| 51 | + |
| 52 | +### Type conversion constructor + `Base.convert` |
| 53 | + |
| 54 | +```julia |
| 55 | +Element{TF}(e::Element) where {TF} = Element{TF}(e.L, e.x, e.compliance, e.mass, e.Cab, e.mu) |
| 56 | +Base.convert(::Type{Element{TF}}, e::Element) where {TF} = Element{TF}(e) |
| 57 | +``` |
| 58 | + |
| 59 | +This enables `convert(Element{Float32}, elem)` and type-parameterized construction from existing instances — important for AD where dual numbers replace Float64. |
| 60 | + |
| 61 | +### Outer constructors infer `TF` via `promote_type` |
| 62 | + |
| 63 | +```julia |
| 64 | +function Element(L, x, compliance, mass, Cab, mu) |
| 65 | + TF = promote_type(typeof(L), eltype(x), eltype(compliance), eltype(mass), eltype(Cab), eltype(mu)) |
| 66 | + return Element{TF}(L, x, compliance, mass, Cab, mu) |
| 67 | +end |
| 68 | +``` |
| 69 | + |
| 70 | +Users call the outer constructor; the `{TF}` constructor is the canonical low-level form. |
| 71 | + |
| 72 | +--- |
| 73 | + |
| 74 | +## Module Organization (`GXBeam.jl`) |
| 75 | + |
| 76 | +### All exports live in the main module file — none in subfiles |
| 77 | + |
| 78 | +```julia |
| 79 | +module GXBeam |
| 80 | + |
| 81 | +# ... using statements ... |
| 82 | + |
| 83 | +export AbstractSystem, StaticSystem, DynamicSystem |
| 84 | +export static_analysis, static_analysis! |
| 85 | +export steady_state_analysis, steady_state_analysis! |
| 86 | +# ... all exports grouped by category ... |
| 87 | + |
| 88 | +# module-level constants |
| 89 | +const GAUSS_NODES = SVector(-0.861..., ...) |
| 90 | + |
| 91 | +# includes with one comment per group |
| 92 | +include("math.jl") # common math functions |
| 93 | +include("assembly.jl") # assembly creation |
| 94 | +include("loads.jl") # prescribed conditions, distributed loads, point masses |
| 95 | +include("system.jl") # system storage and pointers |
| 96 | +include("analyses.jl") # system analyses |
| 97 | + |
| 98 | +# interface extensions in subdirectory |
| 99 | +include("interfaces/diffeq.jl") |
| 100 | +include("interfaces/reversediff.jl") |
| 101 | +include("interfaces/writevtk.jl") |
| 102 | + |
| 103 | +end |
| 104 | +``` |
| 105 | + |
| 106 | +### Private/internal functions — no leading underscore, just don't export |
| 107 | + |
| 108 | +GXBeam does not use `_foo` naming for private functions. Private functions simply aren't exported. No `@private` or similar macros. |
| 109 | + |
| 110 | +### RecipesBase is a hard dependency in GXBeam |
| 111 | + |
| 112 | +`using RecipesBase` appears directly in the main module. For WATT.jl we intentionally differ here: use `Requires.jl` conditional loading (see `plan.md` Phase 8). |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## Dispatch Patterns |
| 117 | + |
| 118 | +### Paired allocating / non-allocating functions |
| 119 | + |
| 120 | +```julia |
| 121 | +# Allocating (user-facing): creates system, calls mutating version |
| 122 | +function static_analysis(assembly; kwargs...) |
| 123 | + system = StaticSystem(assembly) |
| 124 | + return static_analysis!(system, assembly; kwargs..., reset_state=false) |
| 125 | +end |
| 126 | + |
| 127 | +# Non-allocating (pre-allocated): takes pre-built system, mutates in place |
| 128 | +function static_analysis!(system::StaticSystem, assembly; kwargs...) |
| 129 | + # ... solve ... |
| 130 | +end |
| 131 | +``` |
| 132 | + |
| 133 | +Pattern: `foo(args)` allocates and delegates to `foo!(preallocated, args)`. The `!` version is the workhorse; the non-`!` version is the convenience wrapper. |
| 134 | + |
| 135 | +### Dispatch over abstract type, then concrete type |
| 136 | + |
| 137 | +```julia |
| 138 | +# First dispatch: accept any AbstractSystem |
| 139 | +function update_body_acceleration_indices!(system::AbstractSystem, prescribed_conditions) |
| 140 | + update_body_acceleration_indices!(system.indices, prescribed_conditions) |
| 141 | + return system |
| 142 | +end |
| 143 | + |
| 144 | +# Second dispatch: work on the concrete SystemIndices |
| 145 | +function update_body_acceleration_indices!(indices::SystemIndices, prescribed_conditions) |
| 146 | + # actual work here |
| 147 | + return indices |
| 148 | +end |
| 149 | +``` |
| 150 | + |
| 151 | +No `if isa(system, StaticSystem)` branching — dispatch handles it. |
| 152 | + |
| 153 | +### Mutating functions return `self` |
| 154 | + |
| 155 | +```julia |
| 156 | +function update_body_acceleration_indices!(system::AbstractSystem, ...) |
| 157 | + # ... mutations ... |
| 158 | + return system # ← always return the mutated object for chaining |
| 159 | +end |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Docstring Format |
| 165 | + |
| 166 | +GXBeam uses `# Section` headers (with `#`), not bold `**Section**`. For WATT.jl we use `**bold**` headers per user preference — note this divergence when writing docs. |
| 167 | + |
| 168 | +### GXBeam actual format (for reference): |
| 169 | + |
| 170 | +```julia |
| 171 | +""" |
| 172 | + function_name(arg1, arg2; kwarg=default) |
| 173 | +
|
| 174 | +One-sentence or two-sentence description. May span lines. |
| 175 | +
|
| 176 | +# Arguments |
| 177 | + - `arg1`: Description (no type annotation in docstring) |
| 178 | + - `arg2`: Description |
| 179 | +
|
| 180 | +# Keyword Arguments |
| 181 | + - `kwarg = default`: Description |
| 182 | +
|
| 183 | +# Fields (for structs) |
| 184 | + - `field`: Description |
| 185 | +""" |
| 186 | +``` |
| 187 | + |
| 188 | +### WATT.jl adapted format (use this): |
| 189 | + |
| 190 | +```julia |
| 191 | +""" |
| 192 | + function_name(arg1, arg2; kwarg=default) -> out1, out2 |
| 193 | +
|
| 194 | +One-sentence description. |
| 195 | +
|
| 196 | +**Arguments** |
| 197 | +- `arg1::Type`: Description |
| 198 | +- `arg2::Type`: Description |
| 199 | +
|
| 200 | +**Keyword Arguments** |
| 201 | +- `kwarg::Type = default`: Description |
| 202 | +
|
| 203 | +**Returns** |
| 204 | +- `out1::Type`: Description |
| 205 | +
|
| 206 | +**Notes** |
| 207 | +AD compatibility notes, performance caveats, references. |
| 208 | +""" |
| 209 | +``` |
| 210 | + |
| 211 | +Key differences from GXBeam: bold headers, explicit return types in synopsis, type annotations on args. |
| 212 | + |
| 213 | +### Struct docstring: |
| 214 | + |
| 215 | +```julia |
| 216 | +""" |
| 217 | + MyStruct{TF} |
| 218 | +
|
| 219 | +One-sentence description. |
| 220 | +
|
| 221 | +**Fields** |
| 222 | +- `field1::SVector{3,TF}`: Description |
| 223 | +- `field2::TF`: Description |
| 224 | +""" |
| 225 | +struct MyStruct{TF} |
| 226 | + ... |
| 227 | +end |
| 228 | +``` |
| 229 | + |
| 230 | +--- |
| 231 | + |
| 232 | +## Keyword Argument Patterns |
| 233 | + |
| 234 | +### Default values use StaticArrays for zero vectors |
| 235 | + |
| 236 | +```julia |
| 237 | +function foo(assembly; |
| 238 | + gravity = (@SVector zeros(3)), # ← not zeros(3) |
| 239 | + prescribed_conditions = Dict{Int, PrescribedConditions{Float64}}(), |
| 240 | + time = 0.0, |
| 241 | + reset_state = true, |
| 242 | + show_trace = false, |
| 243 | + method = :newton, |
| 244 | + ftol = 1e-9, |
| 245 | + iterations = 1000, |
| 246 | + ) |
| 247 | +``` |
| 248 | + |
| 249 | +### Optional parameters use `nothing` + `isnothing` guard |
| 250 | + |
| 251 | +```julia |
| 252 | +function Element(points, start, stop; |
| 253 | + frame = nothing, |
| 254 | + compliance = nothing, |
| 255 | + mass = nothing) |
| 256 | + |
| 257 | + if isnothing(compliance) |
| 258 | + compliance = @SMatrix zeros(6,6) |
| 259 | + end |
| 260 | + ... |
| 261 | +end |
| 262 | +``` |
| 263 | + |
| 264 | +--- |
| 265 | + |
| 266 | +## Other Notable Patterns |
| 267 | + |
| 268 | +### `@unpack` for struct/NamedTuple destructuring |
| 269 | + |
| 270 | +```julia |
| 271 | +@unpack force_scaling, indices = system |
| 272 | +``` |
| 273 | + |
| 274 | +Use `UnPack.jl` `@unpack` rather than manual field access when extracting multiple fields. |
| 275 | + |
| 276 | +### NamedTuple packing with `(; ...)` syntax |
| 277 | + |
| 278 | +```julia |
| 279 | +constants = (; |
| 280 | + assembly, indices, two_dimensional, force_scaling, |
| 281 | + x=system.x, resid=system.r, converged=converged, |
| 282 | +) |
| 283 | +``` |
| 284 | + |
| 285 | +Semicolon-NamedTuple syntax for grouping closure arguments. |
| 286 | + |
| 287 | +### `Ref` for scalar flags passed into closures/callbacks |
| 288 | + |
| 289 | +```julia |
| 290 | +converged = Ref(false) |
| 291 | +# ... pass converged into callback ... |
| 292 | +converged[] = true |
| 293 | +``` |
| 294 | + |
| 295 | +### Abstract type hierarchy for system variants |
| 296 | + |
| 297 | +```julia |
| 298 | +abstract type AbstractSystem end |
| 299 | +mutable struct StaticSystem{...} <: AbstractSystem end |
| 300 | +mutable struct DynamicSystem{...} <: AbstractSystem end |
| 301 | +mutable struct ExpandedSystem{...} <: AbstractSystem end |
| 302 | +``` |
| 303 | + |
| 304 | +Define an abstract supertype even for 2–3 concrete variants — enables dispatch without coupling call sites to concrete types. |
| 305 | + |
| 306 | +### Interface extensions in `interfaces/` subdirectory |
| 307 | + |
| 308 | +AD interfaces (ReverseDiff overloads, ChainRules), DifferentialEquations.jl integration, and WriteVTK integration live in `src/interfaces/`. This keeps the core physics files clean of third-party integration code. |
| 309 | + |
| 310 | +--- |
| 311 | + |
| 312 | +## What GXBeam Does NOT Do |
| 313 | + |
| 314 | +- No `isa` or `typeof` checks inside function bodies — all handled by dispatch |
| 315 | +- No `@show` or `println` in any function |
| 316 | +- No `using Revise` in tests |
| 317 | +- No dead/commented code in source files |
| 318 | +- Exported symbols are all in one place (`GXBeam.jl`), not scattered across files |
| 319 | +- Does not use `NamedTuple` for primary data types — always a proper struct |
0 commit comments