# ModelBuilder

`ModelBuilder` 是公开建模入口。按问题需求选择建模方式见 [指南](../start/quickstart.md)。

本页按接口逐项给出用途、调用形式、参数、返回值和带注释示例。调用形式省略 Python 的关键字参数分隔符；除第一个业务输入外，可选参数建议按名称传入。

## 精确解支持矩阵

`ModelBuilder` 是统一建模入口，但精确解后端不是通用解释器。CP-SAT 和 MILP 只接收各自可降阶的 DAG 子集；如果模型包含某个后端不支持的接口，需要改用启发式、混合策略，或调整建模形式。

按后端查看支持清单，见 [精确求解器接口兼容性](exact-compatibility.md)。

标签含义：

| 标签 | 含义 |
|---|---|
| <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 启发式路径可评估或搜索该接口表达的模型语义。 |
| <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 当前 CP-SAT 精确路径可 lowering 到 `cp_sat`。 |
| <span class="solver-tag solver-tag--milp">MILP</span> | 当前 MILP 精确路径可 lowering 到 `mathopt_mp` / `highs_cpp`。 |
| <span class="solver-tag solver-tag--model">MODEL</span> | 建模、登记或导出接口；不直接代表求解语义。 |

精确解支持矩阵：

| 接口 | 标签 | CP-SAT | MILP | 说明 |
|---|---|---:|---:|---|
| `const` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持整数/布尔常量 | 支持数值/布尔常量 | CP-SAT 要求参与精确解的常量为整数或布尔值。 |
| `bool_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持 | 支持 | MILP 会降为 0/1 变量。 |
| `int_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持有界整数 | 支持 | CP-SAT 要求有限 `lb` / `ub`。 |
| `float_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--milp">MILP</span> | 不支持 | 支持 | CP-SAT 当前只构建整数域精确模型。 |
| `set_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | 当前 exact canonical model 没有集合变量表示。 |
| `sequence_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 通过 `no_overlap` 绑定 interval，或作为直接 `sequence_transition_sum` objective 输入 | 不支持 | CP-SAT 对调度序列用 rank 变量，对 sequence graph objective 用显式 position/item assignment。 |
| `interval_var` / `optional_interval_var` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 mandatory / optional interval | 不支持 | Optional interval 需要 `bool_var` 或布尔常量作为 presence。MILP 当前只做线性 MP lowering，不接收 interval 结构。 |
| `abs` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | MILP 当前未线性化绝对值，需要用户手动改写为线性约束。 |
| `sum` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持 | 支持线性求和 | MILP 要求所有输入保持线性。 |
| `at_most_one` / `at_least_one` / `exactly_one` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持布尔/整数输入 | 支持线性布尔计数 | 这些方法返回 `sum(...)` 比较表达式，兼容性取决于输入表达式。 |
| `min` / `max` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | MILP 当前未自动引入辅助变量和 epigraph 约束。 |
| `and_` / `or_` / `not_` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持布尔表达式 | 不支持 | MILP 当前不做逻辑重写或 big-M 线性化。 |
| `iif` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持整数条件选择 | 不支持 | CP-SAT 会用 enforcement literal 降为辅助整数变量；MILP 当前不做 big-M 线性化。 |
| `set_len` / `set_contains` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | 依赖集合变量，当前 exact canonical model 不包含集合语义。 |
| `sequence_contains` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 仅支持常量 membership check | 不支持 | 对排列型 `sequence_var`，常量在 `[0, size)` 内为恒真，否则恒假。 |
| `sequence_transition_sum` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 IR-owned dense integer path/cycle objective | 不支持 | CP-SAT 只支持直接 objective root、min sense、非负整数成本；sparse/forbidden/default-cost 和 score 语义会显式拒绝。 |
| `sequence_view` / `sequence_successor` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | optional-aware active view 与直接后继表达式；exact route 明确拒绝。 |
| `sequence_transition` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | active view 上的 typed cost/validity；支持稀疏禁止边。 |
| `sequence_dimension` / `sequence_setup` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | 标量前缀状态、reset 和资源 setup gap；exact route 明确拒绝。 |
| `all_different` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | MILP 当前不自动构造 pairwise 或 assignment 线性化。 |
| `element` / `table` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | 当前 exact backend 没有 element/table 原生或线性化实现。 |
| `interval_start` / `interval_end` / `interval_length` / `interval_presence` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | 只在 CP-SAT interval 模型中可用。`interval_presence` 对 mandatory interval 返回恒真表达式。 |
| `selected_start` / `selected_end` / `selected_duration` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | 通过 presence-controlled `iif` 投影被选中的 optional interval；调用方仍需约束 exactly-one。 |
| `alternative` / `add_alternative` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span> | 支持 | 支持线性布尔计数 | `alternative` 返回 exactly-one 表达式；`add_alternative` 同时登记约束。 |
| `cumulative` / `no_overlap` / `precedence` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> | 支持 | 不支持 | 调度约束当前走 CP-SAT；MILP 不做调度结构线性化。 |
| `external_call` | <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> | 不支持 | 不支持 | 精确解后端不能调用任意 Python 黑盒函数并证明全局最优。 |
| `constraint` / `minimize` / `maximize` | <span class="solver-tag solver-tag--model">MODEL</span> | 取决于内部表达式 | 取决于内部表达式 | 登记接口本身可用，精确解兼容性由包裹的表达式决定。 |
| `to_program_spec` / `freeze` | <span class="solver-tag solver-tag--model">MODEL</span> | 高级 | 高级 | 导出或编译模型结构；普通求解请直接 `solve(builder)`。 |

!!! warning "不能只按变量类型选择精确解"
    即使模型用了 `int_var`，只要目标或约束里包含 `external_call`、`element`、集合接口，当前精确解路径仍会拒绝 lowering。选择 CP-SAT 或 MILP 时，以完整表达式图是否兼容为准。

## 接口分组

| 分组 | 方法 |
|---|---|
| 常量与变量 | `const`、`bool_var`、`int_var`、`float_var`、`set_var`、`sequence_var`、`interval_var`、`optional_interval_var` |
| 数值与逻辑表达式 | `abs`、`sum`、`at_most_one`、`at_least_one`、`exactly_one`、`min`、`max`、`and_`、`or_`、`not_`、`iif` |
| 集合与序列 | `set_len`、`set_contains`、`sequence_contains`、`sequence_transition_sum`、`sequence_view`、`sequence_successor`、`sequence_transition`、`sequence_dimension`、`sequence_setup`、`all_different`、`element`、`table` |
| 调度 | `interval_start`、`interval_end`、`interval_length`、`interval_presence`、`selected_start`、`selected_end`、`selected_duration`、`alternative`、`add_alternative`、`cumulative`、`no_overlap`、`precedence` |
| 黑盒 | `external_call` |
| 登记 | `constraint`、`minimize`、`maximize` |
| 高级导出/编译 | `to_program_spec`、`freeze` |

<span id="optagent.ModelBuilder.const"></span>

## const

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`const(value: bool | int | float | list[int] | dict[str, int]) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `value` | `bool | int | float | list[int] | dict[str, int]` | 要接入表达式图的常量值。 |

