diff --git a/.ai/skills/cuopt.yaml b/.ai/skills/cuopt.yaml new file mode 100644 index 000000000..60ee68969 --- /dev/null +++ b/.ai/skills/cuopt.yaml @@ -0,0 +1,9 @@ +# cuOpt agent skill manifest (shim) +# +# Canonical location: +# - .github/.ai/skills/cuopt.yaml +# +# Many tools scan for `.ai/skills/*` at repo root; this file keeps those tools working. +manifest_version: 1 + +canonical: .github/.ai/skills/cuopt.yaml diff --git a/.github/.ai/skills/cuopt.yaml b/.github/.ai/skills/cuopt.yaml new file mode 100644 index 000000000..5cb35bbcb --- /dev/null +++ b/.github/.ai/skills/cuopt.yaml @@ -0,0 +1,85 @@ +# cuOpt agent skill manifest (machine-readable) +manifest_version: 1 + +name: cuOpt +vendor: NVIDIA +summary: GPU-accelerated optimization engine for Routing (TSP/VRP/PDP) and Math Optimization (LP/MILP/QP). + +capabilities: + routing: + problems: [TSP, VRP, PDP] + mathematical_optimization: + problems: [LP, MILP, QP] + notes: + - QP support exists in the Python API and is currently documented as beta. + +compute: + accelerator: NVIDIA_GPU + requirements: + - CUDA 12.0+ (or CUDA 13.0+ depending on package) + - NVIDIA driver compatible with CUDA runtime + - Compute Capability >= 7.0 (Volta+) + +interfaces: + - name: python + kind: library + supports: + routing: true + lp: true + milp: true + qp: true + packages: + - cuopt-cu12 + - cuopt-cu13 + docs: + - docs/cuopt/source/cuopt-python/quick-start.rst + - docs/cuopt/source/cuopt-python/routing/index.rst + - docs/cuopt/source/cuopt-python/lp-qp-milp/index.rst + + - name: c_api + kind: library + library: libcuopt + supports: + routing: false + lp: true + milp: true + qp: true + docs: + - docs/cuopt/source/cuopt-c/index.rst + + - name: server_rest + kind: service + protocol: HTTP + supports: + routing: true + lp: true + milp: true + qp: false + implementation: + - python/cuopt_server/cuopt_server/webserver.py + openapi: + spec_file: docs/cuopt/source/cuopt_spec.yaml + served_path: /cuopt.yaml + ui_paths: + - /cuopt/docs + - /cuopt/redoc + docs: + - docs/cuopt/source/cuopt-server/quick-start.rst + - docs/cuopt/source/cuopt-server/server-api/index.rst + - docs/cuopt/source/open-api.rst + +roles: + cuopt_user: + description: Use cuOpt to solve routing and math optimization problems without modifying cuOpt internals. + rules: + - .github/agents/cuopt-user.md + cuopt_developer: + description: Develop and maintain cuOpt (C++/CUDA/Python/server/docs/CI). + rules: + - .github/agents/cuopt-developer.md + - .github/AGENTS.md + +entrypoints: + human_router: + - .github/AGENTS.md + - AGENTS.md diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 51f799e19..04cdc3703 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,156 +1,15 @@ # AGENTS.md - AI Coding Agent Guidelines for cuOpt -> This file provides essential context for AI coding assistants (Codex, Cursor, GitHub Copilot, etc.) working with the NVIDIA cuOpt codebase. +This file is intentionally **minimal**. Choose a role and follow the matching rules: -> **For setup, building, testing, and contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).** +- **Using cuOpt (model + solve problems)**: `.github/agents/cuopt-user.md` +- **Developing cuOpt (changing this repo)**: `.github/agents/cuopt-developer.md` ---- +### Machine-readable skill manifest -## Project Overview +- **Primary**: `.github/.ai/skills/cuopt.yaml` +- **Root shim (compat)**: `.ai/skills/cuopt.yaml` -**cuOpt** is NVIDIA's GPU-accelerated optimization engine for: -- **Mixed Integer Linear Programming (MILP)** -- **Linear Programming (LP)** -- **Quadratic Programming (QP)** -- **Vehicle Routing Problems (VRP)** including TSP and PDP +### Canonical contribution guide -### Architecture - -``` -cuopt/ -├── cpp/ # Core C++ engine (libcuopt, libmps_parser) -│ ├── include/cuopt/ # Public C/C++ headers -│ ├── src/ # Implementation (CUDA kernels, algorithms) -│ └── tests/ # C++ unit tests (gtest) -├── python/ -│ ├── cuopt/ # Python bindings and routing API -│ ├── cuopt_server/ # REST API server -│ ├── cuopt_self_hosted/ # Self-hosted deployment utilities -│ └── libcuopt/ # Python wrapper for C library -├── ci/ # CI/CD scripts and Docker configurations -├── conda/ # Conda recipes and environment files -├── docs/ # Documentation source -├── datasets/ # Test datasets for LP, MIP, routing -└── notebooks/ # Example Jupyter notebooks -``` - -### Supported APIs - -| API Type | LP | MILP | QP | Routing | -|----------|:--:|:----:|:--:|:-------:| -| C API | ✓ | ✓ | ✓ | ✗ | -| C++ API | ✓ | ✓ | ✓ | ✓ | -| Python | ✓ | ✓ | ✓ | ✓ | -| Server | ✓ | ✓ | ✗ | ✓ | - ---- - -## Coding Style and Conventions - -### C++ Naming Conventions - -- **Base style**: `snake_case` for all names (except test cases: PascalCase) -- **Prefixes/Suffixes**: - - `d_` → device data variables (e.g., `d_locations_`) - - `h_` → host data variables (e.g., `h_data_`) - - `_t` → template type parameters (e.g., `i_t`, `value_t`) - - `_` → private member variables (e.g., `n_locations_`) - -```cpp -// Example naming pattern -template -class locations_t { - private: - i_t n_locations_{}; - i_t* d_locations_{}; // device pointer - i_t* h_locations_{}; // host pointer -}; -``` - -### File Extensions - -| Extension | Usage | -|-----------|-------| -| `.hpp` | C++ headers | -| `.cpp` | C++ source | -| `.cu` | CUDA C++ source (nvcc required) | -| `.cuh` | CUDA headers with device code | - -### Include Order - -1. Local headers -2. RAPIDS headers -3. Related libraries -4. Dependencies -5. STL - -### Python Style - -- Follow PEP 8 -- Use type hints where applicable -- Tests use `pytest` framework - -### Formatting - -- **C++**: Enforced by `clang-format` (config: `cpp/.clang-format`) -- **Python**: Enforced via pre-commit hooks -- See [CONTRIBUTING.md](../CONTRIBUTING.md) for pre-commit setup - ---- - -## Error Handling Patterns - -### Runtime Assertions - -```cpp -// Use CUOPT_EXPECTS for runtime checks -CUOPT_EXPECTS(lhs.type() == rhs.type(), "Column type mismatch"); - -// Use CUOPT_FAIL for unreachable code paths -CUOPT_FAIL("This code path should not be reached."); -``` - -### CUDA Error Checking - -```cpp -// Always wrap CUDA calls -RAFT_CUDA_TRY(cudaMemcpy(&dst, &src, num_bytes)); -``` - ---- - -## Memory Management Guidelines - -- **Never use raw `new`/`delete`** - Use RMM allocators -- **Prefer `rmm::device_uvector`** for device memory -- **All operations should be stream-ordered** - Accept `cuda_stream_view` -- **Views (`*_view` suffix) are non-owning** - Don't manage their lifetime - ---- - -## Key Files Reference - -| Purpose | Location | -|---------|----------| -| Main build script | `build.sh` | -| Dependencies | `dependencies.yaml` | -| C++ formatting | `cpp/.clang-format` | -| Conda environments | `conda/environments/` | -| Test data download | `datasets/get_test_data.sh` | -| CI configuration | `ci/` | -| Version info | `VERSION` | - ---- - -## Common Pitfalls - -| Problem | Solution | -|---------|----------| -| Cython changes not reflected | Rerun: `./build.sh cuopt` | -| Missing `nvcc` | Set `$CUDACXX` or add CUDA to `$PATH` | -| CUDA out of memory | Reduce problem size or use streaming | -| Slow debug library loading | Device symbols cause delay; use selectively | - ---- - -*For detailed setup, build instructions, testing workflows, debugging, and contribution guidelines, see [CONTRIBUTING.md](../CONTRIBUTING.md).* +- `CONTRIBUTING.md` diff --git a/.github/agents/cuopt-developer.md b/.github/agents/cuopt-developer.md new file mode 100644 index 000000000..e426d2828 --- /dev/null +++ b/.github/agents/cuopt-developer.md @@ -0,0 +1,184 @@ +# cuOpt engineering contract (cuopt_developer) + +You are modifying the **cuOpt codebase**. Your priorities are correctness, performance, compatibility, and minimal-risk diffs. + +If you only need to **use** cuOpt (not change it), switch to `cuopt_user` (`.github/agents/cuopt-user.md`). + +## Project overview (developer context) + +**cuOpt** is NVIDIA's GPU-accelerated optimization engine for: + +- **Mixed Integer Linear Programming (MILP)** +- **Linear Programming (LP)** +- **Quadratic Programming (QP)** +- **Vehicle Routing Problems (VRP)** including TSP and PDP + +### Architecture (high level) + +``` +cuopt/ +├── cpp/ # Core C++ engine (libcuopt, libmps_parser) +│ ├── include/cuopt/ # Public C/C++ headers +│ ├── src/ # Implementation (CUDA kernels, algorithms) +│ └── tests/ # C++ unit tests (gtest) +├── python/ +│ ├── cuopt/ # Python bindings and routing API +│ ├── cuopt_server/ # REST API server +│ ├── cuopt_self_hosted/ # Self-hosted deployment utilities +│ └── libcuopt/ # Python wrapper for C library +├── ci/ # CI/CD scripts and Docker configurations +├── conda/ # Conda recipes and environment files +├── docs/ # Documentation source +├── datasets/ # Test datasets for LP, MIP, routing +└── notebooks/ # Example Jupyter notebooks +``` + +### Supported APIs (at a glance) + +| API Type | LP | MILP | QP | Routing | +|----------|:--:|:----:|:--:|:-------:| +| C API | ✓ | ✓ | ✓ | ✗ | +| C++ API | (internal) | (internal) | (internal) | (internal) | +| Python | ✓ | ✓ | ✓ | ✓ | +| Server | ✓ | ✓ | ✗ | ✓ | + +## Canonical project docs (source of truth) + +- **Contributing / build / test / debugging**: `CONTRIBUTING.md` +- **CI scripts**: `ci/README.md` +- **Release/version scripts**: `ci/release/README.md` +- **Documentation build**: `docs/cuopt/README.md` + +## Safety rules for agents + +- **Minimal diffs**: change only what’s necessary; avoid drive-by refactors. +- **No mass reformatting**: don’t run formatters over unrelated code. +- **No API invention**: especially for routing / server schemas—align with `docs/cuopt/source/` + OpenAPI spec. +- **Don’t bypass CI**: don’t suggest skipping checks or using `--no-verify` unless explicitly required and approved. +- **CUDA/GPU hygiene**: keep operations stream-ordered, follow existing RAFT/RMM patterns, avoid raw `new`/`delete`. + +### ⚠️ Mandatory: test impact check (ask before finalizing a change) + +Before landing any behavioral change or new feature, **explicitly ask**: + +- **What scenarios must be covered?** (happy path, edge cases, failure modes, performance regressions) +- **What’s the expected behavior contract?** (inputs/outputs, errors, compatibility constraints) +- **Where should tests live?** (C++ gtests under `cpp/tests/`, Python `pytest` under `python/.../tests`, server tests, etc.) + +**Recommendation:** add or update at least one **unit test** that covers the new behavior so **CI prevents regressions**. If full coverage isn’t feasible, document what’s untested and why, and add the smallest meaningful regression test. + +### Security bar (commands & installs) + +- **Do not run shell commands by default**: Provide commands/instructions; only execute commands if the user explicitly asks you to run them. +- **No dependency installation by default**: Don’t run `pip/conda/apt/brew` installs unless explicitly requested/approved by the user. +- **No privileged/system changes**: Never use `sudo`, modify system config, add package repositories/keys, or change driver/CUDA/toolchain setup unless explicitly requested and the implications are clear. +- **Workspace-only file changes by default**: Only create/modify files inside the checked-out repo/workspace. If writing outside the repo is necessary (e.g., under `$HOME`), ask for explicit permission and explain exactly what will be written where. +- **Prefer safe, reversible changes**: Use local envs; pin versions for reproducibility; avoid “curl | bash”. + +## Before you commit (style + signoff) + +- **Run the same style checks CI runs**: + - `./ci/check_style.sh` + - Or run pre-commit directly: `pre-commit run --all-files --show-diff-on-failure` + - Details: `CONTRIBUTING.md` (Code Formatting / pre-commit) +- **Signed commits are required (DCO sign-off)**: + - Use `git commit -s ...` (or `--signoff`) + - Details: `CONTRIBUTING.md` (Signing Your Work) + +## Coding style and conventions (summary) + +### C++ naming conventions + +- **Base style**: `snake_case` for all names (except test cases: PascalCase) +- **Prefixes/Suffixes**: + - `d_` → device data variables (e.g., `d_locations_`) + - `h_` → host data variables (e.g., `h_data_`) + - `_t` → template type parameters (e.g., `i_t`, `value_t`) + - `_` → private member variables (e.g., `n_locations_`) + +### File extensions + +| Extension | Usage | +|-----------|-------| +| `.hpp` | C++ headers | +| `.cpp` | C++ source | +| `.cu` | CUDA C++ source (nvcc required) | +| `.cuh` | CUDA headers with device code | + +### Include order + +1. Local headers +2. RAPIDS headers +3. Related libraries +4. Dependencies +5. STL + +### Python style + +- Follow PEP 8 +- Use type hints where applicable +- Tests use `pytest` framework + +### Formatting + +- **C++**: Enforced by `clang-format` (config: `cpp/.clang-format`) +- **Python**: Enforced via pre-commit hooks +- See `CONTRIBUTING.md` for pre-commit setup + +## Error handling patterns + +### Runtime assertions + +- Use `CUOPT_EXPECTS` for runtime checks +- Use `CUOPT_FAIL` for unreachable code paths + +### CUDA error checking + +- Wrap CUDA calls (e.g., `RAFT_CUDA_TRY(...)`) + +## Memory management guidelines + +- **Never use raw `new`/`delete`** - use RMM allocators +- **Prefer `rmm::device_uvector`** for device memory +- **All operations should be stream-ordered** - accept `cuda_stream_view` +- **Views (`*_view` suffix) are non-owning** - don't manage their lifetime + +## Repo navigation (practical) + +- **C++/CUDA core**: `cpp/` (includes `libmps_parser`, `libcuopt`) +- **Python packages**: `python/` (`cuopt`, `libcuopt`, `cuopt_server`, `cuopt_self_hosted`) +- **Docs (Sphinx)**: `docs/cuopt/source/` +- **Datasets**: `datasets/` + +## Build & test (quick reference; defer details to CONTRIBUTING) + +- **Build**: `./build.sh` (supports building individual components; see `./build.sh --help`) +- **C++ tests**: `ctest --test-dir cpp/build` +- **Python tests**: `pytest -v python/cuopt/cuopt/tests` (dataset env vars may be required; see `CONTRIBUTING.md`) +- **Docs build**: `./build.sh docs` or `make html` under `docs/cuopt` + +## Release discipline + +- Do not change versioning/release files unless explicitly requested. +- Prefer changes that are forward-merge friendly with RAPIDS branching conventions (see `CONTRIBUTING.md`). + +## Key files reference + +| Purpose | Location | +|---------|----------| +| Main build script | `build.sh` | +| Dependencies | `dependencies.yaml` | +| C++ formatting | `cpp/.clang-format` | +| Conda environments | `conda/environments/` | +| Test data download | `datasets/get_test_data.sh` | +| CI configuration | `ci/` | +| Version info | `VERSION` | + +## Common pitfalls + +| Problem | Solution | +|---------|----------| +| Cython changes not reflected | Rerun: `./build.sh cuopt` | +| Missing `nvcc` | Set `$CUDACXX` or add CUDA to `$PATH` | +| CUDA out of memory | Reduce problem size or use streaming | +| Slow debug library loading | Device symbols cause delay; use selectively | diff --git a/.github/agents/cuopt-user.md b/.github/agents/cuopt-user.md new file mode 100644 index 000000000..84d5cb2ef --- /dev/null +++ b/.github/agents/cuopt-user.md @@ -0,0 +1,620 @@ +# cuOpt agent skill (cuopt_user) + +**Purpose:** Help users correctly use NVIDIA cuOpt as an end user (modeling, solving, integration), do **not** modify cuOpt internals unless explicitly asked; if you need to change cuOpt itself, switch to `cuopt_developer` (`.github/agents/cuopt-developer.md`). + +--- + +## Scope & safety rails (read first) + +This agent **assists users of cuOpt**, not cuOpt developers. +Canonical product documentation lives under `docs/cuopt/source/` (Sphinx). Prefer linking to and following those docs instead of guessing. + +--- + +## ⚠️ FIRST ACTION: Confirm the interface (mandatory unless explicit) + +**Before writing code, payloads, or implementation steps, confirm which cuOpt interface the user wants.** + +Ask the user something like: + +- **“Which interface are you using for cuOpt?”** + - **Python API** — scripts/notebooks/in-process integration + - **REST Server API** — services/microservices/production deployments + - **C API** — native C/C++ embedding + - **CLI** — quick terminal runs (typically from `.mps`) + +If your agent environment supports **multiple-choice questions**, use it. Otherwise, ask plainly in text. + +**Skip asking only if the interface is already unambiguous**, for example: + +- The user explicitly says “Python script/notebook”, “curl”, “REST endpoint”, “C API”, “cuopt_cli”, etc. +- The user provides code or payloads that clearly match one interface. +- The question is about a specific interface feature/doc path. + +--- + +## ⚠️ BEFORE WRITING CODE: Read the canonical example first (mandatory) + +After the interface is clear, **read a canonical example for that interface/problem type** and copy the pattern (imports, method names, payload structure). Do not guess API names. + +### Python API (LP/MILP/QP) agent-friendly examples (start here) + +- `.github/agents/resources/cuopt-user/python_examples.md` + +### Python API (LP/MILP/QP) canonical examples (source of truth) + +- **LP**: `docs/cuopt/source/cuopt-python/lp-qp-milp/examples/simple_lp_example.py` +- **MILP**: `docs/cuopt/source/cuopt-python/lp-qp-milp/examples/simple_milp_example.py` +- **QP (beta)**: `docs/cuopt/source/cuopt-python/lp-qp-milp/examples/simple_qp_example.py` + +### Routing examples + +- (Agent-friendly) `.github/agents/resources/cuopt-user/python_examples.md` +- (Canonical) `docs/cuopt/source/cuopt-python/routing/examples/smoke_test_example.sh` + +### REST Server API agent-friendly examples (start here) + +- `.github/agents/resources/cuopt-user/server_examples.md` + +### REST Server API canonical sources (source of truth) + +- **OpenAPI guide**: `docs/cuopt/source/open-api.rst` +- **OpenAPI spec**: `docs/cuopt/source/cuopt_spec.yaml` (treat as the schema source-of-truth) + +### C API agent-friendly examples (start here) + +- `.github/agents/resources/cuopt-user/c_api_examples.md` + +### C API canonical sources (source of truth) + +- C API docs: `docs/cuopt/source/cuopt-c/index.rst` +- C examples: `docs/cuopt/source/cuopt-c/lp-qp-milp/examples/` + +### CLI agent-friendly examples (start here) + +- `.github/agents/resources/cuopt-user/cli_examples.md` + +### CLI canonical sources (source of truth) + +- CLI docs: `docs/cuopt/source/cuopt-cli/index.rst` +- CLI examples: `docs/cuopt/source/cuopt-cli/cli-examples.rst` + +### Interface summary + +#### Link access note (important) + +- **If the agent has the repo checked out**: local paths like `docs/cuopt/source/...` are accessible and preferred. +- **If the agent only receives this file as context (no repo access)**: prefer **public docs** and **GitHub links**: + - Official docs: [cuOpt User Guide (latest)](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html) + - Source repo: [NVIDIA/cuopt](https://github.com/NVIDIA/cuopt) + - Examples/notebooks: [NVIDIA/cuopt-examples](https://github.com/NVIDIA/cuopt-examples) + - Issues: [NVIDIA/cuopt issues](https://github.com/NVIDIA/cuopt/issues) + +If you need an online link for any local path in this document, convert it with one of these templates: + +- **GitHub (view file)**: `https://github.com/NVIDIA/cuopt/blob/main/` +- **GitHub (raw file)**: `https://raw.githubusercontent.com/NVIDIA/cuopt/main/` + +Examples: + +- `docs/cuopt/source/open-api.rst` → `https://github.com/NVIDIA/cuopt/blob/main/docs/cuopt/source/open-api.rst` +- `.github/.ai/skills/cuopt.yaml` → `https://github.com/NVIDIA/cuopt/blob/main/.github/.ai/skills/cuopt.yaml` +- `docs/cuopt/source/cuopt-python/routing/examples/smoke_test_example.sh` → `https://raw.githubusercontent.com/NVIDIA/cuopt/main/docs/cuopt/source/cuopt-python/routing/examples/smoke_test_example.sh` + +```yaml +role: cuopt_user +scope: use_cuopt_only +do_not: + - modify_cuopt_source_or_schemas + - invent_apis_or_payload_fields +repo_base: + view: https://github.com/NVIDIA/cuopt/blob/main/ + raw: https://raw.githubusercontent.com/NVIDIA/cuopt/main/ +interfaces: + c_api: + supports: {routing: false, lp: true, milp: true, qp: true} + python: + supports: {routing: true, lp: true, milp: true, qp: true} + server_rest: + supports: {routing: true, lp: true, milp: true, qp: false} + openapi_served_path: /cuopt.yaml + cli: + supports: {routing: false, lp: true, milp: true, qp: false} + mps_note: + - MPS can also be used via C API, Python API examples and via the server local-file feature; CLI is not mandatory. +escalate_to: .github/agents/cuopt-developer.md +``` + +### What cuOpt solves + +- **Routing**: TSP / VRP / PDP (GPU-accelerated) +- **Math optimization**: **LP / MILP / QP** (QP is documented as beta for the Python API) + +### DO +- **Confirm the interface first** (Python API vs REST Server vs C API vs CLI) unless the user already made it explicit. +- Help users model, solve, and integrate optimization problems using **documented cuOpt interfaces** +- Choose the **correct interface** (C API, Python API, REST server, CLI) +- Follow official documentation and examples + +### DO NOT +- Modify cuOpt internals, solver logic, schemas, or source code +- Invent APIs, fields, endpoints, or solver behaviors +- Guess payload formats or method names + +### Security bar (commands & installs) + +- **Do not run shell commands by default**: Prefer instructions and copy-pastable commands; only execute commands if the user explicitly asks you to run them. +- **No package installs by default**: Do not `pip install` / `conda install` / `apt-get install` / `brew install` unless the user explicitly requests it (or explicitly approves after you propose it). +- **No privileged/system changes**: Never use `sudo`, edit system files, add repositories/keys, or change firewall/kernel/driver settings unless the user explicitly asks and understands the impact. +- **Workspace-only file changes by default**: Only create/modify files inside the checked-out repo/workspace. If writing outside the repo is necessary (e.g., under `$HOME`), ask for explicit permission and explain exactly what will be written where. +- **Minimize risk**: Prefer user-space/virtualenv/conda environments; prefer pinned versions; avoid “curl | bash” style install instructions. + +### SWITCH TO `cuopt_developer` IF: +- User asks to change solver behavior, internals, performance heuristics +- User asks to modify OpenAPI schema or cuOpt source +- User asks to add new endpoints or features + +--- + +## Interface selection (critical) + +**🚨 STOP: Confirm the interface first (do not assume Python by default).** + +If the user didn’t explicitly specify, ask: + +- “Do you want a Python API solution, a REST Server payload/workflow, a C API embedding example, or a CLI command?” + +Proceed only after the interface is clear. + +### Interface selection workflow (decision tree) + +START → Did the user specify the interface? + +- **YES** → Use the specified interface +- **NO** → Ask which interface (Python / REST Server / C API / CLI) → Then proceed + +### ⚠️ Terminology Warning: REST vs Python API + +| Concept | REST Server API | Python API | +|---------|----------------|------------| +| Jobs/Tasks | `task_data`, `task_locations` | `set_order_locations()` | +| Time windows | `task_time_windows` | `set_order_time_windows()` | +| Service times | `service_times` | `set_order_service_times()` | + +**The REST API uses "task" terminology. The Python API uses "order" terminology.** + +--- + +## QP : critical constraints (do not miss) + +- **QP is beta** (see `docs/cuopt/source/cuopt-python/lp-qp-milp/examples/simple_qp_example.py`) +- **Quadratic objectives must be MINIMIZE** (the solver rejects maximize for QP) + - **Workaround for maximization**: maximize \(f(x)\) by minimizing \(-f(x)\) +- **QP uses Barrier** internally (different from typical LP/MILP defaults) + +If a user hits an error like “Quadratic problems must be minimized”, it usually means they attempted a maximize sense with a quadratic objective. + +--- + +## Good vs bad agent behavior (interface selection) + +### ❌ Bad + +User: “Build a car rental application using cuOpt MILP.” +Agent: Immediately starts writing Python code (without confirming interface). + +### ✅ Good + +User: “Build a car rental application using cuOpt MILP.” +Agent: “Which interface do you want to use: Python API, REST Server API, C API, or CLI?” +User: “REST Server API.” +Agent: Proceeds with server deployment + request/solution workflow and validates payloads against OpenAPI. + +### Use C API when: +- User explicitly requests native integration +- User is embedding cuOpt into C/C++ systems +- **Do not** recommend the **C++ API** to end users (it is not documented and may change; see repo `README.md` note). + +➡ Use: + - C API header reference: `cpp/include/cuopt/linear_programming/cuopt_c.h` + - C overview: `docs/cuopt/source/cuopt-c/index.rst` + - C quickstart: `docs/cuopt/source/cuopt-c/quick-start.rst` + - C LP/QP/MILP API + examples: `docs/cuopt/source/cuopt-c/lp-qp-milp/index.rst` + +### Use Python API when: +- User gives equations, variables, constraints +- User wants to solve routing / LP / MILP / QP directly +- User wants in-process solving (scripts, notebooks) + +➡ Use: + - Quickstart: `docs/cuopt/source/cuopt-python/quick-start.rst` + - Routing API reference: + - `python/cuopt/cuopt/routing/vehicle_routing.py` + - `python/cuopt/cuopt/routing/assignment.py` + - `docs/cuopt/source/cuopt-python/routing/routing-api.rst` + - LP/MILP/QP API reference: + - `python/cuopt/cuopt/linear_programming/problem.py` + - `python/cuopt/cuopt/linear_programming/data_model/data_model.py` + - `python/cuopt/cuopt/linear_programming/solver_settings/solver_settings.py` + - `python/cuopt/cuopt/linear_programming/solver/solver.py` + - `docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-api.rst` + +### Use Server REST API when: +- User wants production deployment +- User asks for REST payloads or HTTP calls +- User wants asynchronous or remote solving + +➡ Use: + - Server source: `python/cuopt_server/cuopt_server/webserver.py` + - Server quickstart (includes curl smoke test): `docs/cuopt/source/cuopt-server/quick-start.rst` + - API overview: `docs/cuopt/source/cuopt-server/server-api/index.rst` + - OpenAPI reference (Swagger): `docs/cuopt/source/open-api.rst` + - OpenAPI spec exactly (`cuopt.yaml` / `cuopt_spec.yaml`) + +### Use CLI when: +- User wants **quick testing** / **research** / **reproducible debugging** from a terminal +- User wants to solve **LP/MILP from MPS files** without writing code + +➡ Use: + - CLI source: `cpp/cuopt_cli.cpp` + - CLI overview: `docs/cuopt/source/cuopt-cli/index.rst` + - CLI quickstart: `docs/cuopt/source/cuopt-cli/quick-start.rst` + - CLI examples: `docs/cuopt/source/cuopt-cli/cli-examples.rst` + +**Note on MPS inputs:** having an `.mps` file does **not** imply you must use the CLI. +Choose based on integration/deployment needs: + +- **CLI**: fastest local repro (LP/MILP from MPS) +- **C API**: native embedding; includes MPS-based examples under `docs/cuopt/source/cuopt-c/lp-qp-milp/examples/` +- **Server**: can use its local-file feature (see server docs/OpenAPI) when running a service + +--- + +## ⚠️ Status Checking (Critical for LP/MILP) + +**Status enum values use PascalCase, not ALL_CAPS.** + +| Correct | Wrong | +|---------|-------| +| `"Optimal"` | `"OPTIMAL"` | +| `"FeasibleFound"` | `"FEASIBLE"` | +| `"Infeasible"` | `"INFEASIBLE"` | + +**Always check status like this:** + +```python +# ✅ CORRECT - matches actual enum names +if problem.Status.name in ["Optimal", "FeasibleFound"]: + print(f"Solution: {problem.ObjValue}") + +# ✅ ALSO CORRECT - case-insensitive +if problem.Status.name.upper() == "OPTIMAL": + print(f"Solution: {problem.ObjValue}") + +# ❌ WRONG - will silently fail! +if problem.Status.name == "OPTIMAL": # Never matches + print(f"Solution: {problem.ObjValue}") +``` + +**LP status values:** `Optimal`, `NoTermination`, `NumericalError`, `PrimalInfeasible`, `DualInfeasible`, `IterationLimit`, `TimeLimit`, `PrimalFeasible` + +**MILP status values:** `Optimal`, `FeasibleFound`, `Infeasible`, `Unbounded`, `TimeLimit`, `NoTermination` + +--- + +## Installation (minimal) + +Pick **one** installation method and match it to your CUDA major version (cuOpt publishes CUDA-variant packages). + +### pip + +- **Python API**: + +```bash +# Simplest (latest compatible from the index): +# CUDA 13 +pip install --extra-index-url=https://pypi.nvidia.com cuopt-cu13 + +# CUDA 12 +pip install --extra-index-url=https://pypi.nvidia.com cuopt-cu12 + +# Recommended (reproducible; pin to the current major/minor release line): +# CUDA 13 +pip install --extra-index-url=https://pypi.nvidia.com 'cuopt-cu13==26.2.*' + +# CUDA 12 +pip install --extra-index-url=https://pypi.nvidia.com 'cuopt-cu12==26.2.*' +``` + +- **Server + thin client (self-hosted)**: + +```bash +# Simplest: +# CUDA 12 example +pip install --extra-index-url=https://pypi.nvidia.com \ + cuopt-server-cu12 cuopt-sh-client + +# Recommended (reproducible): +# CUDA 12 example +pip install --extra-index-url=https://pypi.nvidia.com \ + nvidia-cuda-runtime-cu12==12.9.* \ + cuopt-server-cu12==26.02.* cuopt-sh-client==26.02.* +``` + +### conda + +```bash +# Simplest: +# Python API +conda install -c rapidsai -c conda-forge -c nvidia cuopt + +# Server + thin client +conda install -c rapidsai -c conda-forge -c nvidia cuopt-server cuopt-sh-client + +# Recommended (reproducible): +# Python API +conda install -c rapidsai -c conda-forge -c nvidia cuopt=26.02.* cuda-version=26.02.* + +# Server + thin client +conda install -c rapidsai -c conda-forge -c nvidia cuopt-server=26.02.* cuopt-sh-client=26.02.* +``` + +### container + +```bash +docker pull nvidia/cuopt:latest-cuda12.9-py3.13 +docker run --gpus all -it --rm -p 8000:8000 -e CUOPT_SERVER_PORT=8000 nvidia/cuopt:latest-cuda12.9-py3.13 +``` + +For full, up-to-date installation instructions (including nightlies), see: + +- `docs/cuopt/source/cuopt-python/quick-start.rst` +- `docs/cuopt/source/cuopt-server/quick-start.rst` + +--- + +## C API Examples & Templates + +Use the C examples + Makefile under `docs/cuopt/source/cuopt-c/lp-qp-milp/examples/`. + +Long-form examples (kept out of this doc): + +- `.github/agents/resources/cuopt-user/c_api_examples.md` + +--- + +## Python API Examples & Templates + +Long-form examples (routing + LP/MILP/QP; kept out of this doc): + +- `.github/agents/resources/cuopt-user/python_examples.md` + +--- + +## Server REST API Examples & Templates + +Long-form examples (kept out of this doc): + +- `.github/agents/resources/cuopt-user/server_examples.md` + +--- + +## CLI Examples & Templates + +Long-form examples (kept out of this doc): + +- `.github/agents/resources/cuopt-user/cli_examples.md` + +--- + +## Debugging Checklist + +### Problem: Results are empty/None when status looks OK + +**Diagnosis:** +```python +# Check actual status string (case matters!) +print(f"Status: '{problem.Status.name}'") +print(f"Is 'Optimal'?: {problem.Status.name == 'Optimal'}") +print(f"Is 'OPTIMAL'?: {problem.Status.name == 'OPTIMAL'}") # Wrong case! +``` + +**Fix:** Use `status in ["Optimal", "FeasibleFound"]` not `status == "OPTIMAL"` + +### Problem: Objective near zero when expecting large value + +**Diagnosis:** +```python +# Check if variables are all zero +for var in [var1, var2, var3]: + print(f"{var.name}: {var.getValue()}") +print(f"ObjValue: {problem.ObjValue}") +``` + +**Common causes:** +- Model formulation error (constraints too restrictive) +- Objective coefficients have wrong sign +- "Do nothing" is optimal (check constraint logic) + +### Problem: Integer variables have fractional values + +**Diagnosis:** +```python +# Verify variable was defined as INTEGER +val = int_var.getValue() +print(f"Value: {val}, Is integer?: {abs(val - round(val)) < 1e-6}") +``` + +**Common causes:** +- Variable defined as `CONTINUOUS` instead of `INTEGER` +- Solver hit time limit before finding integer solution (check `FeasibleFound` vs `Optimal`) + +### Problem: Routing solution empty or status != 0 + +**Diagnosis:** +```python +print(f"Status: {solution.get_status()}") # 0=SUCCESS, 1=FAIL, 2=TIMEOUT, 3=EMPTY +print(f"Message: {solution.get_message()}") +print(f"Error: {solution.get_error_message()}") + +# Check for dropped/infeasible orders +infeasible = solution.get_infeasible_orders() +if len(infeasible) > 0: + print(f"Infeasible orders: {infeasible.to_list()}") +``` + +**Common causes:** +- Time windows too tight (order earliest > vehicle latest) +- Total demand exceeds total capacity +- Cost/time matrix dimensions don't match n_locations +- Missing `add_transit_time_matrix()` when using time windows + +### Problem: Server REST API returns 422 validation error + +**Diagnosis:** +- Check the `error` field in response for specific validation message +- Common issues: + - `transit_time_matrix_data` → should be `travel_time_matrix_data` + - `capacities` format: `[[cap_v1, cap_v2]]` not `[[cap_v1], [cap_v2]]` + - Missing required fields: `fleet_data`, `task_data` + +**Fix:** Compare payload against OpenAPI spec at `/cuopt.yaml` + +### Problem: OutOfMemoryError + +**Diagnosis:** +```python +# Check problem size +print(f"Variables: {problem.num_variables}") +print(f"Constraints: {problem.num_constraints}") +# For routing: +print(f"Locations: {n_locations}, Orders: {n_orders}, Fleet: {n_fleet}") +``` + +**Common causes:** +- Problem too large for GPU memory +- Dense constraint matrix (try sparse representation) +- Too many vehicles × locations in routing + +### Problem: cudf type casting warnings or errors + +**Diagnosis:** +```python +# Check dtypes before passing to cuOpt +print(f"cost_matrix dtype: {cost_matrix.dtypes}") +print(f"demand dtype: {demand.dtype}") +``` + +**Fix:** Explicitly cast to expected types: +```python +cost_matrix = cost_matrix.astype("float32") +demand = demand.astype("int32") +order_locations = order_locations.astype("int32") +``` + +### Problem: MPS file parsing fails + +**Diagnosis:** +```bash +# Check MPS file format +head -20 problem.mps +# Look for: NAME, ROWS, COLUMNS, RHS, BOUNDS, ENDATA sections +``` + +**Common causes:** +- Missing `ENDATA` marker +- Incorrect section order +- Invalid characters or encoding +- Integer markers (`'MARKER'`, `'INTORG'`, `'INTEND'`) malformed + +### Problem: Time windows make problem infeasible + +**Diagnosis:** +```python +# Check for impossible time windows +for i in range(len(order_earliest)): + if order_earliest[i] > order_latest[i]: + print(f"Order {i}: earliest {order_earliest[i]} > latest {order_latest[i]}") + +# Check vehicle can reach orders in time +for i in range(len(order_locations)): + loc = order_locations[i] + travel_time = transit_time_matrix[0][loc] # from depot + if travel_time > order_latest[i]: + print(f"Order {i}: unreachable (travel={travel_time}, latest={order_latest[i]})") +``` + +--- + +## Common user requests → action map + +| User asks | First action | Then | +|----------|--------| +| "Build an optimization app" | **Ask which interface** (Python / REST / C / CLI) | Implement in the chosen interface | +| "Embed cuOpt in C/C++ app" | Confirm they want **C API** | Use C API docs/examples | +| "Solve this routing problem" | Ask **Python vs REST** (unless explicit) | Use routing API / server payloads accordingly | +| "Solve this LP/MILP" | Ask **Python vs REST vs C vs CLI** (unless explicit) | Use the chosen interface | +| "Write a Python script to..." | Use **Python API** | Implement the script | +| "Give REST payload" / provides `curl` | Use **REST Server API** | Validate against OpenAPI spec | +| "I have MPS file" | Ask **CLI vs embedding/service** | CLI for quick repro **or** C API MPS examples **or** Server local-file feature | +| "422 / schema error" | Use **REST Server API** | Fix payload using OpenAPI spec | +| "Solver too slow" | Confirm interface + constraints | Adjust documented settings (time limits, gaps, etc.) | +| "Change solver logic" | Switch to `cuopt_developer` | Modify codebase per dev rules | + +--- + +## Solver settings (safe adjustments) + +Allowed: +- Time limit +- Gap tolerances (if documented) +- Verbosity / logging + +Not allowed: +- Changing heuristics +- Modifying internals +- Undocumented parameters + +--- + +## Data formats & performance + +- **Payload formats**: JSON is the default; msgpack/zlib are supported for some endpoints (see server docs/OpenAPI). +- **GPU constraints**: requires a supported NVIDIA GPU/driver/CUDA runtime; see the system requirements in the main README and docs. +- **Tuning**: use solver settings (e.g., time limits) and avoid unnecessary host↔device churn; follow the feature docs under `docs/cuopt/source/`. + +--- + +## Error handling (agent rules) + +- **Validation errors (HTTP 4xx)**: treat as schema/typing issues; consult OpenAPI spec and fix the request payload. +- **Server errors (HTTP 5xx)**: capture `reqId`, poll logs/status endpoints where applicable, and reproduce with the smallest request. +- **Never "paper over" errors** by changing schemas or endpoints—align with the documented API. +- **Debugging a failure**: search existing [GitHub Issues](https://github.com/NVIDIA/cuopt/issues) first (use exact error text + cuOpt/CUDA/driver versions). If no match, file a new issue with a minimal repro, expected vs actual behavior, environment details, and any logs/`reqId`. + +For common troubleshooting and known issues, see: + +- `docs/cuopt/source/faq.rst` +- `docs/cuopt/source/resources.rst` + +--- + +## Additional resources (when to use) + +- **Examples / notebooks**: [NVIDIA/cuopt-examples](https://github.com/NVIDIA/cuopt-examples) → runnable notebooks +- **Google Colab**: [cuopt-examples notebooks on Colab](https://colab.research.google.com/github/nvidia/cuopt-examples/) → runnable examples +- **Official docs**: [cuOpt User Guide](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html) → modeling correctness +- **Videos/tutorials**: [cuOpt examples and tutorials videos](https://docs.nvidia.com/cuopt/user-guide/latest/resources.html#cuopt-examples-and-tutorials-videos) → unclear behavior +- **Try in the cloud**: [NVIDIA Launchable](https://brev.nvidia.com/launchable/deploy?launchableID=env-2qIG6yjGKDtdMSjXHcuZX12mDNJ) → GPU environments +- **Support / questions**: [NVIDIA Developer Forums (cuOpt)](https://forums.developer.nvidia.com/c/ai-data-science/nvidia-cuopt/514) → unclear behavior +- **Bugs / feature requests**: [GitHub Issues](https://github.com/NVIDIA/cuopt/issues) → unclear behavior + +--- + +## Final agent rules (non-negotiable) + +- Never invent APIs +- Never assume undocumented behavior +- Always choose interface first +- Prefer correctness over speed +- When unsure → open docs or ask user to clarify diff --git a/.github/agents/resources/cuopt-user/README.md b/.github/agents/resources/cuopt-user/README.md new file mode 100644 index 000000000..15ec4a7aa --- /dev/null +++ b/.github/agents/resources/cuopt-user/README.md @@ -0,0 +1,12 @@ +# cuopt-user resources + +This folder contains **long-form examples** referenced by `.github/agents/cuopt-user.md`. + +They are kept out of the main agent doc to reduce prompt bloat in environments that eagerly ingest Markdown files. + +Contents: + +- `c_api_examples.md` — C API examples + build/run notes +- `python_examples.md` — Python API examples (routing + LP/MILP/QP) +- `server_examples.md` — REST server examples (start server, curl, Python `requests`) +- `cli_examples.md` — CLI examples (MPS creation + solve commands) diff --git a/.github/agents/resources/cuopt-user/c_api_examples.md b/.github/agents/resources/cuopt-user/c_api_examples.md new file mode 100644 index 000000000..f42dcd5d3 --- /dev/null +++ b/.github/agents/resources/cuopt-user/c_api_examples.md @@ -0,0 +1,245 @@ +# C API examples (cuOpt) + +## C API: Simple LP Example + +```c +/* + * Simple LP C API Example + * + * Solve: minimize -0.2*x1 + 0.1*x2 + * subject to 3.0*x1 + 4.0*x2 <= 5.4 + * 2.7*x1 + 10.1*x2 <= 4.9 + * x1, x2 >= 0 + * + * Expected: x1 = 1.8, x2 = 0.0, objective = -0.36 + */ +#include +#include +#include + +int main() { + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + + cuopt_int_t num_variables = 2; + cuopt_int_t num_constraints = 2; + + // Constraint matrix in CSR format + cuopt_int_t row_offsets[] = {0, 2, 4}; + cuopt_int_t column_indices[] = {0, 1, 0, 1}; + cuopt_float_t values[] = {3.0, 4.0, 2.7, 10.1}; + + // Objective coefficients: minimize -0.2*x1 + 0.1*x2 + cuopt_float_t objective_coefficients[] = {-0.2, 0.1}; + + // Constraint bounds (ranged form: lower <= Ax <= upper) + cuopt_float_t constraint_upper_bounds[] = {5.4, 4.9}; + cuopt_float_t constraint_lower_bounds[] = {-CUOPT_INFINITY, -CUOPT_INFINITY}; + + // Variable bounds: x1, x2 >= 0 + cuopt_float_t var_lower_bounds[] = {0.0, 0.0}; + cuopt_float_t var_upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + + // Variable types: both continuous + char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; + + cuopt_int_t status; + cuopt_float_t time; + cuopt_int_t termination_status; + cuopt_float_t objective_value; + + // Create the problem + status = cuOptCreateRangedProblem( + num_constraints, + num_variables, + CUOPT_MINIMIZE, + 0.0, // objective offset + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_lower_bounds, + constraint_upper_bounds, + var_lower_bounds, + var_upper_bounds, + variable_types, + &problem + ); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + return 1; + } + + // Create solver settings + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings: %d\n", status); + goto DONE; + } + + // Set solver parameters + cuOptSetFloatParameter(settings, CUOPT_ABSOLUTE_PRIMAL_TOLERANCE, 0.0001); + cuOptSetFloatParameter(settings, CUOPT_TIME_LIMIT, 60.0); + + // Solve the problem + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem: %d\n", status); + goto DONE; + } + + // Get and print results + cuOptGetSolveTime(solution, &time); + cuOptGetTerminationStatus(solution, &termination_status); + cuOptGetObjectiveValue(solution, &objective_value); + + printf("Termination status: %d\n", termination_status); + printf("Solve time: %f seconds\n", time); + printf("Objective value: %f\n", objective_value); + + // Get solution values + cuopt_float_t* solution_values = (cuopt_float_t*)malloc( + num_variables * sizeof(cuopt_float_t) + ); + cuOptGetPrimalSolution(solution, solution_values); + for (cuopt_int_t i = 0; i < num_variables; i++) { + printf("x%d = %f\n", i + 1, solution_values[i]); + } + free(solution_values); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + + return (status == CUOPT_SUCCESS) ? 0 : 1; +} +``` + +## C API: MILP Example (with integer variables) + +```c +/* + * Simple MILP C API Example + * + * Solve: minimize -0.2*x1 + 0.1*x2 + * subject to 3.0*x1 + 4.0*x2 <= 5.4 + * 2.7*x1 + 10.1*x2 <= 4.9 + * x1 integer, x2 continuous, both >= 0 + */ +#include +#include +#include + +int main() { + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + + cuopt_int_t num_variables = 2; + cuopt_int_t num_constraints = 2; + + // Constraint matrix in CSR format + cuopt_int_t row_offsets[] = {0, 2, 4}; + cuopt_int_t column_indices[] = {0, 1, 0, 1}; + cuopt_float_t values[] = {3.0, 4.0, 2.7, 10.1}; + + // Objective coefficients + cuopt_float_t objective_coefficients[] = {-0.2, 0.1}; + + // Constraint bounds + cuopt_float_t constraint_upper_bounds[] = {5.4, 4.9}; + cuopt_float_t constraint_lower_bounds[] = {-CUOPT_INFINITY, -CUOPT_INFINITY}; + + // Variable bounds + cuopt_float_t var_lower_bounds[] = {0.0, 0.0}; + cuopt_float_t var_upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + + // Variable types: x1 = INTEGER, x2 = CONTINUOUS + char variable_types[] = {CUOPT_INTEGER, CUOPT_CONTINUOUS}; + + cuopt_int_t status; + cuopt_float_t time; + cuopt_int_t termination_status; + cuopt_float_t objective_value; + + // Create the problem (same API, but with integer variable types) + status = cuOptCreateRangedProblem( + num_constraints, + num_variables, + CUOPT_MINIMIZE, + 0.0, + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_lower_bounds, + constraint_upper_bounds, + var_lower_bounds, + var_upper_bounds, + variable_types, + &problem + ); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + return 1; + } + + // Create solver settings + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) goto DONE; + + // Set MIP-specific parameters + cuOptSetFloatParameter(settings, CUOPT_MIP_ABSOLUTE_TOLERANCE, 0.0001); + cuOptSetFloatParameter(settings, CUOPT_MIP_RELATIVE_GAP, 0.01); // 1% gap + cuOptSetFloatParameter(settings, CUOPT_TIME_LIMIT, 120.0); + + // Solve + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) goto DONE; + + // Get results + cuOptGetSolveTime(solution, &time); + cuOptGetTerminationStatus(solution, &termination_status); + cuOptGetObjectiveValue(solution, &objective_value); + + printf("Termination status: %d\n", termination_status); + printf("Solve time: %f seconds\n", time); + printf("Objective value: %f\n", objective_value); + + cuopt_float_t* solution_values = malloc(num_variables * sizeof(cuopt_float_t)); + cuOptGetPrimalSolution(solution, solution_values); + printf("x1 (integer) = %f\n", solution_values[0]); + printf("x2 (continuous) = %f\n", solution_values[1]); + free(solution_values); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + + return (status == CUOPT_SUCCESS) ? 0 : 1; +} +``` + +## C API: Build & Run + +```bash +# Find include and library paths (adjust based on installation) +# If installed via conda: +export INCLUDE_PATH="${CONDA_PREFIX}/include" +export LIBCUOPT_LIBRARY_PATH="${CONDA_PREFIX}/lib" + +# Or find automatically: +# INCLUDE_PATH=$(find / -name "cuopt_c.h" -path "*/linear_programming/*" \ +# -printf "%h\n" | sed 's/\/linear_programming//' 2>/dev/null) +# LIBCUOPT_LIBRARY_PATH=$(dirname $(find / -name "libcuopt.so" 2>/dev/null)) + +# Compile +gcc -I ${INCLUDE_PATH} -L ${LIBCUOPT_LIBRARY_PATH} \ + -o simple_lp_example simple_lp_example.c -lcuopt + +# Run +LD_LIBRARY_PATH=${LIBCUOPT_LIBRARY_PATH}:$LD_LIBRARY_PATH ./simple_lp_example +``` diff --git a/.github/agents/resources/cuopt-user/cli_examples.md b/.github/agents/resources/cuopt-user/cli_examples.md new file mode 100644 index 000000000..020ca0046 --- /dev/null +++ b/.github/agents/resources/cuopt-user/cli_examples.md @@ -0,0 +1,106 @@ +# CLI examples (cuOpt) + +## CLI: LP from MPS File + +```bash +# Create sample LP problem in MPS format +cat > production.mps << 'EOF' +* Production Planning Problem +* maximize 40*chairs + 30*tables +* s.t. 2*chairs + 3*tables <= 240 (wood) +* 4*chairs + 2*tables <= 200 (labor) +NAME PRODUCTION +ROWS + N PROFIT + L WOOD + L LABOR +COLUMNS + CHAIRS PROFIT -40.0 + CHAIRS WOOD 2.0 + CHAIRS LABOR 4.0 + TABLES PROFIT -30.0 + TABLES WOOD 3.0 + TABLES LABOR 2.0 +RHS + RHS1 WOOD 240.0 + RHS1 LABOR 200.0 +ENDATA +EOF + +# Solve with cuopt_cli +cuopt_cli production.mps + +# Solve with options +cuopt_cli production.mps --time-limit 30 + +# Cleanup +rm -f production.mps +``` + +## CLI: MILP from MPS File + +```bash +# Create MILP problem (with integer variables) +cat > facility.mps << 'EOF' +* Facility location - simplified +* Binary variables for opening facilities +NAME FACILITY +ROWS + N COST + G DEMAND1 + L CAP1 + L CAP2 +COLUMNS + MARKER 'MARKER' 'INTORG' + OPEN1 COST 100.0 + OPEN1 CAP1 50.0 + OPEN2 COST 150.0 + OPEN2 CAP2 70.0 + MARKER 'MARKER' 'INTEND' + SHIP11 COST 5.0 + SHIP11 DEMAND1 1.0 + SHIP11 CAP1 -1.0 + SHIP21 COST 7.0 + SHIP21 DEMAND1 1.0 + SHIP21 CAP2 -1.0 +RHS + RHS1 DEMAND1 30.0 +BOUNDS + BV BND1 OPEN1 + BV BND1 OPEN2 + LO BND1 SHIP11 0.0 + LO BND1 SHIP21 0.0 +ENDATA +EOF + +# Solve MILP +cuopt_cli facility.mps --time-limit 60 --mip-relative-tolerance 0.01 + +# Cleanup +rm -f facility.mps +``` + +## CLI: Common Options + +```bash +# Show all options +cuopt_cli --help + +# Set time limit (seconds) +cuopt_cli problem.mps --time-limit 120 + +# Set MIP relative gap tolerance (for MILP, e.g., 0.1% = 0.001) +cuopt_cli problem.mps --mip-relative-tolerance 0.001 + +# Set MIP absolute tolerance (for MILP) +cuopt_cli problem.mps --mip-absolute-tolerance 0.0001 + +# Enable presolve +cuopt_cli problem.mps --presolve + +# Set iteration limit +cuopt_cli problem.mps --iteration-limit 10000 + +# Specify solver method (0=auto, 1=pdlp, 2=dual_simplex, 3=barrier, etc.) +cuopt_cli problem.mps --method 1 +``` diff --git a/.github/agents/resources/cuopt-user/python_examples.md b/.github/agents/resources/cuopt-user/python_examples.md new file mode 100644 index 000000000..ee2d4ee20 --- /dev/null +++ b/.github/agents/resources/cuopt-user/python_examples.md @@ -0,0 +1,381 @@ +# Python API examples (cuOpt) + +## Python: Routing with Time Windows & Capacities (VRP) + +```python +""" +Vehicle Routing Problem with: +- 1 depot (location 0) +- 5 customer locations (1-5) +- 2 vehicles with capacity 100 each +- Time windows for each location +- Demand at each customer +""" +import cudf +from cuopt import routing + +# Cost/distance matrix (6x6: depot + 5 customers) +cost_matrix = cudf.DataFrame([ + [0, 10, 15, 20, 25, 30], # From depot + [10, 0, 12, 18, 22, 28], # From customer 1 + [15, 12, 0, 10, 15, 20], # From customer 2 + [20, 18, 10, 0, 8, 15], # From customer 3 + [25, 22, 15, 8, 0, 10], # From customer 4 + [30, 28, 20, 15, 10, 0], # From customer 5 +], dtype="float32") + +# Also use as transit time matrix (same values for simplicity) +transit_time_matrix = cost_matrix.copy(deep=True) + +# Order data (customers 1-5) +order_locations = cudf.Series([1, 2, 3, 4, 5]) # Location indices for orders + +# Demand at each customer (single capacity dimension) +demand = cudf.Series([20, 30, 25, 15, 35], dtype="int32") # Units to deliver + +# Vehicle capacities (must match demand dimensions) +vehicle_capacity = cudf.Series([100, 100], dtype="int32") # Each vehicle can carry 100 units + +# Time windows for orders [earliest, latest] +order_earliest = cudf.Series([0, 10, 20, 0, 30], dtype="int32") +order_latest = cudf.Series([50, 60, 70, 80, 90], dtype="int32") + +# Service time at each customer +service_times = cudf.Series([5, 5, 5, 5, 5], dtype="int32") + +# Fleet configuration +n_fleet = 2 + +# Vehicle start/end locations (both start and return to depot) +vehicle_start = cudf.Series([0, 0], dtype="int32") +vehicle_end = cudf.Series([0, 0], dtype="int32") + +# Vehicle time windows (operating hours) +vehicle_earliest = cudf.Series([0, 0], dtype="int32") +vehicle_latest = cudf.Series([200, 200], dtype="int32") + +# Build the data model +dm = routing.DataModel( + n_locations=cost_matrix.shape[0], + n_fleet=n_fleet, + n_orders=len(order_locations) +) + +# Add matrices +dm.add_cost_matrix(cost_matrix) +dm.add_transit_time_matrix(transit_time_matrix) + +# Add order data +dm.set_order_locations(order_locations) +dm.set_order_time_windows(order_earliest, order_latest) +dm.set_order_service_times(service_times) + +# Add capacity dimension (name, demand_per_order, capacity_per_vehicle) +dm.add_capacity_dimension("weight", demand, vehicle_capacity) + +# Add fleet data +dm.set_vehicle_locations(vehicle_start, vehicle_end) +dm.set_vehicle_time_windows(vehicle_earliest, vehicle_latest) + +# Configure solver +ss = routing.SolverSettings() +ss.set_time_limit(10) # seconds + +# Solve +solution = routing.Solve(dm, ss) + +# Check solution status +print(f"Status: {solution.get_status()}") + +# Display routes +if solution.get_status() == 0: # Success + print("\n--- Solution Found ---") + solution.display_routes() + + # Get detailed route data + route_df = solution.get_route() + print("\nDetailed route data:") + print(route_df) + + # Get objective value (total cost) + print(f"\nTotal cost: {solution.get_total_objective()}") +else: + print("No feasible solution found (status != 0).") +``` + +## Python: Pickup and Delivery Problem (PDP) + +```python +""" +Pickup and Delivery Problem: +- Items must be picked up from one location and delivered to another +- Same vehicle must do both pickup and delivery +- Pickup must occur before delivery +""" +import cudf +from cuopt import routing + +# Cost matrix (depot + 4 locations) +cost_matrix = cudf.DataFrame([ + [0, 10, 20, 30, 40], + [10, 0, 15, 25, 35], + [20, 15, 0, 10, 20], + [30, 25, 10, 0, 15], + [40, 35, 20, 15, 0], +], dtype="float32") + +transit_time_matrix = cost_matrix.copy(deep=True) + +n_fleet = 2 +n_orders = 4 # 2 pickup-delivery pairs = 4 orders + +# Orders: pickup at loc 1 -> deliver at loc 2, pickup at loc 3 -> deliver at loc 4 +order_locations = cudf.Series([1, 2, 3, 4]) + +# Pickup and delivery pairs (indices into order array) +# Order 0 (pickup) pairs with Order 1 (delivery) +# Order 2 (pickup) pairs with Order 3 (delivery) +pickup_indices = cudf.Series([0, 2]) +delivery_indices = cudf.Series([1, 3]) + +# Demand: positive for pickup, negative for delivery (must sum to 0 per pair) +demand = cudf.Series([10, -10, 15, -15], dtype="int32") +vehicle_capacity = cudf.Series([50, 50], dtype="int32") + +# Build model +dm = routing.DataModel( + n_locations=cost_matrix.shape[0], + n_fleet=n_fleet, + n_orders=n_orders +) + +dm.add_cost_matrix(cost_matrix) +dm.add_transit_time_matrix(transit_time_matrix) +dm.set_order_locations(order_locations) + +# Add capacity dimension +dm.add_capacity_dimension("load", demand, vehicle_capacity) + +# Set pickup and delivery constraints +dm.set_pickup_delivery_pairs(pickup_indices, delivery_indices) + +# Fleet setup +dm.set_vehicle_locations( + cudf.Series([0, 0]), # Start at depot + cudf.Series([0, 0]) # Return to depot +) + +# Solve +ss = routing.SolverSettings() +ss.set_time_limit(10) +solution = routing.Solve(dm, ss) + +print(f"Status: {solution.get_status()}") +if solution.get_status() == 0: + solution.display_routes() +``` + +## Python: Linear Programming (LP) + +```python +""" +Production Planning LP: + maximize 40*chairs + 30*tables (profit) + subject to 2*chairs + 3*tables <= 240 (wood constraint) + 4*chairs + 2*tables <= 200 (labor constraint) + chairs, tables >= 0 +""" +from cuopt.linear_programming.problem import Problem, CONTINUOUS, MAXIMIZE +from cuopt.linear_programming.solver_settings import SolverSettings + +# Create problem +problem = Problem("ProductionPlanning") + +# Decision variables (continuous, non-negative) +chairs = problem.addVariable(lb=0, vtype=CONTINUOUS, name="chairs") +tables = problem.addVariable(lb=0, vtype=CONTINUOUS, name="tables") + +# Constraints +problem.addConstraint(2 * chairs + 3 * tables <= 240, name="wood") +problem.addConstraint(4 * chairs + 2 * tables <= 200, name="labor") + +# Objective: maximize profit +problem.setObjective(40 * chairs + 30 * tables, sense=MAXIMIZE) + +# Solver settings +settings = SolverSettings() +settings.set_parameter("time_limit", 60) +settings.set_parameter("log_to_console", 1) + +# Solve +problem.solve(settings) + +# Check status and extract results +status = problem.Status.name +print(f"Status: {status}") + +if status in ["Optimal", "PrimalFeasible"]: + print(f"Optimal profit: ${problem.ObjValue:.2f}") + print(f"Chairs to produce: {chairs.getValue():.1f}") + print(f"Tables to produce: {tables.getValue():.1f}") + + # Get dual values (shadow prices) + wood_constraint = problem.getConstraint("wood") + labor_constraint = problem.getConstraint("labor") + print(f"\nShadow price (wood): ${wood_constraint.DualValue:.2f} per unit") + print(f"Shadow price (labor): ${labor_constraint.DualValue:.2f} per unit") +else: + print(f"No optimal solution found. Status: {status}") +``` + +## Python: Mixed-Integer Linear Programming (MILP) + +```python +""" +Facility Location MILP: +- Decide which warehouses to open (binary) +- Assign customers to open warehouses +- Minimize fixed costs + transportation costs +""" +from cuopt.linear_programming.problem import ( + Problem, CONTINUOUS, INTEGER, MINIMIZE +) +from cuopt.linear_programming.solver_settings import SolverSettings + +# Problem data +warehouses = ["W1", "W2", "W3"] +customers = ["C1", "C2", "C3", "C4"] + +fixed_costs = {"W1": 100, "W2": 150, "W3": 120} +capacities = {"W1": 50, "W2": 70, "W3": 60} +demands = {"C1": 20, "C2": 25, "C3": 15, "C4": 30} + +# Transportation cost from warehouse to customer +transport_cost = { + ("W1", "C1"): 5, ("W1", "C2"): 8, ("W1", "C3"): 6, ("W1", "C4"): 10, + ("W2", "C1"): 7, ("W2", "C2"): 4, ("W2", "C3"): 9, ("W2", "C4"): 5, + ("W3", "C1"): 6, ("W3", "C2"): 7, ("W3", "C3"): 4, ("W3", "C4"): 8, +} + +# Create problem +problem = Problem("FacilityLocation") + +# Decision variables +# y[w] = 1 if warehouse w is open (binary: INTEGER with bounds 0-1) +y = {w: problem.addVariable(lb=0, ub=1, vtype=INTEGER, name=f"open_{w}") for w in warehouses} + +# x[w,c] = units shipped from w to c +x = { + (w, c): problem.addVariable(lb=0, vtype=CONTINUOUS, name=f"ship_{w}_{c}") + for w in warehouses for c in customers +} + +# Objective: minimize fixed + transportation costs +problem.setObjective( + sum(fixed_costs[w] * y[w] for w in warehouses) + + sum(transport_cost[w, c] * x[w, c] for w in warehouses for c in customers), + sense=MINIMIZE +) + +# Constraints +# 1. Meet customer demand +for c in customers: + problem.addConstraint( + sum(x[w, c] for w in warehouses) == demands[c], + name=f"demand_{c}" + ) + +# 2. Respect warehouse capacity (only if open) +for w in warehouses: + problem.addConstraint( + sum(x[w, c] for c in customers) <= capacities[w] * y[w], + name=f"capacity_{w}" + ) + +# Solver settings +settings = SolverSettings() +settings.set_parameter("time_limit", 120) +settings.set_parameter("mip_relative_gap", 0.01) # 1% optimality gap + +# Solve +problem.solve(settings) + +# Check status and extract results +status = problem.Status.name +print(f"Status: {status}") + +if status in ["Optimal", "FeasibleFound"]: + print(f"Total cost: ${problem.ObjValue:.2f}") + print("\nOpen warehouses:") + for w in warehouses: + if y[w].getValue() > 0.5: + print(f" {w} (fixed cost: ${fixed_costs[w]})") + + print("\nShipments:") + for w in warehouses: + for c in customers: + shipped = x[w, c].getValue() + if shipped > 0.01: + print(f" {w} -> {c}: {shipped:.1f} units") +else: + print(f"No solution found. Status: {status}") +``` + +## Python: Quadratic Programming (QP) - Beta + +```python +""" +Portfolio Optimization QP (more complex): + minimize x^T * Q * x (variance/risk) + subject to sum(x) = 1 (fully invested) + r^T * x >= target (minimum return) + x >= 0 (no short selling) +""" +from cuopt.linear_programming.problem import Problem, CONTINUOUS, MINIMIZE +from cuopt.linear_programming.solver_settings import SolverSettings + +# Create problem +problem = Problem("PortfolioOptimization") + +# Decision variables: portfolio weights for 3 assets +x1 = problem.addVariable(lb=0.0, ub=1.0, vtype=CONTINUOUS, name="stock_a") +x2 = problem.addVariable(lb=0.0, ub=1.0, vtype=CONTINUOUS, name="stock_b") +x3 = problem.addVariable(lb=0.0, ub=1.0, vtype=CONTINUOUS, name="stock_c") + +# Expected returns +r1, r2, r3 = 0.12, 0.08, 0.05 # 12%, 8%, 5% +target_return = 0.08 + +# Quadratic objective: minimize variance = x^T * Q * x +# Expand: 0.04*x1^2 + 0.02*x2^2 + 0.01*x3^2 + 2*0.01*x1*x2 + 2*0.005*x1*x3 + 2*0.008*x2*x3 +problem.setObjective( + 0.04 * x1 * x1 + 0.02 * x2 * x2 + 0.01 * x3 * x3 + + 0.02 * x1 * x2 + 0.01 * x1 * x3 + 0.016 * x2 * x3, + sense=MINIMIZE +) + +# Constraints +problem.addConstraint(x1 + x2 + x3 == 1, name="fully_invested") +problem.addConstraint(r1 * x1 + r2 * x2 + r3 * x3 >= target_return, name="min_return") + +# Solve +settings = SolverSettings() +settings.set_parameter("time_limit", 60) +problem.solve(settings) + +# Check status and extract results +status = problem.Status.name +print(f"Status: {status}") + +if status in ["Optimal", "PrimalFeasible"]: + print(f"Portfolio variance: {problem.ObjValue:.6f}") + print(f"Portfolio std dev: {problem.ObjValue**0.5:.4f}") + print(f"\nOptimal allocation:") + print(f" Stock A: {x1.getValue()*100:.2f}%") + print(f" Stock B: {x2.getValue()*100:.2f}%") + print(f" Stock C: {x3.getValue()*100:.2f}%") + exp_return = r1*x1.getValue() + r2*x2.getValue() + r3*x3.getValue() + print(f"\nExpected return: {exp_return*100:.2f}%") +else: + print(f"No optimal solution found. Status: {status}") +``` diff --git a/.github/agents/resources/cuopt-user/server_examples.md b/.github/agents/resources/cuopt-user/server_examples.md new file mode 100644 index 000000000..4cf4147ba --- /dev/null +++ b/.github/agents/resources/cuopt-user/server_examples.md @@ -0,0 +1,199 @@ +# Server REST API examples (cuOpt) + +## Server: Start the Server + +```bash +# Start server in background +python3 -m cuopt_server.cuopt_service --ip 0.0.0.0 --port 8000 & +SERVER_PID=$! + +# Wait for server to be ready +sleep 5 +curl -fsS "http://localhost:8000/cuopt/health" +``` + +## Server: Routing Request (curl) + +```bash +# Submit a VRP request +REQID=$(curl -s --location "http://localhost:8000/cuopt/request" \ + --header 'Content-Type: application/json' \ + --header "CLIENT-VERSION: custom" \ + -d '{ + "cost_matrix_data": { + "data": { + "0": [ + [0, 10, 15, 20], + [10, 0, 12, 18], + [15, 12, 0, 10], + [20, 18, 10, 0] + ] + } + }, + "travel_time_matrix_data": { + "data": { + "0": [ + [0, 10, 15, 20], + [10, 0, 12, 18], + [15, 12, 0, 10], + [20, 18, 10, 0] + ] + } + }, + "task_data": { + "task_locations": [1, 2, 3], + "demand": [[10, 15, 20]], + "task_time_windows": [[0, 100], [10, 80], [20, 90]], + "service_times": [5, 5, 5] + }, + "fleet_data": { + "vehicle_locations": [[0, 0], [0, 0]], + "capacities": [[50, 50]], + "vehicle_time_windows": [[0, 200], [0, 200]] + }, + "solver_config": { + "time_limit": 5 + } + }' | jq -r '.reqId') + +echo "Request ID: $REQID" + +# Poll for solution +sleep 2 +curl -s --location "http://localhost:8000/cuopt/solution/${REQID}" \ + --header 'Content-Type: application/json' \ + --header "CLIENT-VERSION: custom" | jq . +``` + +## Server: Routing Request (Python with requests) + +```python +import requests +import time + +SERVER_URL = "http://localhost:8000" +HEADERS = { + "Content-Type": "application/json", + "CLIENT-VERSION": "custom" +} + +# VRP problem data +payload = { + "cost_matrix_data": { + "data": { + "0": [ + [0, 10, 15, 20, 25], + [10, 0, 12, 18, 22], + [15, 12, 0, 10, 15], + [20, 18, 10, 0, 8], + [25, 22, 15, 8, 0] + ] + } + }, + "travel_time_matrix_data": { + "data": { + "0": [ + [0, 10, 15, 20, 25], + [10, 0, 12, 18, 22], + [15, 12, 0, 10, 15], + [20, 18, 10, 0, 8], + [25, 22, 15, 8, 0] + ] + } + }, + "task_data": { + "task_locations": [1, 2, 3, 4], + "demand": [[20, 30, 25, 15]], + "task_time_windows": [[0, 50], [10, 60], [20, 70], [0, 80]], + "service_times": [5, 5, 5, 5] + }, + "fleet_data": { + "vehicle_locations": [[0, 0], [0, 0]], + "capacities": [[100, 100]], + "vehicle_time_windows": [[0, 200], [0, 200]] + }, + "solver_config": { + "time_limit": 10 + } +} + +# Submit request +response = requests.post( + f"{SERVER_URL}/cuopt/request", + json=payload, + headers=HEADERS +) +response.raise_for_status() +req_id = response.json()["reqId"] +print(f"Request submitted: {req_id}") + +# Poll for solution +max_attempts = 30 +for attempt in range(max_attempts): + response = requests.get( + f"{SERVER_URL}/cuopt/solution/{req_id}", + headers=HEADERS + ) + result = response.json() + + if "response" in result: + solver_response = result["response"].get("solver_response", {}) + print(f"\nSolution found!") + print(f"Status: {solver_response.get('status', 'N/A')}") + print(f"Cost: {solver_response.get('solution_cost', 'N/A')}") + + if "vehicle_data" in solver_response: + for vid, vdata in solver_response["vehicle_data"].items(): + route = vdata.get("route", []) + print(f"Vehicle {vid}: {' -> '.join(map(str, route))}") + break + else: + print(f"Waiting... (attempt {attempt + 1})") + time.sleep(1) +``` + +## Server: LP/MILP Request + +```bash +# Submit LP problem via REST +# Production Planning: maximize 40*chairs + 30*tables +# subject to: 2*chairs + 3*tables <= 240 (wood) +# 4*chairs + 2*tables <= 200 (labor) +# chairs, tables >= 0 +REQID=$(curl -s --location "http://localhost:8000/cuopt/request" \ + --header 'Content-Type: application/json' \ + --header "CLIENT-VERSION: custom" \ + -d '{ + "csr_constraint_matrix": { + "offsets": [0, 2, 4], + "indices": [0, 1, 0, 1], + "values": [2.0, 3.0, 4.0, 2.0] + }, + "constraint_bounds": { + "upper_bounds": [240.0, 200.0], + "lower_bounds": ["ninf", "ninf"] + }, + "objective_data": { + "coefficients": [40.0, 30.0], + "scalability_factor": 1.0, + "offset": 0.0 + }, + "variable_bounds": { + "upper_bounds": ["inf", "inf"], + "lower_bounds": [0.0, 0.0] + }, + "maximize": true, + "solver_config": { + "tolerances": {"optimality": 0.0001}, + "time_limit": 60 + } + }' | jq -r '.reqId') + +echo "Request ID: $REQID" + +# Get solution +sleep 2 +curl -s --location "http://localhost:8000/cuopt/solution/${REQID}" \ + --header 'Content-Type: application/json' \ + --header "CLIENT-VERSION: custom" | jq . +``` diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1cd7c9e43 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,7 @@ +# AGENTS.md (shim) + +The canonical AI-agent entrypoint for this repo is: + +- `.github/AGENTS.md` + +If you are a coding agent, start there.