Skip to content

Commit 3803f4b

Browse files
authored
Merge branch 'main' into cuD-PDLP
2 parents e9faba8 + ac1fd96 commit 3803f4b

8 files changed

Lines changed: 68 additions & 12 deletions

cpp/src/grpc/codegen/FIELD_REGISTRY_REFERENCE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,10 @@ section:
242242
(the setter / assignment is skipped). This is the case the flag is designed
243243
for: without `optional:`, an external client that omits the field silently
244244
overwrites the C++ default with the proto3 zero.
245-
- **Problem-chunked**: currently *not honored* — the chunked path reads from
246-
the hand-written `ChunkedProblemHeader` message which does not declare
247-
`optional` on its fields. Tracked for unification (see §6).
245+
- **Problem-chunked**: the C++ field's in-class default is preserved, provided
246+
the matching field in the hand-written `ChunkedProblemHeader` message is
247+
also declared `optional`. The generator emits a `has_X()` guard for registry
248+
fields marked `optional`.
248249
- **Solution**: cosmetic. Solutions are constructor-built each call; there's no
249250
pre-existing default to preserve. The `optional` keyword adds `has_X()` to
250251
the proto for client-side presence detection, but the from-proto path still

cpp/src/grpc/codegen/field_registry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,10 @@ optimization_problem:
819819
getter: get_sense()
820820
- objective_scaling_factor:
821821
field_num: 4
822+
# C++ defaults to 1 while an absent proto3 scalar reads as 0. Track
823+
# presence so external clients may omit the field without changing the
824+
# objective to an unsupported zero scaling factor.
825+
optional: true
822826
- objective_offset:
823827
field_num: 5
824828

cpp/src/grpc/codegen/generate_conversions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2420,22 +2420,22 @@ def _gen_chunked_header_to_problem(registry, indent=" "):
24202420
ind = indent
24212421
lines = []
24222422

2423-
# `ChunkedProblemHeader` is hand-written (see cuopt_remote_service.proto);
2424-
# it does not declare `optional` for any field, so we pass has_check=None
2425-
# to suppress the has_X() guard. `sentinel:` still applies — the wire
2426-
# mapping must stay consistent with the unary path.
2423+
# `ChunkedProblemHeader` is hand-written (see cuopt_remote_service.proto).
2424+
# Fields marked optional in the registry must also be declared optional in
2425+
# that message so the chunked path can preserve non-zero C++ defaults.
24272426
for entry in obj.get("scalars", []):
24282427
f = parse_field(entry)
24292428
pname = _proto_cpp_name(f["name"])
24302429
setter = _default_problem_setter(f)
2430+
has_check = f"header.has_{pname}()" if f.get("optional") else None
24312431
lines.extend(
24322432
emit_scalar_from_proto_assign(
24332433
lambda v, s=setter: f"cpu_problem.{s}({v});",
24342434
f"header.{pname}()",
24352435
f,
24362436
registry,
24372437
ind,
2438-
has_check=None,
2438+
has_check=has_check,
24392439
)
24402440
)
24412441

