The settings file

A vsm_settings.yaml configures a whole run in one place: the flight condition, each wing and how it is discretised, and the numerical solver. VSMSettings reads it, and Wing, Solver and set_va! are each built from the object it returns.

settings = VSMSettings("ram_air_kite/vsm_settings.yaml")           # under data/
settings = VSMSettings("my/vsm_settings.yaml"; data_prefix=false)  # as written

wing = Wing(settings)
body_aero = BodyAerodynamics([wing])
solver = Solver(body_aero, settings)
set_va!(body_aero, settings)

The file has three top-level blocks — condition:, wings: and solver_settings: — and each may be left out, in which case its defaults apply. wings: is a list, so a multi-wing configuration repeats the entry.

An annotated file

Every key below is optional except a wing's name, n_panels, spanwise_panel_distribution, spanwise_direction and remove_nan, and — whenever solver_settings: is present — its aerodynamic_model_type and type_initial_gamma_distribution. An omitted key keeps its default: the condition: values shown are those defaults, and the docstrings linked below carry the rest.

condition:
  wind_speed: 10.0                # free-stream velocity magnitude [m/s]
  alpha: 5.0                      # angle of attack [°]
  beta: 0.0                       # sideslip angle [°]
  yaw_rate: 0.0                   # yaw rate [°/s]

wings:
  - name: main_wing               # label the wing carries into plots and output
    # sections and polars, resolved against the working directory
    geometry_file: data/ram_air_kite/geometry.yaml
    n_panels: 50                  # panels over the span; two sections make a panel
    # LINEAR, COSINE, SPLIT_PROVIDED, UNCHANGED or BILLOWING
    spanwise_panel_distribution: LINEAR
    spanwise_direction: [0, 1, 0] # unit vector along the span, in the CAD frame
    remove_nan: true              # interpolate over NaN entries in the polar tables
    use_prior_polar: false        # reuse polars on disk instead of regenerating them
    billowing_percentage: 0.0     # trailing-edge billow, as % of arc length
    crease_frac: 0.75             # chordwise position of the deflection hinge [-]

    mesh:                         # how the sections were sliced from a CAD mesh
      obj_file: data/ram_air_kite/ram_air_kite.obj  # the mesh sections come from
      n_sections: 45              # sections sliced from the mesh
      n_bins: 60                  # leading-edge stations marched across the span
      # rows of the mesh-to-slicer rotation, whose x = chord, y = span, z = up
      rotation: [[0, 0, -1], [-1, 0, 0], [0, 1, 0]]
      wingtip_distance: 0.0       # arc length the outermost sections stop short [m]
      clearance: 0.006            # shrink-wrap offset outside the cloud [chord fraction]
      min_concave_radius: 0.02    # shrink-wrap rolling-ball radius [chord fraction]

    airfoil:                      # the 2D backend and the polars it tabulates
      solver: xfoil               # section backend: neuralfoil or xfoil
      model_size: large           # NeuralFoil network size
      n_crit: 9.0                 # e^N transition criticality; lower transitions earlier
      xtr_upper: 0.05             # forced upper-surface transition [chord fraction]
      xtr_lower: 0.05             # forced lower-surface transition [chord fraction]
      alpha_range: [-180, 1, 180] # angle-of-attack sweep [°] as [first, step, last]
      delta_range: [-40, 10, 40]  # flap-deflection sweep [°]; null for no flap sweep
      # angles off the reference angle a live polar is re-solved at [°]
      live_offsets: [-12, -9, -6, -3, 0, 3, 6, 9, 12]
      v_app: 25.0                 # apparent wind the Reynolds number is taken at [m/s]
      chord_ref: 1.0              # reference (maximum panel) chord [m]
      table_format: arrow         # per-node table format: csv or arrow

solver_settings:
  aerodynamic_model_type: VSM     # VSM or LLT
  type_initial_gamma_distribution: ELLIPTIC  # ELLIPTIC or ZEROS
  solver_type: LOOP               # LOOP or NONLIN
  density: 1.225                  # air density [kg/m³]
  mu: 1.81e-5                     # dynamic viscosity [N·s/m²]
  rtol: 1e-6                      # relative tolerance on the circulation residual [-]
  relaxation_factor: 0.01         # under-relaxation of the circulation update [-]

SolverSettings lists the rest of solver_settings:; a key left out keeps its default. n_panels given there is ignored — the total is summed from the wings.

mesh:

MeshSettings — which .obj mesh a wing's sections were cut from, and how. The block's obj_file is the mesh the sections are generated from, an input to the geometry_file the wing then flies; a wing's own top-level obj_file is a different route — straight from a mesh and a .dat, with no polar generation — and cannot be given alongside geometry_file.

rotation_matrix, slice_args and preview_args turn the block into the arguments obj_to_yaml and plot_slices_3d take, and ShrinkWrap is built from clearance and min_concave_radius. A wing naming no mesh: block slices exactly as an unconfigured obj_to_yaml call does. From CAD mesh to aerodynamic model walks through what those arguments do.

airfoil:

AirfoilSettings — the 2D section backend and the polars it tabulates. solver: chooses the viscous panel code (xfoil) or the neural surrogate (neuralfoil) for a whole dataset from the file, and airfoil_solver returns the XFoilSolver or NeuralFoilSolver it names.

One block answers for both the tables a mesh is sliced into and the live polars a deformed section is re-solved on, so the two cannot be generated at different transition settings or off different networks. alpha_range, delta_range and reynolds turn the sweeps and the density * v_app * chord_ref / mu reference into what the polar generator takes.