Background
#1635 split the C++ libraries into libcuopt-client, libcuopt-mathopt and libcuopt-routing, so a routing-only install no longer pulls cudss, nccl or nvjitlink. That work is done and on main.
The Python side did not follow. cuopt is still one wheel covering both engines, so pip install cuopt for a VRP-only user still installs libcuopt-mathopt and its CUDA math libraries -- roughly 1.2 GB of uncompressed on-disk libraries that are never called. The per-solver Python packages were part of #1635's original plan and are the half that remains.
The split is already latent in the tree
The Python package maps almost one-to-one onto the C++ components, and the two engines are already independent.
No cross-imports. Nothing under cuopt/routing or cuopt/distance_engine imports or cimports cuopt.linear_programming, and nothing under cuopt/linear_programming reaches into cuopt.routing. This mirrors the C++ libraries, where readelf -d shows no DT_NEEDED between the two engines.
Each directory links only what it needs:
| Directory |
Linked libraries |
routing/ |
cuopt::routing, cuopt::client |
distance_engine/ |
cuopt::routing |
linear_programming/ |
cuopt::mathopt, cuopt::client |
grpc/client/ |
cuopt::client (plus rmm::rmm, for the exception handlers Cython emits) |
Partial installs are already anticipated. cuopt/__init__.py imports libcuopt inside a try/except ModuleNotFoundError and resolves linear_programming, routing, distance_engine and grpc lazily through __getattr__, with the stated reason being CPU-only hosts using remote solve. import cuopt therefore already succeeds when a submodule is absent; the import only fails on attribute access.
Tests already sit on the boundary, under tests/routing and tests/linear_programming.
Proposed boundaries
cuopt-vrp -- routing/, distance_engine/; depends on libcuopt-routing
cuopt-lp -- linear_programming/; depends on libcuopt-mathopt
cuopt -- metapackage depending on both, so existing installs are unaffected
Open questions
Where the shared code goes. cuopt/utilities/ (exception_handler.py, type_casting.py, utils.py) is imported by both halves, as is cuopt._version. These need a common home -- either a small shared package or, following the C++ boundary, the client package.
How cuopt becomes splittable at all. Shipping cuopt.routing and cuopt.linear_programming from different wheels means cuopt has to become a namespace package (PEP 420) or carry an equivalent shim. This is the main technical decision and worth settling before any code moves, since it affects every import path.
Relationship to the Python client. Separating the gRPC client into its own CUDA-free package is a related but distinct piece, discussed in #1635 (comment): grpc/client already links cuopt::client alone, and is blocked only by three symbols that DataModel and SolverSettings resolve from mathopt. If both splits happen, cuopt/grpc belongs with the client package rather than with either engine.
Whether the metapackage keeps the name. cuopt as a metapackage preserves pip install cuopt, but import cuopt.routing then depends on a package the user may not have installed, and today that surfaces as an AttributeError from __getattr__ rather than a useful message. Worth an explicit error that names the missing package.
Out of scope
cuopt-sh-client and cuopt_mcp are separate packaging axes; cuopt_mcp has its own issue in #1755.
Background
#1635 split the C++ libraries into
libcuopt-client,libcuopt-mathoptandlibcuopt-routing, so a routing-only install no longer pulls cudss, nccl or nvjitlink. That work is done and onmain.The Python side did not follow.
cuoptis still one wheel covering both engines, sopip install cuoptfor a VRP-only user still installslibcuopt-mathoptand its CUDA math libraries -- roughly 1.2 GB of uncompressed on-disk libraries that are never called. The per-solver Python packages were part of #1635's original plan and are the half that remains.The split is already latent in the tree
The Python package maps almost one-to-one onto the C++ components, and the two engines are already independent.
No cross-imports. Nothing under
cuopt/routingorcuopt/distance_engineimports or cimportscuopt.linear_programming, and nothing undercuopt/linear_programmingreaches intocuopt.routing. This mirrors the C++ libraries, wherereadelf -dshows noDT_NEEDEDbetween the two engines.Each directory links only what it needs:
routing/cuopt::routing,cuopt::clientdistance_engine/cuopt::routinglinear_programming/cuopt::mathopt,cuopt::clientgrpc/client/cuopt::client(plusrmm::rmm, for the exception handlers Cython emits)Partial installs are already anticipated.
cuopt/__init__.pyimportslibcuoptinside atry/except ModuleNotFoundErrorand resolveslinear_programming,routing,distance_engineandgrpclazily through__getattr__, with the stated reason being CPU-only hosts using remote solve.import cuopttherefore already succeeds when a submodule is absent; the import only fails on attribute access.Tests already sit on the boundary, under
tests/routingandtests/linear_programming.Proposed boundaries
cuopt-vrp--routing/,distance_engine/; depends onlibcuopt-routingcuopt-lp--linear_programming/; depends onlibcuopt-mathoptcuopt-- metapackage depending on both, so existing installs are unaffectedOpen questions
Where the shared code goes.
cuopt/utilities/(exception_handler.py,type_casting.py,utils.py) is imported by both halves, as iscuopt._version. These need a common home -- either a small shared package or, following the C++ boundary, the client package.How
cuoptbecomes splittable at all. Shippingcuopt.routingandcuopt.linear_programmingfrom different wheels meanscuopthas to become a namespace package (PEP 420) or carry an equivalent shim. This is the main technical decision and worth settling before any code moves, since it affects every import path.Relationship to the Python client. Separating the gRPC client into its own CUDA-free package is a related but distinct piece, discussed in #1635 (comment):
grpc/clientalready linkscuopt::clientalone, and is blocked only by three symbols thatDataModelandSolverSettingsresolve from mathopt. If both splits happen,cuopt/grpcbelongs with the client package rather than with either engine.Whether the metapackage keeps the name.
cuoptas a metapackage preservespip install cuopt, butimport cuopt.routingthen depends on a package the user may not have installed, and today that surfaces as anAttributeErrorfrom__getattr__rather than a useful message. Worth an explicit error that names the missing package.Out of scope
cuopt-sh-clientandcuopt_mcpare separate packaging axes;cuopt_mcphas its own issue in #1755.