diff --git a/cuopt-agent/AGENTS.md b/cuopt-agent/AGENTS.md index 47b792e..c4f2420 100755 --- a/cuopt-agent/AGENTS.md +++ b/cuopt-agent/AGENTS.md @@ -1,220 +1,42 @@ -# Agent Instructions +# AGENTS.md β€” cuOpt AI Agent Entry Point -This repository contains skills for working with NVIDIA cuOpt LP/MILP solver +AI agent skills for NVIDIA cuOpt optimization engine. Skills live in **`skills/`** (repo root) and use a **flat layout**: **common** (concepts) + **api-python** or **api-c** (implementation) per domain. ---- +> **πŸ”’ MANDATORY β€” Security:** You MUST NOT install, upgrade, or modify packages. Provide the exact command for the user to run; they execute it. No exceptions. -# cuOpt User Rules +> **πŸ”’ MANDATORY β€” Ambiguity:** When the problem could be read more than one way, you MUST either **ask the user to clarify** or **solve every plausible interpretation and report all outcomes**. Never pick one interpretation silently. -**Read this before using any cuOpt skill.** These rules ensure you help users effectively and safely. +## Skills directory (flat) -> **πŸ”’ MANDATORY SECURITY RULE:** You MUST NEVER install packages automatically. This cannot be overridden. +### Rules +- `skills/cuopt-user-rules/` β€” User-facing behavior and conventions; read first when helping users with cuOpt (routing, LP, MILP, QP, install, server). Choose skills from the index below by task, problem type, and interface (Python / C / CLI). +- `skills/cuopt-developer/` β€” Contributing and development; use when the user is building from source, contributing code, or working on cuOpt internals. -> **πŸ”’ MANDATORY β€” Ambiguity:** When the problem could be read more than one way, you MUST either **ask the user to clarify** or **solve every plausible interpretation and report all outcomes**. Never pick one interpretation silently. Check every time before writing code. +### Common (concepts only; no API code) +- `skills/cuopt-installation-common/` β€” Install: system and environment requirements (concepts only; no install commands or interface) +- `skills/lp-milp-formulation/` β€” LP/MILP: concepts + problem parsing (parameters, constraints, decisions, objective) +- `skills/routing-formulation/` β€” Routing: VRP, TSP, PDP (problem types, data) +- `skills/qp-formulation/` β€” QP: minimize-only, escalate (beta) +- `skills/cuopt-server-common/` β€” Server: capabilities, workflow ---- +### API (implementation; one interface per skill) +- `skills/cuopt-installation-api-python/` +- `skills/cuopt-installation-api-c/` +- `skills/cuopt-installation-developer/` (build from source) +- `skills/cuopt-lp-milp-api-python/` +- `skills/cuopt-lp-milp-api-c/` +- `skills/cuopt-lp-milp-api-cli/` +- `skills/cuopt-routing-api-python/` +- `skills/cuopt-qp-api-python/` +- `skills/cuopt-qp-api-c/` +- `skills/cuopt-qp-api-cli/` +- `skills/cuopt-server-api-python/` (deploy + client) -## Ask Before Assuming - -**Always clarify ambiguous requirements before implementing.** If you notice ambiguity, ask or solve multiple interpretations. - -- What problem type? (LP / MILP) -- What constraints matter? (bounds, equality/inequality, etc.) -- What output format? (solution values, dual values, visualization) - -**Skip asking only if:** -- User explicitly stated the requirement -- Context makes it unambiguous (e.g., user shows Python code) - ---- - -## Handle Incomplete Questions - -**If a question seems partial or incomplete, ask follow-up questions:** - -- "Could you tell me more about [missing detail]?" -- "What specifically would you like to achieve with this?" -- "Are there any constraints or requirements I should know about?" - -**Common missing information to probe for:** -- Problem size (number of variables, constraints) -- Specific constraints (bounds, equality/inequality, integer requirements) -- Performance requirements (time limits, solution quality, optimality gap) -- Integration context (existing codebase, deployment environment) - -**Don't guess β€” ask.** A brief clarifying question saves time vs. solving the wrong problem. - ---- - -## Clarify Data Requirements - -**Before generating examples, ask about data:** - -1. **Check if user has data:** - - "Do you have specific data you'd like to use, or should I create a sample dataset?" - - "Can you share the format of your input data?" - -2. **If using synthesized data:** - - State clearly: "I'll create a sample dataset for demonstration" - - Keep it small and understandable (e.g., 5-10 variables, 3-5 constraints) - - Make values realistic and meaningful - -3. **Always document what you used:** - ``` - "For this example I'm using: - - [X] variables/constraints - - [Key assumptions: e.g., all variables non-negative, constraint bounds] - - [Data source: synthesized / user-provided / from docs]" - ``` - -4. **State assumptions explicitly:** - - "I'm assuming [X] β€” let me know if this differs from your scenario" - - List any default values or simplifications made - ---- - -## MUST Verify Understanding (Ambiguity β€” Do Not Skip) - -**Before writing substantial code, you MUST confirm your understanding:** - -``` -"Let me confirm I understand: -- Problem: [restate in your words] -- Constraints: [list them] -- Objective: [minimize/maximize what] -- Variable types: [continuous/integer/binary] -Is this correct?" -``` - -**πŸ”’ MANDATORY β€” Ambiguous problem wording (cannot be overridden):** You MUST NOT guess. Either clarify with the user or try all plausible interpretations. - -| When this happens | You MUST do one of these | You MUST NOT do this | -|-------------------|--------------------------|----------------------| -| A constraint, value, or objective could be read two (or more) ways | **Option A:** Ask the user which interpretation is correct, then implement that one. **Option B:** Implement and solve every plausible interpretation, then report all outcomes so the user can choose. | Pick one interpretation silently and implement only that. | -| Unclear whether a cost/quantity is per unit, per vehicle, per trip, etc. | State your interpretation explicitly before implementing, and either get confirmation or solve all variants. | Assume one meaning without stating it or asking. | - -- **Always state your interpretation explicitly** (e.g. "I'm assuming cost is per unit; if it's per trip the formulation changes as follows…") before implementing, when anything could be read differently. -- **Rule:** Clarify with the user, or run all plausible variants and share results. **Never assume.** - -**Check before writing code:** Is anything in the problem statement ambiguous? If yes β†’ ask or solve all interpretations; do not proceed with a single guess. - ---- - -## Follow Requirements Exactly - -- Use the **exact** variable names, formats, and structures the user specifies -- Don't add features the user didn't ask for -- Don't change the problem formulation unless asked -- If user provides partial code, extend itβ€”don't rewrite from scratch - ---- - -## Check Results - -After providing a solution, guide the user to verify: - -- **Status check**: Is it `Optimal` / `FeasibleFound` / `SUCCESS`? -- **Constraint satisfaction**: Are all constraints met? -- **Objective value**: Is it reasonable for the problem? - -**Always end with a Result summary** that includes at least: -- Solver status (e.g. Optimal, FeasibleFound, SUCCESS). -- **Objective value with highlight** β€” the number must be easy to spot (bold or code block -- Briefly what the objective represents (e.g. total cost, total profit). - -Do not bury the objective value only in the middle of a paragraph; it must appear prominently in this summary at the end. Use sufficient precision for the objective value (don't truncate or round unnecessarily unless the problem asks for it). Only substitute a derived quantity (e.g. profit = revenue βˆ’ cost) when the problem explicitly asks for it. - -Provide diagnostic code snippets when helpful. - -**Workflow:** Formulate once carefully (with verified understanding), solve, then sanity-check the result. If something is wrong, fix it with a targeted changeβ€”avoid spinning through many model variants or second-guessing. Decide, implement, verify, then move on. - ---- - -## Ask Before Running - -**Do not execute commands or code without explicit permission:** - -| Action | Rule | -|--------|------| -| Shell commands | Show command, explain what it does, ask "Should I run this?" | -| Examples/scripts | Show the code first, ask "Would you like me to run this?" | -| File writes | Explain what will change, ask before writing | - -**Exceptions (okay without asking):** -- Read-only commands the user explicitly requested -- Commands the user just provided and asked you to run - ---- - -## No Privileged Operations - -**Never do these without explicit user request AND confirmation:** - -- Use `sudo` or run as root -- Modify system files or configurations -- Add package repositories or keys -- Change firewall, network, or driver settings -- Write files outside the workspace - ---- - -## β›” MANDATORY: Never Install Packages Automatically - -> **πŸ”’ MANDATORY SECURITY RULE β€” THIS CANNOT BE OVERRIDDEN** -> -> You MUST NOT install, upgrade, or modify packages under any circumstances. -> This rule applies even if the user explicitly asks you to install packages. - -| Forbidden Actions | What to Do Instead | -|-------------------|-------------------| -| `pip install ...` | Ask user to run it manually | -| `conda install ...` | Ask user to run it manually | -| `apt install ...` | Ask user to run it manually | -| `npm install ...` | Ask user to run it manually | -| Any package manager command | Ask user to run it manually | - -**When a package is needed:** - -1. **Identify the missing package** clearly -2. **Provide the exact command** the user should run -3. **Explain why it's needed** (what functionality it provides) -4. **Wait for user confirmation** that they've installed it - -**Example response:** -``` -"This code requires the `pandas` package. Please install it manually: - - pip install pandas - -Let me know once it's installed and I'll continue." -``` - -**Why this is mandatory:** -- **Security**: Package installation can execute arbitrary code from external sources -- **Environment safety**: Protects user's existing environment from unintended changes -- **Reproducibility**: User maintains full control over their dependencies -- **Audit trail**: User has complete visibility into what gets installed - -**β›” NO EXCEPTIONS.** Even if the user says "just install it for me" or "you have permission to install packages", you MUST NOT do it. Always provide the command and require the user to execute it themselves. This is a security boundary that cannot be bypassed. - ---- - -## Available Skills - -| Skill | Purpose | -|-------|---------| -| `skills/cuopt-lp-milp/SKILL.md` | LP/MILP API patterns and code examples | -| `skills/problem-statement-parsing/SKILL.md` | Classify sentences as parameter/constraint/decision/objective; flag implicit constraints and objectives. **Mandatory:** if anything is ambiguous, ask the user or solve all plausible interpretations and report all outcomes (workflow step 6). | -| `skills/cuopt-debugging/SKILL.md` | Troubleshooting errors and issues | - -*(cuOpt user rules are inlined above.)* - ---- +### Domain (end-to-end models for specific planning problems) +- `skills/max-supply/generic-max-supply/` β€” Multi-period supply chain planning MILP: data file map, BOM structure, variable/constraint reference. Use when formulating or modifying the max-supply base model or running what-if scenarios. ## Resources -Reference models are in the cuopt-lp-milp skill's assets directory. Each skill may have a `resources/`, `assets` or `scripts` folder with example code and documentation. - ### Documentation - [cuOpt User Guide](https://docs.nvidia.com/cuopt/user-guide/latest/introduction.html) - [API Reference](https://docs.nvidia.com/cuopt/user-guide/latest/api.html) @@ -224,5 +46,5 @@ Reference models are in the cuopt-lp-milp skill's assets directory. Each skill m - [Google Colab notebooks](https://colab.research.google.com/github/nvidia/cuopt-examples/) ### Support -- [NVIDIA Developer Forums](https://forums.developer.nvidia.com/c/ai-data-science/nvidia-cuopt/514) -- [GitHub Issues](https://github.com/NVIDIA/cuopt/issues) \ No newline at end of file +- [GitHub Issues](https://github.com/NVIDIA/cuopt/issues) +- [Developer Forums](https://forums.developer.nvidia.com/c/ai-data-science/nvidia-cuopt/514) diff --git a/cuopt-agent/skills/max-supply/cuopt-debugging/SKILL.md b/cuopt-agent/skills/max-supply/cuopt-debugging/SKILL.md deleted file mode 100755 index 5ff5761..0000000 --- a/cuopt-agent/skills/max-supply/cuopt-debugging/SKILL.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -name: cuopt-debugging -description: Troubleshoot cuOpt LP/MILP problems including errors, wrong results, infeasible solutions, performance issues, and status codes. Use when the user says something isn't working, gets unexpected results, or needs help diagnosing issues. ---- - -# cuOpt Debugging Skill - -Diagnose and fix issues with cuOpt LP/MILP solutions, errors, and performance. - -## Before You Start: Required Questions - -**Ask these to understand the problem:** - -1. **What's the symptom?** - - Error message? - - Wrong/unexpected results? - - Empty solution? - - Performance too slow? - -2. **What's the status?** - - `problem.Status.name` β€” what value does it show? - -3. **Can you share?** - - The error message (exact text) - - The code that produces it - - Problem size (variables, constraints) - -## Quick Diagnosis by Symptom - -### "Solution is empty/None but status looks OK" - -**Most common cause: Wrong status string case** - -```python -# ❌ WRONG - "OPTIMAL" never matches, silently fails -if problem.Status.name == "OPTIMAL": - print(problem.ObjValue) # Never runs! - -# βœ… CORRECT - use PascalCase -if problem.Status.name in ["Optimal", "FeasibleFound"]: - print(problem.ObjValue) -``` - -**Diagnostic code:** -```python -print(f"Actual status: '{problem.Status.name}'") -print(f"Matches 'Optimal': {problem.Status.name == 'Optimal'}") -print(f"Matches 'OPTIMAL': {problem.Status.name == 'OPTIMAL'}") -``` - -### "Objective value is wrong/zero" - -**Check if variables are actually used:** -```python -for var in problem.getVariables(): - print(f"{var.VariableName} = {var.Value}") -print(f"Objective: {problem.ObjValue}") - -# Or with direct variable references -for var in [x, y, z]: - print(f"{var.VariableName}: {var.getValue()}") -``` - -**Common causes:** -- Constraints too restrictive (all zeros is feasible) -- Objective coefficients have wrong sign -- Wrong variable in objective - -### "Infeasible" status - -**For LP/MILP:** -```python -if problem.Status.name in ["PrimalInfeasible", "Infeasible"]: - print("Problem has no feasible solution") - # Review constraints for conflicts - for c in problem.getConstraints(): - print(f"{c.ConstraintName}") -``` - -**Common causes:** -- Conflicting constraints (x <= 5 AND x >= 10) -- Bounds too tight -- Missing a "slack" variable for soft constraints - -### "Integer variable has fractional value" - -```python -# Check how variable was defined -int_var = problem.addVariable( - lb=0, ub=10, - vtype=INTEGER, # Must be INTEGER, not CONTINUOUS - name="count" -) - -# Also check if status is actually optimal -if problem.Status.name == "FeasibleFound": - print("Warning: not fully optimal, may have fractional intermediate values") -``` - -### "Unbounded" status - -**Problem has no finite optimum:** -```python -if problem.Status.name in ["DualInfeasible", "Unbounded"]: - print("Problem is unbounded - objective can improve infinitely") -``` - -**Common causes:** -- Missing variable upper/lower bounds -- Constraint direction wrong (>= instead of <=) -- Missing constraints - -### "Maximum recursion depth exceeded" when building expressions - -Building large objectives or constraints with many chained `+` operations can hit Python recursion limits. Use **LinearExpression** instead: - -```python -from cuopt.linear_programming.problem import LinearExpression - -# Instead of: expr = c1*v1 + c2*v2 + ... + cn*vn (many terms) -vars_list = [v1, v2, v3, ...] -coeffs_list = [c1, c2, c3, ...] -expr = LinearExpression(vars_list, coeffs_list, constant=0.0) -problem.setObjective(expr, sense=MINIMIZE) -``` - -See the LP/MILP "Building large expressions" section and reference models in the project for examples. - -### OutOfMemoryError - -**Check problem size:** -```python -print(f"Variables: {len(problem.getVariables())}") -print(f"Constraints: {len(problem.getConstraints())}") -``` - -**Mitigations:** -- Reduce problem size -- Use sparse constraint matrix -- Set time limit to get partial solution - -## Status Code Reference - -### LP Status Values -| Status | Meaning | -|--------|---------| -| `Optimal` | Found optimal solution | -| `PrimalFeasible` | Found feasible but may not be optimal | -| `PrimalInfeasible` | No feasible solution exists | -| `DualInfeasible` | Problem is unbounded | -| `TimeLimit` | Stopped due to time limit | -| `IterationLimit` | Stopped due to iteration limit | -| `NumericalError` | Numerical issues encountered | -| `NoTermination` | Solver didn't converge | - -### MILP Status Values -| Status | Meaning | -|--------|---------| -| `Optimal` | Found optimal solution | -| `FeasibleFound` | Found feasible, within gap tolerance | -| `Infeasible` | No feasible solution exists | -| `Unbounded` | Problem is unbounded | -| `TimeLimit` | Stopped due to time limit | -| `NoTermination` | No solution found yet | - -## Performance Debugging - -### Slow LP/MILP Solve - -```python -settings = SolverSettings() -settings.set_parameter("log_to_console", 1) # See progress -settings.set_parameter("time_limit", 60) # Don't wait forever - -# For MILP, accept good-enough solution -settings.set_parameter("mip_relative_gap", 0.05) # 5% gap -``` - -### Check Solve Time - -```python -problem.solve(settings) -print(f"Solve time: {problem.SolveTime:.2f} seconds") -``` - -## Diagnostic Checklist - -``` -β–‘ Status checked with correct case (PascalCase)? -β–‘ All variables have correct vtype (INTEGER vs CONTINUOUS)? -β–‘ Constraint directions correct (<= vs >= vs ==)? -β–‘ Objective sense correct (MINIMIZE vs MAXIMIZE)? -β–‘ Variable bounds specified where needed? -``` - -## Diagnostic Code Snippets - -See [resources/diagnostic_snippets.md](resources/diagnostic_snippets.md) for copy-paste diagnostic code: -- Status checking -- Variable inspection -- Constraint analysis -- Memory and performance checks - -## When to Escalate - -File a GitHub issue if: -- Reproducible bug with minimal example -- Include: cuOpt version, CUDA version, error message, minimal repro code diff --git a/cuopt-agent/skills/max-supply/cuopt-debugging/resources/diagnostic_snippets.md b/cuopt-agent/skills/max-supply/cuopt-debugging/resources/diagnostic_snippets.md deleted file mode 100755 index 3ff524e..0000000 --- a/cuopt-agent/skills/max-supply/cuopt-debugging/resources/diagnostic_snippets.md +++ /dev/null @@ -1,191 +0,0 @@ -# Debugging: Diagnostic Snippets - -## LP/MILP Diagnostics - -### Check Status Properly - -```python -# Print actual status value -print(f"Status: '{problem.Status.name}'") - -# Common mistake: wrong case -print(f"== 'Optimal': {problem.Status.name == 'Optimal'}") # βœ… -print(f"== 'OPTIMAL': {problem.Status.name == 'OPTIMAL'}") # ❌ Always False -``` - -### Inspect Variables - -```python -# Check all variable values after solving -for var in problem.getVariables(): - print(f"{var.VariableName} = {var.Value}, ReducedCost = {var.ReducedCost}") - -# Or using getter methods -for var in problem.getVariables(): - print(f"{var.getVariableName()}: value={var.getValue()}") - -# Check variable bounds and type -for var in problem.getVariables(): - print(f"{var.VariableName}: LB={var.LB}, UB={var.UB}, Type={var.VariableType}") - -# Check if integer variables are actually integer -for var in problem.getVariables(): - if var.VariableType == INTEGER: - val = var.Value - is_int = abs(val - round(val)) < 1e-6 - print(f"{var.VariableName}: {val} (is_integer: {is_int})") -``` - -### Inspect Constraints - -```python -# Check constraint dual values (LP only) -for c in problem.getConstraints(): - print(f"{c.ConstraintName}: DualValue={c.DualValue}, Slack={c.Slack}") - -# Check constraint details -for c in problem.getConstraints(): - print(f"{c.getConstraintName()}: Sense={c.Sense}, RHS={c.RHS}") -``` - -### Check Problem Size - -```python -print(f"Variables: {len(problem.getVariables())}") -print(f"Constraints: {len(problem.getConstraints())}") -``` - -### Full Problem Summary - -```python -def print_problem_summary(problem): - """Print a summary of the optimization problem.""" - print("=== Problem Summary ===") - print(f"Variables: {len(problem.getVariables())}") - print(f"Constraints: {len(problem.getConstraints())}") - print(f"Status: {problem.Status.name}") - - if problem.Status.name in ["Optimal", "FeasibleFound", "PrimalFeasible"]: - print(f"Objective: {problem.ObjValue}") - print(f"Solve time: {problem.SolveTime:.4f}s") - - print("\n=== Variable Values ===") - for var in problem.getVariables(): - print(f" {var.VariableName} = {var.Value}") -``` - -## Infeasibility Diagnosis - -### Check for Conflicting Bounds - -```python -# Check if any variable has lb > ub -for var in problem.getVariables(): - if var.LB > var.UB: - print(f"ERROR: {var.VariableName} has LB={var.LB} > UB={var.UB}") -``` - -### Print All Constraints - -```python -# List all constraints for manual review -print("=== Constraints ===") -for c in problem.getConstraints(): - sense_str = {0: "<=", 1: ">=", 2: "=="}[c.Sense] - print(f" {c.ConstraintName}: ... {sense_str} {c.RHS}") -``` - -## Expression Building (avoid recursion depth) - -### Prefer LinearExpression for large expressions - -If you hit "maximum recursion depth exceeded" when building objectives or constraints with many terms, avoid chained `+` and use `LinearExpression`: - -```python -from cuopt.linear_programming.problem import LinearExpression - -vars_list = [x1, x2, x3] # your variables -coeffs_list = [1.0, 2.0, 3.0] -expr = LinearExpression(vars_list, coeffs_list, constant=0.0) -problem.addConstraint(expr <= 100) -# or problem.setObjective(expr, sense=MINIMIZE) -``` - -See reference models in this project's assets for full examples. - -## Memory Diagnostics - -### Check GPU Memory - -```python -import subprocess -result = subprocess.run(['nvidia-smi'], capture_output=True, text=True) -print(result.stdout) -``` - -### Estimate Problem Memory - -```python -# Rough estimate for LP/MILP -n_vars = len(problem.getVariables()) -n_constraints = len(problem.getConstraints()) - -# Very rough: constraint matrix dominates -# Assuming ~10 non-zeros per constraint average -nnz_estimate = n_constraints * 10 -memory_mb = (nnz_estimate * 8) / 1e6 # 8 bytes per double -print(f"Estimated constraint matrix: ~{memory_mb:.1f} MB") -``` - -## Performance Diagnostics - -### Time the Solve - -```python -import time - -start = time.time() -problem.solve(settings) -elapsed = time.time() - start -print(f"Wall clock time: {elapsed:.2f}s") -print(f"Solver reported time: {problem.SolveTime:.2f}s") -``` - -### Enable Solver Logging - -```python -from cuopt.linear_programming.solver_settings import SolverSettings - -settings = SolverSettings() -settings.set_parameter("log_to_console", 1) -``` - -### MILP Progress Monitoring - -```python -# For long MILP solves, use callbacks to monitor progress -from cuopt.linear_programming.internals import GetSolutionCallback - -class ProgressCallback(GetSolutionCallback): - def __init__(self): - super().__init__() - self.count = 0 - - def get_solution(self, solution, cost): - self.count += 1 - obj = cost.copy_to_host()[0] - print(f"Incumbent {self.count}: objective = {obj:.4f}") - -settings = SolverSettings() -callback = ProgressCallback() -settings.set_mip_callback(callback) -``` - ---- - -## Additional References - -| Topic | Resource | -|-------|----------| -| Troubleshooting guide | [NVIDIA cuOpt Docs](https://docs.nvidia.com/cuopt/user-guide/latest/troubleshooting.html) | -| LP/MILP problem | LP/MILP example scripts in the project | \ No newline at end of file