Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@
"source": "./skills/cuopt-server-api-python",
"skills": "./",
"description": "cuOpt REST server — start server, endpoints, Python/curl client examples. Use when the user is deploying or calling the REST API."
},
{
"name": "skill-evolution",
"source": "./skills/skill-evolution",
"skills": "./",
"description": "After solving a non-trivial problem, detect generalizable learnings and propose skill updates. Always active."
}
]
}
2 changes: 1 addition & 1 deletion .claude/CLAUDE.md
8 changes: 8 additions & 0 deletions .cursor/rules/skill-evolution.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
description: Trigger skill evolution — after solving non-trivial problems, propose skill updates.
alwaysApply: true
---

# Skill Evolution

After resolving a non-trivial problem, read and follow `skills/skill-evolution/SKILL.md` to check whether the learning should be captured as a skill update.
17 changes: 16 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md — cuOpt AI Agent Entry Point

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.
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. Skills evolve through agent interactions — see `skills/skill-evolution/` for the evolution workflow.

> **🔒 MANDATORY — Security:** You MUST NOT install, upgrade, or modify packages. Provide the exact command for the user to run; they execute it. No exceptions.

Expand All @@ -11,6 +11,7 @@ AI agent skills for NVIDIA cuOpt optimization engine. Skills live in **`skills/`
### 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.
- `skills/skill-evolution/` — Skill evolution: after solving a non-trivial problem, propose skill updates to capture generalizable learnings.

### Common (concepts only; no API code)
- `skills/cuopt-installation-common/` — Install: system and environment requirements (concepts only; no install commands or interface)
Expand All @@ -32,6 +33,20 @@ AI agent skills for NVIDIA cuOpt optimization engine. Skills live in **`skills/`
- `skills/cuopt-qp-api-cli/`
- `skills/cuopt-server-api-python/` (deploy + client)

## Skill evolution

Skills improve through agent interactions. When you solve a problem and discover a generalizable fix, gotcha, or pattern, follow `skills/skill-evolution/SKILL.md` to propose a skill update. All proposals go through the same CI validation as manual edits.

### Post-correction hook (mandatory)

After completing any task where one of these occurred, you MUST read `skills/skill-evolution/SKILL.md` and evaluate whether to propose a skill update:
- The user corrected your output or approach
- Your initial solution failed and you had to retry with a different approach
- You discovered undocumented API behavior or a non-obvious gotcha
- You used a workaround not described in any existing skill

Finish solving the problem first, then evaluate. Not every correction warrants a proposal — only generalizable learnings that would help future interactions.

## Resources

### Documentation
Expand Down
59 changes: 0 additions & 59 deletions agents/AGENTS.md

This file was deleted.

1 change: 1 addition & 0 deletions agents/AGENTS.md
12 changes: 12 additions & 0 deletions ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ Similarly, for Conda package,


There are other scripts in this directory which are used to build and test the code and are also used in the workflows as utlities.

## Skill validation

These checks validate the agent skills under `skills/` (structure, content, and runnable assets).

### Existing tests

| Test | What it does | How to run |
|------|--------------|------------|
| **validate_skills.sh** | Every skill has `SKILL.md`, frontmatter (`name:`, `description:`), version in sync with `VERSION`, and is referenced in plugin/AGENTS config. | `./ci/utils/validate_skills.sh` (also runs in pre-commit). |
| **test_skills_assets.sh** | Runs executable assets: Python scripts, C programs, CLI via `cuopt_cli` on MPS files. | `./ci/test_skills_assets.sh` |
| **validate_developer_skills.sh** | Ensures developer SKILL.md files contain required sections and key workflow commands. | `./ci/utils/validate_developer_skills.sh` |
5 changes: 5 additions & 0 deletions skills/cuopt-user-rules/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ Do not bury the objective value only in the middle of a paragraph; it must appea

Provide diagnostic code snippets when helpful.

### Post-correction check (mandatory)
<!-- skill-evolution:start — post-correction hook -->
If the result required a correction, retry, or workaround to reach this point, you MUST evaluate the skill-evolution workflow (`skills/skill-evolution/SKILL.md`) before moving on. Do not skip this step.
<!-- skill-evolution:end -->

---

## Check Environment First
Expand Down
112 changes: 112 additions & 0 deletions skills/lp-milp-formulation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,115 @@ When the user gives **problem text**, classify every sentence and then summarize
Result: Parameters = 3 factories, 500 units target. Constraints = produce exactly 500 (implicit from "plans to produce"). Decisions = production allocation across factories, overtime amounts. Objective = minimize cost.

**Implicit-objective example:** A problem that asks to "determine the production plan" (or similar) and gives cost components (e.g. workshop, inspection, sales) but does not state "minimize" or "maximize" → **Objective is implicit: minimize total cost**. Always state it explicitly: "The objective is to minimize total cost."

---

<!-- skill-evolution:start — piecewise-linear with integer totals -->
## Piecewise-linear objectives with integer production

When modeling **concave piecewise-linear** profit/cost functions (e.g. decreasing marginal profit for bulk sales), the standard approach uses continuous segment variables with upper bounds equal to each segment's width. For a maximization with concave profit, the solver fills higher-profit segments first naturally.

**Gotcha:** If the quantity being produced is discrete (pieces, units, items), the **total production** variable must be **INTEGER**, even though segment variables can remain **CONTINUOUS**. Without this, the LP relaxation may yield a fractional total that produces a different (higher or lower) objective than the true integer optimum.

### Pattern

```
x_total — INTEGER (total production of a product)
s1, s2, … — CONTINUOUS (amount sold in each price segment, bounded by segment width)

Link: x_total = s1 + s2 + …
Resource constraints use x_total.
Objective uses segment variables × segment profit rates.
```
<!-- skill-evolution:end -->

