Skip to content
Closed
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
3 changes: 1 addition & 2 deletions python/cuopt/cuopt/grpc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
# SPDX-License-Identifier: Apache-2.0
# cmake-format: on

add_subdirectory(linear_programming)
add_subdirectory(routing)
add_subdirectory(client)
14 changes: 10 additions & 4 deletions python/cuopt/cuopt/grpc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@

"""gRPC clients for remote cuOpt execution.

This package is the namespace for domain-specific async clients:
This package is the namespace for domain-specific clients:

- :mod:`cuopt.grpc.linear_programming` — LP/MILP/QP (submit, result, incumbents)
- :mod:`cuopt.grpc.routing` — VRP/TSP/PDP (future)
- :mod:`cuopt.grpc.routing` — VRP/TSP/PDP

Shared job lifecycle (connect, status, wait, cancel, delete, logs) may live
here later as a base client type. Import domain clients explicitly, e.g.
Both are re-exports from :mod:`cuopt.grpc.client`, a single extension module.
They wrap the same C++ client object, so they are compiled as one unit; the
split into two public modules is an API boundary, not a packaging one. Keeping
one unit is what allows the client to be detached from the solver engines as a
single GPU-free package later — two units would each need their own copy of the
transport layer.

Import domain clients explicitly, e.g.
``from cuopt.grpc.linear_programming import Client``.

Do not re-export ``Client`` from this package — callers must choose the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
# SPDX-License-Identifier: Apache-2.0
# cmake-format: on

# One extension module for both arms. See grpc_client.pyx for why they share a
# unit: they wrap the same C++ client object, and keeping them together lets the
# client be detached from the engines as a single package later.
set(cython_sources grpc_client.pyx)
set(linked_libraries cuopt::cuopt)

rapids_cython_create_modules(
CXX
SOURCE_FILES "${cython_sources}"
LINKED_LIBRARIES "${linked_libraries}"
MODULE_PREFIX grpc_
MODULE_PREFIX grpc_client_
ASSOCIATED_TARGETS cuopt
)

foreach(_cython_target IN LISTS RAPIDS_CYTHON_CREATED_TARGETS)
target_include_directories(
${_cython_target} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../../linear_programming/solver")
endforeach()
32 changes: 32 additions & 0 deletions python/cuopt/cuopt/grpc/client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Compiled gRPC clients for remote cuOpt solves (LP/MIP and routing).

Prefer the domain-specific entry points, which re-export from here:
:mod:`cuopt.grpc.linear_programming` and :mod:`cuopt.grpc.routing`.
"""

from cuopt.grpc.client.grpc_client import (
Client,
GrpcError,
HANDLED_SETTERS,
JobNotReadyError,
JobStatus,
RoutingClient,
RoutingSolveError,
TlsConfig,
problem_summary,
)

__all__ = [
"Client",
"GrpcError",
"HANDLED_SETTERS",
"JobNotReadyError",
"JobStatus",
"RoutingClient",
"RoutingSolveError",
"TlsConfig",
"problem_summary",
]
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from libc.stdint cimport int32_t, uint8_t
from libcpp cimport bool
# Single set of declarations for the one C++ client class, cuopt::cython::
# grpc_python_client_t, which carries both the LP/MIP and the routing arms.
# These used to be declared twice -- once under grpc/linear_programming and once
# under grpc/routing -- and the two copies had already drifted (routing omitted
# is_mip and most methods, and used bool where LP used bint).

from libc.stdint cimport int32_t, int64_t, uint8_t
from libcpp.map cimport map as cpp_map
from libcpp.memory cimport unique_ptr
from libcpp.string cimport string
from libcpp.vector cimport vector

from cuopt.linear_programming.data_model.data_model cimport data_model_view_t
from cuopt.linear_programming.solver.solver cimport solver_ret_t
from cuopt.linear_programming.solver_settings.solver_settings cimport (
solver_settings_t as lp_solver_settings_t,
)

cdef extern from "cuopt/routing/cpu_routing_problem.hpp" namespace "cuopt::routing": # noqa
cdef cppclass cpu_cost_matrix_t:
Expand Down Expand Up @@ -93,14 +103,29 @@ cdef extern from "cuopt/routing/cpu_routing_problem.hpp" namespace "cuopt::routi