**返回值**：`Expr`，常量表达式节点。

```python
# 1. 把业务中的固定罚分转成 builder 表达式。
penalty = builder.const(10)

# 2. 常量表达式可以和变量表达式一起构造约束。
builder.constraint(x + penalty <= 20, name="budget")
```

<span id="optagent.ModelBuilder.bool_var"></span>

## bool_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`bool_var(default: bool = False, name: str | None = None) -> Expr[bool]`

| 参数 | 类型 | 含义 |
|---|---|---|
| `default` | `bool` | 初始布尔值，默认 `False`。 |
| `name` | `str | None` | 变量名，用于输出、trace 和调试。 |

**返回值**：`Expr`，布尔决策变量。

```python
# 1. 创建一个是否选择某个方案的 0/1 决策。
pick = builder.bool_var(default=False, name="pick_item")

# 2. 把业务规则登记成正式约束。
builder.constraint(pick == 1, name="must_pick")
```

<span id="optagent.ModelBuilder.int_var"></span>

## int_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`int_var(default: int = 0, lb: int | None = None, ub: int | None = None, name: str | None = None) -> Expr[int]`

!!! note "CP-SAT 边界"
    CP-SAT 精确路径要求整数变量有有限 `lb` / `ub`。如果省略边界，建模仍然有效，但 CP-SAT lowering 会拒绝该模型。

| 参数 | 类型 | 含义 |
|---|---|---|
| `default` | `int` | 初始整数值。 |
| `lb` | `int | None` | 下界，`None` 表示不显式设置。 |
| `ub` | `int | None` | 上界，`None` 表示不显式设置。 |
| `name` | `str | None` | 变量名。 |

**返回值**：`Expr`，整数决策变量。

```python
# 1. 建立一个范围为 [0, 10] 的整数变量。
quantity = builder.int_var(default=0, lb=0, ub=10, name="quantity")

# 2. 在后续表达式里使用该变量。
builder.constraint(quantity <= 6, name="stock_limit")
```

<span id="optagent.ModelBuilder.float_var"></span>

## float_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`float_var(default: float = 0.0, lb: float | None = None, ub: float | None = None, name: str | None = None) -> Expr[float]`

| 参数 | 类型 | 含义 |
|---|---|---|
| `default` | `float` | 初始连续值。 |
| `lb` | `float | None` | 下界。 |
| `ub` | `float | None` | 上界。 |
| `name` | `str | None` | 变量名。 |

**返回值**：`Expr`，连续决策变量。

```python
# 1. 创建比例变量，限制在 0 到 1 之间。
ratio = builder.float_var(default=0.0, lb=0.0, ub=1.0, name="ratio")

# 2. 用比例变量表达连续成本。
builder.minimize(ratio * 100.0, name="scaled_cost")
```

<span id="optagent.ModelBuilder.set_var"></span>

## set_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**签名**：`set_var(size: int, default: list[int] | set[int] | None = None, name: str | None = None) -> Expr[list[int]]`

!!! note "精确解边界"
    当前 CP-SAT / MILP canonical model 都没有集合变量表示；集合模型通常需要走启发式，或手动改写为 0/1 变量和线性约束。

| 参数 | 类型 | 含义 |
|---|---|---|
| `size` | `int` | 候选元素数量，元素索引通常为 `0..size-1`。 |
| `default` | `list[int] | set[int] | None` | 初始选中集合。 |
| `name` | `str | None` | 变量名。 |

**返回值**：`Expr`，集合变量。