<!-- skill-evolution:start — cutting stock waste = total area minus useful area -->
## Cutting stock / trim loss problems

In cutting stock problems, **waste area** includes both **trim loss** (unused width within each cutting pattern) and **over-production** (excess strips produced beyond demand). Minimizing only trim loss (waste width × length per pattern) ignores over-production and yields an incorrect objective.

### Correct objective

Since the total useful area demanded is a constant, minimizing waste is equivalent to minimizing total material area consumed:

```
minimize sum_j (roll_width_j × x_j)
```

where `x_j` is the length cut using pattern `j`. The waste area is then:

```
waste = total_material_area − required_useful_area
```

where `required_useful_area = sum_i (order_width_i × order_length_i)`.

### Gotcha

Using `sum_j (waste_width_j × x_j)` as the objective only captures trim loss — the unused strip within each pattern. It does **not** penalize over-production of an order. The solver will over-produce narrow orders to fill patterns efficiently, but that excess material is still waste. Always use total material area as the objective.
<!-- skill-evolution:end -->
## Goal programming (preemptive / lexicographic)
<!-- skill-evolution:start — goal programming section -->

Goal programming optimizes multiple objectives in priority order. Implement it as **sequential solves** — one per priority level.

### Formulation pattern

1. **Hard constraints** — capacity limits, non-negativity, etc. These hold in every phase.
2. **Goal constraints** — for each goal, introduce deviation variables (d⁻ for underachievement, d⁺ for overachievement) and write an equality: `expression + d⁻ − d⁺ = target`.
3. **Solve sequentially by priority:**
- Phase 1: minimize (or maximize) the relevant deviation for the highest-priority goal.
- Phase k: fix all higher-priority deviations at their optimal values, then optimize priority k's deviation.

### Variable types in goal programming

Deviation variables (d⁻, d⁺) and slack/idle-time variables are always **continuous**. However, **decision variables must still be INTEGER when they represent discrete/countable quantities** (units produced, vehicles, workers, etc.). Do not let the presence of continuous deviation variables cause you to make all variables continuous — the integrality of decision variables directly affects feasibility and objective values.

---

<!-- skill-evolution:start — inventory capacity must bound stock-after-purchase -->
## Multi-period inventory / purchasing models

In problems with buying, selling, and warehouse capacity over multiple periods, decide which capacity constraints to include based on the problem's timing assumptions.

### Pattern

For each period *t* with inventory balance `stock[t] = stock[t-1] + buy[t] - sell[t]`:

- **End-of-period capacity** (variable bound): `stock[t] <= capacity` — always needed.
- **After-purchase capacity** (explicit constraint): `stock[t-1] + buy[t] <= capacity` — prevents buying more than the warehouse can hold before any sales occur within the period.

### When to include the after-purchase constraint

- **Include it** when the problem states or implies that purchases are received before sales happen within a period (sequential operations), or when the warehouse physically cannot exceed capacity at any instant.
- **Omit it** when buying and selling are concurrent within a period (common in textbook trading/inventory problems) and the capacity applies only to end-of-period stock. Many classic problems only constrain end-of-period inventory.

**Key interaction with the sell constraint:** If the model already has `sell[t] <= stock[t-1]` (grain bought this period cannot be sold this period), the model is bounded even without the after-purchase constraint. The sell constraint prevents unbounded buy-sell cycling. The after-purchase constraint is then an additional physical restriction, not a mathematical necessity.

**Default:** If the problem does not specify timing within a period, use **only** end-of-period capacity (`stock[t] <= capacity`). Add the after-purchase constraint only if the problem explicitly requires it.
<!-- skill-evolution:end -->

<!-- skill-evolution:start — blending with shared mixing tank (intermediate processing) -->
## Blending with shared mixing / intermediate processing

In some blending problems, a subset of raw materials must be **mixed together first** (e.g., in a mixing tank) before being allocated to different products. The resulting intermediate has a **uniform composition** — you cannot independently assign different raw materials to different products.

### Why the standard blending LP is wrong here

The standard blending LP uses variables `x[i][j]` (amount of raw material `i` in product `j`) and freely allocates each raw material to each product. When raw materials share a mixing step, the proportions of those raw materials must be **identical** in every product that receives the intermediate. This proportionality constraint is **bilinear** (`x[A,1]*x[B,2] = x[B,1]*x[A,2]`) and cannot be directly expressed in an LP.

### Linearization strategies

1. **Single-product allocation:** If analysis shows the intermediate is profitable in only one product, allocate all intermediate to that product (set intermediate allocation to other products to zero). The proportionality constraint becomes trivially satisfied. This is the most common case — check profitability of intermediate in each product before attempting a general split.

2. **Parametric over intermediate concentration:** Fix the sulfur/quality concentration of the intermediate as a parameter `σ`. For each fixed `σ`, the problem is a standard LP (intermediate becomes a virtual raw material with known properties). Solve for a grid of `σ` values or use the structure to find the optimum analytically.

3. **Scenario enumeration:** When only 2–3 products exist, enumerate which products receive the intermediate (all-to-A, all-to-B, split). For each scenario with a single recipient, the LP is standard. For split scenarios, use strategy 2.

### Profitability check

Before formulating, check whether using the intermediate in each product is profitable:
- Compare the **minimum cost per ton** of the intermediate (using cheapest feasible raw material mix) against each product's **selling price**.
- If `cost_intermediate > sell_price[j]` for some product `j`, the intermediate should not be allocated to product `j`. Raw material C (or other direct inputs) alone may also be unprofitable if `cost_C > sell_price[j]`.
- This analysis often eliminates the need for a bilinear split entirely.
<!-- skill-evolution:end -->
Loading
Loading