Skip to content

CasADi panel functions: polar tables as inputs, so one graph serves every deformed shape - #167

Merged
ocayon merged 1 commit into
feature/quarter-chord-xp-kernelsfrom
feature/casadi-polar-table-inputs
Nov 3, 2026
Merged

ocayon merged 1 commit into
feature/quarter-chord-xp-kernelsfrom
feature/casadi-polar-table-inputs

Conversation

@ocayon

@ocayon ocayon commented Nov 3, 2026

Copy link
Copy Markdown
Member

Stacked on #166. The base branch is feature/quarter-chord-xp-kernels. Retarget to develop once #166 is merged.

Why

On a deforming wing, each panel's polar is a blend of its two section polars with geometry-dependent weights, so the tables change slightly every coupled iteration. The CasADi residual and panel-force functions embedded the tables as constants and were cached per table set. A caller that re-trims deformed shapes (AWETrim's CasadiTrim in the aerostructural loop) therefore rebuilt its whole graph on every iteration, and could not afford to SX-expand it.

What changes

Both CasADi panel functions can take the polar tables as inputs. One function then serves every shape with the same panel count and alpha grid.

New and changed signatures (Solver)

_casadi_newton_function(table_inputs: bool = False) -> casadi.Function
casadi_panel_force_function(table_inputs: bool = False) -> casadi.Function
casadi_polar_table_inputs() -> {"cl_table", "cd_table", "cm_table"}        # each (n_panels, n_alpha)
casadi_stall_angle_inputs() -> {"stall_angles", "stall_angles_neg"}        # (n_panels,), rad

table_inputs=False (default): unchanged, the tables are embedded and the function is cached per table set. VSM's own gamma_loop_casadi_newton keeps using it.

table_inputs=True:

  • Inputs: the same as before, with the same names, plus cl_table, cd_table and cm_table at the end. Row i is panel i on the shared alpha grid (panel.panel_polar_data[:, 0], rad). The outputs are unchanged.
  • Cache key: panel count + alpha-grid digest + interpolation + viscosity settings; the table values are not part of it.
  • Lookup: one 1-D parametric linear interpolant on the shared grid, mapped over the panels. It uses the same nodes and clamping as the embedded 2-D table, so the outputs are equal to round-off. That includes the corner-averaged *_avg outputs and the viscosity lift slope.
  • Omitted table inputs default to NaN. CasADi otherwise fills an omitted input with 0, which would give a silently zero polar.
  • Supported cases: linear polars on one shared alpha grid, which is the default and the normal outcome of batch polar generation. Two cases raise a clear ValueError:
    • polar_interpolation="bspline": a parametric cubic spline refits its coefficients on every call, about 87 ms per evaluation;
    • per-panel alpha grids.

What AWETrim must change

  1. Build once:
    • panel_fn = vsm._casadi_newton_function(table_inputs=True) (and casadi_panel_force_function(table_inputs=True) if used).
    • Add three numeric parameters cl_table, cd_table, cm_table to the graph, and pass them through panel_fn.call({... , **tables}).
  2. Per shape (update_geometry):
    • pass vsm.casadi_polar_table_inputs() as those parameters;
    • also pass vsm.casadi_stall_angle_inputs(). Today the stall angles are fixed at build, which is only right while the tables are fixed.
  3. Cache key: key the reusable graph on panel count + alpha grid + options, and drop the table values (_casadi_newton_key).
  4. SX-expand the graph once. Unexpanded, the parametric lookup is slower (see the timings below).

Validation

Outputs unchanged:

New tests (tests/Solver/test_casadi_polar_table_inputs.py, 7):

  • the table-input functions equal the embedded-table functions, with and without artificial viscosity (gate active), all 13 residual outputs and all panel-force outputs, to 1e-12;
  • reuse: the function built on shape A (V3, uniform panels), evaluated with the tables and geometry of shape B (V3, cosine panels; the blended polars differ by > 1e-3), equals a fresh embedded build for B, with no new cache entry;
  • omitted tables give NaN;
  • expand() works and matches;
  • VSM's loop never builds a table-input function;
  • unsupported cases raise.

Full suite: 208 passed, 5 skipped (201 + 7 new).

Timings

Residual function, V3, artificial viscosity on, 83 alpha nodes, best of 5. Measured on a machine under other load, so trust the ratios more than the absolute values.

36 panels 135 panels
build: embedded / tables 7 / 4 ms 21 / 6 ms
SX expand: embedded / tables 5 / 10 ms 17 / 41 ms
eval MX: embedded / tables 171 / 319 µs 623 / 1131 µs
eval SX: embedded / tables 157 / 183 µs 618 / 627 µs

SX-expanded, the table-input function is as fast as the embedded one: +2 % at 135 panels, +17 % at 36.

Unexpanded MX, it is about 1.9× slower, which is why VSM's own loop keeps the embedded function.

The VSM function's own build is milliseconds either way. The 2–6 s rebuilds per coupled iteration seen in AWETrim are its full trim graph and Jacobian, which this change lets it build once.

Written by Claude, reviewed by Oriol Cayon

On a deforming wing each panel's blended polar changes slightly every
coupled iteration. The CasADi residual and panel-force functions embedded
the tables as constants and were cached per table set, so a caller that
re-trims deformed shapes (AWETrim's CasadiTrim) rebuilt its graph every
iteration and could not afford to SX-expand it.

- _casadi_newton_function(table_inputs=False) and
  casadi_panel_force_function(table_inputs=False): with True the functions
  take cl_table, cd_table, cm_table, each (n_panels, n_alpha), as extra
  inputs and are cached per (panel count, alpha grid, settings), not per
  table values. The lookup is one 1-D parametric linear interpolant on the
  shared grid, mapped over panels: same nodes and clamping, so the outputs
  equal the embedded-table ones to round-off. Omitted tables default to
  NaN, never to a silently zero polar.
- Solver.casadi_polar_table_inputs() and casadi_stall_angle_inputs(): the
  current tables and stall-gate angles in the input layout.
- Linear polars on one shared alpha grid only (a parametric cubic spline
  refits per call, ~0.1 s); other cases raise a clear ValueError.

VSM's own loops keep the embedded-table functions and are unchanged: all
solve outputs are bit-identical to a801b5b (4 loop types, both
is_aoa_corrected settings, viscosity on/off). Expanded to SX, the table
function evaluates as fast as the embedded one (+2% at 135 panels).

Generated with Claude Code
Copilot AI balanced review requested due to automatic review settings November 3, 2026 13:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.

@ocayon
ocayon merged commit bdce11a into feature/quarter-chord-xp-kernels Nov 3, 2026
ocayon added a commit that referenced this pull request Nov 3, 2026
Land #167 on develop (polar tables as CasADi inputs)
ocayon added a commit that referenced this pull request Nov 4, 2026
Release prep v2.3.0: restore the unmerged v2.2.0 content, land #167, changelog
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.

2 participants