```python
# 1. 表示从 5 个任务中选择一个子集，默认选择 0 和 2。
selected = builder.set_var(size=5, default={0, 2}, name="selected_jobs")

# 2. 限制最多选择 3 个任务。
builder.constraint(builder.set_len(selected) <= 3, name="max_selected")
```

<span id="optagent.ModelBuilder.sequence_var"></span>

## sequence_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`sequence_var(size: int, default: list[int] | None = None, name: str | None = None) -> Expr[list[int]]`

!!! note "CP-SAT 边界"
    CP-SAT 当前支持两类序列：绑定到 `no_overlap(sequence, *intervals)` 的调度序列，以及作为直接 `sequence_transition_sum(...)` objective 输入的 dense integer sequence graph。路线黑盒或其它单独序列约束仍应视为启发式语义。

| 参数 | 类型 | 含义 |
|---|---|---|
| `size` | `int` | 序列元素数量。 |
| `default` | `list[int] | None` | 初始排列；不传时默认为 `list(range(size))`。 |
| `name` | `str | None` | 变量名。 |

**返回值**：`Expr`，序列变量。

```python
# 1. 用序列变量表示 4 个地点的访问顺序。
route = builder.sequence_var(size=4, name="route")

# 2. 要求地点 2 必须在路线中出现。
builder.constraint(builder.sequence_contains(route, 2) == 1, name="visit_2")
```

<span id="optagent.ModelBuilder.interval_var"></span>

## interval_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`interval_var(start: int = 0, length: int = 0, lb_start: int | None = None, ub_start: int | None = None, lb_length: int | None = 0, ub_length: int | None = None, name: str | None = None) -> Expr[dict[str, int]]`

| 参数 | 类型 | 含义 |
|---|---|---|
| `start` | `int` | 默认开始时间。 |
| `length` | `int` | 默认持续时间。 |
| `lb_start` / `ub_start` | `int | None` | 开始时间上下界。 |
| `lb_length` / `ub_length` | `int | None` | 持续时间上下界。 |
| `name` | `str | None` | interval 名称。 |

**返回值**：`Expr`，调度 interval 变量。

```python
# 1. 建立一个长度为 3、开始时间范围为 [0, 10] 的任务。
job = builder.interval_var(start=0, length=3, lb_start=0, ub_start=10, name="job_a")

# 2. 限制任务必须在时间 12 之前结束。
builder.constraint(builder.interval_end(job) <= 12, name="job_a_deadline")
```

<span id="optagent.ModelBuilder.optional_interval_var"></span>

## optional_interval_var

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`optional_interval_var(presence: Expr | bool, start: int = 0, length: int = 0, lb_start: int | None = None, ub_start: int | None = None, lb_length: int | None = 0, ub_length: int | None = None, name: str | None = None) -> Expr[dict[str, int]]`

创建由 presence 控制的 optional interval。interval 的结构始终存在；`no_overlap(...)`、`precedence(...)` 等 optional-aware 调度约束会根据 presence 判断它是否参与。

!!! note "presence 限制"
    `presence` 必须是 `bool_var(...)` 或布尔常量，不能是任意比较表达式。需要复杂 presence 逻辑时，先建布尔变量，再用额外约束绑定它。

| 参数 | 类型 | 含义 |
|---|---|---|
| `presence` | `Expr | bool` | 控制 interval 是否启用的布尔变量或布尔常量。 |
| `start` / `length` | `int` | 默认开始时间和持续时间。 |
| `lb_start` / `ub_start` | `int | None` | 开始时间上下界。 |
| `lb_length` / `ub_length` | `int | None` | 持续时间上下界。 |
| `name` | `str | None` | interval 名称。 |

**返回值**：`Expr`，optional interval 变量。

```python
# 1. presence 决定该加工方式是否被选择。
use_machine_a = builder.bool_var(name="op10_on_machine_a")

# 2. 创建可选 interval。
op10_a = builder.optional_interval_var(
    presence=use_machine_a,
    start=0,
    length=3,
    lb_start=0,
    ub_start=20,
    lb_length=3,
    ub_length=3,
    name="op10_machine_a",
)
```

<span id="optagent.ModelBuilder.abs"></span>

## abs

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`abs(expr: Expr | Any) -> Expr`

!!! note "MILP 边界"
    MILP 当前未自动把绝对值改写为辅助变量和线性约束。需要 MILP 精确解时，应在模型中显式线性化。

| 参数 | 类型 | 含义 |
|---|---|---|
| `expr` | `Expr | Any` | 要取绝对值的表达式或字面量。 |

**返回值**：`Expr`，绝对值表达式。

```python
# 1. 计算变量距离目标值 5 的偏差。
distance = builder.abs(x - 5)

# 2. 最小化该偏差。
builder.minimize(distance, name="distance_to_target")
```

<span id="optagent.ModelBuilder.sum"></span>

## sum

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`sum(*exprs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `*exprs` | `Expr | Any` | 要求和的表达式或字面量。 |

**返回值**：`Expr`，求和表达式。

```python
# 1. 把多个装载量变量聚合为总装载量。
total_load = builder.sum(load_a, load_b, load_c)

