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/cpp/src/grpc/codegen/field_registry.yaml b/cpp/src/grpc/codegen/field_registry.yaml index 5d7a784897..718b79a052 100644 --- a/cpp/src/grpc/codegen/field_registry.yaml +++ b/cpp/src/grpc/codegen/field_registry.yaml @@ -38,6 +38,21 @@ # 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 — +# 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 @@ -450,32 +465,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 +541,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 +554,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 +583,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 +617,101 @@ 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, 2 the + Sturm/SeDuMi-style mu-based primal+dual initial point. + 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,10 +721,19 @@ 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 - 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 @@ -582,19 +742,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 +800,110 @@ 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: >- + 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: >- + Relative term in the constraint-feasibility check; multiplies the + constraint's bound magnitude. See absolute_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 +912,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 +946,148 @@ 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: >- + 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: >- + 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: + 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 +1099,12 @@ 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. + default: "true" field_num: 29 type: bool optional: true @@ -766,6 +1114,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 +1131,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 +1151,98 @@ 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 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 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..7fd19d2857 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 @@ -339,6 +351,48 @@ 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 + 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") + words = str(description).split() if description is not None else [] + if default is not None: + 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 words: + 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 +1329,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) @@ -1545,8 +1601,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";', "", @@ -3226,6 +3281,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/generated/cuopt_remote_data.proto b/cpp/src/grpc/codegen/generated/cuopt_remote_data.proto index 344b0e1392..cf00d8c760 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"; @@ -164,95 +164,340 @@ 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, 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 + // 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; + // 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; } 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; + // 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; + // 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; + // 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; + // 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) 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. (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; + // 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 + // (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 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) 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; } 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; 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..5077a85cab --- /dev/null +++ b/cpp/src/grpc/codegen/lint_registry_defaults.py @@ -0,0 +1,179 @@ +#!/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, +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 +import sys +from decimal import Decimal +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 Decimal(cpp_num.group()) != Decimal(reg_num.group()): + 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/SKILL.md b/skills/cuopt-developer/SKILL.md index 2aee1e7c75..6981e082cb 100644 --- a/skills/cuopt-developer/SKILL.md +++ b/skills/cuopt-developer/SKILL.md @@ -264,6 +264,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