cdef extern from "cuopt/routing/solver_settings.hpp" namespace "cuopt::routing": # noqa
cdef cppclass solver_settings_t[i_t, f_t]:
solver_settings_t() except +
# Aliased: cuopt::routing::solver_settings_t and the LP one above share a
# name but are unrelated types.
# bint, not libcpp bool: cimporting bool into this .pxd would shadow the
# Python builtin inside grpc_client.pyx, which the LP arm calls.
cdef cppclass routing_solver_settings_t "cuopt::routing::solver_settings_t" [i_t, f_t]: # noqa
routing_solver_settings_t() except +
void set_time_limit(f_t seconds) except +
void set_verbose_mode(bool verbose) except +
void set_error_logging_mode(bool logging) except +
void set_verbose_mode(bint verbose) except +
void set_error_logging_mode(bint logging) except +


cdef extern from "cuopt/grpc/cython_grpc_client.hpp" namespace "cuopt::cython":
ctypedef enum grpc_python_tls_mode_t "cuopt::cython::grpc_python_tls_mode_t":
ENV "cuopt::cython::grpc_python_tls_mode_t::ENV"
DISABLED "cuopt::cython::grpc_python_tls_mode_t::DISABLED"
EXPLICIT "cuopt::cython::grpc_python_tls_mode_t::EXPLICIT"

cdef cppclass grpc_python_client_connect_options_t:
grpc_python_tls_mode_t tls_mode
string tls_root_certs
string tls_client_cert
string tls_client_key

cdef extern from "cuopt/grpc/cython_grpc_client.hpp" namespace "cuopt::cython": # noqa
ctypedef enum grpc_job_status_t "cuopt::cython::grpc_job_status_t":
QUEUED "cuopt::cython::grpc_job_status_t::QUEUED"
PROCESSING "cuopt::cython::grpc_job_status_t::PROCESSING"
Expand All @@ -110,9 +135,10 @@ cdef extern from "cuopt/grpc/cython_grpc_client.hpp" namespace "cuopt::cython":
NOT_FOUND "cuopt::cython::grpc_job_status_t::NOT_FOUND"

cdef cppclass grpc_submit_result_t:
bool success
bint success
string error_message
string job_id
bint is_mip

cdef cppclass grpc_status_result_t:
bint success
Expand All @@ -121,18 +147,75 @@ cdef extern from "cuopt/grpc/cython_grpc_client.hpp" namespace "cuopt::cython":
string message
long long result_size_bytes

cdef cppclass grpc_result_outcome_t:
bint not_ready
bint success
string error_message
unique_ptr[solver_ret_t] solution

cdef cppclass grpc_vrp_result_outcome_t:
bool not_ready
bool success
bint not_ready
bint success
string error_message
cpu_routing_solution_t solution

cdef cppclass grpc_logs_result_t:
bint success
string error_message
vector[string] lines

cdef cppclass grpc_incumbent_entry_t:
int64_t index
double objective
vector[double] assignment

cdef cppclass grpc_incumbents_result_t:
bint success
string error_message
vector[grpc_incumbent_entry_t] incumbents
int64_t next_index
bint job_complete

ctypedef int (*grpc_log_line_callback_t)(
const char* line, size_t line_len, int job_complete, void* user_data
) noexcept nogil

cdef cppclass grpc_python_client_t:
grpc_python_client_t(const string& host, int port) except +
bool connect(string& error_out) except +
grpc_python_client_t(
const string& host,
int port,
const grpc_python_client_connect_options_t& options,
) except +
bint connect(string& error_out) except +
string last_error()

# Shared job control
grpc_status_result_t status(const string& job_id) except +
grpc_status_result_t wait(const string& job_id, int timeout_seconds) except +
bint cancel(const string& job_id, string& error_out) except +
bint delete_job(const string& job_id, string& error_out) except +

# LP / MIP arm
grpc_submit_result_t submit(
data_model_view_t[int, double]* data_model,
lp_solver_settings_t[int, double]* settings,
bint enable_incumbents,
) except +
grpc_result_outcome_t result(const string& job_id) except +
grpc_logs_result_t fetch_logs(const string& job_id, long long from_byte) except +
bint stream_logs(
const string& job_id,
long long from_byte,
grpc_log_line_callback_t callback,
void* user_data,
) except +
grpc_incumbents_result_t fetch_incumbents(
const string& job_id, int64_t from_index, int max_count
) except +

# Routing arm
grpc_submit_result_t submit_vrp(
cpu_routing_problem_t* problem,
solver_settings_t[int, float]* settings) except +
grpc_status_result_t wait(const string& job_id, int timeout_seconds) except +
routing_solver_settings_t[int, float]* settings) except +
grpc_vrp_result_outcome_t result_vrp(const string& job_id) except +
bool delete_job(const string& job_id, string& error_out) except +
Loading
Loading