Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2e9de8d
add vrp support to the grpc server
tmckayus Jul 10, 2026
818bf71
Remove to_host_problem; pivot VRP gRPC to a single C++/Cython client
ramakrishnap-nv Jul 21, 2026
ce75942
Add compiled C++/Cython VRP gRPC client (single architecture)
ramakrishnap-nv Jul 21, 2026
bca7e01
Add VRP client serialization coverage tests + probe
ramakrishnap-nv Jul 21, 2026
a329c6c
Simplify VRP client: drop dead solve_vrp + unused decls
ramakrishnap-nv Jul 21, 2026
002c971
Address review: honor time_limit=0; result_vrp not_ready pre-check
ramakrishnap-nv Jul 21, 2026
7a61e65
Address review: sanitize VRP error; fix to_device host-buffer lifetim…
ramakrishnap-nv Jul 21, 2026
3dbe430
Merge branch 'main' into routing-grpc-vrp-e2e
ramakrishnap-nv Jul 22, 2026
ff54bba
Drive VRP proto/enum from field_registry (fix codegen-verify)
ramakrishnap-nv Jul 22, 2026
08a8bbc
Rename routing grpc tests to unique basenames
ramakrishnap-nv Jul 22, 2026
5db33e2
grpc: keep problem-representation logic out of the codegen for VRP
ramakrishnap-nv Jul 23, 2026
3db6b3a
Merge remote-tracking branch 'origin/main' into routing-grpc-vrp-e2e
ramakrishnap-nv Jul 24, 2026
ae8d5d8
grpc: state problem_category enum values explicitly (LP=0, MIP=1)
ramakrishnap-nv Jul 27, 2026
1f2efb4
grpc/vrp: fix result size accounting, time_limit=0, and client error …
ramakrishnap-nv Jul 28, 2026
e4ccc75
grpc/vrp: carry RoutingSolution as a structured message, not a bytes …
ramakrishnap-nv Jul 28, 2026
7d76dea
Merge remote-tracking branch 'origin/main' into routing-grpc-vrp-e2e
ramakrishnap-nv Jul 29, 2026
23f611b
Merge branch 'main' into routing-grpc-vrp-e2e
ramakrishnap-nv Aug 12, 2026
f74a057
grpc: isolate the routing arm so the component split stays mechanical
ramakrishnap-nv Aug 13, 2026
fd605da
grpc: export the routing symbols cuopt_grpc_server links
ramakrishnap-nv Aug 13, 2026
e0702dd
python: build the gRPC clients as one extension module
ramakrishnap-nv Aug 14, 2026
4183bd5
build(cmake): scope the routing gRPC define and fetch nlohmann_json v…
ramakrishnap-nv Aug 14, 2026
3eda07e
Merge branch 'main' into routing-grpc-vrp-e2e
ramakrishnap-nv Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 89 additions & 7 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,18 @@ if (NOT SKIP_GRPC_BUILD)
add_compile_definitions(CUOPT_ENABLE_GRPC)
message(STATUS "gRPC enabled (target gRPC::grpc++ is available)")

# The routing arm of the gRPC layer needs the routing engine. The wire
# protocol (including the routing protos) is always generated so LP-only
# builds still speak the same protocol; only the mappers and the VRP
# client/worker paths are dropped.
if (NOT SKIP_ROUTING_BUILD)
set(CUOPT_ENABLE_GRPC_ROUTING ON)
add_compile_definitions(CUOPT_ENABLE_GRPC_ROUTING)
Comment thread
tmckayus marked this conversation as resolved.
Outdated
message(STATUS "gRPC routing (VRP) support enabled")
else ()
message(STATUS "gRPC routing (VRP) support disabled (SKIP_ROUTING_BUILD)")
endif ()

# Find protoc compiler (provided by config package or target)
if (TARGET protobuf::protoc)
get_target_property(_PROTOBUF_PROTOC protobuf::protoc IMPORTED_LOCATION_RELEASE)
Expand Down Expand Up @@ -423,9 +435,30 @@ if (NOT SKIP_GRPC_BUILD)
endif ()
endif ()

# Proto search paths: manual protos in src/grpc, generated data proto in src/grpc/codegen/generated
# Proto search paths: LP/protocol protos in src/grpc, routing protos in
# src/grpc/routing, generated data proto in src/grpc/codegen/generated.
# Routing protos live under their own search root so the routing gRPC layer
# stays a self-contained directory; their import names are unchanged.
set(PROTO_PATH_MANUAL "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc")
set(PROTO_PATH_GEN "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated")
set(PROTO_PATH_ROUTING "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/routing")

