From b9785811f2ff2e7bc37493c83b26d3a06b37e220 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 11 Aug 2026 11:37:27 -0500 Subject: [PATCH 01/14] Document solver settings in the gRPC codegen field registry Adds `description:` and `default:` attributes to field_registry.yaml and emits them as leading comments on the generated proto fields. The generated .proto is part of the public wire contract (GRPC_INTERFACE.md, "Custom Clients"), but carried no comments, so a third-party client could not learn what a settings field means or what omitting it does. Covers all 85 fields in pdlp_settings and mip_settings. Two fields (postsolve_info, presolve_absolute_tolerance) have no documentation anywhere, so they carry only `default:` plus a note to add prose later. `default:` values are taken from the C++ member initializers rather than from docs/cuopt/source/*-settings.rst, which disagrees in four places (barrier_iterative_refinement, barrier_step_scale, pdlp_precision, semi_continuous_big_m). Those look like docs bugs and are left for a separate change. Fields without either attribute generate byte-identical output, so the only generated file that changes is cuopt_remote_data.proto, and the change is purely additive comments. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 388 ++++++++++++++++++ cpp/src/grpc/codegen/generate_conversions.py | 44 +- .../codegen/generated/cuopt_remote_data.proto | 235 +++++++++++ 3 files changed, 666 insertions(+), 1 deletion(-) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 5d7a784897..8e54beb889 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -38,6 +38,17 @@ # Attributes (all optional unless noted): # # Per-field: +# description – human-readable meaning of the field, emitted as a +# leading comment on the generated proto field so a +# third-party client reading only the .proto learns +# what it does (see GRPC_INTERFACE.md, "Custom +# Clients"). Currently consumed for settings messages. +# default – what the field does when omitted, written as a string +# describing the C++ member initializer (e.g. "1e-4", +# "-1 (automatic)"). Appended to the generated proto +# comment as "(default: ...)". Documentation only — +# the generator neither derives nor validates it, and +# it does not affect the wire format. # type – proto wire type (default: double for scalars, repeated double for arrays) # field_num – proto message field number (see "Field numbers" above) # array_id – enum value for ArrayFieldId or ResultFieldId @@ -450,32 +461,74 @@ pdlp_settings: # Termination tolerances (nested: settings.tolerances.) - tolerances: - absolute_gap_tolerance: + description: >- + Absolute term in PDLP's duality gap check: + duality_gap < absolute_gap_tolerance + relative_gap_tolerance * + (|primal_objective| + |dual_objective|). + default: "1e-4" field_num: 1 optional: true - relative_gap_tolerance: + description: >- + Relative term in PDLP's duality gap check; multiplies + (|primal_objective| + |dual_objective|). Significant impact on + accuracy and runtime. + default: "1e-4" field_num: 2 optional: true - primal_infeasible_tolerance: + description: >- + Tolerance used when PDLP declares the problem primal infeasible. + Only consulted when detect_infeasibility is enabled. + default: "1e-10" field_num: 3 optional: true - dual_infeasible_tolerance: + description: >- + Tolerance used when PDLP declares the problem dual infeasible + (unbounded). Only consulted when detect_infeasibility is enabled. + default: "1e-10" field_num: 4 optional: true - absolute_dual_tolerance: + description: >- + Absolute term in PDLP's dual feasibility check: + dual_feasibility < absolute_dual_tolerance + relative_dual_tolerance + * l2_norm(c). + default: "1e-4" field_num: 5 optional: true - relative_dual_tolerance: + description: >- + Relative term in PDLP's dual feasibility check; multiplies the + objective vector L2 norm. + default: "1e-4" field_num: 6 optional: true - absolute_primal_tolerance: + description: >- + Absolute term in PDLP's primal feasibility check: + primal_feasibility < absolute_primal_tolerance + + relative_primal_tolerance * l2_norm(b). + default: "1e-4" field_num: 7 optional: true - relative_primal_tolerance: + description: >- + Relative term in PDLP's primal feasibility check; multiplies the + right-hand-side vector L2 norm. + default: "1e-4" field_num: 8 optional: true # Limits - time_limit: + description: >- + Wall-clock limit in seconds after which the solver stops and returns + the current solution. Checked periodically rather than continuously, + so the solver may run slightly over. When set together with + iteration_limit, the first limit reached wins. + default: "no limit (infinity)" field_num: 9 optional: true - iteration_limit: @@ -484,6 +537,12 @@ pdlp_settings: # (max() <=> -1, preserving 26.04 compatibility) and `optional` adds the # missing piece — an omitted field would otherwise decode to the wire # zero (0), which is `>= 0` and would overwrite the C++ default with 0. + description: >- + Iteration limit after which the solver stops and returns the current + solution. Checked periodically, so the solver may run a few extra + iterations. When set together with time_limit, the first limit reached + wins. Encoded on the wire as -1 for "no limit". + default: "no limit (INT_MAX)" field_num: 10 type: int64 sentinel: max_as_negative_1 @@ -491,13 +550,27 @@ pdlp_settings: # Solver configuration - log_to_console: + description: >- + Whether the solver writes log output to the console. Logs may still be + written to a file when this is false. + default: "true" field_num: 11 type: bool optional: true - detect_infeasibility: + description: >- + Whether PDLP attempts to detect infeasibility. Detection is not always + accurate — some problems reported infeasible converge under a different + tolerance — and costs roughly 3-7% runtime and 10-20% memory. Dual + simplex always detects infeasibility regardless of this setting. + default: "false" field_num: 12 type: bool - strict_infeasibility: + description: >- + When true, PDLP stops if either the current or the average solution is + detected infeasible. When false, both must be detected infeasible. + default: "false" field_num: 13 type: bool - pdlp_solver_mode: @@ -506,13 +579,31 @@ pdlp_settings: # omitted field would silently apply `Stable1` instead of the cuOpt # default. Declared `optional` so the mapper preserves the C++ # default via `has_pdlp_solver_mode()`. + description: >- + Mode under which PDLP operates; changes how the problem is internally + optimized and can drastically change solve time. Stable3 is the best + overall mode from cuOpt experiments; Methodical1 takes slower but fewer + steps at 1.3-1.7x the memory; Fast1 is fastest but converges less + often. There is no way to know upfront which mode suits a given + problem — testing is encouraged. + default: "Stable3" field_num: 14 type: pdlp_solver_mode optional: true - method: + description: >- + Method used to solve the LP. Concurrent runs PDLP, dual simplex and + barrier in parallel. Default accuracy differs per method: PDLP 1e-4 + relative, barrier 1e-8 relative, dual simplex 1e-6 absolute. + default: "Concurrent" field_num: 15 type: lp_method - presolver: + description: >- + Which presolver performs presolve reductions: 0 disables presolve, + 1 selects Papilo, 2 selects PSLP. By default LP uses PSLP and MIP uses + Papilo. + default: "Default (PSLP for LP, Papilo for MIP)" field_num: 16 type: int32 from_proto_cast: "presolver_t" @@ -522,45 +613,100 @@ pdlp_settings: # omits this field gets the solver default rather than the proto3 zero # (`false`). See also the matching `optional` markers on # `barrier_iterative_refinement` (pdlp) and `probing` (mip). + description: >- + Whether dual postsolve runs when the Papilo presolver is used on an LP. + Disabling it can improve solve time at the cost of losing access to the + dual solution. Not relevant for MIP. + default: "true" field_num: 17 type: bool optional: true - crossover: + description: >- + Whether PDLP or barrier crosses over to a basic solution after reaching + optimality. PDLP and barrier solutions need not lie at a vertex; + crossover yields a basic solution with n - m variables on their bounds. + Significant impact on accuracy and runtime. + default: "false" field_num: 18 type: bool - num_gpus: + description: >- + Number of GPUs used for the solve. Relevant to LP in concurrent mode + (up to 2 GPUs), where PDLP and barrier run on separate GPUs rather than + sharing one. For distributed PDLP, -1 selects all visible devices. + default: "1" field_num: 19 type: int32 optional: true - per_constraint_residual: + description: >- + Whether PDLP computes primal and dual residuals per constraint instead + of globally. + default: "false" field_num: 20 type: bool - cudss_deterministic: + description: >- + Whether cuDSS runs in deterministic mode. Deterministic mode makes + results reproducible across runs but may be slower. + default: "false" field_num: 21 type: bool - folding: + description: >- + Barrier: whether to fold the LP, reducing problem size by exploiting + symmetry. -1 automatic, 0 disabled, 1 forced. + default: "-1 (automatic)" field_num: 22 type: int32 optional: true - augmented: + description: >- + Barrier: which linear system to solve at each iteration. -1 automatic, + 0 the ADAT system (normal equations), 1 the augmented system. The + augmented system can be more stable, ADAT is often faster. + default: "-1 (automatic)" field_num: 23 type: int32 optional: true - dualize: + description: >- + Barrier: whether presolve dualizes the LP, which can help problems with + inequality constraints that have more constraints than variables. + -1 automatic, 0 disabled, 1 forced. + default: "-1 (automatic)" field_num: 24 type: int32 optional: true - ordering: + description: >- + Barrier: ordering algorithm cuDSS uses for sparse factorizations, which + can significantly affect runtime. -1 automatic, 0 cuDSS default + ordering, 1 AMD (approximate minimum degree). + default: "-1 (automatic)" field_num: 25 type: int32 optional: true - barrier_dual_initial_point: + description: >- + Barrier: how the dual initial point is computed, which affects the + iteration count. -1 automatic, 0 the Lustig-Marsten-Shanno heuristic, + 1 a least-squares problem minimizing the norms of the dual variables + and reduced costs subject to the dual equality constraints. + default: "-1 (automatic)" field_num: 26 type: int32 from_proto_cast: "barrier_dual_initial_point_t" optional: true - eliminate_dense_columns: + description: >- + Barrier: whether dense columns are eliminated from the constraint + matrix before solving, reducing factorization fill-in at the cost of + extra solves per iteration. Only has an effect when the ADAT (normal + equation) system is solved. + default: "true" field_num: 27 type: bool optional: true @@ -570,6 +716,10 @@ pdlp_settings: # proto3 `optional` so that a client which omits this field preserves # the solver default; without `optional`, the proto3 wire zero (`false`) # would silently overwrite the C++ default. + description: >- + Barrier: whether iterative refinement runs after each barrier solve to + improve solution accuracy (see cpp/src/barrier/barrier.cu). + default: "true" field_num: 31 type: bool optional: true @@ -582,19 +732,49 @@ pdlp_settings: # binding restricts the range to [0.5, 0.9999] with default 0.9; see # solver_settings.cu. No post-decode clamp is applied here for # consistency with the other f_t scalars (tolerances, time_limit, ...). + description: >- + Barrier: scaling factor applied to the primal/dual step size. Must be + strictly less than 1 — 0.9 is conservative, 0.999 aggressive. The + local-solve binding restricts the range to [0.5, 0.9999]; this field is + not clamped after decoding, for consistency with the other float + scalars. + default: "0.9" field_num: 32 optional: true - postsolve_info: + # No user-facing documentation exists for this field yet (it is absent + # from docs/cuopt/source/convex-settings.rst); only the C++ default is + # recorded here. Add a `description:` once the behavior is documented. + default: "-1" field_num: 33 type: int32 optional: true - save_best_primal_so_far: + description: >- + Whether PDLP keeps the best primal solution seen so far. When enabled, + a primal feasible iterate always beats an infeasible one; among + feasible iterates the best primal objective wins; among infeasible + ones the lowest primal residual wins, breaking ties on objective. + default: "false" field_num: 28 type: bool - first_primal_feasible: + description: >- + Whether the solver stops as soon as a primal feasible iterate is found, + without waiting for optimality or dual feasibility. Composable with + per_constraint_residual. + default: "false" field_num: 29 type: bool - pdlp_precision: + description: >- + Precision mode for the PDLP solver. DefaultPrecision uses the problem's + native precision; SinglePrecision runs PDHG in FP32 (half the memory, + roughly 2x faster iterations, possibly more of them); DoublePrecision + forces FP64; MixedPrecision stores the constraint matrix in FP32 for + faster SpMV while keeping vectors and compute in FP64 (convergence + checks still use the FP64 matrix, so memory is not reduced). + default: "DefaultPrecision" field_num: 30 type: int32 from_proto_cast: "pdlp_precision_t" @@ -610,52 +790,103 @@ mip_settings: fields: # Limits - time_limit: + description: >- + Wall-clock limit in seconds after which the solver stops and returns + the best solution found so far. When set together with node_limit or + work_limit, the first limit reached wins. + default: "no limit (infinity)" field_num: 1 optional: true # Tolerances (nested: settings.tolerances.) - tolerances: - relative_mip_gap: + description: >- + Relative gap at which the solve terminates: + abs(best_objective - dual_bound) / abs(best_objective). The gap is + zero when both are zero, and infinite when only the best objective + is zero. + default: "1e-4" field_num: 2 optional: true - absolute_mip_gap: + description: >- + Absolute gap at which the solve terminates: best_objective - + dual_bound when minimizing, dual_bound - best_objective when + maximizing. + default: "1e-10" field_num: 3 optional: true - integrality_tolerance: + description: >- + How close to an integer a variable must be to count as integral. + default: "1e-5" field_num: 4 optional: true - absolute_tolerance: + description: MIP absolute tolerance. + default: "1e-6" field_num: 5 optional: true - relative_tolerance: + description: MIP relative tolerance. + default: "1e-12" field_num: 6 optional: true - presolve_absolute_tolerance: + # No user-facing documentation exists for this field yet (it is absent + # from docs/cuopt/source/mip-settings.rst); only the C++ default is + # recorded here. Add a `description:` once the behavior is documented. + default: "1e-6" field_num: 7 optional: true # Solver configuration - log_to_console: + description: >- + Whether the solver writes log output to the console. Logs may still be + written to a file when this is false. + default: "true" field_num: 8 type: bool optional: true - heuristics_only: + description: >- + When true only the GPU heuristics run, improving the primal bound + alone. When false both GPU and CPU are used and the dual bound is + improved on the CPU. + default: "false" field_num: 9 type: bool - num_cpu_threads: + description: >- + Number of CPU threads used by the MIP solver. Lower values cap cuOpt's + CPU footprint; higher values speed up the CPU-parallel parts. -1 + derives the count from the number of CPU cores. + default: "-1 (automatic)" field_num: 10 type: int32 optional: true - num_gpus: + description: Number of GPUs used for the solve. + default: "1" field_num: 11 type: int32 optional: true - presolver: + description: >- + Which presolver performs presolve reductions: 0 disables presolve, + 1 selects Papilo, 2 selects PSLP. MIP uses Papilo by default. + default: "Default (Papilo for MIP)" field_num: 12 type: int32 from_proto_cast: "presolver_t" optional: true - mip_scaling: + description: >- + Whether scaling is applied to the MIP problem. 0 off, 1 on, 2 applied + but not to the objective. + default: "2 (no objective scaling)" field_num: 13 type: int32 optional: true @@ -664,18 +895,33 @@ mip_settings: # 1 (orbital fixing), 2 (orbital fixing + lexical reduction). The # mapper clamps out-of-range values to -1 to match the local-solve # range check in cpp/src/math_optimization/solver_settings.cu. + description: >- + Symmetry detection and handling. -1 automatic, 0 disabled, 1 orbital + fixing, 2 orbital fixing plus lexical reduction. + default: "-1 (automatic)" field_num: 33 type: int32 optional: true # Additional limits - work_limit: + description: >- + Limit in work units — a machine-independent measure of solver effort — + after which the solver stops and returns the current solution. When set + together with time_limit or node_limit, the first limit reached wins. + default: "no limit (infinity)" field_num: 14 optional: true - node_limit: # C++ default `max()`. See iteration_limit for the rationale on # composing `sentinel` with `optional` (sentinel for the explicit # wire-encoded "default" value, optional for the omitted-field case). + description: >- + Maximum number of branch-and-bound nodes explored before the solver + stops and returns the best feasible solution found, if any. When set + together with time_limit, the first limit reached wins. Encoded on the + wire as -1 for "no limit". + default: "no limit (INT_MAX)" field_num: 15 type: int32 sentinel: max_as_negative_1 @@ -683,69 +929,147 @@ mip_settings: # Branching - reliability_branching: + description: >- + Reliability branching mode. -1 automatic (the solver picks whether to + use it and with what factor), 0 disabled, k > 0 enables it and treats a + variable as reliable once it has been branched on k times. + default: "-1 (automatic)" field_num: 16 type: int32 optional: true - mip_batch_pdlp_strong_branching: + description: >- + Whether strong branching at the root evaluates candidates with a single + batched PDLP solve instead of solving them in parallel with dual + simplex, which can cut strong-branching time when dual simplex + struggles. 0 disabled, 1 enabled. + default: "0 (disabled)" field_num: 17 type: int32 - mip_batch_pdlp_reliability_branching: + description: >- + Whether reliability-branching candidates are evaluated simultaneously + with a single batched PDLP solve. 0 disabled, 1 enabled. + default: "0 (disabled)" field_num: 32 type: int32 - strong_branching_simplex_iteration_limit: + description: >- + Maximum simplex iterations per candidate during strong branching. + Lowering it speeds up strong branching at the cost of less accurate + candidate evaluations. + default: "-1 (automatic)" field_num: 30 type: int32 optional: true # Cut configuration + # The individual cut-family toggles (mir_cuts, mixed_integer_gomory_cuts, + # knapsack_cuts, clique_cuts, zero_half_cuts, implied_bound_cuts, + # strong_chvatal_gomory_cuts) share the same tri-state contract: + # -1 automatic, 0 disabled, 1 enabled. - max_cut_passes: + description: >- + Maximum number of cut passes to run. 0 disables cuts entirely; larger + values perform more passes. + default: "10" field_num: 18 type: int32 optional: true - mir_cuts: + description: >- + Whether mixed-integer rounding cuts are used. -1 automatic (the solver + decides from problem characteristics), 0 disabled, 1 enabled. + default: "-1 (automatic)" field_num: 19 type: int32 optional: true - mixed_integer_gomory_cuts: + description: >- + Whether mixed-integer Gomory cuts are used. -1 automatic, 0 disabled, + 1 enabled. + default: "-1 (automatic)" field_num: 20 type: int32 optional: true - knapsack_cuts: + description: >- + Whether knapsack cuts are used. -1 automatic, 0 disabled, 1 enabled. + default: "-1 (automatic)" field_num: 21 type: int32 optional: true - clique_cuts: + description: >- + Whether clique cuts are used. -1 automatic, 0 disabled, 1 enabled. + default: "-1 (automatic)" field_num: 22 type: int32 optional: true - zero_half_cuts: + description: >- + Whether zero-half cuts are used. -1 automatic, 0 disabled, 1 enabled. + default: "-1 (automatic)" field_num: 52 type: int32 optional: true - implied_bound_cuts: + description: >- + Whether implied bound cuts are used. -1 automatic, 0 disabled, + 1 enabled. + default: "-1 (automatic)" field_num: 31 type: int32 optional: true - strong_chvatal_gomory_cuts: + description: >- + Whether strong Chvatal-Gomory cuts are used. -1 automatic, 0 disabled, + 1 enabled. + default: "-1 (automatic)" field_num: 23 type: int32 optional: true - reduced_cost_strengthening: + description: >- + Whether integer feasible solutions are used to strengthen integer + variable bounds. -1 automatic, 0 disabled, 1 during the root cut + passes, 2 during the root cut passes and after strong branching. + default: "-1 (automatic)" field_num: 24 type: int32 optional: true - cut_change_threshold: + description: >- + Required improvement in the dual bound per cut pass. Larger values + demand significant improvement each pass; -1 lets cut passes continue + even without improvement. + default: "-1 (no threshold)" field_num: 25 optional: true - cut_min_orthogonality: + description: >- + Minimum orthogonality a cut needs to be added to the LP relaxation. + Values near 1 require cuts to be nearly orthogonal to each other; + values near 0 admit more cuts. + default: "0.5" field_num: 26 optional: true # Determinism and reproducibility - determinism_mode: + description: >- + 0 opportunistic — results may vary between runs due to parallelism. + 1 deterministic — improves reproducibility across runs with the same + thread count. Deterministic mode is experimental and does not yet + guarantee fully deterministic results in every scenario. + default: "0 (opportunistic)" field_num: 27 type: int32 - seed: + description: >- + Random seed. A fixed seed gives reproducible results when running in + deterministic mode. + default: "-1 (chosen automatically)" field_num: 28 type: int32 optional: true @@ -757,6 +1081,13 @@ mip_settings: # `optional`, the proto3 wire zero (`false`) would silently overwrite # the C++ default. See also `dual_postsolve` and # `barrier_iterative_refinement` in pdlp_settings. + description: >- + Whether the probing-cache step of MIP presolve runs. Probing evaluates + variable fixings to discover implications later used by branch-and-bound + and the rounding heuristics. Only meaningful when presolve is otherwise + enabled — presolver=0 disables the whole pipeline. Probing is also + skipped in deterministic mode and on LP-only solves. + default: "true" field_num: 29 type: bool optional: true @@ -766,6 +1097,14 @@ mip_settings: # cannot derive a finite UB. Local-solve binding restricts the range to # [1.0, +inf] with default 1e10; see solver_settings.cu. - semi_continuous_big_m: + description: >- + Big-M coefficient used when linearizing semi-continuous variable + constraints (such a variable is either zero or lies in + [lower_bound, upper_bound]). Should be at least as large as the upper + bound of any semi-continuous variable in the problem. Serves as the + fallback upper bound when bounds-strengthening cannot derive a finite + one. The local-solve binding restricts the range to [1.0, +inf]. + default: "1e10" field_num: 34 optional: true @@ -775,12 +1114,19 @@ mip_settings: # All fields carry non-zero defaults on the C++ side, so they are declared # `optional` in the proto: a client that omits any of them gets the C++ # default rather than the proto3 wire zero (0). + # These are internal tuning knobs with no user-facing documentation page; + # the descriptions below are taken from the declaring comments in + # heuristics_hyper_params.hpp rather than from docs/. - heuristic_params: - population_size: + description: Maximum number of solutions held in the solution pool. + default: "32" field_num: 35 type: int32 optional: true - num_cpufj_threads: + description: Number of parallel CPU Feasibility Jump climbers. + default: "8" field_num: 36 type: int32 optional: true @@ -788,54 +1134,96 @@ mip_settings: # presolve stopped taking a wall budget. Do not reuse: an older client # still sends them on those numbers. - root_lp_time_ratio: + description: Fraction of the total time budget given to the root LP. + default: "0.1" field_num: 39 optional: true - root_lp_max_time: + description: Hard cap in seconds on the root LP solve. + default: "15.0" field_num: 40 optional: true - rins_time_limit: + description: Per-call time budget in seconds for the RINS sub-MIP. + default: "3.0" field_num: 41 optional: true - rins_max_time_limit: + description: >- + Ceiling in seconds on the adaptive RINS time budget. + default: "20.0" field_num: 42 optional: true - rins_fix_rate: + description: Fraction of variables RINS fixes before solving the sub-MIP. + default: "0.5" field_num: 43 optional: true - stagnation_trigger: + description: >- + Number of Feasibility Pump loops without improvement before + recombination is triggered. + default: "3" field_num: 44 type: int32 optional: true - max_iterations_without_improvement: + description: Depth of the diversity step taken after stagnation. + default: "8" field_num: 45 type: int32 optional: true - initial_infeasibility_weight: + description: Seed value for the constraint violation penalty. + default: "1000.0" field_num: 46 optional: true - n_of_minimums_for_exit: + description: >- + Number of local minima after which the Feasibility Jump baseline + exits. + default: "7000" field_num: 47 type: int32 optional: true - enabled_recombiners: + description: >- + Bitmask of enabled recombiners: 1 bound propagation, 2 feasibility + pump, 4 local search, 8 sub-MIP. + default: "15 (all enabled)" field_num: 48 type: int32 optional: true - cycle_detection_length: + description: >- + Size of the ring buffer used to detect Feasibility Pump assignment + cycles. + default: "30" field_num: 49 type: int32 optional: true - relaxed_lp_time_limit: + description: >- + Base time cap in seconds for relaxed LP solves inside the heuristics. + default: "1.0" field_num: 50 optional: true - related_vars_time_limit: + description: >- + Time in seconds allowed for building the related-variable structure. + default: "30.0" field_num: 51 optional: true - presolve_max_rounds: + description: >- + Cap on Papilo presolve rounds. A value <= 0 removes the cap entirely. + default: "-1 (no override)" field_num: 53 type: int32 optional: true - papilo_probing_max_badgesize: + description: Ceiling on Papilo's probing.minbadgesize. + default: "-1 (no override)" field_num: 54 type: int32 optional: true diff --git a/cpp/src/grpc/codegen/generate_conversions.py b/cpp/src/grpc/codegen/generate_conversions.py index 74183e1a62..db2d191cd6 100644 --- a/cpp/src/grpc/codegen/generate_conversions.py +++ b/cpp/src/grpc/codegen/generate_conversions.py @@ -339,6 +339,46 @@ def _array_wire_type_comment(f): return f"raw bytes ({size} B/elem)" +_DOC_COMMENT_WIDTH = 78 + + +def _field_doc_comment(f, indent=" "): + """Render a field's `description:` / `default:` registry attributes as + proto leading comment lines. + + The generated proto is part of the public wire contract (see + GRPC_INTERFACE.md, "Custom Clients"), so a third-party client reading + only the .proto should learn what a settings field means and what it + does when omitted. Returns [] when the field carries neither + attribute, so undocumented fields emit exactly as before. + + `default:` is rendered verbatim from the registry — it is a string + describing the C++ member initializer, not a value the generator + derives or validates. + """ + description = f.get("description") + default = f.get("default") + if not description and default is None: + return [] + body = " ".join(str(description).split()) if description else "" + if default is not None: + suffix = f"(default: {default})" + body = f"{body} {suffix}" if body else suffix + prefix = f"{indent}// " + width = max(_DOC_COMMENT_WIDTH - len(prefix), 20) + lines, current = [], "" + for word in body.split(): + candidate = f"{current} {word}" if current else word + if current and len(candidate) > width: + lines.append(f"{prefix}{current}") + current = word + else: + current = candidate + if current: + lines.append(f"{prefix}{current}") + return lines + + # ============================================================================ # Enum helpers — convention-based derivation # ============================================================================ @@ -1275,7 +1315,9 @@ def generate_settings_message_proto(registry, message_name, obj): continue ptype = _settings_field_proto_type(registry, f) prefix = "optional " if f.get("optional") else "" - lines.append((num, f" {prefix}{ptype} {f['name']} = {num};")) + decl = f" {prefix}{ptype} {f['name']} = {num};" + doc = _field_doc_comment(f) + lines.append((num, "\n".join(doc + [decl]))) lines.extend(_iter_embeds(obj)) lines.sort(key=lambda x: x[0]) return "\n".join(item[1] for item in lines) diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 344b0e1392..76bb1a8013 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -164,95 +164,330 @@ message OptimizationProblem { } message PDLPSolverSettings { + // Absolute term in PDLP's duality gap check: duality_gap < + // absolute_gap_tolerance + relative_gap_tolerance * (|primal_objective| + + // |dual_objective|). (default: 1e-4) optional double absolute_gap_tolerance = 1; + // Relative term in PDLP's duality gap check; multiplies (|primal_objective| + // + |dual_objective|). Significant impact on accuracy and runtime. + // (default: 1e-4) optional double relative_gap_tolerance = 2; + // Tolerance used when PDLP declares the problem primal infeasible. Only + // consulted when detect_infeasibility is enabled. (default: 1e-10) optional double primal_infeasible_tolerance = 3; + // Tolerance used when PDLP declares the problem dual infeasible + // (unbounded). Only consulted when detect_infeasibility is enabled. + // (default: 1e-10) optional double dual_infeasible_tolerance = 4; + // Absolute term in PDLP's dual feasibility check: dual_feasibility < + // absolute_dual_tolerance + relative_dual_tolerance * l2_norm(c). (default: + // 1e-4) optional double absolute_dual_tolerance = 5; + // Relative term in PDLP's dual feasibility check; multiplies the objective + // vector L2 norm. (default: 1e-4) optional double relative_dual_tolerance = 6; + // Absolute term in PDLP's primal feasibility check: primal_feasibility < + // absolute_primal_tolerance + relative_primal_tolerance * l2_norm(b). + // (default: 1e-4) optional double absolute_primal_tolerance = 7; + // Relative term in PDLP's primal feasibility check; multiplies the + // right-hand-side vector L2 norm. (default: 1e-4) optional double relative_primal_tolerance = 8; + // Wall-clock limit in seconds after which the solver stops and returns the + // current solution. Checked periodically rather than continuously, so the + // solver may run slightly over. When set together with iteration_limit, the + // first limit reached wins. (default: no limit (infinity)) optional double time_limit = 9; + // Iteration limit after which the solver stops and returns the current + // solution. Checked periodically, so the solver may run a few extra + // iterations. When set together with time_limit, the first limit reached + // wins. Encoded on the wire as -1 for "no limit". (default: no limit + // (INT_MAX)) optional int64 iteration_limit = 10; + // Whether the solver writes log output to the console. Logs may still be + // written to a file when this is false. (default: true) optional bool log_to_console = 11; + // Whether PDLP attempts to detect infeasibility. Detection is not always + // accurate — some problems reported infeasible converge under a different + // tolerance — and costs roughly 3-7% runtime and 10-20% memory. Dual + // simplex always detects infeasibility regardless of this setting. + // (default: false) bool detect_infeasibility = 12; + // When true, PDLP stops if either the current or the average solution is + // detected infeasible. When false, both must be detected infeasible. + // (default: false) bool strict_infeasibility = 13; + // Mode under which PDLP operates; changes how the problem is internally + // optimized and can drastically change solve time. Stable3 is the best + // overall mode from cuOpt experiments; Methodical1 takes slower but fewer + // steps at 1.3-1.7x the memory; Fast1 is fastest but converges less often. + // There is no way to know upfront which mode suits a given problem — + // testing is encouraged. (default: Stable3) optional PDLPSolverMode pdlp_solver_mode = 14; + // Method used to solve the LP. Concurrent runs PDLP, dual simplex and + // barrier in parallel. Default accuracy differs per method: PDLP 1e-4 + // relative, barrier 1e-8 relative, dual simplex 1e-6 absolute. (default: + // Concurrent) LPMethod method = 15; + // Which presolver performs presolve reductions: 0 disables presolve, 1 + // selects Papilo, 2 selects PSLP. By default LP uses PSLP and MIP uses + // Papilo. (default: Default (PSLP for LP, Papilo for MIP)) optional int32 presolver = 16; + // Whether dual postsolve runs when the Papilo presolver is used on an LP. + // Disabling it can improve solve time at the cost of losing access to the + // dual solution. Not relevant for MIP. (default: true) optional bool dual_postsolve = 17; + // Whether PDLP or barrier crosses over to a basic solution after reaching + // optimality. PDLP and barrier solutions need not lie at a vertex; + // crossover yields a basic solution with n - m variables on their bounds. + // Significant impact on accuracy and runtime. (default: false) bool crossover = 18; + // Number of GPUs used for the solve. Relevant to LP in concurrent mode (up + // to 2 GPUs), where PDLP and barrier run on separate GPUs rather than + // sharing one. For distributed PDLP, -1 selects all visible devices. + // (default: 1) optional int32 num_gpus = 19; + // Whether PDLP computes primal and dual residuals per constraint instead of + // globally. (default: false) bool per_constraint_residual = 20; + // Whether cuDSS runs in deterministic mode. Deterministic mode makes + // results reproducible across runs but may be slower. (default: false) bool cudss_deterministic = 21; + // Barrier: whether to fold the LP, reducing problem size by exploiting + // symmetry. -1 automatic, 0 disabled, 1 forced. (default: -1 (automatic)) optional int32 folding = 22; + // Barrier: which linear system to solve at each iteration. -1 automatic, 0 + // the ADAT system (normal equations), 1 the augmented system. The augmented + // system can be more stable, ADAT is often faster. (default: -1 + // (automatic)) optional int32 augmented = 23; + // Barrier: whether presolve dualizes the LP, which can help problems with + // inequality constraints that have more constraints than variables. -1 + // automatic, 0 disabled, 1 forced. (default: -1 (automatic)) optional int32 dualize = 24; + // Barrier: ordering algorithm cuDSS uses for sparse factorizations, which + // can significantly affect runtime. -1 automatic, 0 cuDSS default ordering, + // 1 AMD (approximate minimum degree). (default: -1 (automatic)) optional int32 ordering = 25; + // Barrier: how the dual initial point is computed, which affects the + // iteration count. -1 automatic, 0 the Lustig-Marsten-Shanno heuristic, 1 a + // least-squares problem minimizing the norms of the dual variables and + // reduced costs subject to the dual equality constraints. (default: -1 + // (automatic)) optional int32 barrier_dual_initial_point = 26; + // Barrier: whether dense columns are eliminated from the constraint matrix + // before solving, reducing factorization fill-in at the cost of extra + // solves per iteration. Only has an effect when the ADAT (normal equation) + // system is solved. (default: true) optional bool eliminate_dense_columns = 27; + // Whether PDLP keeps the best primal solution seen so far. When enabled, a + // primal feasible iterate always beats an infeasible one; among feasible + // iterates the best primal objective wins; among infeasible ones the lowest + // primal residual wins, breaking ties on objective. (default: false) bool save_best_primal_so_far = 28; + // Whether the solver stops as soon as a primal feasible iterate is found, + // without waiting for optimality or dual feasibility. Composable with + // per_constraint_residual. (default: false) bool first_primal_feasible = 29; + // Precision mode for the PDLP solver. DefaultPrecision uses the problem's + // native precision; SinglePrecision runs PDHG in FP32 (half the memory, + // roughly 2x faster iterations, possibly more of them); DoublePrecision + // forces FP64; MixedPrecision stores the constraint matrix in FP32 for + // faster SpMV while keeping vectors and compute in FP64 (convergence checks + // still use the FP64 matrix, so memory is not reduced). (default: + // DefaultPrecision) optional int32 pdlp_precision = 30; + // Barrier: whether iterative refinement runs after each barrier solve to + // improve solution accuracy (see cpp/src/barrier/barrier.cu). (default: + // true) optional bool barrier_iterative_refinement = 31; + // Barrier: scaling factor applied to the primal/dual step size. Must be + // strictly less than 1 — 0.9 is conservative, 0.999 aggressive. The + // local-solve binding restricts the range to [0.5, 0.9999]; this field is + // not clamped after decoding, for consistency with the other float scalars. + // (default: 0.9) optional double barrier_step_scale = 32; + // (default: -1) optional int32 postsolve_info = 33; optional int32 barrier_adaptive_regularization = 34; PDLPWarmStartData warm_start_data = 50; } message MIPSolverSettings { + // Wall-clock limit in seconds after which the solver stops and returns the + // best solution found so far. When set together with node_limit or + // work_limit, the first limit reached wins. (default: no limit (infinity)) optional double time_limit = 1; + // Relative gap at which the solve terminates: abs(best_objective - + // dual_bound) / abs(best_objective). The gap is zero when both are zero, + // and infinite when only the best objective is zero. (default: 1e-4) optional double relative_mip_gap = 2; + // Absolute gap at which the solve terminates: best_objective - dual_bound + // when minimizing, dual_bound - best_objective when maximizing. (default: + // 1e-10) optional double absolute_mip_gap = 3; + // How close to an integer a variable must be to count as integral. + // (default: 1e-5) optional double integrality_tolerance = 4; + // MIP absolute tolerance. (default: 1e-6) optional double absolute_tolerance = 5; + // MIP relative tolerance. (default: 1e-12) optional double relative_tolerance = 6; + // (default: 1e-6) optional double presolve_absolute_tolerance = 7; + // Whether the solver writes log output to the console. Logs may still be + // written to a file when this is false. (default: true) optional bool log_to_console = 8; + // When true only the GPU heuristics run, improving the primal bound alone. + // When false both GPU and CPU are used and the dual bound is improved on + // the CPU. (default: false) bool heuristics_only = 9; + // Number of CPU threads used by the MIP solver. Lower values cap cuOpt's + // CPU footprint; higher values speed up the CPU-parallel parts. -1 derives + // the count from the number of CPU cores. (default: -1 (automatic)) optional int32 num_cpu_threads = 10; + // Number of GPUs used for the solve. (default: 1) optional int32 num_gpus = 11; + // Which presolver performs presolve reductions: 0 disables presolve, 1 + // selects Papilo, 2 selects PSLP. MIP uses Papilo by default. (default: + // Default (Papilo for MIP)) optional int32 presolver = 12; + // Whether scaling is applied to the MIP problem. 0 off, 1 on, 2 applied but + // not to the objective. (default: 2 (no objective scaling)) optional int32 mip_scaling = 13; + // Limit in work units — a machine-independent measure of solver effort — + // after which the solver stops and returns the current solution. When set + // together with time_limit or node_limit, the first limit reached wins. + // (default: no limit (infinity)) optional double work_limit = 14; + // Maximum number of branch-and-bound nodes explored before the solver stops + // and returns the best feasible solution found, if any. When set together + // with time_limit, the first limit reached wins. Encoded on the wire as -1 + // for "no limit". (default: no limit (INT_MAX)) optional int32 node_limit = 15; + // Reliability branching mode. -1 automatic (the solver picks whether to use + // it and with what factor), 0 disabled, k > 0 enables it and treats a + // variable as reliable once it has been branched on k times. (default: -1 + // (automatic)) optional int32 reliability_branching = 16; + // Whether strong branching at the root evaluates candidates with a single + // batched PDLP solve instead of solving them in parallel with dual simplex, + // which can cut strong-branching time when dual simplex struggles. 0 + // disabled, 1 enabled. (default: 0 (disabled)) int32 mip_batch_pdlp_strong_branching = 17; + // Maximum number of cut passes to run. 0 disables cuts entirely; larger + // values perform more passes. (default: 10) optional int32 max_cut_passes = 18; + // Whether mixed-integer rounding cuts are used. -1 automatic (the solver + // decides from problem characteristics), 0 disabled, 1 enabled. (default: + // -1 (automatic)) optional int32 mir_cuts = 19; + // Whether mixed-integer Gomory cuts are used. -1 automatic, 0 disabled, 1 + // enabled. (default: -1 (automatic)) optional int32 mixed_integer_gomory_cuts = 20; + // Whether knapsack cuts are used. -1 automatic, 0 disabled, 1 enabled. + // (default: -1 (automatic)) optional int32 knapsack_cuts = 21; + // Whether clique cuts are used. -1 automatic, 0 disabled, 1 enabled. + // (default: -1 (automatic)) optional int32 clique_cuts = 22; + // Whether strong Chvatal-Gomory cuts are used. -1 automatic, 0 disabled, 1 + // enabled. (default: -1 (automatic)) optional int32 strong_chvatal_gomory_cuts = 23; + // Whether integer feasible solutions are used to strengthen integer + // variable bounds. -1 automatic, 0 disabled, 1 during the root cut passes, + // 2 during the root cut passes and after strong branching. (default: -1 + // (automatic)) optional int32 reduced_cost_strengthening = 24; + // Required improvement in the dual bound per cut pass. Larger values demand + // significant improvement each pass; -1 lets cut passes continue even + // without improvement. (default: -1 (no threshold)) optional double cut_change_threshold = 25; + // Minimum orthogonality a cut needs to be added to the LP relaxation. + // Values near 1 require cuts to be nearly orthogonal to each other; values + // near 0 admit more cuts. (default: 0.5) optional double cut_min_orthogonality = 26; + // 0 opportunistic — results may vary between runs due to parallelism. 1 + // deterministic — improves reproducibility across runs with the same thread + // count. Deterministic mode is experimental and does not yet guarantee + // fully deterministic results in every scenario. (default: 0 + // (opportunistic)) int32 determinism_mode = 27; + // Random seed. A fixed seed gives reproducible results when running in + // deterministic mode. (default: -1 (chosen automatically)) optional int32 seed = 28; + // Whether the probing-cache step of MIP presolve runs. Probing evaluates + // variable fixings to discover implications later used by branch-and-bound + // and the rounding heuristics. Only meaningful when presolve is otherwise + // enabled — presolver=0 disables the whole pipeline. Probing is also + // skipped in deterministic mode and on LP-only solves. (default: true) optional bool probing = 29; + // Maximum simplex iterations per candidate during strong branching. + // Lowering it speeds up strong branching at the cost of less accurate + // candidate evaluations. (default: -1 (automatic)) optional int32 strong_branching_simplex_iteration_limit = 30; + // Whether implied bound cuts are used. -1 automatic, 0 disabled, 1 enabled. + // (default: -1 (automatic)) optional int32 implied_bound_cuts = 31; + // Whether reliability-branching candidates are evaluated simultaneously + // with a single batched PDLP solve. 0 disabled, 1 enabled. (default: 0 + // (disabled)) int32 mip_batch_pdlp_reliability_branching = 32; + // Symmetry detection and handling. -1 automatic, 0 disabled, 1 orbital + // fixing, 2 orbital fixing plus lexical reduction. (default: -1 + // (automatic)) optional int32 symmetry = 33; + // Big-M coefficient used when linearizing semi-continuous variable + // constraints (such a variable is either zero or lies in [lower_bound, + // upper_bound]). Should be at least as large as the upper bound of any + // semi-continuous variable in the problem. Serves as the fallback upper + // bound when bounds-strengthening cannot derive a finite one. The + // local-solve binding restricts the range to [1.0, +inf]. (default: 1e10) optional double semi_continuous_big_m = 34; + // Maximum number of solutions held in the solution pool. (default: 32) optional int32 population_size = 35; + // Number of parallel CPU Feasibility Jump climbers. (default: 8) optional int32 num_cpufj_threads = 36; + // Fraction of the total time budget given to the root LP. (default: 0.1) optional double root_lp_time_ratio = 39; + // Hard cap in seconds on the root LP solve. (default: 15.0) optional double root_lp_max_time = 40; + // Per-call time budget in seconds for the RINS sub-MIP. (default: 3.0) optional double rins_time_limit = 41; + // Ceiling in seconds on the adaptive RINS time budget. (default: 20.0) optional double rins_max_time_limit = 42; + // Fraction of variables RINS fixes before solving the sub-MIP. (default: + // 0.5) optional double rins_fix_rate = 43; + // Number of Feasibility Pump loops without improvement before recombination + // is triggered. (default: 3) optional int32 stagnation_trigger = 44; + // Depth of the diversity step taken after stagnation. (default: 8) optional int32 max_iterations_without_improvement = 45; + // Seed value for the constraint violation penalty. (default: 1000.0) optional double initial_infeasibility_weight = 46; + // Number of local minima after which the Feasibility Jump baseline exits. + // (default: 7000) optional int32 n_of_minimums_for_exit = 47; + // Bitmask of enabled recombiners: 1 bound propagation, 2 feasibility pump, + // 4 local search, 8 sub-MIP. (default: 15 (all enabled)) optional int32 enabled_recombiners = 48; + // Size of the ring buffer used to detect Feasibility Pump assignment + // cycles. (default: 30) optional int32 cycle_detection_length = 49; + // Base time cap in seconds for relaxed LP solves inside the heuristics. + // (default: 1.0) optional double relaxed_lp_time_limit = 50; + // Time in seconds allowed for building the related-variable structure. + // (default: 30.0) optional double related_vars_time_limit = 51; + // Whether zero-half cuts are used. -1 automatic, 0 disabled, 1 enabled. + // (default: -1 (automatic)) optional int32 zero_half_cuts = 52; + // Cap on Papilo presolve rounds. A value <= 0 removes the cap entirely. + // (default: -1 (no override)) optional int32 presolve_max_rounds = 53; + // Ceiling on Papilo's probing.minbadgesize. (default: -1 (no override)) optional int32 papilo_probing_max_badgesize = 54; } From 64614dafab46257e0d93bb28dcd0a68d89f2cecd Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 11 Aug 2026 11:42:00 -0500 Subject: [PATCH 02/14] Add gRPC codegen guidance to cuopt-developer The skill had no coverage of cpp/src/grpc/codegen/, so an agent editing a wire field could hand-edit the generated output or skip regeneration. Codegen is an explicit ./build.sh target that never runs during a normal build, so a missed regeneration surfaces only as a CI failure. Adds references/grpc_codegen.md covering the registry workflow, the optional/sentinel presence traps, field-number permanence, and the rule that the C++ member initializer rather than docs/ is ground truth for a setting's default. Adds a safety rule and pointer section in SKILL.md. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Ramakrishna Prabhu --- skills/cuopt-developer/SKILL.md | 15 +++ .../references/grpc_codegen.md | 102 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 skills/cuopt-developer/references/grpc_codegen.md diff --git a/skills/cuopt-developer/SKILL.md b/skills/cuopt-developer/SKILL.md index 2aee1e7c75..31112d2eab 100644 --- a/skills/cuopt-developer/SKILL.md +++ b/skills/cuopt-developer/SKILL.md @@ -167,6 +167,11 @@ cuopt/ - Never suggest `--no-verify` or skipping checks - All PRs must pass CI +### Never Hand-Edit Generated Files +- `cpp/src/grpc/codegen/generated/` is generated from `field_registry.yaml` +- Edit the registry, then run `./build.sh codegen`, then commit both +- Codegen is an explicit build target, never automatic — see [gRPC wire fields](#grpc-wire-fields-and-codegen) + ### CUDA/GPU Hygiene - Keep operations stream-ordered - Follow existing RAFT/RMM patterns @@ -264,6 +269,8 @@ For build/test pitfalls (Cython rebuild, OOM, CUDA driver mismatch, missing `nvc | Conda environments | `conda/environments/` | | Test data | `datasets/` | | CI scripts | `ci/` | +| gRPC field registry | `cpp/src/grpc/codegen/field_registry.yaml` | +| gRPC generated output | `cpp/src/grpc/codegen/generated/` (never hand-edit) | ## Canonical Documentation @@ -275,6 +282,14 @@ For build/test pitfalls (Cython rebuild, OOM, CUDA driver mismatch, missing `nvc _Shell-execution, install, conda-env, and sudo policies are covered by [Refusal Rules — Read First](#refusal-rules--read-first) at the top of this skill._ +## gRPC wire fields and codegen + +When adding or changing anything that crosses the cuOpt gRPC wire — a problem field, a solution field, or a solver setting — read: + +- **`references/grpc_codegen.md`** — the `field_registry.yaml` → `./build.sh codegen` → commit-`generated/` workflow, the `optional` / `sentinel` presence traps, field-number permanence, and why the C++ member initializer (not the docs) is ground truth for a setting's default. + +Read it **before** editing `field_registry.yaml` or any file under `cpp/src/grpc/codegen/generated/`. Codegen never runs as part of a normal build, so a skipped regeneration surfaces only as a CI failure. + ## VRP dimension internals (routing engine) When implementing or debugging **VRP dimensions** (constraints, objectives, forward/backward propagation, `combine`, local-search deltas), read: diff --git a/skills/cuopt-developer/references/grpc_codegen.md b/skills/cuopt-developer/references/grpc_codegen.md new file mode 100644 index 0000000000..02ebe99c61 --- /dev/null +++ b/skills/cuopt-developer/references/grpc_codegen.md @@ -0,0 +1,102 @@ +# gRPC Wire Fields and the Codegen Registry + +Read this before touching anything that crosses the cuOpt gRPC wire: problem +fields, solution fields, or solver settings. + +## The one rule + +**Never hand-edit files under `cpp/src/grpc/codegen/generated/`.** They are +generated from `cpp/src/grpc/codegen/field_registry.yaml` by +`cpp/src/grpc/codegen/generate_conversions.py`, and CI will reject a mismatch. + +Every field that crosses the wire is declared once in the registry. One entry +drives ~28 generated artifacts: the `.proto`, the C++ to/from-proto converters, +the chunked upload/download paths, and the size estimator. + +## Workflow + +```bash +# 1. edit cpp/src/grpc/codegen/field_registry.yaml +# 2. regenerate +./build.sh codegen +# 3. commit BOTH the registry and cpp/src/grpc/codegen/generated/ +# 4. optional local pre-check of what CI runs +bash ci/verify_grpc_codegen.sh +``` + +**Codegen is not part of the build.** `build.sh` guards it behind an explicit +`codegen` argument, and CMake only *consumes* the checked-in `generated/` +directory. Building cuOpt will never regenerate for you, and it will never warn +you that the registry has drifted — `ci/verify_grpc_codegen.sh` is what catches +that, in CI, after you push. + +`./build.sh codegen` needs only `pyyaml` — no GPU, no compile. It is cheap to +run and cheap to re-run; there is no reason to skip it. + +## Registry sections + +| Section | Generates | +|---------|-----------| +| `enums` | proto enums + C++ converters | +| `optimization_problem` | `OptimizationProblem` message, problem converters | +| `pdlp_settings` / `mip_settings` | `PDLPSolverSettings` / `MIPSolverSettings` | +| `lp_solution` / `mip_solution` | solution messages and converters | +| `chunked_result_header` | `ChunkedResultHeader` for the chunked download path | + +## Attributes worth knowing + +Full reference: `cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md`. The ones +that bite: + +- **`optional`** — emits proto3 presence tracking. Required whenever the C++ + default differs from the proto3 zero value. Without it, a client that *omits* + the field silently overwrites the solver default with `0` / `false` / the + first enum value. `bool foo{true}` and enums whose C++ default is not the + first declared value both need this. +- **`sentinel`** — maps a C++ sentinel (e.g. `numeric_limits::max()`) to a + reserved wire value (e.g. `-1`). Composes with `optional`: the sentinel covers + the explicitly-sent case, `optional` covers the omitted case. `iteration_limit` + and `node_limit` need both. +- **`description`** / **`default`** — documentation, emitted as leading comments + on the generated proto field. The generated `.proto` is part of the public + wire contract (`cpp/src/grpc/GRPC_INTERFACE.md`, "Custom Clients"), so a + third-party client reading only the `.proto` should learn what a settings + field means and what omitting it does. `default` is a free-text string + describing the C++ member initializer (`"1e-4"`, `"-1 (automatic)"`); the + generator neither derives nor validates it. + +## Field numbers are permanent + +`field_num` and `array_id` are wire identifiers. Never renumber or reuse a +retired number — older clients still send data on it. The registry records +retired numbers in comments (see the note on 37/38 in `mip_settings`); follow +that convention when removing a field. + +## Adding a settings field: checklist + +1. Add the C++ member with its initializer. +2. Add the registry entry with the next free `field_num`. +3. Add `optional:` if the C++ default is not the proto3 zero value. +4. Add `description:` and `default:` — match the C++ initializer, not the docs + (see below). +5. `./build.sh codegen`, commit registry + `generated/`. +6. Document the user-facing parameter in `docs/cuopt/source/convex-settings.rst` + (LP) or `mip-settings.rst` (MIP), and add the constant to + `cpp/include/cuopt/mathematical_optimization/constants.h`. + +## Documented defaults drift from the code + +`docs/cuopt/source/*-settings.rst` is the prose source for what a setting means, +but its stated defaults have been observed to disagree with the C++ member +initializers. **The C++ initializer is ground truth.** When writing `default:`, +read the struct — `pdlp/solver_settings.hpp`, `mip/solver_settings.hpp`, +`mip/heuristics_hyper_params.hpp` — and treat a docs mismatch as a docs bug to +report separately, not as something to propagate into the registry. + +## The registry is not a complete mirror of the settings structs + +Some settings exist in C++ and in the user docs but have no registry entry, +which means they cannot be set over gRPC at all. Before assuming a parameter is +remotely settable, grep `field_registry.yaml` for it. Adding a missing one is a +wire change (new `field_num`), not a documentation change — scope it as its own +PR. From 8876f1902130bb079da1e0d4e68bb9e571630325 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 11 Aug 2026 11:48:51 -0500 Subject: [PATCH 03/14] Generalize the generated-files rule beyond gRPC codegen The rule only named cpp/src/grpc/codegen/generated/, which reads as if that were the only generated tree. Replaces it with a source -> generated -> regenerate table covering the dependency files, doc versions, and skill/plugin version sync, and calls out that gRPC codegen is the only one not fixed automatically by a pre-commit hook. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Ramakrishna Prabhu --- skills/cuopt-developer/SKILL.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/skills/cuopt-developer/SKILL.md b/skills/cuopt-developer/SKILL.md index 31112d2eab..e80e03d337 100644 --- a/skills/cuopt-developer/SKILL.md +++ b/skills/cuopt-developer/SKILL.md @@ -168,9 +168,16 @@ cuopt/ - All PRs must pass CI ### Never Hand-Edit Generated Files -- `cpp/src/grpc/codegen/generated/` is generated from `field_registry.yaml` -- Edit the registry, then run `./build.sh codegen`, then commit both -- Codegen is an explicit build target, never automatic — see [gRPC wire fields](#grpc-wire-fields-and-codegen) +Edit the source, run the generator, commit both. A generated file usually says so in its first line or two — check before editing anything unfamiliar. + +| Generated | Source | Regenerate with | +|-----------|--------|-----------------| +| `cpp/src/grpc/codegen/generated/` | `cpp/src/grpc/codegen/field_registry.yaml` | `./build.sh codegen` | +| `conda/environments/*.yaml`, `pyproject.toml` | `dependencies.yaml` | `pre-commit run --all-files` | +| `docs/cuopt/source/versions1.json` | `ci/utils/update_doc_versions.py` | `pre-commit run --all-files` | +| `version:` in `skills/*/SKILL.md`, plugin/marketplace JSONs | `VERSION` | `pre-commit run --all-files` | + +Only the gRPC codegen needs an explicit command — the rest are pre-commit hooks that fix the file for you, so a `git commit` that trips one just needs the regenerated file staged and re-committed. **The gRPC codegen never runs on its own**, so a missed `./build.sh codegen` surfaces only as a CI failure in `ci/verify_grpc_codegen.sh`. See [gRPC wire fields](#grpc-wire-fields-and-codegen). ### CUDA/GPU Hygiene - Keep operations stream-ordered From c1befc550219797ee40adbb99ae466981364a079 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 11:48:26 -0500 Subject: [PATCH 04/14] feat(grpc-codegen): validate optional-vs-zero-value defaults, add best-effort drift lint Two checks against the settings-field maintenance gap: a non-optional field's default silently diverging from the proto3 zero value (the mlubin PDLP review thread), and a registry default: drifting from the actual C++ initializer it describes (docs/ is already known to do this). - generate_conversions.py: blocking check in the same validation pass as field-number uniqueness. For every non-optional bool/enum/numeric settings field, the registry default: must textually match the proto3 zero value, or generation fails. Verified it both passes clean on the current registry (77 fields, 0 violations) and catches an injected mismatch. - lint_registry_defaults.py: manual, warn-only. Diffs simple single-line C++ initializers (pdlp/mip tolerances, top-level settings, heuristic_params) against registry default: text. Can't parse arbitrary C++, so it skips anything it can't confidently read rather than guessing. Currently 0 drift across 77 checkable fields. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/generate_conversions.py | 37 ++++ .../grpc/codegen/lint_registry_defaults.py | 175 ++++++++++++++++++ .../references/grpc_codegen.md | 9 +- 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 cpp/src/grpc/codegen/lint_registry_defaults.py diff --git a/cpp/src/grpc/codegen/generate_conversions.py b/cpp/src/grpc/codegen/generate_conversions.py index db2d191cd6..186c61409b 100644 --- a/cpp/src/grpc/codegen/generate_conversions.py +++ b/cpp/src/grpc/codegen/generate_conversions.py @@ -3268,6 +3268,43 @@ def _validate_registry_uniqueness(registry): # for enums whose proto3 zero (the first value listed in the registry) # does not coincide with the documented C++ default — e.g. # pdlp_solver_mode (proto-zero=Stable1, C++ default=Stable3). + # + # A field that omits `optional` is only correct if its declared + # `default:` is the proto3 zero value for its type -- otherwise an + # omitting client silently gets the zero value instead of the intended + # default. Checked here so that changing a default without adding + # `optional: true` fails CI instead of shipping a silent regression. + for msg, key in [ + ("PDLPSolverSettings", "pdlp_settings"), + ("MIPSolverSettings", "mip_settings"), + ]: + section = registry.get(key) or {} + for f in parse_settings_fields(section.get("fields", [])): + if f.get("optional") or f.get("default") is None: + continue + ftype = f.get("type", "double") + default_norm = str(f["default"]).strip().lower() + edef = _lookup_enum(registry, ftype) + if edef is not None and "values" in edef: + zero_name, _, _ = parse_enum_entry(edef["values"][0]) + mismatch = default_norm != zero_name.lower() + zero_desc = f"{zero_name!r} (first enum value)" + elif ftype == "bool": + mismatch = default_norm != "false" + zero_desc = "'false'" + elif ftype in ("int32", "int64", "double"): + m = re.match(r"-?\d+(\.\d+)?", default_norm) + mismatch = m is None or float(m.group()) != 0.0 + zero_desc = "0" + else: + continue + if mismatch: + errors.append( + f"{msg}.{f['member']}: not optional, but default " + f"{f['default']!r} does not match the proto3 zero " + f"value {zero_desc} -- add optional: true or fix " + "default:" + ) if errors: raise ValueError( diff --git a/cpp/src/grpc/codegen/lint_registry_defaults.py b/cpp/src/grpc/codegen/lint_registry_defaults.py new file mode 100644 index 0000000000..7107f0059f --- /dev/null +++ b/cpp/src/grpc/codegen/lint_registry_defaults.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Best-effort lint: flag field_registry.yaml `default:` strings that look +stale against the C++ member initializer they describe. + +Not part of CI -- run manually: + python cpp/src/grpc/codegen/lint_registry_defaults.py + +`default:` is documented (field_registry.yaml's header comment, +references/grpc_codegen.md) as free text the generator neither derives nor +validates. This tool cannot parse arbitrary C++ either -- it only compares +members with a simple `= VALUE;` or `{VALUE};` initializer on one line, and +it prints warnings rather than failing, since a false positive here should +never block a PR. Treat its output as a prompt to double-check, not a verdict. +""" + +import re +import sys +from pathlib import Path + +import yaml + +sys.path.insert(0, str(Path(__file__).parent)) +import generate_conversions as gc # noqa: E402 + +REPO_ROOT = Path(__file__).resolve().parents[4] + +# (registry section, header file, nested-struct name or None for top-level, +# member prefix) +SOURCES = [ + ( + "pdlp_settings", + "cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp", + "tolerances_t", + "tolerances.", + ), + ( + "pdlp_settings", + "cpp/include/cuopt/mathematical_optimization/pdlp/solver_settings.hpp", + None, + "", + ), + ( + "mip_settings", + "cpp/include/cuopt/mathematical_optimization/mip/solver_settings.hpp", + "tolerances_t", + "tolerances.", + ), + ( + "mip_settings", + "cpp/include/cuopt/mathematical_optimization/mip/solver_settings.hpp", + None, + "", + ), + ( + "mip_settings", + "cpp/include/cuopt/mathematical_optimization/mip/heuristics_hyper_params.hpp", + "mip_heuristics_hyper_params_t", + "heuristic_params.", + ), +] + +_INIT_RE = re.compile( + r"^\s*(?:f_t|i_t|bool)\s+(\w+)\s*(?:=\s*(?P[^;]+)|\{(?P
[^}]+)\})\s*;", + re.MULTILINE, +) +_NUMBER_RE = re.compile(r"-?\d+\.?\d*(?:[eE][-+]?\d+)?") + + +def _extract_initializers(text): + """Return {member_name: raw_initializer_text} for single-line members.""" + out = {} + for m in _INIT_RE.finditer(text): + name = m.group(1) + value = (m.group("eq") or m.group("br")).split("//")[0].strip() + out[name] = value + return out + + +def _cpp_defaults(header_path, struct_name): + text = (REPO_ROOT / header_path).read_text() + if struct_name is None: + return _extract_initializers(text) + block = re.search( + rf"struct {re.escape(struct_name)}\b[^{{]*\{{(.*?)\n\s*\}};", + text, + re.DOTALL, + ) + if not block: + print(f" (could not locate struct {struct_name} in {header_path})") + return {} + return _extract_initializers(block.group(1)) + + +def _sentinel_kind(cpp_value): + if "numeric_limits" not in cpp_value: + return None + return "infinity" if "infinity" in cpp_value else "max" + + +def _mismatch(cpp_value, registry_default): + if registry_default is None: + return None # nothing to compare against + reg_lower = str(registry_default).lower() + + kind = _sentinel_kind(cpp_value) + if kind == "infinity": + ok = any(w in reg_lower for w in ("infinity", "no limit", "inf")) + return ( + None + if ok + else "C++ is infinity-sentineled, default: doesn't say so" + ) + if kind == "max": + ok = any(w in reg_lower for w in ("no limit", "max")) + return None if ok else "C++ is max-sentineled, default: doesn't say so" + + if cpp_value in ("true", "false"): + ok = cpp_value in reg_lower + return ( + None + if ok + else f"C++ default is {cpp_value}, default: doesn't match" + ) + + cpp_num = _NUMBER_RE.search(cpp_value) + if cpp_num is None: + return None # unrecognized expression (e.g. a function call) -- skip + reg_num = _NUMBER_RE.search(reg_lower) + if reg_num is None: + return None # default: is pure prose (e.g. context-dependent) -- skip + if abs(float(cpp_num.group()) - float(reg_num.group())) > 1e-12: + return f"C++ default is {cpp_num.group()}, default: says {reg_num.group()}" + return None + + +def main(): + registry = yaml.safe_load( + (Path(__file__).parent / "field_registry.yaml").read_text() + ) + warnings = [] + checked = 0 + for section, header_path, struct_name, prefix in SOURCES: + cpp_defaults = _cpp_defaults(header_path, struct_name) + fields = registry.get(section, {}).get("fields", []) + for f in gc.parse_settings_fields(fields): + member = f["member"] + if not member.startswith(prefix): + continue + local_name = member[len(prefix) :] + if "." in local_name or local_name not in cpp_defaults: + continue + checked += 1 + reason = _mismatch(cpp_defaults[local_name], f.get("default")) + if reason: + warnings.append( + f"{section}.{member}: {reason} " + f"(C++: {cpp_defaults[local_name]!r}, " + f"registry default: {f.get('default')!r})" + ) + + print( + f"Checked {checked} fields with a simple single-line C++ initializer." + ) + if warnings: + print(f"\n{len(warnings)} possible drift(s) -- review by hand:\n") + for w in warnings: + print(f" WARN: {w}") + else: + print("No drift found among the fields this tool could parse.") + + +if __name__ == "__main__": + main() diff --git a/skills/cuopt-developer/references/grpc_codegen.md b/skills/cuopt-developer/references/grpc_codegen.md index 02ebe99c61..d845b6b3c9 100644 --- a/skills/cuopt-developer/references/grpc_codegen.md +++ b/skills/cuopt-developer/references/grpc_codegen.md @@ -52,7 +52,14 @@ that bite: default differs from the proto3 zero value. Without it, a client that *omits* the field silently overwrites the solver default with `0` / `false` / the first enum value. `bool foo{true}` and enums whose C++ default is not the - first declared value both need this. + first declared value both need this. `./build.sh codegen` checks this for + every bool/enum/numeric settings field: a non-`optional` field whose + `default:` doesn't textually match the proto3 zero value fails generation. + It can only catch registry self-inconsistency, not a C++ default that + changed without a matching `default:` update — for that, run + `python cpp/src/grpc/codegen/lint_registry_defaults.py` by hand; it's a + best-effort, warn-only check (not part of CI) that diffs simple + single-line C++ initializers against the registry. - **`sentinel`** — maps a C++ sentinel (e.g. `numeric_limits::max()`) to a reserved wire value (e.g. `-1`). Composes with `optional`: the sentinel covers the explicitly-sent case, `optional` covers the omitted case. `iteration_limit` From e7c29d43e217ac0a409dab6db29a3c29d7fb30a4 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 11:22:11 -0500 Subject: [PATCH 05/14] docs(skill): list generate_conversions.py as a source for cpp/src/grpc/codegen/generated/ The generated-output table named only field_registry.yaml, but the generator script is an equally valid source of drift -- as this PR's own enum-unpack bug just demonstrated. Signed-off-by: Ramakrishna Prabhu --- skills/cuopt-developer/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/cuopt-developer/SKILL.md b/skills/cuopt-developer/SKILL.md index e80e03d337..3ad1c2bf08 100644 --- a/skills/cuopt-developer/SKILL.md +++ b/skills/cuopt-developer/SKILL.md @@ -172,7 +172,7 @@ Edit the source, run the generator, commit both. A generated file usually says s | Generated | Source | Regenerate with | |-----------|--------|-----------------| -| `cpp/src/grpc/codegen/generated/` | `cpp/src/grpc/codegen/field_registry.yaml` | `./build.sh codegen` | +| `cpp/src/grpc/codegen/generated/` | `cpp/src/grpc/codegen/field_registry.yaml`, `cpp/src/grpc/codegen/generate_conversions.py` | `./build.sh codegen` | | `conda/environments/*.yaml`, `pyproject.toml` | `dependencies.yaml` | `pre-commit run --all-files` | | `docs/cuopt/source/versions1.json` | `ci/utils/update_doc_versions.py` | `pre-commit run --all-files` | | `version:` in `skills/*/SKILL.md`, plugin/marketplace JSONs | `VERSION` | `pre-commit run --all-files` | From 265b8dbe9252866917bd77570dff29fe7451fb7e Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 13:19:52 -0500 Subject: [PATCH 06/14] fix(grpc-codegen): describe mip_absolute_tolerance vs absolute_mip_gap The MIP absolute_tolerance/relative_tolerance descriptions were generic enough to read as duplicates of absolute_mip_gap/relative_mip_gap. They control constraint-feasibility checks (get_cstr_tolerance), not solve termination -- described accordingly, matching the PDLP entries' style. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 11 +++++++++-- .../grpc/codegen/generated/cuopt_remote_data.proto | 9 +++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 8e54beb889..46eb9e24af 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -824,12 +824,19 @@ mip_settings: field_num: 4 optional: true - absolute_tolerance: - description: MIP absolute tolerance. + description: >- + Absolute term in the constraint-feasibility check: a constraint is + feasible when its bound violation is less than absolute_tolerance + + relative_tolerance * bound_magnitude. Distinct from + absolute_mip_gap, which controls when the solve terminates, not + what counts as feasible. default: "1e-6" field_num: 5 optional: true - relative_tolerance: - description: MIP relative tolerance. + description: >- + Relative term in the constraint-feasibility check; multiplies the + constraint's bound magnitude. See absolute_tolerance. default: "1e-12" field_num: 6 optional: true diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 76bb1a8013..d38381e497 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -329,9 +329,14 @@ message MIPSolverSettings { // How close to an integer a variable must be to count as integral. // (default: 1e-5) optional double integrality_tolerance = 4; - // MIP absolute tolerance. (default: 1e-6) + // Absolute term in the constraint-feasibility check: a constraint is + // feasible when its bound violation is less than absolute_tolerance + + // relative_tolerance * bound_magnitude. Distinct from absolute_mip_gap, + // which controls when the solve terminates, not what counts as feasible. + // (default: 1e-6) optional double absolute_tolerance = 5; - // MIP relative tolerance. (default: 1e-12) + // Relative term in the constraint-feasibility check; multiplies the + // constraint's bound magnitude. See absolute_tolerance. (default: 1e-12) optional double relative_tolerance = 6; // (default: 1e-6) optional double presolve_absolute_tolerance = 7; From 74de89295d58eba09d3a56c1fba4ac32dd4a5290 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 13:21:25 -0500 Subject: [PATCH 07/14] docs(grpc-codegen): fix stale validation claims, tighten lint precision - field_registry.yaml, grpc_codegen.md: "the generator neither derives nor validates" default: was accurate when written but is now partly wrong -- generate_conversions.py's optional-vs-zero-value check validates it (partially). Corrected both. - lint_registry_defaults.py: numeric drift comparison used an absolute 1e-12 tolerance, so e.g. a C++ default of 1e-13 against a registry default of 0 produced no warning. Switched to exact Decimal equality, which still treats "1.0e-4" and "1e-4" as equal (same value) while catching small real drifts. Verified: ./build.sh codegen produces an unchanged generated/, and lint_registry_defaults.py still finds 0 drift across 77 fields. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 8 ++++++-- cpp/src/grpc/codegen/lint_registry_defaults.py | 3 ++- skills/cuopt-developer/references/grpc_codegen.md | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 46eb9e24af..6fb5683654 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -47,8 +47,12 @@ # describing the C++ member initializer (e.g. "1e-4", # "-1 (automatic)"). Appended to the generated proto # comment as "(default: ...)". Documentation only — -# the generator neither derives nor validates it, and -# it does not affect the wire format. +# it does not affect the wire format. The generator +# does not derive it from the C++ struct or detect +# initializer drift; it only checks, for a +# non-optional field, that this string textually +# matches the proto3 zero value (see `optional` +# above). # type – proto wire type (default: double for scalars, repeated double for arrays) # field_num – proto message field number (see "Field numbers" above) # array_id – enum value for ArrayFieldId or ResultFieldId diff --git a/cpp/src/grpc/codegen/lint_registry_defaults.py b/cpp/src/grpc/codegen/lint_registry_defaults.py index 7107f0059f..e1c33a5228 100644 --- a/cpp/src/grpc/codegen/lint_registry_defaults.py +++ b/cpp/src/grpc/codegen/lint_registry_defaults.py @@ -17,6 +17,7 @@ import re import sys +from decimal import Decimal from pathlib import Path import yaml @@ -130,7 +131,7 @@ def _mismatch(cpp_value, registry_default): reg_num = _NUMBER_RE.search(reg_lower) if reg_num is None: return None # default: is pure prose (e.g. context-dependent) -- skip - if abs(float(cpp_num.group()) - float(reg_num.group())) > 1e-12: + if Decimal(cpp_num.group()) != Decimal(reg_num.group()): return f"C++ default is {cpp_num.group()}, default: says {reg_num.group()}" return None diff --git a/skills/cuopt-developer/references/grpc_codegen.md b/skills/cuopt-developer/references/grpc_codegen.md index d845b6b3c9..7bda780c30 100644 --- a/skills/cuopt-developer/references/grpc_codegen.md +++ b/skills/cuopt-developer/references/grpc_codegen.md @@ -70,7 +70,9 @@ that bite: third-party client reading only the `.proto` should learn what a settings field means and what omitting it does. `default` is a free-text string describing the C++ member initializer (`"1e-4"`, `"-1 (automatic)"`); the - generator neither derives nor validates it. + generator doesn't derive it from the C++ struct or detect initializer drift + (only the optional/zero-value check above), so take it from the C++ struct + — `docs/` is known to disagree in several places. ## Field numbers are permanent From 54dbcfe0c398203a455a879f22a25fa9d6d8d2f4 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 13:59:45 -0500 Subject: [PATCH 08/14] fix(grpc-codegen): add missing description/default for barrier_adaptive_regularization Only field_num/type/optional were set -- the generated proto documented neither the field nor its omitted behavior. Matches the C++ comment (-1 automatic, 0 disabled, 1 enabled). Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 6fb5683654..46a88bd6c5 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -728,6 +728,11 @@ pdlp_settings: type: bool optional: true - barrier_adaptive_regularization: + description: >- + Whether the barrier method scales its KKT regularization up or down + on later iterations: -1 automatic (uses the built-in heuristic), 0 + disabled, 1 enabled. + default: "-1 (automatic)" field_num: 34 type: int32 optional: true From b2e4cfbaa03853a7f5a70b9f1509bcae1ed64daf Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 14:21:41 -0500 Subject: [PATCH 09/14] refactor(grpc-codegen): simplify _field_doc_comment word handling Addresses review (KyleFromNVIDIA): build a single words list instead of splitting the description, rejoining it, then splitting the combined body again for wrapping -- also removes the inconsistency between checking description with a truthy test and default with `is None`, since both now go through the same words-list construction. Verified: empty description, missing description, and description="" all still correctly produce no comment lines; wrapping/whitespace normalization unchanged. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/generate_conversions.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cpp/src/grpc/codegen/generate_conversions.py b/cpp/src/grpc/codegen/generate_conversions.py index 186c61409b..10d865cc56 100644 --- a/cpp/src/grpc/codegen/generate_conversions.py +++ b/cpp/src/grpc/codegen/generate_conversions.py @@ -358,16 +358,15 @@ def _field_doc_comment(f, indent=" "): """ description = f.get("description") default = f.get("default") - if not description and default is None: - return [] - body = " ".join(str(description).split()) if description else "" + words = str(description).split() if description is not None else [] if default is not None: - suffix = f"(default: {default})" - body = f"{body} {suffix}" if body else suffix + words += f"(default: {default})".split() + if not words: + return [] prefix = f"{indent}// " width = max(_DOC_COMMENT_WIDTH - len(prefix), 20) lines, current = [], "" - for word in body.split(): + for word in words: candidate = f"{current} {word}" if current else word if current and len(candidate) > width: lines.append(f"{prefix}{current}") From f5d8936eb6f17ea2b061bd8da33f6a8ab2933f52 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 16 Sep 2026 15:16:57 -0500 Subject: [PATCH 10/14] fix(grpc-codegen): commit regenerated cuopt_remote_data.proto 54dbcfe0 added description/default for barrier_adaptive_regularization to field_registry.yaml but only staged that file, not the regenerated generated/ output -- caught by conda-cpp-tests' ci/verify_grpc_codegen.sh run, which compares against the committed files, not the working tree. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/generated/cuopt_remote_data.proto | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index d38381e497..075a318e50 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -309,6 +309,9 @@ message PDLPSolverSettings { optional double barrier_step_scale = 32; // (default: -1) optional int32 postsolve_info = 33; + // Whether the barrier method scales its KKT regularization up or down on + // later iterations: -1 automatic (uses the built-in heuristic), 0 disabled, + // 1 enabled. (default: -1 (automatic)) optional int32 barrier_adaptive_regularization = 34; PDLPWarmStartData warm_start_data = 50; } From 46a4f40803708091b5ae10032834b6baa5927bc0 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 17 Sep 2026 07:52:27 -0500 Subject: [PATCH 11/14] docs(grpc-codegen): move codegen guidance out of the agent-only skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review (mlubin, PR review 5235568192): references/grpc_codegen.md duplicated content that already existed in cpp/src/grpc/GRPC_CODE_GENERATION.md and cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md -- developer-facing docs every contributor reads, not just agents -- and SKILL.md's "Never Hand-Edit Generated Files" section duplicated the "DO NOT EDIT" header every generated file already carries. - Removed references/grpc_codegen.md and both SKILL.md sections pointing at it (the workflow paragraph and the generated-files table, entirely new to this PR, not established content). - Folded the genuinely new material -- description:/default: settings-field docs, the optional-vs-zero-value validation check, and lint_registry_defaults.py -- into FIELD_REGISTRY_REFERENCE.md (as new §2.1 table rows and additions to §4.3) and GRPC_CODE_GENERATION.md's quick start, since neither existed there before this PR. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/GRPC_CODE_GENERATION.md | 6 + .../grpc/codegen/FIELD_REGISTRY_REFERENCE.md | 16 ++- skills/cuopt-developer/SKILL.md | 20 ---- .../references/grpc_codegen.md | 111 ------------------ 4 files changed, 19 insertions(+), 134 deletions(-) delete mode 100644 skills/cuopt-developer/references/grpc_codegen.md diff --git a/cpp/src/grpc/GRPC_CODE_GENERATION.md b/cpp/src/grpc/GRPC_CODE_GENERATION.md index 924ea2111e..3dd8aa67d4 100644 --- a/cpp/src/grpc/GRPC_CODE_GENERATION.md +++ b/cpp/src/grpc/GRPC_CODE_GENERATION.md @@ -34,6 +34,12 @@ with conda). The `--auto-number` and `--strip` options additionally require After editing `field_registry.yaml`, run codegen and commit the regenerated files alongside your changes. +If you changed a settings field's `default:`, `python +cpp/src/grpc/codegen/lint_registry_defaults.py` is a best-effort, manual check +(not run in CI) that diffs it against the actual C++ member initializer it +describes — see §2.1/§4.3 of +[`FIELD_REGISTRY_REFERENCE.md`](codegen/FIELD_REGISTRY_REFERENCE.md). + ## File layout The codegen lives under `cpp/src/grpc/codegen/`: diff --git a/cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md b/cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md index bff662a6fb..f2a2321b7c 100644 --- a/cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md +++ b/cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md @@ -120,6 +120,8 @@ scalars: | `member` | no | ``, with nesting prefix auto-prepended for settings sub-structs (`tolerances.`, `heuristic_params.`, …) | C++ struct member name. Set explicitly only when the C++ name diverges from the wire field name — in practice, warm-start fields whose C++ members carry a trailing underscore (`initial_primal_weight_`). | | `from_proto_cast` | no | — | Explicit C++ cast when reading from proto. Most commonly used to wrap a wire `int32` back into an enum class (e.g. `presolver_t`, `method_t`) or a narrow type (e.g. `char` for `constraint_row_type`). When set, the matching to-proto cast back to the wire's C++ type (e.g. `int32_t` for wire `int32`) is derived automatically — no `to_proto_cast:` needed. | | `to_proto_cast` | no | derived from `type:` when `from_proto_cast` is set; otherwise no cast | Escape hatch: explicit C++ cast when writing to proto (e.g. `int32_t`). Rarely needed — set only when the auto-derivation above is wrong. | +| `description` | no | — | Settings fields only. Human-readable meaning of the field, emitted into `cuopt_mcp_schema.json` as the JSON Schema `description` an MCP client shows a model deciding whether to set the field. | +| `default` | no | — | Settings fields only. Free-text string describing the C++ member initializer (e.g. `"1e-4"`, `"-1 (automatic)"`), appended to the emitted `description`. Documentation only — doesn't affect the wire format, and the generator doesn't derive it from the C++ struct or detect initializer drift; write it by reading the struct (`pdlp/solver_settings.hpp`, `mip/solver_settings.hpp`, `mip/heuristics_hyper_params.hpp`), not `docs/cuopt/source/*-settings.rst`, which has been observed to disagree with the code. For a non-`optional` field, `./build.sh codegen` fails if `default` doesn't textually match the proto3 zero value (§4.3); run `python cpp/src/grpc/codegen/lint_registry_defaults.py` by hand for a best-effort (not CI-enforced) check that it still matches the actual C++ initializer. | ### 2.2 Array fields @@ -632,9 +634,17 @@ entries — so always re-run `--auto-number` after a `--strip`. ### 4.3 Validation The generator runs a uniqueness check per scope and fails with a precise error -on collision. CI runs `verify_grpc_codegen.sh` to ensure the committed -`generated/` directory matches what the generator produces — drift fails the -build. +on collision. It also checks, for every non-`optional` settings field, that +`default:` (§2.1) textually matches the proto3 zero value for its type — this +can only catch registry self-inconsistency, not a C++ default that changed +without a matching `default:` update. CI runs `verify_grpc_codegen.sh` to +ensure the committed `generated/` directory matches what the generator +produces — drift fails the build. + +`field_num` and `array_id` are wire identifiers: never renumber or reuse a +retired number, since older clients may still send data on it. Record retired +numbers in a comment (see the note on 37/38 in `mip_settings`) and follow that +convention when removing a field. --- diff --git a/skills/cuopt-developer/SKILL.md b/skills/cuopt-developer/SKILL.md index 3ad1c2bf08..6981e082cb 100644 --- a/skills/cuopt-developer/SKILL.md +++ b/skills/cuopt-developer/SKILL.md @@ -167,18 +167,6 @@ cuopt/ - Never suggest `--no-verify` or skipping checks - All PRs must pass CI -### Never Hand-Edit Generated Files -Edit the source, run the generator, commit both. A generated file usually says so in its first line or two — check before editing anything unfamiliar. - -| Generated | Source | Regenerate with | -|-----------|--------|-----------------| -| `cpp/src/grpc/codegen/generated/` | `cpp/src/grpc/codegen/field_registry.yaml`, `cpp/src/grpc/codegen/generate_conversions.py` | `./build.sh codegen` | -| `conda/environments/*.yaml`, `pyproject.toml` | `dependencies.yaml` | `pre-commit run --all-files` | -| `docs/cuopt/source/versions1.json` | `ci/utils/update_doc_versions.py` | `pre-commit run --all-files` | -| `version:` in `skills/*/SKILL.md`, plugin/marketplace JSONs | `VERSION` | `pre-commit run --all-files` | - -Only the gRPC codegen needs an explicit command — the rest are pre-commit hooks that fix the file for you, so a `git commit` that trips one just needs the regenerated file staged and re-committed. **The gRPC codegen never runs on its own**, so a missed `./build.sh codegen` surfaces only as a CI failure in `ci/verify_grpc_codegen.sh`. See [gRPC wire fields](#grpc-wire-fields-and-codegen). - ### CUDA/GPU Hygiene - Keep operations stream-ordered - Follow existing RAFT/RMM patterns @@ -289,14 +277,6 @@ For build/test pitfalls (Cython rebuild, OOM, CUDA driver mismatch, missing `nvc _Shell-execution, install, conda-env, and sudo policies are covered by [Refusal Rules — Read First](#refusal-rules--read-first) at the top of this skill._ -## gRPC wire fields and codegen - -When adding or changing anything that crosses the cuOpt gRPC wire — a problem field, a solution field, or a solver setting — read: - -- **`references/grpc_codegen.md`** — the `field_registry.yaml` → `./build.sh codegen` → commit-`generated/` workflow, the `optional` / `sentinel` presence traps, field-number permanence, and why the C++ member initializer (not the docs) is ground truth for a setting's default. - -Read it **before** editing `field_registry.yaml` or any file under `cpp/src/grpc/codegen/generated/`. Codegen never runs as part of a normal build, so a skipped regeneration surfaces only as a CI failure. - ## VRP dimension internals (routing engine) When implementing or debugging **VRP dimensions** (constraints, objectives, forward/backward propagation, `combine`, local-search deltas), read: diff --git a/skills/cuopt-developer/references/grpc_codegen.md b/skills/cuopt-developer/references/grpc_codegen.md deleted file mode 100644 index 7bda780c30..0000000000 --- a/skills/cuopt-developer/references/grpc_codegen.md +++ /dev/null @@ -1,111 +0,0 @@ -# gRPC Wire Fields and the Codegen Registry - -Read this before touching anything that crosses the cuOpt gRPC wire: problem -fields, solution fields, or solver settings. - -## The one rule - -**Never hand-edit files under `cpp/src/grpc/codegen/generated/`.** They are -generated from `cpp/src/grpc/codegen/field_registry.yaml` by -`cpp/src/grpc/codegen/generate_conversions.py`, and CI will reject a mismatch. - -Every field that crosses the wire is declared once in the registry. One entry -drives ~28 generated artifacts: the `.proto`, the C++ to/from-proto converters, -the chunked upload/download paths, and the size estimator. - -## Workflow - -```bash -# 1. edit cpp/src/grpc/codegen/field_registry.yaml -# 2. regenerate -./build.sh codegen -# 3. commit BOTH the registry and cpp/src/grpc/codegen/generated/ -# 4. optional local pre-check of what CI runs -bash ci/verify_grpc_codegen.sh -``` - -**Codegen is not part of the build.** `build.sh` guards it behind an explicit -`codegen` argument, and CMake only *consumes* the checked-in `generated/` -directory. Building cuOpt will never regenerate for you, and it will never warn -you that the registry has drifted — `ci/verify_grpc_codegen.sh` is what catches -that, in CI, after you push. - -`./build.sh codegen` needs only `pyyaml` — no GPU, no compile. It is cheap to -run and cheap to re-run; there is no reason to skip it. - -## Registry sections - -| Section | Generates | -|---------|-----------| -| `enums` | proto enums + C++ converters | -| `optimization_problem` | `OptimizationProblem` message, problem converters | -| `pdlp_settings` / `mip_settings` | `PDLPSolverSettings` / `MIPSolverSettings` | -| `lp_solution` / `mip_solution` | solution messages and converters | -| `chunked_result_header` | `ChunkedResultHeader` for the chunked download path | - -## Attributes worth knowing - -Full reference: `cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md`. The ones -that bite: - -- **`optional`** — emits proto3 presence tracking. Required whenever the C++ - default differs from the proto3 zero value. Without it, a client that *omits* - the field silently overwrites the solver default with `0` / `false` / the - first enum value. `bool foo{true}` and enums whose C++ default is not the - first declared value both need this. `./build.sh codegen` checks this for - every bool/enum/numeric settings field: a non-`optional` field whose - `default:` doesn't textually match the proto3 zero value fails generation. - It can only catch registry self-inconsistency, not a C++ default that - changed without a matching `default:` update — for that, run - `python cpp/src/grpc/codegen/lint_registry_defaults.py` by hand; it's a - best-effort, warn-only check (not part of CI) that diffs simple - single-line C++ initializers against the registry. -- **`sentinel`** — maps a C++ sentinel (e.g. `numeric_limits::max()`) to a - reserved wire value (e.g. `-1`). Composes with `optional`: the sentinel covers - the explicitly-sent case, `optional` covers the omitted case. `iteration_limit` - and `node_limit` need both. -- **`description`** / **`default`** — documentation, emitted as leading comments - on the generated proto field. The generated `.proto` is part of the public - wire contract (`cpp/src/grpc/GRPC_INTERFACE.md`, "Custom Clients"), so a - third-party client reading only the `.proto` should learn what a settings - field means and what omitting it does. `default` is a free-text string - describing the C++ member initializer (`"1e-4"`, `"-1 (automatic)"`); the - generator doesn't derive it from the C++ struct or detect initializer drift - (only the optional/zero-value check above), so take it from the C++ struct - — `docs/` is known to disagree in several places. - -## Field numbers are permanent - -`field_num` and `array_id` are wire identifiers. Never renumber or reuse a -retired number — older clients still send data on it. The registry records -retired numbers in comments (see the note on 37/38 in `mip_settings`); follow -that convention when removing a field. - -## Adding a settings field: checklist - -1. Add the C++ member with its initializer. -2. Add the registry entry with the next free `field_num`. -3. Add `optional:` if the C++ default is not the proto3 zero value. -4. Add `description:` and `default:` — match the C++ initializer, not the docs - (see below). -5. `./build.sh codegen`, commit registry + `generated/`. -6. Document the user-facing parameter in `docs/cuopt/source/convex-settings.rst` - (LP) or `mip-settings.rst` (MIP), and add the constant to - `cpp/include/cuopt/mathematical_optimization/constants.h`. - -## Documented defaults drift from the code - -`docs/cuopt/source/*-settings.rst` is the prose source for what a setting means, -but its stated defaults have been observed to disagree with the C++ member -initializers. **The C++ initializer is ground truth.** When writing `default:`, -read the struct — `pdlp/solver_settings.hpp`, `mip/solver_settings.hpp`, -`mip/heuristics_hyper_params.hpp` — and treat a docs mismatch as a docs bug to -report separately, not as something to propagate into the registry. - -## The registry is not a complete mirror of the settings structs - -Some settings exist in C++ and in the user docs but have no registry entry, -which means they cannot be set over gRPC at all. Before assuming a parameter is -remotely settable, grep `field_registry.yaml` for it. Adding a missing one is a -wire change (new `field_num`), not a documentation change — scope it as its own -PR. From 8f462b633bb6b2b0920e9cf032d198bafc59ee84 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 17 Sep 2026 07:53:46 -0500 Subject: [PATCH 12/14] fix(grpc-codegen): derive the .proto header from the same source as the .inc HEADER The "AUTO-GENERATED / DO NOT EDIT" notice was hand-duplicated between HEADER (.inc files) and generate_data_proto's own literal strings, and had drifted to different wording ("DO NOT EDIT MANUALLY" vs "DO NOT EDIT") and different regenerate commands (a bare `python generate_conversions.py` vs the full path). Both now build from one _GENERATED_NOTICE tuple, pointing at the documented ./build.sh codegen entry point instead of a raw script invocation. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/generate_conversions.py | 27 +++++++++++++------ .../codegen/generated/cuopt_remote_data.proto | 2 +- .../generated_array_field_element_size.inc | 2 +- .../generated_build_array_chunks.inc | 2 +- .../generated_chunked_arrays_to_problem.inc | 2 +- .../generated_chunked_header_to_problem.inc | 2 +- .../generated_chunked_to_lp_solution.inc | 2 +- .../generated_chunked_to_mip_solution.inc | 2 +- .../generated/generated_collect_lp_arrays.inc | 2 +- .../generated_collect_mip_arrays.inc | 2 +- .../generated_enum_converters_problem.inc | 2 +- .../generated_enum_converters_settings.inc | 2 +- .../generated_enum_converters_solution.inc | 2 +- .../generated_estimate_problem_size.inc | 2 +- .../generated/generated_lp_chunked_header.inc | 2 +- .../generated_lp_solution_to_proto.inc | 2 +- .../generated_mip_chunked_header.inc | 2 +- .../generated_mip_settings_to_proto.inc | 2 +- .../generated_mip_solution_to_proto.inc | 2 +- .../generated_pdlp_settings_to_proto.inc | 2 +- .../generated_populate_chunked_header_lp.inc | 2 +- .../generated_populate_chunked_header_mip.inc | 2 +- .../generated/generated_problem_to_proto.inc | 2 +- .../generated_proto_to_lp_solution.inc | 2 +- .../generated_proto_to_mip_settings.inc | 2 +- .../generated_proto_to_mip_solution.inc | 2 +- .../generated_proto_to_pdlp_settings.inc | 2 +- .../generated/generated_proto_to_problem.inc | 2 +- .../generated_result_enums.proto.inc | 2 +- 29 files changed, 47 insertions(+), 36 deletions(-) diff --git a/cpp/src/grpc/codegen/generate_conversions.py b/cpp/src/grpc/codegen/generate_conversions.py index 10d865cc56..fec9ace701 100644 --- a/cpp/src/grpc/codegen/generate_conversions.py +++ b/cpp/src/grpc/codegen/generate_conversions.py @@ -28,12 +28,24 @@ # Utilities # ============================================================================ -HEADER = """\ -// ============================================================================ -// AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py -// ============================================================================ -""" +# Single source for the "don't hand-edit this" notice every generated file +# carries. Previously duplicated by hand between HEADER (below, used by the +# .inc files) and the .proto header -- they'd drifted to different wording +# and commands. Both are now built from these two lines. +_GENERATED_NOTICE = ( + "AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml", + "DO NOT EDIT — regenerate with: ./build.sh codegen", +) + +HEADER = ( + "// " + + "=" * 76 + + "\n" + + "".join(f"// {line}\n" for line in _GENERATED_NOTICE) + + "// " + + "=" * 76 + + "\n" +) # ============================================================================ # Protobuf wire-size estimator constants @@ -1586,8 +1598,7 @@ def generate_data_proto(registry): ) afid = afid_doc + "enum ArrayFieldId {\n" + afid_body + "\n}\n" parts = [ - "// AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml", - "// DO NOT EDIT — regenerate with: python cpp/src/grpc/codegen/generate_conversions.py", + *(f"// {line}" for line in _GENERATED_NOTICE), "", 'syntax = "proto3";', "", diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 075a318e50..14ed34a7c6 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -1,5 +1,5 @@ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT — regenerate with: python cpp/src/grpc/codegen/generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen syntax = "proto3"; diff --git a/cpp/src/grpc/codegen/generated/generated_array_field_element_size.inc b/cpp/src/grpc/codegen/generated/generated_array_field_element_size.inc index b7c80002b7..6a376ddc38 100644 --- a/cpp/src/grpc/codegen/generated/generated_array_field_element_size.inc +++ b/cpp/src/grpc/codegen/generated/generated_array_field_element_size.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ if (container_field_num < 0) { switch (field_id) { diff --git a/cpp/src/grpc/codegen/generated/generated_build_array_chunks.inc b/cpp/src/grpc/codegen/generated/generated_build_array_chunks.inc index f11a28fdc0..02e9f774a9 100644 --- a/cpp/src/grpc/codegen/generated/generated_build_array_chunks.inc +++ b/cpp/src/grpc/codegen/generated/generated_build_array_chunks.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ std::vector requests; diff --git a/cpp/src/grpc/codegen/generated/generated_chunked_arrays_to_problem.inc b/cpp/src/grpc/codegen/generated/generated_chunked_arrays_to_problem.inc index f72d28c304..969ccb9694 100644 --- a/cpp/src/grpc/codegen/generated/generated_chunked_arrays_to_problem.inc +++ b/cpp/src/grpc/codegen/generated/generated_chunked_arrays_to_problem.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ map_chunked_header_to_problem(header, cpu_problem); diff --git a/cpp/src/grpc/codegen/generated/generated_chunked_header_to_problem.inc b/cpp/src/grpc/codegen/generated/generated_chunked_header_to_problem.inc index 6558b31d0f..0a445a3bd1 100644 --- a/cpp/src/grpc/codegen/generated/generated_chunked_header_to_problem.inc +++ b/cpp/src/grpc/codegen/generated/generated_chunked_header_to_problem.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ cpu_problem.set_problem_name(header.problem_name()); cpu_problem.set_objective_name(header.objective_name()); diff --git a/cpp/src/grpc/codegen/generated/generated_chunked_to_lp_solution.inc b/cpp/src/grpc/codegen/generated/generated_chunked_to_lp_solution.inc index 8e3935d1b6..fba1f1225d 100644 --- a/cpp/src/grpc/codegen/generated/generated_chunked_to_lp_solution.inc +++ b/cpp/src/grpc/codegen/generated/generated_chunked_to_lp_solution.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ auto primal_solution = bytes_to_typed(arrays, cuopt::remote::RESULT_PRIMAL_SOLUTION); auto dual_solution = bytes_to_typed(arrays, cuopt::remote::RESULT_DUAL_SOLUTION); diff --git a/cpp/src/grpc/codegen/generated/generated_chunked_to_mip_solution.inc b/cpp/src/grpc/codegen/generated/generated_chunked_to_mip_solution.inc index 774ba6058d..a74c1fa261 100644 --- a/cpp/src/grpc/codegen/generated/generated_chunked_to_mip_solution.inc +++ b/cpp/src/grpc/codegen/generated/generated_chunked_to_mip_solution.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ auto mip_solution = bytes_to_typed(arrays, cuopt::remote::RESULT_MIP_SOLUTION); diff --git a/cpp/src/grpc/codegen/generated/generated_collect_lp_arrays.inc b/cpp/src/grpc/codegen/generated/generated_collect_lp_arrays.inc index e4c4a9d286..e296ae107b 100644 --- a/cpp/src/grpc/codegen/generated/generated_collect_lp_arrays.inc +++ b/cpp/src/grpc/codegen/generated/generated_collect_lp_arrays.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ std::map> arrays; const auto& _primal_solution = solution.get_primal_solution_host(); diff --git a/cpp/src/grpc/codegen/generated/generated_collect_mip_arrays.inc b/cpp/src/grpc/codegen/generated/generated_collect_mip_arrays.inc index f2961850a5..eb6a26dc7b 100644 --- a/cpp/src/grpc/codegen/generated/generated_collect_mip_arrays.inc +++ b/cpp/src/grpc/codegen/generated/generated_collect_mip_arrays.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ std::map> arrays; const auto& _mip_solution = solution.get_solution_host(); diff --git a/cpp/src/grpc/codegen/generated/generated_enum_converters_problem.inc b/cpp/src/grpc/codegen/generated/generated_enum_converters_problem.inc index e14510cdb8..5a878de23b 100644 --- a/cpp/src/grpc/codegen/generated/generated_enum_converters_problem.inc +++ b/cpp/src/grpc/codegen/generated/generated_enum_converters_problem.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ cuopt::remote::VariableType to_proto_variable_type(var_t v) { diff --git a/cpp/src/grpc/codegen/generated/generated_enum_converters_settings.inc b/cpp/src/grpc/codegen/generated/generated_enum_converters_settings.inc index 328ef06732..7086570f65 100644 --- a/cpp/src/grpc/codegen/generated/generated_enum_converters_settings.inc +++ b/cpp/src/grpc/codegen/generated/generated_enum_converters_settings.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ cuopt::remote::PDLPSolverMode to_proto_pdlp_solver_mode(pdlp_solver_mode_t v) { diff --git a/cpp/src/grpc/codegen/generated/generated_enum_converters_solution.inc b/cpp/src/grpc/codegen/generated/generated_enum_converters_solution.inc index 02f8edc074..80826b58ca 100644 --- a/cpp/src/grpc/codegen/generated/generated_enum_converters_solution.inc +++ b/cpp/src/grpc/codegen/generated/generated_enum_converters_solution.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ cuopt::remote::PDLPTerminationStatus to_proto_pdlp_termination_status(pdlp_termination_status_t v) { diff --git a/cpp/src/grpc/codegen/generated/generated_estimate_problem_size.inc b/cpp/src/grpc/codegen/generated/generated_estimate_problem_size.inc index a499bdf65f..3ac3aa560f 100644 --- a/cpp/src/grpc/codegen/generated/generated_estimate_problem_size.inc +++ b/cpp/src/grpc/codegen/generated/generated_estimate_problem_size.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ // Protobuf wire-size estimate (conservative upper bound). Each literal // below corresponds to a protobuf wire-format encoding: diff --git a/cpp/src/grpc/codegen/generated/generated_lp_chunked_header.inc b/cpp/src/grpc/codegen/generated/generated_lp_chunked_header.inc index a514f7c57d..143adb37e1 100644 --- a/cpp/src/grpc/codegen/generated/generated_lp_chunked_header.inc +++ b/cpp/src/grpc/codegen/generated/generated_lp_chunked_header.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ header->set_problem_category(cuopt::remote::LP); header->set_lp_termination_status(to_proto_pdlp_termination_status(solution.get_termination_status())); diff --git a/cpp/src/grpc/codegen/generated/generated_lp_solution_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_lp_solution_to_proto.inc index d457ae87f6..042a64e712 100644 --- a/cpp/src/grpc/codegen/generated/generated_lp_solution_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_lp_solution_to_proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ pb_solution->set_lp_termination_status(to_proto_pdlp_termination_status(solution.get_termination_status())); pb_solution->set_error_message(solution.get_error_status().what()); diff --git a/cpp/src/grpc/codegen/generated/generated_mip_chunked_header.inc b/cpp/src/grpc/codegen/generated/generated_mip_chunked_header.inc index 459bd7736e..09a25f9225 100644 --- a/cpp/src/grpc/codegen/generated/generated_mip_chunked_header.inc +++ b/cpp/src/grpc/codegen/generated/generated_mip_chunked_header.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ header->set_problem_category(cuopt::remote::MIP); header->set_mip_termination_status(to_proto_mip_termination_status(solution.get_termination_status())); diff --git a/cpp/src/grpc/codegen/generated/generated_mip_settings_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_mip_settings_to_proto.inc index e92e156053..8c6f9b63f3 100644 --- a/cpp/src/grpc/codegen/generated/generated_mip_settings_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_mip_settings_to_proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ pb_settings->set_time_limit(settings.time_limit); pb_settings->set_relative_mip_gap(settings.tolerances.relative_mip_gap); diff --git a/cpp/src/grpc/codegen/generated/generated_mip_solution_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_mip_solution_to_proto.inc index b04002e758..63377625c4 100644 --- a/cpp/src/grpc/codegen/generated/generated_mip_solution_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_mip_solution_to_proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ pb_solution->set_mip_termination_status(to_proto_mip_termination_status(solution.get_termination_status())); pb_solution->set_mip_error_message(solution.get_error_status().what()); diff --git a/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc index 54d8e754bc..07d3d59bbe 100644 --- a/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_pdlp_settings_to_proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ pb_settings->set_absolute_gap_tolerance(settings.tolerances.absolute_gap_tolerance); pb_settings->set_relative_gap_tolerance(settings.tolerances.relative_gap_tolerance); diff --git a/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_lp.inc b/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_lp.inc index 656e1bde67..b9c16c5689 100644 --- a/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_lp.inc +++ b/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_lp.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ auto* rh = header->mutable_header(); rh->set_version(1); diff --git a/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_mip.inc b/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_mip.inc index 0e8621b54c..d530e4e336 100644 --- a/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_mip.inc +++ b/cpp/src/grpc/codegen/generated/generated_populate_chunked_header_mip.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ auto* rh = header->mutable_header(); rh->set_version(1); diff --git a/cpp/src/grpc/codegen/generated/generated_problem_to_proto.inc b/cpp/src/grpc/codegen/generated/generated_problem_to_proto.inc index 22a604d067..e06fce98b6 100644 --- a/cpp/src/grpc/codegen/generated/generated_problem_to_proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_problem_to_proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ pb_problem->set_problem_name(cpu_problem.get_problem_name()); pb_problem->set_objective_name(cpu_problem.get_objective_name()); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_lp_solution.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_lp_solution.inc index d586f60425..700b102546 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_lp_solution.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_lp_solution.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ std::vector primal_solution(pb_solution.primal_solution().begin(), pb_solution.primal_solution().end()); std::vector dual_solution(pb_solution.dual_solution().begin(), pb_solution.dual_solution().end()); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_mip_settings.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_mip_settings.inc index 04241b3407..388aecdb81 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_mip_settings.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_mip_settings.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ if (pb_settings.has_time_limit()) { settings.time_limit = pb_settings.time_limit(); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_mip_solution.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_mip_solution.inc index 68aa029718..36d80db5f2 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_mip_solution.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_mip_solution.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ std::vector mip_solution(pb_solution.mip_solution().begin(), pb_solution.mip_solution().end()); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc index 076929eab0..4f749cb301 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_pdlp_settings.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ if (pb_settings.has_absolute_gap_tolerance()) { settings.tolerances.absolute_gap_tolerance = pb_settings.absolute_gap_tolerance(); diff --git a/cpp/src/grpc/codegen/generated/generated_proto_to_problem.inc b/cpp/src/grpc/codegen/generated/generated_proto_to_problem.inc index fad8e0f4d1..525cc7547b 100644 --- a/cpp/src/grpc/codegen/generated/generated_proto_to_problem.inc +++ b/cpp/src/grpc/codegen/generated/generated_proto_to_problem.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ cpu_problem.set_problem_name(pb_problem.problem_name()); cpu_problem.set_objective_name(pb_problem.objective_name()); diff --git a/cpp/src/grpc/codegen/generated/generated_result_enums.proto.inc b/cpp/src/grpc/codegen/generated/generated_result_enums.proto.inc index bbf829e3b1..cd6196bf56 100644 --- a/cpp/src/grpc/codegen/generated/generated_result_enums.proto.inc +++ b/cpp/src/grpc/codegen/generated/generated_result_enums.proto.inc @@ -1,6 +1,6 @@ // ============================================================================ // AUTO-GENERATED by src/grpc/codegen/generate_conversions.py from field_registry.yaml -// DO NOT EDIT MANUALLY — regenerate with: python generate_conversions.py +// DO NOT EDIT — regenerate with: ./build.sh codegen // ============================================================================ // ResultFieldId enum entries option allow_alias = true; From 749851a352cfb69c1c8d788190b1989fb61a0510 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 17 Sep 2026 09:29:42 -0500 Subject: [PATCH 13/14] fix(grpc-codegen): correct tri-state field descriptions, drop unverified probing claim, fix stale docstrings - mip_batch_pdlp_strong_branching / mip_batch_pdlp_reliability_branching: solver_settings.hpp documents these as three-way (0 dual simplex only, 1 cooperative, 2 batched-PDLP-only), but the registry described them as plain booleans. Corrected both descriptions and defaults. - probing: dropped the "skipped in deterministic mode and on LP-only solves" claim -- traced the only gating in diversity_manager.cu (settings.probing, guarded by a constexpr fj_only_run=false) and found no determinism_mode or LP-only check on this path. Looks copied from mip-settings.rst prose rather than the code, which this PR's own rule says is not ground truth. - _field_doc_comment (generate_conversions.py) and lint_registry_defaults.py's module docstring still said the generator "neither derives nor validates" default: -- stale since the optional-vs-zero-value check landed earlier in this PR. Also fixed a dangling reference to the now-deleted references/grpc_codegen.md. Verified: ./build.sh codegen produces the corrected proto comments; ci/verify_grpc_codegen.sh and lint_registry_defaults.py both still pass. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 20 +++++++++---------- cpp/src/grpc/codegen/generate_conversions.py | 7 +++++-- .../codegen/generated/cuopt_remote_data.proto | 17 ++++++++-------- .../grpc/codegen/lint_registry_defaults.py | 13 +++++++----- 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 46a88bd6c5..f02bf9eea0 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -955,18 +955,19 @@ mip_settings: optional: true - mip_batch_pdlp_strong_branching: description: >- - Whether strong branching at the root evaluates candidates with a single - batched PDLP solve instead of solving them in parallel with dual - simplex, which can cut strong-branching time when dual simplex - struggles. 0 disabled, 1 enabled. - default: "0 (disabled)" + How strong branching at the root evaluates candidates: 0 dual simplex + only, 1 cooperative dual simplex + a single batched PDLP solve, 2 + batched PDLP only. Batching can cut strong-branching time when dual + simplex struggles. + default: "0 (dual simplex only)" field_num: 17 type: int32 - mip_batch_pdlp_reliability_branching: description: >- - Whether reliability-branching candidates are evaluated simultaneously - with a single batched PDLP solve. 0 disabled, 1 enabled. - default: "0 (disabled)" + How reliability-branching candidates are evaluated: 0 dual simplex + only, 1 cooperative dual simplex + a single batched PDLP solve, 2 + batched PDLP only. + default: "0 (dual simplex only)" field_num: 32 type: int32 - strong_branching_simplex_iteration_limit: @@ -1101,8 +1102,7 @@ mip_settings: Whether the probing-cache step of MIP presolve runs. Probing evaluates variable fixings to discover implications later used by branch-and-bound and the rounding heuristics. Only meaningful when presolve is otherwise - enabled — presolver=0 disables the whole pipeline. Probing is also - skipped in deterministic mode and on LP-only solves. + enabled — presolver=0 disables the whole pipeline. default: "true" field_num: 29 type: bool diff --git a/cpp/src/grpc/codegen/generate_conversions.py b/cpp/src/grpc/codegen/generate_conversions.py index fec9ace701..7fd19d2857 100644 --- a/cpp/src/grpc/codegen/generate_conversions.py +++ b/cpp/src/grpc/codegen/generate_conversions.py @@ -365,8 +365,11 @@ def _field_doc_comment(f, indent=" "): attribute, so undocumented fields emit exactly as before. `default:` is rendered verbatim from the registry — it is a string - describing the C++ member initializer, not a value the generator - derives or validates. + describing the C++ member initializer, not a value the generator derives + from it. It is checked, not fully validated: for a non-optional settings + field, `_validate_registry_uniqueness` fails generation if this string + doesn't textually match the proto3 zero value. That check can't catch a + C++ default that changed with no matching update here. """ description = f.get("description") default = f.get("default") diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 14ed34a7c6..4b5452cbcc 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -378,10 +378,10 @@ message MIPSolverSettings { // variable as reliable once it has been branched on k times. (default: -1 // (automatic)) optional int32 reliability_branching = 16; - // Whether strong branching at the root evaluates candidates with a single - // batched PDLP solve instead of solving them in parallel with dual simplex, - // which can cut strong-branching time when dual simplex struggles. 0 - // disabled, 1 enabled. (default: 0 (disabled)) + // How strong branching at the root evaluates candidates: 0 dual simplex + // only, 1 cooperative dual simplex + a single batched PDLP solve, 2 batched + // PDLP only. Batching can cut strong-branching time when dual simplex + // struggles. (default: 0 (dual simplex only)) int32 mip_batch_pdlp_strong_branching = 17; // Maximum number of cut passes to run. 0 disables cuts entirely; larger // values perform more passes. (default: 10) @@ -427,8 +427,7 @@ message MIPSolverSettings { // Whether the probing-cache step of MIP presolve runs. Probing evaluates // variable fixings to discover implications later used by branch-and-bound // and the rounding heuristics. Only meaningful when presolve is otherwise - // enabled — presolver=0 disables the whole pipeline. Probing is also - // skipped in deterministic mode and on LP-only solves. (default: true) + // enabled — presolver=0 disables the whole pipeline. (default: true) optional bool probing = 29; // Maximum simplex iterations per candidate during strong branching. // Lowering it speeds up strong branching at the cost of less accurate @@ -437,9 +436,9 @@ message MIPSolverSettings { // Whether implied bound cuts are used. -1 automatic, 0 disabled, 1 enabled. // (default: -1 (automatic)) optional int32 implied_bound_cuts = 31; - // Whether reliability-branching candidates are evaluated simultaneously - // with a single batched PDLP solve. 0 disabled, 1 enabled. (default: 0 - // (disabled)) + // How reliability-branching candidates are evaluated: 0 dual simplex only, + // 1 cooperative dual simplex + a single batched PDLP solve, 2 batched PDLP + // only. (default: 0 (dual simplex only)) int32 mip_batch_pdlp_reliability_branching = 32; // Symmetry detection and handling. -1 automatic, 0 disabled, 1 orbital // fixing, 2 orbital fixing plus lexical reduction. (default: -1 diff --git a/cpp/src/grpc/codegen/lint_registry_defaults.py b/cpp/src/grpc/codegen/lint_registry_defaults.py index e1c33a5228..5077a85cab 100644 --- a/cpp/src/grpc/codegen/lint_registry_defaults.py +++ b/cpp/src/grpc/codegen/lint_registry_defaults.py @@ -8,11 +8,14 @@ python cpp/src/grpc/codegen/lint_registry_defaults.py `default:` is documented (field_registry.yaml's header comment, -references/grpc_codegen.md) as free text the generator neither derives nor -validates. This tool cannot parse arbitrary C++ either -- it only compares -members with a simple `= VALUE;` or `{VALUE};` initializer on one line, and -it prints warnings rather than failing, since a false positive here should -never block a PR. Treat its output as a prompt to double-check, not a verdict. +FIELD_REGISTRY_REFERENCE.md sec 2.1) as free text the generator doesn't +derive from the C++ struct; it only checks, for a non-optional settings +field, that this string textually matches the proto3 zero value -- it can't +catch a C++ default that changed with no matching registry update. This +tool cannot parse arbitrary C++ either -- it only compares members with a +simple `= VALUE;` or `{VALUE};` initializer on one line, and it prints +warnings rather than failing, since a false positive here should never +block a PR. Treat its output as a prompt to double-check, not a verdict. """ import re From d223ffef9f3bf8b185fb5adc3014d87a983a9dd1 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 17 Sep 2026 09:34:27 -0500 Subject: [PATCH 14/14] fix(grpc-codegen): audit remaining descriptions against C++ code, fix 2 more real gaps Systematically cross-checked every settings field with a numbered/enumerated claim (22 fields) against its actual C++ initializer, comment, or usage site, following the two bugs tmckayus/AI-reviewer found. Two more: - enabled_recombiners: bit 4 was described as "local search", copying the C++ header comment's ambiguous "LS" abbreviation. The actual enum (recombiner_stats.hpp) is LINE_SEGMENT, a specific recombination technique distinct from the solver's general local search (FJ climbers, ls_config_t, etc.) -- described accurately instead. - barrier_dual_initial_point: registry documented 3 of the type's actual 4 values (barrier_dual_initial_point_t in internals.hpp has Automatic, LustigMarstenShanno, DualLeastSquares, SedumiMu) -- added the missing SedumiMu case (2), named per its own comment in barrier.cu. Everything else checked (mip_scaling, reduced_cost_strengthening, ordering, folding/augmented/dualize, symmetry, determinism_mode, presolver x2, reliability_branching, the *_cuts family) matched their C++ source exactly. Verified: ./build.sh codegen regenerates cleanly; ci/verify_grpc_codegen.sh and lint_registry_defaults.py both still pass. Signed-off-by: Ramakrishna Prabhu --- cpp/src/grpc/codegen/field_registry.yaml | 7 +++++-- cpp/src/grpc/codegen/generated/cuopt_remote_data.proto | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index f02bf9eea0..718b79a052 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -698,7 +698,8 @@ pdlp_settings: Barrier: how the dual initial point is computed, which affects the iteration count. -1 automatic, 0 the Lustig-Marsten-Shanno heuristic, 1 a least-squares problem minimizing the norms of the dual variables - and reduced costs subject to the dual equality constraints. + and reduced costs subject to the dual equality constraints, 2 the + Sturm/SeDuMi-style mu-based primal+dual initial point. default: "-1 (automatic)" field_num: 26 type: int32 @@ -1205,7 +1206,9 @@ mip_settings: - enabled_recombiners: description: >- Bitmask of enabled recombiners: 1 bound propagation, 2 feasibility - pump, 4 local search, 8 sub-MIP. + pump, 4 line-segment recombination, 8 sub-MIP. (Bit 4 is the + `LINE_SEGMENT` recombiner specifically, not the solver's general + local search.) default: "15 (all enabled)" field_num: 48 type: int32 diff --git a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 4b5452cbcc..cf00d8c760 100644 --- a/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto +++ b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto @@ -272,7 +272,8 @@ message PDLPSolverSettings { // Barrier: how the dual initial point is computed, which affects the // iteration count. -1 automatic, 0 the Lustig-Marsten-Shanno heuristic, 1 a // least-squares problem minimizing the norms of the dual variables and - // reduced costs subject to the dual equality constraints. (default: -1 + // reduced costs subject to the dual equality constraints, 2 the + // Sturm/SeDuMi-style mu-based primal+dual initial point. (default: -1 // (automatic)) optional int32 barrier_dual_initial_point = 26; // Barrier: whether dense columns are eliminated from the constraint matrix @@ -477,7 +478,9 @@ message MIPSolverSettings { // (default: 7000) optional int32 n_of_minimums_for_exit = 47; // Bitmask of enabled recombiners: 1 bound propagation, 2 feasibility pump, - // 4 local search, 8 sub-MIP. (default: 15 (all enabled)) + // 4 line-segment recombination, 8 sub-MIP. (Bit 4 is the `LINE_SEGMENT` + // recombiner specifically, not the solver's general local search.) + // (default: 15 (all enabled)) optional int32 enabled_recombiners = 48; // Size of the ring buffer used to detect Feasibility Pump assignment // cycles. (default: 30)