This page is the contributor checklist for adding new selector-driven options without drifting the YAML -> launcher -> C runtime contract.
For every new selector value:
picurv_cli/core.py,Use the canonical value only. Do not add placeholder enum values or compatibility aliases for unimplemented options.
Steps 7 and 8 are not advisory. Coverage is enforced for every registered family, so make audit-capability fails until they are done, and the full source chain - validator, C parser, enum, runtime dispatch - must agree. If the value belongs to a family nothing yet covers, make audit-family-census will have already refused the new public surface.
Concretely, adding a selector value means:
The audit checks four things a reviewer would otherwise have to check by hand:
supported value has evidence, an experimental or known-defective value states limitations, a deprecated value gives migration.If the value is a synonym rather than a new capability, declare spelling_of instead and it inherits the canonical value's status without needing its own entry. If it is a retired name, declare alias_of and scaffold the three-part stub with scaffold_documentation.py alias.
Full contract: Documentation Extension Framework.
solver.yml -> strategy.momentum_solverExplicit RK4Dual Time Picard Jameson RKNewton KrylovDual Time Picard RK4 normalizes to Dual Time Picard Jameson RK; the C enum keeps MOMENTUM_SOLVER_DUALTIME_PICARD_RK4 as an alias of the Jameson value.normalize_momentum_solver_type() in picurv_cli/core.pystrategy.momentum_solver -> -mom_solver_typeMomentumSolverType in include/variables.h-mom_solver_type in CreateSimulationContext (src/setup.c)case.yml -> boundary_conditionsBC_TYPE_MAPBC_HANDLER_SPECSvalidate_and_prepare_boundary_conditions() in picurv_cli/core.pybcs.runValidateBCHandlerForBCTypeUserCtxtests/test_cli_smoke.pycase.yml -> models.physics.particles.init_modeSurfaceVolumePointSourceSurfaceEdgesnormalize_particle_init_mode() in picurv_cli/core.pyinit_mode -> -pinitParticleInitializationType in include/variables.h-pinit in CreateSimulationContexttests/test_cli_smoke.pysolver.yml -> interpolation.methodTrilinearCornerAveragednormalize_interpolation_method() in picurv_cli/core.pyinterpolation_method -> -interpolation_methodInterpolationMethod in include/variables.h-interpolation_method in CreateSimulationContext (src/setup.c)tests/test_cli_smoke.pycase.yml -> properties.initial_conditions.modegenerated with generator: zero|constant|streamwise_constant|poiseuille|ic_gen|spectral_random_velocity|resampled_velocity|channel_spectral_velocity|duct_spectral_velocityfile with field: Ucat|Ucontresolve_initial_condition_config()stage_initial_condition_file()run_initial_condition_generator()normalize_flow_direction_token()resolve_ic_cli_params()-finit and parameter flagsic_gen -> -finit 4 -ic_field ... -ic_dir ...initialConditionMode, initialConditionField, initialConditionDirectory, icVelocityPhysical, and flowDirection in SimCtxsrc/initialcondition.c — PopulateInitialUcont, SetInitialInteriorFieldsrc/setup.c — Cart2Contra, UniformCart2Contratests/test_cli_smoke.pysolver.yml -> operation_mode.analytical_typeTGV3DZERO_FLOWUNIFORM_FLOWnormalize_analytical_type() in picurv_cli/core.pyanalytical_type -> -analytical_typeAnalyticalSolutionType in SimCtx-analytical_type in CreateSimulationContexttests/test_cli_smoke.pysolver.yml -> verification.sources.*verification_sources.*verification.sources.diffusivityverification.sources.scalarLINEAR_XCONSTANT, LINEAR_X, SIN_PRODUCTpicurv_cli/core.pyverification.sources.diffusivity.mode -> -verification_diffusivity_modeverification.sources.diffusivity.profile -> -verification_diffusivity_profileverification.sources.diffusivity.gamma0 -> -verification_diffusivity_gamma0verification.sources.diffusivity.slope_x -> -verification_diffusivity_slope_xverification.sources.scalar.mode -> -verification_scalar_modeverification.sources.scalar.profile -> -verification_scalar_profileverification.sources.scalar.value -> -verification_scalar_valueverification.sources.scalar.phi0 -> -verification_scalar_phi0verification.sources.scalar.slope_x -> -verification_scalar_slope_xverification.sources.scalar.amplitude -> -verification_scalar_amplitudeverification.sources.scalar.kx/ky/kz -> -verification_scalar_kx/-verification_scalar_ky/-verification_scalar_kzverificationDiffusivity and verificationScalar in SimCtxsrc/verification_sources.csrc/rhs.c for diffusivitysrc/AnalyticalSolutions.csrc/ParticlePhysics.csrc/logging.c (<run.analysis.metrics>/scatter_metrics.csv)AnalyticalSolutions, not in model-evolution code such as ParticlePhysicscase.yml -> grid.modecase.yml -> grid.generator.grid_typeprogrammatic_cfilegrid_genpicurv_cli/core.py grid validation and run-dir staginggenerators/grid.genprogrammatic_c emits -im/-jm/-km/...file emits -grid_filegrid_gen shells out to grid.gentests/test_cli_smoke.pymonitor.yml -> profiling.timestep_output.modeoffselectedallresolve_profiling_config()prepare_monitor_files() in picurv_cli/core.pytimestep_output.mode -> -profiling_timestep_modetimestep_output.file -> -profiling_timestep_filefinal_summary.enabled -> -profiling_final_summaryprofile.run + -profile_config_fileSimCtxtests/test_cli_smoke.pypost.yml -> statistics_pipelinemsdnormalize_statistics_task() in picurv_cli/core.pymsd -> ComputeMSD token in generated post.run (dispatched to ComputeParticleMSD)tests/test_cli_smoke.pyOne LES closure is active per run, chosen from an enum and dispatched by a branch, the same shape the momentum solvers use. There is no registry object: a closure is a function, not an instance with a lifecycle, which is what separates this from the boundary-condition handlers in section 3.
case.yml -> models.physics.turbulence.lesconstant_smagorinsky, dynamic_smagorinsky, vreman, walefilter_width: cube_root_volume, geometric_mean, max_edge, scottitest_filter.kernel: volume_weighted_box, simpson_ikaveraging.mode: local, homogeneous, globalclipping.mode: clamp, clip_negative, nonepicurv_cli/core.py:normalize_les_model()normalize_les_filter_width()normalize_les_test_filter()normalize_les_averaging_mode()normalize_les_clip_mode()normalize_les_averaging_directions()validate_les_configuration() for cross-key rulesappend_les_parameter_flags() for emission-les and -les_* flags listed in Developer Ingestion Mapinclude/variables.h:
ParseLESConfiguration() in src/setup.c, called from CreateSimulationContext. Defaults come from LESConfigSetDefaults(), also in src/setup.c, so the control-file ingress and the test fixtures cannot disagree about them. Both live with the rest of the ingress rather than in src/les.c, because the postprocessor builds a simulation context without linking the closure.A new closure should consume the existing kernels rather than restate them. They are declared in include/les.h and are the reason a second eddy-viscosity model is one function: StrainRateFromGradients, ComputeCellFilterWidth, SymTensorDeviator, SymTensorContract, SymTensorTimesVector, ClipModelCoefficient, and EddyViscosityFromCoefficient. The Germano-specific pieces, LeonardStress and GermanoModelTensor, belong to the dynamic procedure alone.
Coefficient averaging is not an LES function. It is PicurvSpatialRatioAverage in include/statistics_target.h, the module that already owns which points a spatial operation may touch. The closure resolves a SpatialTargetPlan for its loops through the same entry point the statistics pipeline uses, so both agree on which indices are physical and neither carries its own copy of the fluid threshold.
tests/c/test_les.ctests/test_config_regressions.pytests/tooling/capability_families.json and tests/tooling/subsystem_records.json