From 10ab62e1f3acdb9ab6b053fe8dec51c8f91fec12 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 1 Sep 2026 14:56:20 -0500 Subject: [PATCH 1/8] docs(grpc): document the VRP gRPC client (RoutingClient) cuopt.grpc.routing.RoutingClient (merged in #1597) has been solvable against cuopt_grpc_server since then, but every page in the cuopt-grpc docs still said routing over gRPC was unavailable. Add a routing.rst page (prerequisites, connect-and-solve walkthrough, job lifecycle, settings surface, solution fields, and a limitations/roadmap section linking the open follow-up issues), a matching example script, and update the stale caveats in index/quick-start/examples/api/advanced to distinguish the explicit RoutingClient path (available) from transparent remote execution via CUOPT_REMOTE_HOST/PORT (still #1633). Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/advanced.rst | 2 +- docs/cuopt/source/cuopt-grpc/api.rst | 13 +- docs/cuopt/source/cuopt-grpc/examples.rst | 16 +- .../examples/remote_routing_demo.py | 43 +++++ docs/cuopt/source/cuopt-grpc/index.rst | 27 ++- docs/cuopt/source/cuopt-grpc/quick-start.rst | 10 +- docs/cuopt/source/cuopt-grpc/routing.rst | 154 ++++++++++++++++++ 7 files changed, 246 insertions(+), 19 deletions(-) create mode 100644 docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py create mode 100644 docs/cuopt/source/cuopt-grpc/routing.rst diff --git a/docs/cuopt/source/cuopt-grpc/advanced.rst b/docs/cuopt/source/cuopt-grpc/advanced.rst index 6e6e958686..c90dcfa5c1 100644 --- a/docs/cuopt/source/cuopt-grpc/advanced.rst +++ b/docs/cuopt/source/cuopt-grpc/advanced.rst @@ -364,7 +364,7 @@ See :doc:`python-async-client` for the full job API. Limitations and Scope ===================== -* **Problem types** — **LP**, **MIP**, and **QP** are supported on the gRPC remote path. **Routing** (VRP, TSP, PDP) is **not** supported yet; use the :doc:`REST self-hosted server <../cuopt-server/index>` for remote routing until a future release adds routing over ``CuOptRemoteService``. +* **Problem types** — **LP**, **MIP**, and **QP** support both remote execution and gRPC clients. **Routing** (VRP, TSP, PDP) supports the explicit :doc:`VRP gRPC client ` only; there is no ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` remote-execution path for routing yet, and the client has no log/incumbent streaming (see :ref:`Limitations and Roadmap `). The :doc:`REST self-hosted server <../cuopt-server/index>` is also available for remote routing. * **Message size** — Large problems use chunking; very large models can still hit gRPC max message / timeout limits. Tune ``CUOPT_CHUNK_SIZE``, ``CUOPT_MAX_MESSAGE_BYTES``, server ``--max-message-mb``, and solver ``time_limit`` as needed. * **``CUOPT_GRPC_ARGS``** — Parsed on whitespace only; arguments containing spaces are awkward unless you invoke ``cuopt_grpc_server`` directly. * **CRL / OCSP** — Not handled by the integrated gRPC TLS stack; use a private CA rotation strategy or a TLS-terminating proxy if you need revocation workflows. diff --git a/docs/cuopt/source/cuopt-grpc/api.rst b/docs/cuopt/source/cuopt-grpc/api.rst index 918108723c..cc294aac71 100644 --- a/docs/cuopt/source/cuopt-grpc/api.rst +++ b/docs/cuopt/source/cuopt-grpc/api.rst @@ -97,10 +97,15 @@ Streaming and Callbacks Messages and Constraints ======================== -* **Problem types** — Wire categories are LP/QP or MIP. QP is submitted as - ``lp_request`` (``SolveLPRequest``) with quadratic fields on - ``OptimizationProblem``. **Routing** over this gRPC service is **not** - available yet (planned; use REST for remote routing today). +* **Problem types** — Wire categories are LP/QP, MIP, or VRP. QP is submitted + as ``lp_request`` (``SolveLPRequest``) with quadratic fields on + ``OptimizationProblem``. **VRP** rides the same ``SubmitJob``/``GetResult`` + RPCs as LP/MIP, as a ``vrp_request`` payload typed by + ``cpp/src/grpc/routing/cuopt_routing.proto`` (problem) and + ``cuopt_routing_solution.proto`` (result) -- not a separate service. There + is no ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` remote-execution path for + routing yet; use ``cuopt.grpc.routing.RoutingClient`` (:doc:`routing`) or + REST for remote routing today. * **Solver settings** — Carried as ``PDLPSolverSettings`` or ``MIPSolverSettings`` inside the request or chunked header, aligned with the NVIDIA cuOpt solver options documentation. * **Errors** — Transport failures use gRPC status codes. Some outcomes use ``Status::OK`` with response fields: ``CheckStatus`` reports unknown jobs as diff --git a/docs/cuopt/source/cuopt-grpc/examples.rst b/docs/cuopt/source/cuopt-grpc/examples.rst index 35bb1e535e..5d02571ac3 100644 --- a/docs/cuopt/source/cuopt-grpc/examples.rst +++ b/docs/cuopt/source/cuopt-grpc/examples.rst @@ -24,7 +24,10 @@ Add TLS or tuning variables from :doc:`advanced` if your deployment uses them. .. note:: - Routing solve over gRPC is not supported. For solving routing problems remotely today, use the HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` and :doc:`Examples <../cuopt-server/examples/index>`. + Routing has no remote-execution path over gRPC -- use the explicit + :ref:`VRP gRPC client ` below, or the + HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` and + :doc:`Examples <../cuopt-server/examples/index>`. Where to Find Examples ====================== @@ -70,6 +73,17 @@ without ``CUOPT_REMOTE_*``, use ``cuopt.grpc.linear_programming.Client``: * :doc:`python-async-client-examples` — log streaming and incumbent streaming * :doc:`python-async-client-api` — API reference +Routing (VRP) +------------- + +.. _cuopt-grpc-examples-routing: + +For VRP, TSP, and PDP problems, use ``cuopt.grpc.routing.RoutingClient`` -- +the same submit / wait / result / delete lifecycle, with no +``CUOPT_REMOTE_*`` equivalent yet: + +* :doc:`routing` — overview, API reference, and :download:`remote_routing_demo.py ` + Custom gRPC Client ------------------ diff --git a/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py new file mode 100644 index 0000000000..14482bd444 --- /dev/null +++ b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Minimal VRP demo for the NVIDIA cuOpt VRP gRPC client. + +Unlike LP/MIP, routing has no ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` +transparent path yet -- build a :class:`cuopt.routing.DataModel` and solve it +with :class:`cuopt.grpc.routing.RoutingClient`, an explicit client (host and +port passed directly). + +Start the server first:: + + cuopt_grpc_server --port 5001 --workers 1 + +Then:: + + python remote_routing_demo.py +""" + +import numpy as np +from cuopt import routing +from cuopt.grpc.routing import RoutingClient + +dm = routing.DataModel(5, 2) +cost_matrix = np.array( + [ + [0, 1, 2, 2, 1], + [1, 0, 1, 2, 2], + [2, 1, 0, 1, 2], + [2, 2, 1, 0, 1], + [1, 2, 2, 1, 0], + ], + dtype=np.float32, +) +dm.add_cost_matrix(cost_matrix) + +client = RoutingClient("localhost:5001") +solution = client.solve(dm, {"time_limit": 5.0}) + +print("Status: ", solution["status_message"]) +print("Vehicles: ", solution["vehicle_count"]) +print("Objective: ", solution["total_objective_value"]) +print("Route: ", solution["route"]) diff --git a/docs/cuopt/source/cuopt-grpc/index.rst b/docs/cuopt/source/cuopt-grpc/index.rst index c0ecb83010..f3747c1515 100644 --- a/docs/cuopt/source/cuopt-grpc/index.rst +++ b/docs/cuopt/source/cuopt-grpc/index.rst @@ -18,8 +18,10 @@ NVIDIA cuOpt can run LP, MIP, and QP solves on a remote GPU host through **gRPC clients** (explicit client) Your program opens a gRPC connection and manages jobs itself. Use the :doc:`Python async gRPC client ` - (``cuopt.grpc.linear_programming.Client``) for job management, or speak - ``CuOptRemoteService`` directly from a custom client (:doc:`api`). + (``cuopt.grpc.linear_programming.Client``) for LP/MIP/QP job management, the + :doc:`VRP gRPC client ` (``cuopt.grpc.routing.RoutingClient``) for + routing, or speak ``CuOptRemoteService`` directly from a custom client + (:doc:`api`). In this section, **remote execution** always means the zero-code-change path above. When talking about programs that construct a client and call gRPC @@ -27,10 +29,14 @@ themselves, we say **gRPC client**. .. note:: - **Problem types:** LP, MIP, and QP are supported today. **Routing** (VRP, - TSP, PDP, and related APIs) over gRPC is **not** available yet; support is - planned for an **upcoming** release. For remote routing today, use the - HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>`. + **Problem types:** LP, MIP, and QP support both remote execution and + gRPC clients. **Routing** (VRP, TSP, PDP) supports the explicit + :doc:`VRP gRPC client ` only -- there is no + ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` remote-execution path for + routing yet (tracked in `#1633 + `_). The HTTP/JSON + :doc:`REST self-hosted server <../cuopt-server/index>` is also available + for remote routing. This is **not** the HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` (FastAPI). REST is for arbitrary HTTP clients; gRPC serves remote execution @@ -43,13 +49,15 @@ When to Choose Which Path scripts and APIs as a local solve. * **Python async gRPC client** — explicit job control: submit now, wait or poll later, cancel, stream solver logs, stream MIP incumbents. +* **VRP gRPC client** — explicit job control for routing (submit / wait / + result / delete); no remote execution or streaming yet. * **Custom ``CuOptRemoteService`` client** — non-Python (or fully custom) integrations that speak the protos directly. See :doc:`api`. Start with :doc:`quick-start` (install, server, and a minimal LP). Use -:doc:`python-async-client` for the Python gRPC client; :doc:`advanced` for -TLS, Docker, environment variables, and troubleshooting; :doc:`examples` for -additional patterns. +:doc:`python-async-client` for the LP/MIP/QP gRPC client, :doc:`routing` for +the VRP gRPC client; :doc:`advanced` for TLS, Docker, environment variables, +and troubleshooting; :doc:`examples` for additional patterns. .. toctree:: :maxdepth: 2 @@ -60,6 +68,7 @@ additional patterns. python-async-client.rst python-async-client-examples.rst python-async-client-api.rst + routing.rst advanced.rst examples.rst api.rst diff --git a/docs/cuopt/source/cuopt-grpc/quick-start.rst b/docs/cuopt/source/cuopt-grpc/quick-start.rst index 96caccd40c..8425ac41ce 100644 --- a/docs/cuopt/source/cuopt-grpc/quick-start.rst +++ b/docs/cuopt/source/cuopt-grpc/quick-start.rst @@ -22,10 +22,12 @@ directly (see :doc:`api`). .. note:: - **Problem types:** **LP**, **MIP**, and **QP** are supported today. - **Routing** (VRP, TSP, PDP) over gRPC is **not** available; for remote - routing, use the HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>`. - This guide is **not** the REST server. + **Problem types:** **LP**, **MIP**, and **QP** support remote execution + (this guide) and gRPC clients. **Routing** (VRP, TSP, PDP) supports the + explicit :doc:`VRP gRPC client ` only -- there is no remote + execution path for routing yet. The HTTP/JSON + :doc:`REST self-hosted server <../cuopt-server/index>` is also available + for remote routing. This guide is **not** the REST server. How Remote Execution Works ========================== diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst new file mode 100644 index 0000000000..754a26d0fd --- /dev/null +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -0,0 +1,154 @@ +.. + SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + SPDX-License-Identifier: Apache-2.0 + +============================= +VRP gRPC Client (Routing) +============================= + +``cuopt.grpc.routing.RoutingClient`` is an explicit gRPC client for solving +**VRP** (vehicle routing, including TSP and PDP) problems on +``cuopt_grpc_server``. It uses the same job lifecycle as the LP/MIP +:doc:`Python async gRPC client `: **submit** → **wait** +→ **result** → **delete**, plus a **solve** convenience method that does all +four. + +There is no ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` transparent path for +routing yet (unlike LP/MIP/QP) -- always construct ``RoutingClient`` with an +explicit host and port. See :ref:`Limitations and Roadmap +` below. + +Prerequisites +============= + +A running ``cuopt_grpc_server`` on a GPU host (see :doc:`quick-start`): + +.. code-block:: bash + + cuopt_grpc_server --port 5001 --workers 1 + +Connect and Solve +================== + +.. code-block:: python + + import numpy as np + from cuopt import routing + from cuopt.grpc.routing import RoutingClient + + dm = routing.DataModel(5, 2) + cost_matrix = np.array( + [ + [0, 1, 2, 2, 1], + [1, 0, 1, 2, 2], + [2, 1, 0, 1, 2], + [2, 2, 1, 0, 1], + [1, 2, 2, 1, 0], + ], + dtype=np.float32, + ) + dm.add_cost_matrix(cost_matrix) + + client = RoutingClient("localhost:5001") + solution = client.solve(dm, {"time_limit": 5.0}) + + print(solution["status_message"]) + print(solution["total_objective_value"]) + print(solution["route"]) + +``RoutingClient.submit()`` accepts a :class:`cuopt.routing.DataModel` built +the same way as for a local :func:`cuopt.routing.Solve`. ``solve()`` submits, +waits, and deletes the job's server-side state when done (pass +``delete=False`` to keep it around for a later ``result()`` call). + +:download:`remote_routing_demo.py ` + +.. literalinclude:: examples/remote_routing_demo.py + :language: python + :linenos: + +Job Lifecycle +============= + +* ``submit(data_model, settings=None)`` — serializes the problem and settings, returns a ``job_id``. +* ``wait(job_id, timeout=0)`` — blocks until the job reaches a terminal state; returns the status. +* ``result(job_id)`` — returns the solution dict, or ``None`` if the job has not finished. +* ``delete(job_id)`` — releases the job's server-side result. +* ``solve(data_model, settings=None, *, timeout=0, delete=True)`` — submit + wait + result, deleting the job afterward unless ``delete=False``. + +A failed or non-completed job raises ``RoutingSolveError`` from ``submit``, +``wait``, or ``solve``. + +Settings +======== + +``settings`` accepts a ``dict`` or a :class:`cuopt.routing.SolverSettings`. +Today only ``time_limit`` is forwarded to the remote solve; other +``SolverSettings`` options (``verbose``, ``error_logging``, +``dump_best_results_path``/``interval``) are not yet mapped over gRPC (see +:ref:`Limitations and Roadmap `). + +Solution Fields +================ + +``result()`` and ``solve()`` return a ``dict`` with the same fields as a +local :class:`cuopt.routing.Assignment`, read directly off the wire: + +.. list-table:: + :header-rows: 1 + + * - Key + - Description + * - ``status`` / ``status_message`` + - Integer and human-readable solve status. + * - ``error_message`` + - Set when the solve failed. + * - ``vehicle_count`` + - Number of vehicles used. + * - ``total_objective_value`` / ``objective_values`` + - Overall cost and the per-objective breakdown. + * - ``route``, ``truck_id``, ``locations``, ``node_types``, ``arrival_stamp`` + - Per-stop route arrays, one entry per stop across all vehicles. + * - ``unserviced_nodes`` + - Orders that could not be served. + * - ``accepted`` + - Orders accepted, for prize-collection problems. + +.. _cuopt-grpc-routing-limitations: + +Limitations and Roadmap +========================= + +* **No transparent remote execution** — routing does not read + ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT``; always pass host and port to + ``RoutingClient`` explicitly. Tracked in `#1633 + `_. +* **Settings surface** — only ``time_limit`` is forwarded today. Tracked in + `#1632 `_. +* **No log or incumbent streaming** — unlike the LP/MIP client, there is no + ``start_log_stream``/``start_incumbent_stream`` equivalent yet. Tracked in + `#1630 `_. +* **Input validation** — malformed problems may fail late or with a generic + error rather than an early, descriptive one. Tracked in `#1631 + `_. + +API Reference +============= + +Import path: ``cuopt.grpc.routing``. + +.. autoclass:: cuopt.grpc.routing.RoutingClient + :members: + :undoc-members: + +.. autoexception:: cuopt.grpc.routing.RoutingSolveError + :members: + :show-inheritance: + +See Also +======== + +* :doc:`index` — when to use gRPC vs. the REST self-hosted server +* :doc:`python-async-client` — the LP/MIP/QP equivalent client +* :doc:`api` — how VRP rides ``CuOptRemoteService``'s RPCs +* :doc:`../cuopt-python/routing/index` — the local routing Python API From 4ed246244cf5554492d793c92ff91bcb1c28a19b Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 1 Sep 2026 14:59:48 -0500 Subject: [PATCH 2/8] docs(grpc): drop duplicate inline example in routing.rst The Connect and Solve section showed the full script twice: once inline, once via literalinclude of the identical remote_routing_demo.py right below it. Every other page in this doc set uses one canonical copy per script (literalinclude for a file-backed example, inline code-block only for a delta not backed by its own file, as quick-start.rst does for the async-client variant) -- keep the literalinclude and drop the inline duplicate. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/routing.rst | 26 ------------------------ 1 file changed, 26 deletions(-) diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst index 754a26d0fd..0828ffa39a 100644 --- a/docs/cuopt/source/cuopt-grpc/routing.rst +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -30,32 +30,6 @@ A running ``cuopt_grpc_server`` on a GPU host (see :doc:`quick-start`): Connect and Solve ================== -.. code-block:: python - - import numpy as np - from cuopt import routing - from cuopt.grpc.routing import RoutingClient - - dm = routing.DataModel(5, 2) - cost_matrix = np.array( - [ - [0, 1, 2, 2, 1], - [1, 0, 1, 2, 2], - [2, 1, 0, 1, 2], - [2, 2, 1, 0, 1], - [1, 2, 2, 1, 0], - ], - dtype=np.float32, - ) - dm.add_cost_matrix(cost_matrix) - - client = RoutingClient("localhost:5001") - solution = client.solve(dm, {"time_limit": 5.0}) - - print(solution["status_message"]) - print(solution["total_objective_value"]) - print(solution["route"]) - ``RoutingClient.submit()`` accepts a :class:`cuopt.routing.DataModel` built the same way as for a local :func:`cuopt.routing.Solve`. ``solve()`` submits, waits, and deletes the job's server-side state when done (pass From 23d2937245ccef1fb3fd67cd072c49ce3f2e21aa Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 1 Sep 2026 15:08:05 -0500 Subject: [PATCH 3/8] docs(grpc): stop calling RoutingClient's gRPC solves "no remote execution" RoutingClient does execute remotely over gRPC -- "remote execution" is just this doc set's term of art for the specific CUOPT_REMOTE_HOST/PORT env-var path (defined in index.rst), which routing indeed lacks. But quick-start.rst and examples.rst stated the narrower claim ("no remote-execution path over gRPC") without naming that mechanism, which reads as routing having no remote gRPC execution at all. Name the env vars directly, as index.rst/api.rst/advanced.rst already do. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/examples.rst | 8 ++++---- docs/cuopt/source/cuopt-grpc/quick-start.rst | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/cuopt/source/cuopt-grpc/examples.rst b/docs/cuopt/source/cuopt-grpc/examples.rst index 5d02571ac3..6d02cc8cbc 100644 --- a/docs/cuopt/source/cuopt-grpc/examples.rst +++ b/docs/cuopt/source/cuopt-grpc/examples.rst @@ -24,10 +24,10 @@ Add TLS or tuning variables from :doc:`advanced` if your deployment uses them. .. note:: - Routing has no remote-execution path over gRPC -- use the explicit - :ref:`VRP gRPC client ` below, or the - HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` and - :doc:`Examples <../cuopt-server/examples/index>`. + Routing does not read ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` -- + use the explicit :ref:`VRP gRPC client ` + below, or the HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` + and :doc:`Examples <../cuopt-server/examples/index>`. Where to Find Examples ====================== diff --git a/docs/cuopt/source/cuopt-grpc/quick-start.rst b/docs/cuopt/source/cuopt-grpc/quick-start.rst index 8425ac41ce..ba0dad824a 100644 --- a/docs/cuopt/source/cuopt-grpc/quick-start.rst +++ b/docs/cuopt/source/cuopt-grpc/quick-start.rst @@ -24,10 +24,10 @@ directly (see :doc:`api`). **Problem types:** **LP**, **MIP**, and **QP** support remote execution (this guide) and gRPC clients. **Routing** (VRP, TSP, PDP) supports the - explicit :doc:`VRP gRPC client ` only -- there is no remote - execution path for routing yet. The HTTP/JSON - :doc:`REST self-hosted server <../cuopt-server/index>` is also available - for remote routing. This guide is **not** the REST server. + explicit :doc:`VRP gRPC client ` only -- it does not read + ``CUOPT_REMOTE_HOST``/``CUOPT_REMOTE_PORT`` the way this guide's LP does. + The HTTP/JSON :doc:`REST self-hosted server <../cuopt-server/index>` is + also available for remote routing. This guide is **not** the REST server. How Remote Execution Works ========================== From a728703e21c8dfbfea119fc0ec7981619a94b7f9 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 1 Sep 2026 15:14:46 -0500 Subject: [PATCH 4/8] docs(grpc): document RoutingClient's TLS, constructor, and settings gaps Cross-checked routing.rst against grpc_client.pyx and cython_grpc_client.hpp more closely and found three undocumented behaviors: - RoutingClient(target) takes one "host:port" string; the LP/MIP Client(host, port) takes two args -- an easy copy-paste trap between the two client docs. - RoutingClient's __cinit__ only calls the 2-arg grpc_python_client_t constructor (tls_mode defaults to ENV), so it does honor CUOPT_TLS_* the same way Client's tls=None does, but has no `tls` argument to override that, unlike Client(host, port, tls=...). Filed as scope on #1632. - The settings dict silently drops any key other than "time_limit" rather than erroring, which the existing text implied without stating outright. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/routing.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst index 0828ffa39a..41e1c77cd0 100644 --- a/docs/cuopt/source/cuopt-grpc/routing.rst +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -30,6 +30,10 @@ A running ``cuopt_grpc_server`` on a GPU host (see :doc:`quick-start`): Connect and Solve ================== +``RoutingClient(target)`` takes a single ``"host:port"`` string, unlike the +LP/MIP client's ``Client(host, port)`` two-argument form -- ``target`` +defaults to ``"localhost:50051"`` if omitted. + ``RoutingClient.submit()`` accepts a :class:`cuopt.routing.DataModel` built the same way as for a local :func:`cuopt.routing.Solve`. ``solve()`` submits, waits, and deletes the job's server-side state when done (pass @@ -60,7 +64,9 @@ Settings Today only ``time_limit`` is forwarded to the remote solve; other ``SolverSettings`` options (``verbose``, ``error_logging``, ``dump_best_results_path``/``interval``) are not yet mapped over gRPC (see -:ref:`Limitations and Roadmap `). +:ref:`Limitations and Roadmap `). A ``dict`` +key other than ``time_limit`` is silently ignored rather than raising an +error. Solution Fields ================ @@ -99,6 +105,13 @@ Limitations and Roadmap `_. * **Settings surface** — only ``time_limit`` is forwarded today. Tracked in `#1632 `_. +* **TLS** — ``RoutingClient`` honors ``CUOPT_TLS_*`` environment variables + the same way the LP/MIP client's default ``tls=None`` does, but has no + ``tls`` constructor argument to disable that or pass an explicit + :class:`~cuopt.grpc.linear_programming.TlsConfig` the way + ``Client(host, port, tls=...)`` does; see :doc:`advanced` for the + environment variables. Tracked in `#1632 + `_. * **No log or incumbent streaming** — unlike the LP/MIP client, there is no ``start_log_stream``/``start_incumbent_stream`` equivalent yet. Tracked in `#1630 `_. From 638389a0632e8c1619665a179e062dc8237fa46e Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Tue, 1 Sep 2026 16:13:57 -0500 Subject: [PATCH 5/8] docs(grpc): update routing.rst for RoutingClient's new (host, port, tls=) signature #1840 changes RoutingClient(target="host:port") to RoutingClient(host, port, *, tls=None), matching Client exactly and fixing the TLS gap this page's Limitations section called out. Update the constructor description, the example script, and the advanced.rst TLS variable table to cover both clients. Swap the now-resolved TLS limitation for the still-real one this page hadn't listed yet: no 2 GiB chunking (#1629). Depends on #1840 landing first (or being rebased together). Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/advanced.rst | 19 ++++++++++++------- .../examples/remote_routing_demo.py | 2 +- docs/cuopt/source/cuopt-grpc/routing.rst | 17 +++++++---------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/cuopt/source/cuopt-grpc/advanced.rst b/docs/cuopt/source/cuopt-grpc/advanced.rst index c90dcfa5c1..fd3d79b68e 100644 --- a/docs/cuopt/source/cuopt-grpc/advanced.rst +++ b/docs/cuopt/source/cuopt-grpc/advanced.rst @@ -127,12 +127,14 @@ to that client. A **custom** gRPC client must configure the channel itself Python Async gRPC Client (``cuopt.grpc``) ----------------------------------------- -``Client(host, port, tls=...)`` takes the server address in code. It does -**not** read ``CUOPT_REMOTE_HOST`` or ``CUOPT_REMOTE_PORT``. +``Client(host, port, tls=...)`` (LP/MIP/QP) and ``RoutingClient(host, port, +tls=...)`` (VRP, :doc:`routing`) take the server address in code. Neither +reads ``CUOPT_REMOTE_HOST`` or ``CUOPT_REMOTE_PORT``. -When ``tls`` is omitted (``None``), the client honors the same ``CUOPT_TLS_*`` -variables as remote execution. Pass ``tls=False`` for plain TCP, or -``tls=TlsConfig(...)`` for explicit PEM paths (see :doc:`python-async-client`). +When ``tls`` is omitted (``None``), both clients honor the same +``CUOPT_TLS_*`` variables as remote execution. Pass ``tls=False`` for plain +TCP, or ``tls=TlsConfig(...)`` for explicit PEM paths (see +:doc:`python-async-client`). .. list-table:: :header-rows: 1 @@ -163,8 +165,11 @@ variables as remote execution. Pass ``tls=False`` for plain TCP, or - ``0`` - Non-zero: extra gRPC client logging -``CUOPT_CHUNK_SIZE`` and ``CUOPT_MAX_MESSAGE_BYTES`` also apply to this client -when set (same defaults as the integrated remote client). +``CUOPT_MAX_MESSAGE_BYTES`` also applies to both clients when set (same +default as the integrated remote client) -- it raises the gRPC channel's +max message size regardless of chunking support. ``CUOPT_CHUNK_SIZE`` only +has an effect for ``Client``; ``RoutingClient`` has no chunking to size (see +:doc:`routing`'s Limitations). Usage ===== diff --git a/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py index 14482bd444..226adb361d 100644 --- a/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py +++ b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py @@ -34,7 +34,7 @@ ) dm.add_cost_matrix(cost_matrix) -client = RoutingClient("localhost:5001") +client = RoutingClient("localhost", 5001) solution = client.solve(dm, {"time_limit": 5.0}) print("Status: ", solution["status_message"]) diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst index 41e1c77cd0..3b45fe8f55 100644 --- a/docs/cuopt/source/cuopt-grpc/routing.rst +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -30,9 +30,9 @@ A running ``cuopt_grpc_server`` on a GPU host (see :doc:`quick-start`): Connect and Solve ================== -``RoutingClient(target)`` takes a single ``"host:port"`` string, unlike the -LP/MIP client's ``Client(host, port)`` two-argument form -- ``target`` -defaults to ``"localhost:50051"`` if omitted. +``RoutingClient(host, port, *, tls=None)`` takes the same arguments as the +LP/MIP client's ``Client(host, port, tls=...)``; see :doc:`python-async-client` +for the ``tls`` options. ``RoutingClient.submit()`` accepts a :class:`cuopt.routing.DataModel` built the same way as for a local :func:`cuopt.routing.Solve`. ``solve()`` submits, @@ -105,13 +105,10 @@ Limitations and Roadmap `_. * **Settings surface** — only ``time_limit`` is forwarded today. Tracked in `#1632 `_. -* **TLS** — ``RoutingClient`` honors ``CUOPT_TLS_*`` environment variables - the same way the LP/MIP client's default ``tls=None`` does, but has no - ``tls`` constructor argument to disable that or pass an explicit - :class:`~cuopt.grpc.linear_programming.TlsConfig` the way - ``Client(host, port, tls=...)`` does; see :doc:`advanced` for the - environment variables. Tracked in `#1632 - `_. +* **No 2 GiB chunking** — VRP is unary-only; a cost/transit matrix or + ``RoutingSolution`` that exceeds the gRPC max message size cannot be sent + or retrieved (the LP/MIP client chunks automatically). Tracked in `#1629 + `_. * **No log or incumbent streaming** — unlike the LP/MIP client, there is no ``start_log_stream``/``start_incumbent_stream`` equivalent yet. Tracked in `#1630 `_. From 89dba0ecba14aa25a5607a2c0cec7a39a0fbe942 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 2 Sep 2026 09:45:09 -0500 Subject: [PATCH 6/8] docs(grpc): merge main (RoutingClient TLS/arg parity), address CodeRabbit review - Merge origin/main now that #1840 landed, so routing.rst's documented RoutingClient(host, port, tls=...) signature matches the real one. - api.rst: list the VRP proto files alongside the LP/MIP ones, mention RoutingClient next to Client in the "most users don't call these directly" summary, and note VRP in the SubmitJob RPC row -- it only described LP/MIP. - remote_routing_demo.py: add the same "tls=None uses CUOPT_TLS_* if set" comment python-async-client.rst's Client(...) call already carries, for the same reason (a reader with CUOPT_TLS_ENABLED set against a plain server would otherwise be surprised). The other two CodeRabbit findings on this PR are already resolved: the routing.rst port-mismatch and examples.rst "no remote-execution path" wording were both fixed in earlier commits before #1840 merged. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/api.rst | 14 +++++++++----- .../cuopt-grpc/examples/remote_routing_demo.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/cuopt/source/cuopt-grpc/api.rst b/docs/cuopt/source/cuopt-grpc/api.rst index cc294aac71..75d498fd98 100644 --- a/docs/cuopt/source/cuopt-grpc/api.rst +++ b/docs/cuopt/source/cuopt-grpc/api.rst @@ -10,14 +10,17 @@ The **CuOptRemoteService** gRPC API is defined in Protocol Buffers under the ``c * ``cpp/src/grpc/cuopt_remote_service.proto`` — service and job/chunk/log RPCs * ``cpp/src/grpc/cuopt_remote.proto`` — LP/MIP problem, settings, and result messages +* ``cpp/src/grpc/routing/cuopt_routing.proto`` — VRP request messages +* ``cpp/src/grpc/routing/cuopt_routing_solution.proto`` — VRP result messages Most users do **not** call these RPCs directly: * **Remote execution** — Python, C (``cuOptSolve``), and ``cuopt_cli`` forward - solves when ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` are set - (:doc:`quick-start`, :doc:`advanced`). -* **Python async gRPC client** — ``cuopt.grpc.linear_programming.Client`` - (:doc:`python-async-client`). + LP/MIP/QP solves when ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` are + set (:doc:`quick-start`, :doc:`advanced`). +* **Python async gRPC client** — ``cuopt.grpc.linear_programming.Client`` for + LP/MIP/QP (:doc:`python-async-client`), ``cuopt.grpc.routing.RoutingClient`` + for VRP (:doc:`routing`). **Custom** clients call ``CuOptRemoteService`` over gRPC using these definitions. This page summarizes the service for custom integrators and debugging. @@ -35,7 +38,8 @@ Asynchronous Jobs * - RPC - Purpose * - ``SubmitJob`` - - Submit an LP or MIP job in one message (within gRPC message size limits). + - Submit an LP, MIP, or VRP job in one message (within gRPC message size + limits; VRP is unary-only today, see :doc:`routing`). * - ``CheckStatus`` - Poll job status by ``job_id``. * - ``GetResult`` diff --git a/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py index 226adb361d..68da94e6a0 100644 --- a/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py +++ b/docs/cuopt/source/cuopt-grpc/examples/remote_routing_demo.py @@ -34,7 +34,7 @@ ) dm.add_cost_matrix(cost_matrix) -client = RoutingClient("localhost", 5001) +client = RoutingClient("localhost", 5001) # tls=None uses CUOPT_TLS_* if set solution = client.solve(dm, {"time_limit": 5.0}) print("Status: ", solution["status_message"]) From da7eca1e87eb74db7777f1db37176fe0c353d1ae Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 2 Sep 2026 10:56:05 -0500 Subject: [PATCH 7/8] docs(grpc): add sample output to the VRP demo, per review Matches the "Sample output" convention already used for the local routing TSP batch example (cuopt-python/routing/routing-examples.rst). Labeled explicitly as illustrative -- there's no GPU in this dev environment to capture a real run against, and VRP status text, vehicle count, objective, and route order aren't guaranteed to be stable across runs anyway. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/routing.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst index 3b45fe8f55..3e1751a071 100644 --- a/docs/cuopt/source/cuopt-grpc/routing.rst +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -45,6 +45,16 @@ waits, and deletes the job's server-side state when done (pass :language: python :linenos: +Illustrative output (exact status text, vehicle count, objective, and route +order depend on the solver run and are not guaranteed to match): + +.. code-block:: text + + Status: Success + Vehicles: 1 + Objective: 5.0 + Route: [0 1 2 3 4 0] + Job Lifecycle ============= From 6e2185ca097ba05a2c37556b98d486211fb2baed Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Wed, 2 Sep 2026 11:20:01 -0500 Subject: [PATCH 8/8] docs(grpc): fix verb tense in routing.rst solution-fields table "could not be served" -> "cannot be served" -- this describes what unserviced_nodes always means, not something that happened to this particular solve. Co-Authored-By: Claude Sonnet 5 --- docs/cuopt/source/cuopt-grpc/routing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cuopt/source/cuopt-grpc/routing.rst b/docs/cuopt/source/cuopt-grpc/routing.rst index 3e1751a071..2827301058 100644 --- a/docs/cuopt/source/cuopt-grpc/routing.rst +++ b/docs/cuopt/source/cuopt-grpc/routing.rst @@ -100,7 +100,7 @@ local :class:`cuopt.routing.Assignment`, read directly off the wire: * - ``route``, ``truck_id``, ``locations``, ``node_types``, ``arrival_stamp`` - Per-stop route arrays, one entry per stop across all vehicles. * - ``unserviced_nodes`` - - Orders that could not be served. + - Orders that cannot be served. * - ``accepted`` - Orders accepted, for prize-collection problems.