Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 20 additions & 1 deletion cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,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}
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 @@ -438,7 +455,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}
COMMENT "Generating gRPC C++ code from cuopt_remote_service.proto"
VERBATIM
)
Expand Down Expand Up @@ -490,12 +507,14 @@ if (NOT SKIP_GRPC_BUILD)
set(GRPC_INFRA_FILES
${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
63 changes: 62 additions & 1 deletion cpp/src/grpc/client/grpc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <cuopt/mathematical_optimization/cpu_optimization_problem.hpp>
#include <utilities/logger.hpp>
#include "grpc_problem_mapper.hpp"
#include "grpc_routing_problem_mapper.hpp"
#include "grpc_service_mapper.hpp"
#include "grpc_settings_mapper.hpp"
#include "grpc_solution_mapper.hpp"
Expand Down Expand Up @@ -761,6 +762,65 @@ remote_mip_result_t<i_t, f_t> grpc_client_t::get_mip_result(const std::string& j
return result;
}

// =============================================================================
// VRP (routing) — unary-only submit / result / solve
// =============================================================================

submit_result_t grpc_client_t::submit_vrp(
const cuopt::routing::cpu_routing_problem_t& problem,
const cuopt::routing::solver_settings_t<int, float>& settings)
{
submit_result_t result;

if (!is_connected()) {
result.error_message = "Not connected to server";
return result;
}

cuopt::remote::SubmitJobRequest submit_request;
auto* vrp = submit_request.mutable_vrp_request();
vrp->mutable_header()->set_version(1);
vrp->mutable_header()->set_problem_category(cuopt::remote::VRP);
map_routing_problem_to_proto(problem, vrp->mutable_problem());
map_routing_settings_to_proto(settings, vrp->mutable_settings());

if (!submit_unary(submit_request, result.job_id)) {
result.error_message = last_error_;
return result;
}
result.success = true;
return result;
}

remote_vrp_result_t grpc_client_t::get_vrp_result(const std::string& job_id)
{
remote_vrp_result_t result;

if (!is_connected()) {
result.error_message = "Not connected to server";
return result;
}

downloaded_result_t dl;
if (!get_result_or_download(job_id, dl)) {
result.error_message = last_error_;
return result;
}
if (dl.was_chunked) {
result.error_message = "chunked VRP result download is not supported";
return result;
}

cuopt::remote::RoutingSolution sol_pb;
if (!sol_pb.ParseFromString(dl.response->routing_solution())) {
result.error_message = "failed to parse RoutingSolution from server response";
return result;
}
map_proto_to_routing_solution(sol_pb, result.solution);
result.success = true;
return result;
}

// =============================================================================
// Polling helper
// =============================================================================
Expand Down Expand Up @@ -1067,7 +1127,8 @@ bool grpc_client_t::get_result_or_download(const std::string& job_id,
auto status = impl_->stub->GetResult(&context, request, response.get());

if (status.ok() && response->status() == cuopt::remote::SUCCESS) {
if (response->has_lp_solution() || response->has_mip_solution()) {
if (response->has_lp_solution() || response->has_mip_solution() ||
response->has_routing_solution()) {
GRPC_CLIENT_THROUGHPUT_LOG(config_, "download_unary", response->ByteSizeLong(), download_t0);
GRPC_CLIENT_DEBUG_LOG(config_,
"[grpc_client] Unary GetResult succeeded, result_size="
Expand Down
Loading