cpp/src/grpc/codegen/generated/cuopt_remote_data.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ message OptimizationProblem {
137137
string problem_name = 1;
138138
string objective_name = 2;
139139
bool maximize = 3;
140-
double objective_scaling_factor = 4;
140+
optional double objective_scaling_factor = 4;
141141
double objective_offset = 5;
142142
repeated string variable_names = 7;
143143
repeated string row_names = 8;

cpp/src/grpc/codegen/generated/generated_chunked_header_to_problem.inc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
cpu_problem.set_problem_name(header.problem_name());
66
cpu_problem.set_objective_name(header.objective_name());
77
cpu_problem.set_maximize(header.maximize());
8-
cpu_problem.set_objective_scaling_factor(header.objective_scaling_factor());
8+
if (header.has_objective_scaling_factor()) {
9+
cpu_problem.set_objective_scaling_factor(header.objective_scaling_factor());
10+
}
911
cpu_problem.set_objective_offset(header.objective_offset());
1012

1113
if (header.variable_names_size() > 0) {

cpp/src/grpc/codegen/generated/generated_proto_to_problem.inc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
cpu_problem.set_problem_name(pb_problem.problem_name());
66
cpu_problem.set_objective_name(pb_problem.objective_name());
77
cpu_problem.set_maximize(pb_problem.maximize());
8-
cpu_problem.set_objective_scaling_factor(pb_problem.objective_scaling_factor());
8+
if (pb_problem.has_objective_scaling_factor()) {
9+
cpu_problem.set_objective_scaling_factor(pb_problem.objective_scaling_factor());
10+
}
911
cpu_problem.set_objective_offset(pb_problem.objective_offset());
1012

1113
if (pb_problem.a_offsets_size() > 0) {

cpp/src/grpc/cuopt_remote_service.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ message ChunkedProblemHeader {
118118

119119
// Problem scalars
120120
bool maximize = 2;
121-
double objective_scaling_factor = 3;
121+
optional double objective_scaling_factor = 3;
122122
double objective_offset = 4;
123123
string problem_name = 5;
124124
string objective_name = 6;

cpp/tests/linear_programming/grpc/grpc_client_test.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,53 @@ TEST(MapperRoundtrip, ProblemWithVariableTypes)
18571857
EXPECT_DOUBLE_EQ(restored_obj[2], 3.0);
18581858
}
18591859

1860+
TEST(MapperRoundtrip, UnaryProblemObjectiveScalingPresence)
1861+
{
1862+
cuopt::remote::OptimizationProblem omitted;
1863+
omitted.add_c(0.0);
1864+
omitted.add_variable_lower_bounds(0.0);
1865+
omitted.add_variable_upper_bounds(1.0);
1866+
ASSERT_FALSE(omitted.has_objective_scaling_factor());
1867+
1868+
cpu_optimization_problem_t<int32_t, double> restored_default;
1869+
map_proto_to_problem(omitted, restored_default);
1870+
EXPECT_DOUBLE_EQ(restored_default.get_objective_scaling_factor(), 1.0);
1871+
1872+
cpu_optimization_problem_t<int32_t, double> orig;
1873+
const std::vector<double> objective{0.0};
1874+
const std::vector<double> lower_bound{0.0};
1875+
const std::vector<double> upper_bound{1.0};
1876+
orig.set_objective_coefficients(objective.data(), objective.size());
1877+
orig.set_variable_lower_bounds(lower_bound.data(), lower_bound.size());
1878+
orig.set_variable_upper_bounds(upper_bound.data(), upper_bound.size());
1879+
orig.set_objective_scaling_factor(2.5);
1880+
cuopt::remote::OptimizationProblem present;
1881+
map_problem_to_proto(orig, &present);
1882+
ASSERT_TRUE(present.has_objective_scaling_factor());
1883+
EXPECT_DOUBLE_EQ(present.objective_scaling_factor(), 2.5);
1884+
1885+
cpu_optimization_problem_t<int32_t, double> restored_present;
1886+
map_proto_to_problem(present, restored_present);
1887+
EXPECT_DOUBLE_EQ(restored_present.get_objective_scaling_factor(), 2.5);
1888+
}
1889+
1890+
TEST(MapperRoundtrip, ChunkedProblemObjectiveScalingPresence)
1891+
{
1892+
cuopt::remote::ChunkedProblemHeader omitted;
1893+
ASSERT_FALSE(omitted.has_objective_scaling_factor());
1894+
1895+
cpu_optimization_problem_t<int32_t, double> restored_default;
1896+
map_chunked_header_to_problem(omitted, restored_default);
1897+
EXPECT_DOUBLE_EQ(restored_default.get_objective_scaling_factor(), 1.0);
1898+
1899+
omitted.set_objective_scaling_factor(2.5);
1900+
ASSERT_TRUE(omitted.has_objective_scaling_factor());
1901+
1902+
cpu_optimization_problem_t<int32_t, double> restored_present;
1903+
map_chunked_header_to_problem(omitted, restored_present);
1904+
EXPECT_DOUBLE_EQ(restored_present.get_objective_scaling_factor(), 2.5);
1905+
}
1906+
18601907
TEST(MapperRoundtrip, MIPSolutionAllFields)
18611908
{
18621909
std::vector<double> sol_vec = {1.0, 0.0, 1.0, 0.0, 1.0};

0 commit comments

Comments
 (0)