# 2. 登记容量约束。
builder.constraint(total_load <= 100, name="capacity")
```

<span id="optagent.ModelBuilder.at_most_one"></span>

## at_most_one

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`at_most_one(*bool_exprs: Expr | Any) -> Expr[bool]`

返回“最多一个表达式为真”的关系表达式，等价于 `sum(...) <= 1`。

```python
choice_ok = builder.at_most_one(use_a, use_b, use_c)
builder.constraint(choice_ok, name="choose_at_most_one")
```

<span id="optagent.ModelBuilder.at_least_one"></span>

## at_least_one

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`at_least_one(*bool_exprs: Expr | Any) -> Expr[bool]`

返回“至少一个表达式为真”的关系表达式，等价于 `sum(...) >= 1`。

```python
has_mode = builder.at_least_one(use_a, use_b, use_c)
builder.constraint(has_mode, name="choose_at_least_one")
```

<span id="optagent.ModelBuilder.exactly_one"></span>

## exactly_one

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`exactly_one(*bool_exprs: Expr | Any) -> Expr[bool]`

返回“恰好一个表达式为真”的关系表达式，等价于 `sum(...) == 1`。

```python
one_machine = builder.exactly_one(use_machine_a, use_machine_b)
builder.constraint(one_machine, name="select_one_machine")
```

<span id="optagent.ModelBuilder.min"></span>

## min

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`min(*exprs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `*exprs` | `Expr | Any` | 要取最小值的表达式或字面量。 |

**返回值**：`Expr`，最小值表达式。

```python
# 1. 表示三个资源池中的最小剩余容量。
least_slack = builder.min(slack_a, slack_b, slack_c)

# 2. 最大化最小剩余容量，表达均衡目标。
builder.maximize(least_slack, name="balance_slack")
```

<span id="optagent.ModelBuilder.max"></span>

## max

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`max(*exprs: Expr | Any) -> Expr`

!!! note "MILP 边界"
    `min` / `max` 当前不会自动降为 MILP 的辅助变量约束。需要 MILP 时，应手动写成线性 epigraph / hypograph 形式。

| 参数 | 类型 | 含义 |
|---|---|---|
| `*exprs` | `Expr | Any` | 要取最大值的表达式或字面量。 |

**返回值**：`Expr`，最大值表达式。

```python
# 1. 找到多台机器里的峰值负载。
peak_load = builder.max(load_a, load_b, load_c)

# 2. 最小化峰值负载。
builder.minimize(peak_load, name="minimize_peak_load")
```

<span id="optagent.ModelBuilder.and_"></span>

## and_

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`and_(lhs: Expr | Any, rhs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `lhs` | `Expr | Any` | 左侧逻辑表达式。 |
| `rhs` | `Expr | Any` | 右侧逻辑表达式。 |

**返回值**：`Expr`，逻辑与表达式。

```python
# 1. 同时检查两个条件是否成立。
both_ready = builder.and_(machine_ready == 1, material_ready == 1)

# 2. 要求两个条件都成立后才能生产。
builder.constraint(both_ready == 1, name="ready_to_run")
```

<span id="optagent.ModelBuilder.or_"></span>

## or_

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`or_(lhs: Expr | Any, rhs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `lhs` | `Expr | Any` | 左侧逻辑表达式。 |
| `rhs` | `Expr | Any` | 右侧逻辑表达式。 |

**返回值**：`Expr`，逻辑或表达式。

```python
# 1. 至少选择一种加急方式。
fast_mode = builder.or_(use_air == 1, use_express == 1)

# 2. 把可选规则登记为约束。
builder.constraint(fast_mode == 1, name="need_fast_mode")
```

<span id="optagent.ModelBuilder.not_"></span>

## not_

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`not_(expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `expr` | `Expr | Any` | 要取反的逻辑表达式。 |

**返回值**：`Expr`，逻辑非表达式。

```python
# 1. 表示不能同时禁用某个安全开关。
disabled = builder.not_(safety_enabled == 1)

# 2. 禁止 disabled 为真。
builder.constraint(disabled == 0, name="safety_required")
```

<span id="optagent.ModelBuilder.iif"></span>

## iif

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`iif(cond: Expr | Any, when_true: Expr | Any, when_false: Expr | Any) -> Expr`

!!! note "精确解边界"
    CP-SAT 支持整数条件选择。MILP 当前不自动把 `iif` 改写为 big-M 线性化。

| 参数 | 类型 | 含义 |
|---|---|---|
| `cond` | `Expr | Any` | 条件表达式。 |
| `when_true` | `Expr | Any` | 条件为真时的值。 |
| `when_false` | `Expr | Any` | 条件为假时的值。 |

**返回值**：`Expr`，条件表达式。

```python
# 1. 如果使用加急模式，成本为 100，否则成本为 30。
cost = builder.iif(use_fast == 1, 100, 30)

# 2. 把条件成本作为目标。
builder.minimize(cost, name="mode_cost")
```

<span id="optagent.ModelBuilder.set_len"></span>

## set_len

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**签名**：`set_len(set_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `set_expr` | `Expr | Any` | 集合变量或集合表达式。 |

**返回值**：`Expr`，集合大小表达式。

```python
# 1. 计算当前选择集合的元素数量。
selected_count = builder.set_len(selected)

# 2. 限制最多选择 2 个元素。
builder.constraint(selected_count <= 2, name="selection_limit")
```

<span id="optagent.ModelBuilder.set_contains"></span>

## set_contains

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**签名**：`set_contains(set_expr: Expr | Any, value_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `set_expr` | `Expr | Any` | 集合变量或集合表达式。 |
| `value_expr` | `Expr | Any` | 要检查的元素值。 |

**返回值**：`Expr`，元素是否属于集合的 0/1 表达式。

```python
# 1. 检查任务 3 是否被选中。
has_job_3 = builder.set_contains(selected, 3)

# 2. 要求任务 3 必须被选中。
builder.constraint(has_job_3 == 1, name="need_job_3")
```

<span id="optagent.ModelBuilder.sequence_transition_sum"></span>

## sequence_transition_sum

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`sequence_transition_sum(sequence: Expr, graph: TransitionGraph | list[list[int | float]] | dict, include_return_edge: bool = False, cost_semantics: str = "penalty", sparse_edge_semantics: str = "default_cost", default_edge_cost: int | float | None = None) -> Expr`

计算序列变量按给定转换图的总转换成本。通常作为 `minimize(...)` 的目标。

!!! note "CP-SAT 边界"
    CP-SAT 当前只支持 dense non-negative integer cost matrix、min sense、作为 objective root 的使用方式。sparse / forbidden / default-cost / score / non-min / negative 或非整数成本模式不做 exact lowering。

| 参数 | 类型 | 含义 |
|---|---|---|
| `sequence` | `Expr` | 序列变量（来自 `sequence_var()`）。 |
| `graph` | `TransitionGraph \| list[list] \| dict` | 转换成本图。推荐使用 `TransitionGraph` builder 对象，也接受 dense matrix 或 raw dict。 |
| `include_return_edge` | `bool` | 是否包含回路边（最后元素到第一个元素的成本）。`False` 为 path，`True` 为 cycle。 |
| `cost_semantics` | `str` | `"penalty"`、`"distance"` 或 `"score"`；CP-SAT exact 当前只接受前两种成本语义。 |
| `sparse_edge_semantics` | `str` | `"default_cost"` 使用默认成本，`"forbidden"` 禁止缺失边，`"implicit"` 将缺失边视为隐式零成本。 |
| `default_edge_cost` | `int \| float \| None` | 稀疏图中缺失边的默认成本值。仅在 `sparse_edge_semantics="default_cost"` 时生效。 |

**返回值**：`Expr`，标量成本表达式。

### 使用 TransitionGraph builder（推荐）

```python
from optagent import ModelBuilder, TransitionGraph

builder = ModelBuilder()
route = builder.sequence_var(size=4, name="route")

# Dense matrix
g = TransitionGraph.from_matrix([
    [0, 4, 7, 3],
    [2, 0, 5, 8],
    [3, 11, 0, 6],
    [9, 1, 4, 0],
])
cost = builder.sequence_transition_sum(route, g)
builder.minimize(cost, name="travel_cost")
```

### 稀疏图 + fluent edge builder

```python
g = (TransitionGraph(size=4)
     .edge(0, 1, cost=4)
     .edge(1, 2, cost=5)
     .edge(2, 3, cost=6)
     .default_cost(10))
cost = builder.sequence_transition_sum(route, g)
```

### 带回路边（cycle）

```python
# include_return_edge=True 时计算完整回路成本（TSP）
cost = builder.sequence_transition_sum(route, g, include_return_edge=True)
builder.minimize(cost, name="tsp_cost")
```

### 直接传入 dense matrix

```python
# 也可以直接传 list[list[int]] 作为 graph 参数
matrix = [[0, 4, 7], [2, 0, 5], [3, 11, 0]]
cost = builder.sequence_transition_sum(route, matrix)
```

## Active scheduling sequence nodes

<span id="optagent.ModelBuilder.sequence_view"></span>
<span id="optagent.ModelBuilder.sequence_successor"></span>
<span id="optagent.ModelBuilder.sequence_transition"></span>
<span id="optagent.ModelBuilder.sequence_dimension"></span>
<span id="optagent.ModelBuilder.sequence_setup"></span>

这些 low-level 方法承载 `SchedulingModel` 高级 sequence 语义：

| 方法 | 返回语义 |
| --- | --- |
| `sequence_view(sequence, presences, item_ids, name=None)` | 按静态 item ID 和 presence 投影 active sequence。 |
| `sequence_successor(sequence, before_item_id, after_item_id)` | 两个 item 是否直接相邻。 |
| `sequence_transition(sequence, graph, item_types, ...)` | typed path 的转换成本或允许边有效性。 |
| `sequence_dimension(sequence, initial_value, contributions, item_types, reset_item_ids, reset_type_edges, query_item_id, query, ...)` | 指定 item 的累计前后值、段号、段总量或段边界。 |
| `sequence_setup(sequence, intervals, graph, item_types, ...)` | 相邻类型所需 setup 是否可被任务间隙和资源 calendar 容纳。 |

通常应通过 `Resource.sequence().view()` 上的 `transition()`、`setup_time()` 和 `dimension()` 使用这些节点。它们当前只支持 heuristic runtime；direct exact lowering 会 fail fast。

<span id="optagent.ModelBuilder.sequence_contains"></span>

## sequence_contains

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`sequence_contains(sequence_expr: Expr | Any, value_expr: Expr | Any) -> Expr`

!!! note "CP-SAT 边界"
    CP-SAT 当前只支持常量 membership check。对排列型 `sequence_var`，常量在 `[0, size)` 内为恒真，否则恒假；动态 membership 表达式不做 exact lowering。

| 参数 | 类型 | 含义 |
|---|---|---|
| `sequence_expr` | `Expr | Any` | 序列变量或序列表达式。 |
| `value_expr` | `Expr | Any` | 要检查的元素值。 |

**返回值**：`Expr`，元素是否出现在序列中的 0/1 表达式。

```python
# 1. 检查地点 2 是否在路线中。
visits_2 = builder.sequence_contains(route, 2)

# 2. 要求路线必须访问地点 2。
builder.constraint(visits_2 == 1, name="visit_2")
```

<span id="optagent.ModelBuilder.all_different"></span>

## all_different

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`all_different(*exprs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `*exprs` | `Expr | Any` | 要求互不相同的一组表达式。 |

**返回值**：`Expr`，互异约束表达式。

```python
# 1. 三个任务必须分配到不同工位。
different_stations = builder.all_different(station_a, station_b, station_c)

# 2. 将互异关系登记为正式约束。
builder.constraint(different_stations, name="all_different_stations")
```

<span id="optagent.ModelBuilder.element"></span>

## element

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**签名**：`element(array_values: list[int | float], index_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `array_values` | `list[int | float]` | 常量数组。 |
| `index_expr` | `Expr | Any` | 取值索引表达式。 |

**返回值**：`Expr`，`array_values[index_expr]` 对应的表达式。

```python
# 1. choice 决定选择哪个成本项。
choice = builder.int_var(default=0, lb=0, ub=2, name="choice")

# 2. 根据 choice 从常量数组中取成本。
picked_cost = builder.element([5, 8, 13], choice)

# 3. 最小化被选中的成本。
builder.minimize(picked_cost, name="picked_cost")
```

<span id="optagent.ModelBuilder.table"></span>

## table

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**签名**：`table(exprs: list[Expr | Any], allowed_tuples: list[list[int]]) -> Expr`

!!! note "精确解边界"
    `element` / `table` 当前未接入 CP-SAT 原生约束，也未线性化到 MILP。

| 参数 | 类型 | 含义 |
|---|---|---|
| `exprs` | `list[Expr | Any]` | 需要联合限制的一组表达式。 |
| `allowed_tuples` | `list[list[int]]` | 允许出现的元组列表。 |

**返回值**：`Expr`，表约束表达式。

```python
# 1. x 和 y 只能取 (0, 1) 或 (1, 0)，等价于二选一。
allowed_pair = builder.table([x, y], [[0, 1], [1, 0]])

# 2. 登记允许元组表约束。
builder.constraint(allowed_pair, name="xor_pairs")
```

<span id="optagent.ModelBuilder.interval_start"></span>

## interval_start

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`interval_start(interval_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `interval_expr` | `Expr | Any` | interval 变量或表达式。 |

**返回值**：`Expr`，interval 的开始时间表达式。

```python
# 1. 读取任务开始时间。
start = builder.interval_start(job)

# 2. 要求任务不能早于时间 2 开始。
builder.constraint(start >= 2, name="release_time")
```

<span id="optagent.ModelBuilder.interval_end"></span>

## interval_end

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`interval_end(interval_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `interval_expr` | `Expr | Any` | interval 变量或表达式。 |

**返回值**：`Expr`，interval 的结束时间表达式。

```python
# 1. 读取任务结束时间。
end = builder.interval_end(job)

# 2. 要求任务在工期窗口内完成。
builder.constraint(end <= 10, name="deadline")
```

<span id="optagent.ModelBuilder.interval_length"></span>

## interval_length

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`interval_length(interval_expr: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `interval_expr` | `Expr | Any` | interval 变量或表达式。 |

**返回值**：`Expr`，interval 的持续时间表达式。

```python
# 1. 读取任务持续时间。
duration = builder.interval_length(job)

# 2. 限制持续时间不能超过 4。
builder.constraint(duration <= 4, name="duration_limit")
```

<span id="optagent.ModelBuilder.interval_presence"></span>

## interval_presence

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`interval_presence(interval_expr: Expr | Any) -> Expr[bool]`

返回 interval 的 presence 表达式。mandatory interval 返回复用的恒真常量；optional interval 返回创建时传入的 presence。

```python
present = builder.interval_presence(op10_a)
builder.constraint(present == 1, name="force_op10_a")
```

<span id="optagent.ModelBuilder.selected_start"></span>

## selected_start

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`selected_start(intervals: list[Expr | Any] | tuple[Expr | Any, ...], presences: list[Expr | Any] | tuple[Expr | Any, ...]) -> Expr`

返回 presence 为真的 interval 的开始时间。调用方需要另行保证 exactly-one presence。

```python
start = builder.selected_start([op10_a, op10_b], [use_machine_a, use_machine_b])
builder.constraint(start >= 2, name="release_time")
```

<span id="optagent.ModelBuilder.selected_end"></span>

## selected_end

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`selected_end(intervals: list[Expr | Any] | tuple[Expr | Any, ...], presences: list[Expr | Any] | tuple[Expr | Any, ...]) -> Expr`

返回 presence 为真的 interval 的结束时间。常用于 flexible scheduling 中计算 task 完工时间。

```python
end = builder.selected_end([op10_a, op10_b], [use_machine_a, use_machine_b])
builder.constraint(end <= 20, name="deadline")
```

<span id="optagent.ModelBuilder.selected_duration"></span>

## selected_duration

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`selected_duration(intervals: list[Expr | Any] | tuple[Expr | Any, ...], presences: list[Expr | Any] | tuple[Expr | Any, ...]) -> Expr`

返回 presence 为真的 interval 的持续时间。

```python
duration = builder.selected_duration([op10_a, op10_b], [use_machine_a, use_machine_b])
builder.minimize(duration, name="selected_duration")
```

<span id="optagent.ModelBuilder.alternative"></span>

## alternative

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`alternative(*presence_exprs: Expr | Any) -> Expr[bool]`

返回 exactly-one alternative 表达式。它不会自动登记约束，需要传给 `constraint(...)`，或直接使用 `add_alternative(...)`。

```python
choice = builder.alternative(use_machine_a, use_machine_b)
builder.constraint(choice, name="op10_select_one_machine")
```

<span id="optagent.ModelBuilder.add_alternative"></span>

## add_alternative

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span> <span class="solver-tag solver-tag--milp">MILP</span>

**签名**：`add_alternative(*presence_exprs: Expr | Any, name: str | None = None) -> Expr[bool]`

创建并登记 exactly-one alternative 约束。

```python
builder.add_alternative(use_machine_a, use_machine_b, name="op10_select_one_machine")
```

<span id="optagent.ModelBuilder.cumulative"></span>

## cumulative

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`cumulative(intervals: list[Expr | Any], demands: list[Expr | Any], capacity: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `intervals` | `list[Expr | Any]` | 占用资源的 interval 列表。 |
| `demands` | `list[Expr | Any]` | 每个 interval 的资源需求。 |
| `capacity` | `Expr | Any` | 资源容量。 |

**返回值**：`Expr`，累积资源约束表达式。

```python
# 1. 两个任务都占用同一台容量为 1 的机器。
machine_capacity = builder.cumulative([job_a, job_b], [1, 1], 1)

# 2. 登记容量约束，避免同时超载。
builder.constraint(machine_capacity, name="machine_capacity")
```

<span id="optagent.ModelBuilder.no_overlap"></span>

## no_overlap

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`no_overlap(sequence_expr: Expr | Any, *interval_exprs: Expr | Any) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `sequence_expr` | `Expr | Any` | 表示任务顺序的序列变量。 |
| `*interval_exprs` | `Expr | Any` | 需要互不重叠的 interval。 |

**返回值**：`Expr`，不重叠约束表达式。

```python
# 1. sequence_var 表示同一机器上的任务顺序。
order = builder.sequence_var(2, name="machine_order")

# 2. no_overlap 约束 job_a 和 job_b 不能同时占用机器。
builder.constraint(builder.no_overlap(order, job_a, job_b), name="no_overlap")
```

<span id="optagent.ModelBuilder.precedence"></span>

## precedence

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span> <span class="solver-tag solver-tag--cpsat">CP-SAT</span>

**签名**：`precedence(before_interval: Expr | Any, after_interval: Expr | Any, lag: Expr | int = 0) -> Expr`

!!! note "MILP 边界"
    `cumulative`、`no_overlap`、`precedence` 属于调度结构约束。当前 MILP 路径不自动生成对应线性 formulation。

| 参数 | 类型 | 含义 |
|---|---|---|
| `before_interval` | `Expr | Any` | 必须先完成的 interval。 |
| `after_interval` | `Expr | Any` | 后续 interval。 |
| `lag` | `Expr | int` | 两个 interval 之间的最小间隔。 |

**返回值**：`Expr`，前后工序约束表达式。

```python
# 1. job_a 完成后至少间隔 1 个时间单位才能开始 job_b。
a_before_b = builder.precedence(job_a, job_b, lag=1)

# 2. 登记工序先后关系。
builder.constraint(a_before_b, name="a_before_b")
```

<span id="optagent.ModelBuilder.external_call"></span>
<span id="external-call"></span>

## external_call

**标签**： <span class="solver-tag solver-tag--heuristic">HEURISTIC</span>

**调用形式**：`external_call(fn, name=None, value_kind="scalar", timeout_ms=None, pure=True, deterministic=True, cacheable=None, batch_fn=None, batch_safe=None, concurrency_mode="shared_serial", worker_factory=None, error_policy="fail", depends_on=()) -> Expr`

!!! note "精确解边界"
    CP-SAT / MILP 不能调用任意 Python 函数并证明全局最优。包含 `external_call` 的目标或约束会使 exact lowering 拒绝该路径。

| 参数 | 类型 | 含义 |
|---|---|---|
| `fn` | `Callable[[ExternalCallbackContext], Any]` | Python 外部评估函数，必须是 `fn(ctx)` 形式。 |
| `name` | `str | None` | 外部调用名；不传时使用函数名。 |
| `value_kind` | `str` | 外部调用返回值类型，默认 `"scalar"`。 |
| `timeout_ms` | `int | None` | 单次调用超时毫秒数。 |
| `pure` | `bool` | 回调是否无副作用。 |
| `deterministic` | `bool` | 相同依赖值是否总是返回相同结果。 |
| `cacheable` | `bool | None` | 是否缓存相同输入的评估结果；不传时由 `pure` 和 `deterministic` 推导。 |
| `batch_fn` | `Callable | None` | 可选批量评价函数，输入多行依赖值并返回等长结果。 |
| `batch_safe` | `bool | None` | 是否允许批量调用；`None` 根据 `batch_fn` 推导。 |
| `concurrency_mode` | `str` | `"shared_serial"`、`"shared_parallel"` 或 `"worker_isolated"`。 |
| `worker_factory` | `Callable | None` | worker-isolated 模式下为每个 worker 创建独立回调。 |
| `error_policy` | `str` | `"fail"` 或 `"skip_candidate"`。 |
| `depends_on` | `tuple[Any, ...] | list[Any]` | 条件分支中 discovery 可能读不到的显式依赖。 |

**返回值**：`Expr`，外部调用结果表达式。

```python
from optagent import ExternalCallbackContext

route = builder.sequence_var(5, name="route")

def route_score(ctx: ExternalCallbackContext) -> int:
    # 1. 在外部函数中实现暂时无法线性化的业务评分。
    route_value = ctx.value(route)  # list[int]
    return len(route_value)


# 2. 将 Python 函数接入模型图，依赖通过 ctx.value(...) 自动发现。
score = builder.external_call(route_score, name="route_score", timeout_ms=100)

# 3. 把黑盒评分登记为目标。
builder.minimize(score, name="score")
```

`ctx.value(route)` 是推荐写法：`route` 的类型是 `Expr[list[int]]`，因此静态类型检查器和 IDE 可以把返回值识别为 `list[int]`。`ctx.value("route")` 仍然支持，用于动态按名称访问变量；这种写法的静态返回类型是 `Any`。

<span id="optagent.ModelBuilder.constraint"></span>

## constraint

**标签**： <span class="solver-tag solver-tag--model">MODEL</span>

**签名**：`constraint(expr: Expr | Any, name: str | None = None) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `expr` | `Expr | Any` | 布尔表达式或关系表达式。 |
| `name` | `str | None` | 约束名称。 |

**返回值**：`Expr`，已登记的约束节点。

```python
# 1. 先构造容量关系表达式。
capacity_ok = x + y <= 10

# 2. 再调用 constraint，把关系加入模型约束列表。
builder.constraint(capacity_ok, name="capacity")
```

!!! note "约束必须登记"
    单独写 `x + y <= 10` 只会创建表达式，不会进入模型约束列表。需要显式调用 `constraint(...)`。

<span id="optagent.ModelBuilder.minimize"></span>

## minimize

**标签**： <span class="solver-tag solver-tag--model">MODEL</span>

**签名**：`minimize(expr: Expr | Any, name: str | None = None) -> Expr`

| 参数 | 类型 | 含义 |
|---|---|---|
| `expr` | `Expr | Any` | 要最小化的目标表达式。 |
| `name` | `str | None` | 目标名称。 |

**返回值**：`Expr`，已登记的目标节点。

```python
# 1. 构造总成本表达式。
total_cost = fixed_cost + variable_cost

# 2. 登记最小化目标。
builder.minimize(total_cost, name="total_cost")
```

<span id="optagent.ModelBuilder.maximize"></span>

## maximize

**标签**： <span class="solver-tag solver-tag--model">MODEL</span>

**签名**：`maximize(expr: Expr | Any, name: str | None = None) -> Expr`

!!! note "精确解边界"
    `constraint`、`minimize`、`maximize` 本身只是登记接口。能否走 CP-SAT 或 MILP，取决于被登记表达式里的变量、算子和约束类型。

| 参数 | 类型 | 含义 |
|---|---|---|
| `expr` | `Expr | Any` | 要最大化的目标表达式。 |
| `name` | `str | None` | 目标名称。 |

**返回值**：`Expr`，已登记的目标节点。

```python
# 1. 构造收益表达式。
profit = (x * 3) + (y * 2)

# 2. 登记最大化目标。
builder.maximize(profit, name="profit")
```

<span id="optagent.ModelBuilder.to_program_spec"></span>

## to_program_spec

**标签**： <span class="solver-tag solver-tag--model">MODEL</span>

!!! warning "高级导出接口"
    普通求解不要先调用 `to_program_spec()`；直接传 `builder` 给 `solve(...)`。`to_program_spec()` 生成的是可序列化 IR，会移除 Python callable。包含 `external_call` 的模型需要通过 `solve(builder)` 或 `builder.freeze()` 保留 external provider。

**签名**：`to_program_spec() -> ProgramSpec`

**参数**：无。

**返回值**：`ProgramSpec`，可序列化的模型结构描述；它不是带 Python callback registry 的运行时对象。

```python
# 1. 导出中间结构，适合调试或持久化检查。
spec = builder.to_program_spec()

# 2. 查看变量、约束、目标节点数量。
print(len(spec.variable_ids), len(spec.constraint_ids), len(spec.objective_ids))
```

<span id="optagent.ModelBuilder.freeze"></span>

## freeze

**标签**： <span class="solver-tag solver-tag--model">MODEL</span>

!!! note "高级编译接口"
    `solve(builder)` 会自动完成这一步。只有在需要复用已编译运行对象、编写测试、或接入底层后端时，才需要显式调用 `freeze()`。

**签名**：`freeze() -> ExecutableProgram`

**参数**：无。

**返回值**：`ExecutableProgram`，保留 external provider registry 的可执行模型。

```python
# 1. 高级用法：在变量、约束、目标都登记完成后冻结模型。
program = builder.freeze()

# 2. 普通求解通常直接 solve(builder)，这里用于复用编译结果。
solution = solve(program)
```

<!-- public-api: optagent.ModelBuilder -->
<!-- public-api: optagent.Expr -->
<!-- public-api: optagent.TransitionGraph -->
<!-- public-api: optagent.ExternalCallbackContext -->
<!-- public-api: optagent.ExternalEvaluationError -->
<!-- public-api: optagent.ExternalTimeoutError -->