# Routing solution proto (leaf; no imports). Embedded structurally by the
# generated data proto and by cuopt_remote.proto, so it is generated first.
set(ROUTING_SOLUTION_PROTO_FILE "${PROTO_PATH_ROUTING}/cuopt_routing_solution.proto")
set(ROUTING_SOLUTION_PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_routing_solution.pb.cc")
set(ROUTING_SOLUTION_PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_routing_solution.pb.h")

add_custom_command(
OUTPUT "${ROUTING_SOLUTION_PROTO_SRCS}" "${ROUTING_SOLUTION_PROTO_HDRS}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR}
--proto_path ${PROTO_PATH_ROUTING}
${ROUTING_SOLUTION_PROTO_FILE}
DEPENDS ${ROUTING_SOLUTION_PROTO_FILE}
COMMENT "Generating C++ code from cuopt_routing_solution.proto"
VERBATIM
)

# Generate C++ code from cuopt_remote_data.proto (auto-generated data definitions)
set(DATA_PROTO_FILE "${PROTO_PATH_GEN}/cuopt_remote_data.proto")
Expand All @@ -437,8 +470,10 @@ if (NOT SKIP_GRPC_BUILD)
COMMAND ${_PROTOBUF_PROTOC}
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR}
--proto_path ${PROTO_PATH_GEN}
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_ROUTING}
${DATA_PROTO_FILE}
DEPENDS ${DATA_PROTO_FILE}
DEPENDS ${DATA_PROTO_FILE} ${ROUTING_SOLUTION_PROTO_FILE}
COMMENT "Generating C++ code from cuopt_remote_data.proto"
VERBATIM
)
Expand All @@ -454,8 +489,9 @@ if (NOT SKIP_GRPC_BUILD)
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR}
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_GEN}
--proto_path ${PROTO_PATH_ROUTING}
${PROTO_FILE}
DEPENDS ${PROTO_FILE} ${DATA_PROTO_FILE}
DEPENDS ${PROTO_FILE} ${DATA_PROTO_FILE} ${ROUTING_SOLUTION_PROTO_FILE}
COMMENT "Generating C++ code from cuopt_remote.proto"
VERBATIM
)
Expand All @@ -467,6 +503,24 @@ if (NOT SKIP_GRPC_BUILD)
set(GRPC_SERVICE_SRCS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_remote_service.grpc.pb.cc")
set(GRPC_SERVICE_HDRS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_remote_service.grpc.pb.h")

# Routing proto (standalone VRP messages; imported by service proto)
set(ROUTING_PROTO_FILE "${PROTO_PATH_ROUTING}/cuopt_routing.proto")
set(ROUTING_PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_routing.pb.cc")
set(ROUTING_PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_routing.pb.h")

add_custom_command(
OUTPUT "${ROUTING_PROTO_SRCS}" "${ROUTING_PROTO_HDRS}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR}
--proto_path ${PROTO_PATH_ROUTING}
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_GEN}
${ROUTING_PROTO_FILE}
DEPENDS ${ROUTING_PROTO_FILE} ${PROTO_FILE} ${DATA_PROTO_FILE} ${ROUTING_SOLUTION_PROTO_FILE}
COMMENT "Generating C++ code from cuopt_routing.proto"
VERBATIM
)

add_custom_command(
OUTPUT "${GRPC_PROTO_SRCS}" "${GRPC_PROTO_HDRS}" "${GRPC_SERVICE_SRCS}" "${GRPC_SERVICE_HDRS}"
COMMAND ${_PROTOBUF_PROTOC}
Expand All @@ -475,8 +529,9 @@ if (NOT SKIP_GRPC_BUILD)
--plugin=protoc-gen-grpc=${_GRPC_CPP_PLUGIN_EXECUTABLE}
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_GEN}
--proto_path ${PROTO_PATH_ROUTING}
${GRPC_PROTO_FILE}
DEPENDS ${GRPC_PROTO_FILE} ${PROTO_FILE} ${DATA_PROTO_FILE}
DEPENDS ${GRPC_PROTO_FILE} ${PROTO_FILE} ${DATA_PROTO_FILE} ${ROUTING_PROTO_FILE} ${ROUTING_SOLUTION_PROTO_FILE}
COMMENT "Generating gRPC C++ code from cuopt_remote_service.proto"
VERBATIM
)
Expand Down Expand Up @@ -524,12 +579,21 @@ if (DEFINE_ASSERT)
endif ()

if (NOT SKIP_GRPC_BUILD)
# Add gRPC mapper files and generated protobuf sources
set(GRPC_INFRA_FILES
# Generated protobuf sources. The whole wire protocol is generated in every
# gRPC build, routing messages included: cuopt_remote.proto embeds
# RoutingSolution, so these carry no dependency on the routing engine.
set(GRPC_PROTO_GENERATED_FILES
${ROUTING_SOLUTION_PROTO_SRCS}
${DATA_PROTO_SRCS}
${PROTO_SRCS}
${ROUTING_PROTO_SRCS}
${GRPC_PROTO_SRCS}
${GRPC_SERVICE_SRCS}
)

# LP/MIP arm: proto <-> mathematical_optimization, plus the shared client
# infrastructure (connection, chunking, polling, Cython shim).
set(GRPC_MATHOPT_FILES
src/grpc/grpc_problem_mapper.cpp
src/grpc/grpc_solution_mapper.cpp
src/grpc/grpc_settings_mapper.cpp
Expand All @@ -539,6 +603,22 @@ if (NOT SKIP_GRPC_BUILD)
src/grpc/client/cython_grpc_client.cpp
src/grpc/client/solve_remote.cpp
)

# Routing (VRP) arm: everything that depends on the routing engine. Kept as
# its own list so a routing-only gRPC client can be split out of the
# cuopt_grpc component without moving code around again.
set(GRPC_ROUTING_FILES
src/grpc/routing/grpc_routing_problem_mapper.cpp
src/grpc/routing/grpc_routing_settings_mapper.cpp
src/grpc/routing/grpc_routing_solution_mapper.cpp
src/grpc/routing/grpc_client_vrp.cpp
src/grpc/routing/cython_grpc_client_vrp.cpp
)

set(GRPC_INFRA_FILES ${GRPC_PROTO_GENERATED_FILES} ${GRPC_MATHOPT_FILES})
if (CUOPT_ENABLE_GRPC_ROUTING)
list(APPEND GRPC_INFRA_FILES ${GRPC_ROUTING_FILES})
endif ()
list(APPEND CUOPT_SRC_FILES ${GRPC_INFRA_FILES})

# Always keep NDEBUG defined for gRPC infrastructure files so that abseil
Expand All @@ -548,7 +628,9 @@ if (NOT SKIP_GRPC_BUILD)
# at runtime with "undefined symbol: absl::…::Mutex::Dtor".
set_property(SOURCE ${GRPC_INFRA_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-DNDEBUG")
set_property(SOURCE ${PROTO_SRCS} ${GRPC_PROTO_SRCS} ${GRPC_SERVICE_SRCS} ${DATA_PROTO_SRCS} DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
# Generated protobuf symbols must stay visible in libcuopt.so: cuopt_grpc_server
# links them from the library rather than compiling the protos itself.
set_property(SOURCE ${GRPC_PROTO_GENERATED_FILES} DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
APPEND PROPERTY COMPILE_OPTIONS "$<$<COMPILE_LANGUAGE:CXX>:-fvisibility=default>")
endif (NOT SKIP_GRPC_BUILD)

Expand Down
24 changes: 24 additions & 0 deletions cpp/include/cuopt/grpc/cython_grpc_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <cuopt/export.hpp>
#include <cuopt/mathematical_optimization/utilities/cython_solve.hpp>
#include <cuopt/routing/cpu_routing_problem.hpp>

#include <cstddef>
#include <cstdint>
Expand All @@ -23,6 +24,11 @@ class data_model_view_t;
} // namespace io
} // namespace cuopt::mathematical_optimization

namespace cuopt::routing {
template <typename i_t, typename f_t>
class solver_settings_t;
} // namespace cuopt::routing

namespace cuopt {
namespace CUOPT_EXPORT cython {

Expand Down Expand Up @@ -58,6 +64,13 @@ struct grpc_result_outcome_t {
std::unique_ptr<solver_ret_t> solution;
};

struct grpc_vrp_result_outcome_t {
bool not_ready = false;
bool success = false;
std::string error_message;
cuopt::routing::cpu_routing_solution_t solution;
};

struct grpc_logs_result_t {
bool success = false;
std::string error_message;
Expand Down Expand Up @@ -139,6 +152,17 @@ class grpc_python_client_t {
*/
grpc_result_outcome_t result(const std::string& job_id);

/**
* @brief Submit a VRP problem (unary only). Reuse status/wait/delete/result_vrp.
*/
grpc_submit_result_t submit_vrp(cuopt::routing::cpu_routing_problem_t* problem,
cuopt::routing::solver_settings_t<int, float>* settings);

/**
* @brief Fetch and parse a completed VRP job's routing solution.
*/
grpc_vrp_result_outcome_t result_vrp(const std::string& job_id);

/**
* @brief Block until the job completes, collecting all solver log lines.
*/
Expand Down
150 changes: 150 additions & 0 deletions cpp/include/cuopt/routing/cpu_routing_problem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

#include <cuopt/export.hpp>
#include <cuopt/routing/data_model_view.hpp>
#include <cuopt/routing/routing_structures.hpp>

#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace raft {
class handle_t;
}

namespace cuopt {
namespace CUOPT_EXPORT routing {

/**
* @brief Host-memory owning routing problem (gRPC / remote-execution analog of
* data_model_view_t). Owns all arrays in std::vector and can materialize a
* device-backed data_model_view_t via to_device().
*/
struct cpu_cost_matrix_t {
uint8_t vehicle_type = 0;
std::vector<float> matrix; // num_locations x num_locations, row-major
};

struct cpu_capacity_dimension_t {
std::string name;
std::vector<int32_t> demand; // per-order
std::vector<int32_t> capacity; // per-vehicle
};

struct cpu_vehicle_break_t {
int32_t earliest = 0;
int32_t latest = 0;
int32_t duration = 0;
std::vector<int32_t> locations;
};

struct cpu_uniform_break_t {
std::vector<int32_t> earliest;
std::vector<int32_t> latest;
std::vector<int32_t> duration;
};

struct cpu_initial_solution_t {
std::vector<int32_t> vehicle_ids;
std::vector<int32_t> routes;
std::vector<int32_t> types; // node_type_t values
std::vector<int32_t> sol_offsets;
};

class cpu_routing_problem_t {
public:
int32_t num_locations = 0;
int32_t fleet_size = 0;
int32_t num_orders = -1; // -1 => same as num_locations

std::vector<cpu_cost_matrix_t> cost_matrices;
std::vector<cpu_cost_matrix_t> transit_time_matrices;

std::vector<int32_t> vehicle_start_locations;
std::vector<int32_t> vehicle_return_locations;
std::vector<int32_t> vehicle_tw_earliest;
std::vector<int32_t> vehicle_tw_latest;
std::vector<uint8_t> vehicle_types;
std::vector<uint8_t> drop_return_trips; // 0/1 (avoid vector<bool>)
std::vector<uint8_t> skip_first_trips; // 0/1
std::vector<float> vehicle_max_costs;
std::vector<float> vehicle_max_times;
std::vector<float> vehicle_fixed_costs;

std::vector<int32_t> order_locations;
std::vector<int32_t> order_tw_earliest;
std::vector<int32_t> order_tw_latest;
std::vector<float> order_prizes;
// vehicle_id -> service times; use -1 for the default (all vehicles)
std::map<int32_t, std::vector<int32_t>> order_service_times;

std::vector<int32_t> pickup_indices;
std::vector<int32_t> delivery_indices;

std::vector<cpu_capacity_dimension_t> capacity_dimensions;

std::vector<int32_t> break_locations;
std::vector<cpu_uniform_break_t> uniform_breaks;
std::map<int32_t, std::vector<cpu_vehicle_break_t>> vehicle_breaks;

std::map<int32_t, std::vector<int32_t>> vehicle_order_match;
std::map<int32_t, std::vector<int32_t>> order_vehicle_match;
std::map<int32_t, std::vector<int32_t>> order_precedence;

std::vector<int32_t> objectives; // objective_t enum values
std::vector<float> objective_weights;
int32_t min_vehicles = 0;

cpu_initial_solution_t initial_solutions;

/** Opaque owner of device buffers backing the returned data_model_view_t. */
struct device_data_t;

/** Deleter so unique_ptr can destroy incomplete device_data_t outside the .cu TU. */
struct device_data_deleter {
void operator()(device_data_t* p) const;
};

using device_data_ptr = std::unique_ptr<device_data_t, device_data_deleter>;

/**
* @brief Copy host data to the GPU and return a non-owning data_model_view_t.
* The returned device_data_t must outlive the view.
*/
std::pair<data_model_view_t<int, float>, device_data_ptr> to_device(raft::handle_t* handle) const;
};

/**
* @brief Host-memory routing solution (remote-execution analog of the parsed
* RoutingSolution proto). Populated on the client from the server's response.
*/
struct cpu_routing_solution_t {
std::vector<int32_t> route;
std::vector<double> arrival_stamp;
std::vector<int32_t> truck_id;
std::vector<int32_t> locations;
std::vector<int32_t> node_types;
std::vector<int32_t> unserviced_nodes;
std::vector<int32_t> accepted;

int32_t vehicle_count = 0;
double total_objective_value = 0.0;
std::map<int32_t, double> objective_values;

int32_t status = 0; // cuopt.remote.RoutingSolutionStatus (0 == SUCCESS)
std::string status_message;
std::string error_message;
};

} // namespace CUOPT_EXPORT routing
} // namespace cuopt
Loading
Loading