Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 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
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
44 changes: 41 additions & 3 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,23 @@ if (NOT SKIP_GRPC_BUILD)
set(PROTO_PATH_MANUAL "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc")
set(PROTO_PATH_GEN "${CMAKE_CURRENT_SOURCE_DIR}/src/grpc/codegen/generated")

# 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_MANUAL}/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_MANUAL}
${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")
set(DATA_PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/cuopt_remote_data.pb.cc")
Expand All @@ -437,8 +454,9 @@ 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}
${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 @@ -455,7 +473,7 @@ if (NOT SKIP_GRPC_BUILD)
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_GEN}
${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 +485,23 @@ 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_MANUAL}/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_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 @@ -476,7 +511,7 @@ if (NOT SKIP_GRPC_BUILD)
--proto_path ${PROTO_PATH_MANUAL}
--proto_path ${PROTO_PATH_GEN}
${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 @@ -526,14 +561,17 @@ endif ()
if (NOT SKIP_GRPC_BUILD)
# Add gRPC mapper files and generated protobuf sources
set(GRPC_INFRA_FILES
${ROUTING_SOLUTION_PROTO_SRCS}
${DATA_PROTO_SRCS}
${PROTO_SRCS}
${ROUTING_PROTO_SRCS}
${GRPC_PROTO_SRCS}
${GRPC_SERVICE_SRCS}
src/grpc/grpc_problem_mapper.cpp
src/grpc/grpc_solution_mapper.cpp
src/grpc/grpc_settings_mapper.cpp
src/grpc/grpc_service_mapper.cpp
src/grpc/grpc_routing_problem_mapper.cpp
src/grpc/client/grpc_client.cpp
src/grpc/client/grpc_client_env.cpp
src/grpc/client/cython_grpc_client.cpp
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 @@ -6,6 +6,7 @@
#pragma once

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

#include <cstddef>
#include <cstdint>
Expand All @@ -22,6 +23,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::cython {

/** Mirrors cuopt::mathematical_optimization::job_status_t for the Python bindings. */
Expand Down Expand Up @@ -56,6 +62,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 @@ -136,6 +149,17 @@ class grpc_python_client_t {
*/
grpc_result_outcome_t result(const std::string& job_id, bool is_mip);

/**
* @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
149 changes: 149 additions & 0 deletions cpp/include/cuopt/routing/cpu_routing_problem.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* 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/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 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 routing
} // namespace cuopt
43 changes: 43 additions & 0 deletions cpp/src/grpc/client/cython_grpc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,49 @@ grpc_submit_result_t grpc_python_client_t::submit(
return out;
}

grpc_submit_result_t grpc_python_client_t::submit_vrp(
cuopt::routing::cpu_routing_problem_t* problem,
cuopt::routing::solver_settings_t<int, float>* settings)
{
grpc_submit_result_t out;
if (problem == nullptr || settings == nullptr) {
out.error_message = "problem and settings must not be null";
return out;
}
auto sub = impl_->client.submit_vrp(*problem, *settings);
out.success = sub.success;
out.error_message = sub.error_message;
out.job_id = sub.job_id;
out.is_mip = false;
return out;
}

grpc_vrp_result_outcome_t grpc_python_client_t::result_vrp(const std::string& job_id)
{
grpc_vrp_result_outcome_t out;

// Mirror result(): surface a structured "still running" signal instead of a
// generic GetResult failure when a caller polls result_vrp on an in-flight job.
auto st = status(job_id);
if (!st.success) {
out.error_message = st.error_message;
return out;
}
if (is_in_flight(st.status)) {
out.not_ready = true;
return out;
}

auto remote = impl_->client.get_vrp_result(job_id);
if (!remote.success) {
out.error_message = remote.error_message;
return out;
}
out.success = true;
out.solution = std::move(remote.solution);
return out;
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
grpc_status_result_t grpc_python_client_t::status(const std::string& job_id)
{
return map_status_result(impl_->client.check_status(job_id));
Expand Down
Loading
Loading