# 求解与配置

先阅读[路径选择](../guide/solvers.md)确认模型兼容性。一次性求解返回 `Solution` 并将本次解的值写入模型；业务代码应先检查 `feasible`。交互式入口返回会话，在 `finish()` 时写回最终解。

## solve · 本地搜索

```python
solution = model.solve(
    time_limit_s=30.0,
    objective_threshold=None,
    log_level="on",
    external_evaluation_limit=None,
    memory_limit_bytes=None,
    deterministic=True,
    evaluation_limit=None,
    random_seed=1,
    worker_threads=None,
)
```

| 参数 | 默认值 | 约束 / 含义 |
| --- | --- | --- |
| `time_limit_s` | `30.0` | 正数且有限；秒，包含序列化和编译 |
| `objective_threshold` | `None` | 目标索引到有限数值的映射；索引从零开始 |
| `log_level` | `"on"` | `"off"`、`"on"`、`"debug"` |
| `external_evaluation_limit` | `None` | 可选正整数，限制实际启动的外部回调次数（失败也计入），已知结果和复用不额外计费 |
| `memory_limit_bytes` | `None` | 默认自动管理内存；正整数可设置管理内存的字节上限，最大为 `sys.maxsize` |
| `deterministic` | `True` | 按固定逻辑顺序推进并行搜索，内部修复使用工作限额；设为 `False` 使用吞吐模式 |
| `evaluation_limit` | `None` | 可选正整数，限制搜索评价和内部试算；不包含初始化评价 |
| `random_seed` | `1` | 随机种子，整数，范围为 `0` 到 `2**64 - 1` |
| `worker_threads` | `None` | 可选正整数，范围为 `1` 到 `1024`；默认采用机器报告的硬件并发数 |

需要重复得到相同结果时，固定模型、种子、线程配置、运行版本和评价预算，并为完成工作留出足够时间：

```python
solution = model.solve(
    deterministic=True,
    evaluation_limit=10000,
    random_seed=1,
    worker_threads=4,
    time_limit_s=120,
)
```

`solution.diagnostics["deterministic_completed"]` 表示本次确定性工作正常完成。
若时间到期、取消、发生资源压力或执行失败，该值为 `False`，返回已有认证解，但不保证终态与其他运行相同。
相同秒数可能完成不同数量的工作，因此仅固定时间和种子不能保证完全相同的结果。

确定性模式暂不支持外部 Python 回调，也不运行依赖时间预算的 HiGHS 搜索路径；
模型包含外部回调时需显式传入 `deterministic=False`。它保留并行计算，但同步和搜索反馈顺序可能影响耗时与解质量。
默认启用确定性模式；`model.solve(deterministic=False)` 可切回吞吐模式。
该模式尚未通过性能与解质量无回退验收，是否适合业务模型应通过同时间预算的对照验证。

默认根据系统和容器可用内存调整搜索并发，在压力出现时回收缓存、降低并发，余量恢复后逐步增加并发。
例如 `model.solve(memory_limit_bytes=2 * 1024**3)` 将管理内存的目标限制在 2 GiB 以内；系统余量不足时实际目标可能更低。

该参数覆盖搜索工作区、候选存储和算法辅助空间，是协作式估算目标，不是整个进程的硬内存上限。
模型编译、Python、外部求解器和单次临时分配仍可能产生额外占用。资源不足时会保留已有的已验证结果，
通过 `solution.diagnostics["termination_reason"]` 报告 `memory_limit`，不表示模型无解。
`solution.diagnostics["memory_policy"]` 提供实际预算、资源来源、探测回退以及并发收缩和恢复次数。
系统资源无法探测时使用至多 256 MiB 的保护目标，并标记 `probe_fallback`。

<!-- public-api: optagent.solve -->

`solution.feasible=False` 表示本次没有交付可行解。到达时间限制且没有可行解时，
`solution.status` 为 `deadline`；其他终止情况保留其停止原因。
当前原生搜索路线不交付全局不可行证明，diagnostics 中的 `globally_infeasible` 为 `False`；
没有找到可行解时不会据此返回 `infeasible`。

## interactive_solve · 暂停、改解与续跑

交互式求解沿用支持暂停、改解和续跑的异步吞吐路径。

```python
from optagent import OptModel, interactive_solve

model = OptModel()
x = model.int(0, 100, default=80)
model.minimize(x * x)

with interactive_solve(model, wall_time_limit_s=60, log_level="off") as session:
    outcome = session.run_for(0.1)
    if not outcome.finished:
        outcome[x] = max(0, outcome[x] - 1)
        evaluated = session.evaluate(outcome)
        if evaluated.feasible and evaluated.improves_incumbent:
            receipt = session.submit(outcome)
            print(receipt.status)
        outcome = session.run_for(0.1)
    result = session.finish()
```

使用建模时声明的变量定位值：`outcome[x]` 读取，`outcome[x] = value` 修改。
已有业务索引可以直接使用，例如 `outcome[start_times[job]] = 20`。
修改立即可读；`x.value` 在会话结束前仍表示上一次终态解。

入口参数按名称传入：

| 参数 | 默认值 | 含义 |
| --- | --- | --- |
| `wall_time_limit_s` | `None` | 可选正数，单位秒；总墙钟预算，包含序列化、初始化、搜索、暂停和评价；默认不限总时间 |
| `objective_threshold` | `None` | 与 `solve` 相同；提交达到阈值后也会终结搜索 |
| `log_level` | `"on"` | `"off"`、`"on"`、`"debug"` |
| `external_evaluation_limit` | `None` | 搜索和交互评价共用的外部函数调用额度 |
| `memory_limit_bytes` | `None` | 与 `solve` 相同的受管内存预算，包含交互评价；Python 导出的值不计入该预算 |

### 会话操作

| 操作 | 返回及约束 |
| --- | --- |
| `session.run_for(seconds)` | 返回 `SolveOutcome`；从可搜索状态开始计时，到期请求安全暂停，保留搜索资源 |
| `session.evaluate(outcome, timeout_s=None)` | 返回 `EvaluationReport`；对当前暂停轮次的完整候选原生验解，不改变全局最优 |
| `session.submit(outcome)` | 返回 `SubmissionReceipt`；只能提交最后一次修改后已成功验解的当前候选 |
| `session.cancel()` | 非阻塞请求终结，可在另一线程或外部函数中调用；之后用 `finish/close` 等待清理 |
| `session.finish()` | 终结并返回最后一个认证的 `Solution`；重复调用返回同一个结果 |
| `session.close()` | 幂等关闭；`with` 退出时自动调用 |

暂停和取消均为协作式操作；在途任务、外部函数及结果排空可能延长返回时间。
`timeout_s` 只限制本次评价；到期的评价不提交，若会话总预算仍有效，可继续修改和评价。
已经执行的 Python 外部函数必须自行返回，无法被硬中断。

### outcome 的读写与认证

`SolveOutcome.finished` 表示返回时搜索已终结，与可行性分开。
`feasible`、`objectives` 是当前版本的认证结果；修改后均变为 `None`，成功评价后恢复。
`revision` 记录成功修改次数，`incumbent_version` 记录创建 outcome 时的全局版本。
非法赋值不改变原值、版本或证书；无认证赋值的终结结果读取决策时抛出 `RuntimeError`。

标量遵守声明的类型和范围。list 读取返回 tuple，set 返回 frozenset；区间读取为不可变映射，
缺席的可选区间为 `None`。修改集合需要整体赋值，例如：

```python
order = list(outcome[route])
order.reverse()
outcome[route] = order
```

变量必须属于原模型且是决策变量。跨变量约束在完整评价时检查，因此可以先修改多个变量再评价。
`EvaluationReport` 提供 `feasible`、`objectives`、`improves_incumbent`、`revision`，以及
`constraint_violations`、`hard_structure_violations`、`undefined_constraints`、
`undefined_objectives` 四项计数。

`SubmissionReceipt.accepted` 表示实际接纳，`status` 为 `accepted`、`infeasible` 或
`not_improving`，`incumbent_version` 是提交后的全局版本。相等目标默认不替换。
未评价、过期或已经终结的提交抛出 `RuntimeError`，其他会话的 outcome 抛出 `ValueError`。
每次赋值会使旧证书失效；修改 Python 中的评价报告不会改变原生认证结果。

续跑不会自动提交修改。旧 outcome 保留原值供读取，但不能在之后的暂停轮次提交。
评价和提交期间修改该 outcome 会立即失败；同时运行第二个控制操作也会失败，`cancel` 除外。
模型声明在整个会话中冻结；外部函数对相同输入的含义也必须保持稳定。
当前仅原生搜索支持此会话接口，其他求解入口仍使用各自的一次性调用方式。

最终 `result.diagnostics["interactive"]` 包含暂停数、评价尝试/完成数、接纳数，
以及主动搜索、暂停排空、暂停等待、交互评价和 outcome 导出的纳秒计时。
暂停等待包含交互评价时间，两者不能重复相加。

<!-- public-api: optagent.interactive_solve -->
<!-- public-api: optagent.SolveSession -->
<!-- public-api: optagent.SolveOutcome -->
<!-- public-api: optagent.EvaluationReport -->
<!-- public-api: optagent.SubmissionReceipt -->

## solve_optx · HiGHS

```python
from optagent import OptxConfig

solution = model.solve_optx(
    config=OptxConfig(time_limit=30.0, mip_rel_gap=0.01, threads=4),
    objective_threshold=None,
)
```

`config=None` 使用默认配置；包装层未配置时间限制时采用 30 秒。
未指定 `threads` 或传入 `None` 时，保留 HiGHS 的 `threads=0` 自动选择与线程池复用；
未指定 `parallel` 时保留原生 `"choose"`。包装层不再按模型规模、整数域或剩余时间推导线程数，
也不额外估算 CPU 配额。受限运行环境可显式设置 `threads`。
正整数线程数和原生选项始终优先，例如 `OptxConfig(threads=4, parallel="on")`。
4 和 8 都不是后端最低要求；`threads=1` 限制后端线程池为单线程。
固定版本中，MIP 树并行需要显式 `parallel="on"` 且线程数大于 1；默认 `"choose"` 不自动开启它。

MIP 树并行开启后，默认采用 `mip_parallel_strategy="neighborhood"`，在有可行候选时分配一个 worker 搜索整数邻域。
需要原版树搜索策略时，可设置 `OptxConfig(threads=4, parallel="on", mip_parallel_strategy="standard")`。
策略效果与模型有关；邻域搜索可能改善候选发现，也可能增加成本。
`neighborhood` 的协作调度允许搜索轨迹变化，固定 seed 不保证节点和 LP 迭代数一致；`standard` 保留原版同步调度。
辅助累计预算 `mip_neighborhood_budget` 默认为 60 秒，时间片 `mip_neighborhood_slice` 默认为 10 秒，均采用协作式停止。
预算耗尽或没有有效邻域后，worker 返回树搜索；预算为 0 时只禁用辅助任务，完整回退应选择 `standard`。
这三个实际参数可在 `solution.diagnostics["backend_attempts"]` 中查询，LP 调用对应值为 `None`。

连续凸 QP 在 HiPO 可用时默认优先使用 `solver="hipo"`，不按二次变量数量设分界。
HiPO 报错、返回 `Unknown` 或候选未通过原模型数值核验时，使用剩余总预算尝试一次 ASM，
保留两次尝试中已通过核验且原目标更好的候选。两次调用共享时限，切换不改变容差、线程或并行选项。
LP/MIP 保留原生算法；显式 `solver` 优先，显式选择的算法不会自动切换。
例如 `OptxConfig(solver="qpasm")` 固定使用 ASM，适合已确认受益的高频小 QP。
HiPO 默认编入；不可用时自动保留原生 QP 路线，显式请求不可用的 HiPO 会报错。
组合搜索内部 worker 继续遵守既有调度与线程配置。

`backend_attempts` 记录从实际 Highs 对象读回的顶层 `solver`、`parallel`、`threads`，
以及 `algorithm_source` 和 `quadratic_variables`；`hipo_available` 表示构建能力。
ASM 备选记录的 `phase` 为 `"qp_asm_fallback"`，`fallback_reason` 为 `"backend_error"`、
`"unknown"` 或 `"numerical_verification"`；`time_limit_s` 是该次后端调用的剩余时间额度。
`verification.candidate_phase` 表示最终交付候选来自哪一次尝试，不一定是最后一次调用。
`threads=0` 表示原生自动选项，不是观测到的活跃线程数，已有线程池可能继续复用。
这些字段也不代表 MIP 子求解器的实时配置或整个进程的线程硬上限。
`mip_rel_gap` 必须有限且非负；其他原生参数的有效性由求解路径与后端检查。

OptX 的模型转换资源配置与 HiGHS 参数分别传递，可通过以下参数控制：

| 参数 | 默认值 | 计量范围 |
| --- | --- | --- |
| `max_variables` | 1,000,000 | 标量变量，包含展开产生的辅助变量 |
| `max_constraints` | 2,000,000 | 转换生成的约束行 |
| `max_nonzeros` | 20,000,000 | 稀疏系数，包含转换工作区 |
| `max_compile_bytes` | 2,147,483,648 | 矩阵与转换工作区的估算字节数 |
| `max_collection_slots` | 4,096 | 有限集合展开槽位 |
| `max_quadratic_work` | 50,000,000 | 每个二次目标的表达式展开、合并和凸性证明操作 |

这些参数必须是正整数；字节数上限为 `2**63 - 1`，其他计数上限为 `2**31 - 1`。字节预算不包含原始模型和后端搜索内存，也不是进程内存硬限制。
例如 `OptxConfig(max_variables=500_000, max_compile_bytes=3 * 1024**3)`。
超限时抛出 `OptxUnsupportedError`，问题详情包含转换阶段、实际规模、上限和来源。
二次操作预算与约束行数独立；多项式缓存、临时结果及凸性证明的 Schur 填充同样受非零项和估算字节预算限制。
超限不跳过凸性证明，也不放宽数值容差。
仿射和二次长链式加法可直接转换，无需通过增加 Python 递归上限来支持。
静态 lambda 调用、聚合、map 和固定数组下标行可与连续凸二次目标组合；二次目标仍只能排在最后优先级。
根合取及布尔条件选中的闭比较可直接转为约束行；连续比较作为一般条件或布尔值时仍受原有限制。
大数组的 Count、固定一维下标及适用的纯符号调用可按需读取；请求完整容器仍计入原集合槽位预算。
完整整数派生集合和 List 使用压缩表示，保留原顺序、唯一性和结果恢复语义。
整数常数除模可避免逐值建表；`//` 向负无穷取整，`%` 的余数跟随分子符号。
最小化 `abs`、`max` 和最大化 `min` 的适用单调目标可保留为连续模型，
也可与连续凸 QP 组合。表达式被一般约束、反向目标或除法等精确运算复用时，仍要求完整函数图和所需有限界。
整数时间轴上的适用区间加权和、根 All 资源比较按覆盖长度或起止事件转换，
支持缺席、零长度及不依赖时间的动态整数需求；一般时间相关函数仍受通用展开预算限制。
凸性核验无法完成时报告 `quadratic_convexity_unverified`，与已确认非凸的
`nonconvex_quadratic_objective` 区分；两者均不进入后端求解。

未知选项、后端不接受的值或超出允许范围的值会抛出 `ValueError`，包含选项名与传入值；
即使模型没有决策变量也会检查。主搜索保留 HiGHS 原生数值默认值：
`primal_feasibility_tolerance=1e-7`、`dual_feasibility_tolerance=1e-7`、
`mip_feasibility_tolerance=1e-6`、`small_matrix_value=1e-9`。
显式配置的有效值会传给后端并从后端读回记录。
`1e-11` 低于后端允许下限，不能用来进一步收紧 primal 和 MIP 可行性容差。

可以显式配置 MIP 容差，并按需查询本次运行实际记录的有效值：

```python
solution = model.solve_optx(
    config=OptxConfig(mip_feasibility_tolerance=1e-7),
)
verification = solution.diagnostics.get("verification", {})
if solution.feasible and verification.get("performed"):
    print(verification["tolerance"], verification["max_row_violation"])
```

`primal_feasibility_tolerance=1e-5` 不会把 MIP 原模型验解容差改成 `1e-5`。
`mip_strict_numerics=True` 收紧内部 LP 的数值策略，也不会自动将 MIP 候选验解容差改成 `1e-10`。
无候选或未完成验解时，`verification.tolerance` 的占位值不能作为有效验收容差。
不同输入格式、显式配置、重试阶段和平台的验证报告，应分别列明这些条件。

OptX 使用固定提交的个人 HiGHS fork，MIP 默认启用 `mip_strict_numerics`；
可显式设为 `False` 使用后端兼容策略。HiGHS 的顶层 LP 容差不直接控制 MIP 的 LP 松弛：
兼容模式的松弛 primal 容差取 MIP 容差，
dual 容差取 `max(1e-10, MIP 容差 * 0.1)`；严格模式的内部 primal 容差不大于 `1e-10`，
dual 容差为 `1e-10`。候选的 MIP 可行性容差仍默认 `1e-6`。
显式顶层容差与内部策略不同时，或显式开启严格模式时，求解前发出 `RuntimeWarning`。

`OptxConfig(numerical_recheck=False)` 默认禁止因不可行报告或验解失败而提高精度重跑主搜索。
需要时可显式设为 `True`；启动前会告知采用的严格 MIP 容差，重试共享剩余总预算。
原模型的约束和变量域始终验解，不能通过验证的候选不会作为可行解交付。
受限修复后的最优性认证可能使用更严格的 LP 精度，启动前告知并单独记录
`certification_started`、`certification_time_s`。严格 MIP 路径的后端不可行结论保留为 `infeasible`；
显式选择兼容策略且未严格复核时，其不可行报告返回 `unknown`，原始状态仍可查询。
该选项不控制自动 QP 的 HiPO→ASM 切换；后者保持原精度，也不重新分配总时限。

不支持的模型抛出 `OptxUnsupportedError`，可读取 `.issues` 中的约束 / 目标名称、表达式路径和限制说明。该入口不回退到本地搜索。

返回值必须通过原模型验解。后端结果只有很小的数值误差时，求解器可固定整数并受限地调整连续变量，
重新检查全部约束和变量边界，并在同一修复范围内按原目标精修。目标值按最终赋值计算。
对有限域标量、单个仿射目标模型，OptX 使用覆盖原表达式舍入误差的全域放松重新求界；
最终可行解与有效全局界的差距达到声明精度时返回 `optimal`，否则返回 `feasible`。
精度由 `OptxConfig(optimality_abs_tol=1e-8, optimality_rel_tol=1e-10)` 配置，
允许间隙为两者中较大者，其中相对精度乘以 `max(1, abs(目标值), abs(全局界))`。
这两项不放宽约束或变量域。修复后的展开、二次和多目标模型当前保留 `feasible`。
`solution.diagnostics` 中的 `numerical_repair`、`repaired_decisions` 和 `maximum_repair`
分别说明是否修复、修复的决策数量和最大改动。

`solution.status` 描述最终交付结果；`solution.diagnostics["backend_status"]`
单独保存最后一次 HiGHS 运行的原始状态文本，例如 `"Optimal"`、`"Infeasible"`。
`optimality_certified` 说明修复后是否通过再次认证，`optimality_bound`、`optimality_gap`、
`optimality_tolerance` 给出界、实际间隙和允许间隙；`optimality_method`、`optimality_reason`
说明认证方法或未认证原因。未修复但仅容差可行的候选也会保留 `feasible`，
不会因后端返回 `Optimal` 就变为 `optimal`；最终结论和后端信息的读取方式见[结果与诊断](../guide/results.md)。
`backend_objectives` 保存修复前的线性化目标值，便于与最终原图目标比较。

`solution.best_bound`、`solution.gap`、`solution.solve_time` 提供后端报告的单目标界限、
相对间隙比值和搜索运行秒数，对应诊断的 `backend_best_bound`、`backend_gap`、
`backend_solve_time_s`。它们未经独立认证，不替代 `optimality_bound` 或 `optimality_gap`。
后端间隙以修正前的后端候选目标计算，可能与最终交付赋值不同；不可用时返回 `None`。
多目标模型没有单一后端界限或间隙，只按阶段提供运行记录。

`backend_attempts` 按运行顺序记录原始 `status`、`status_code`、
`primal_feasibility_tolerance`、`dual_feasibility_tolerance`、`mip_feasibility_tolerance`、
`small_matrix_value`，以及 `requested_numerics`、`phase`、`numerical_warnings` 和
`infeasibility_recheck`、`verification_recheck`。顶层 `backend_status_code` 对应最后一次运行，
`infeasibility_rechecked`、`verification_rechecked` 分别表示是否因不可行报告、
原模型验解失败而执行过严格容差复核。
每次运行还记录 `objective_index`、`objective_value`、`best_bound`、`gap` 和 `solve_time_s`。
目标值和界限已恢复为原目标的最小化或最大化方向；缺失或非有限值返回 `None`。
没有实际进入 HiGHS 时，原始状态和状态码为 `None`，运行记录为空。

运行记录还包含 `mip_strict_numerics` 与 `mip_proof_feasibility_tolerance`，后者描述
预处理和 LP 原始可行性计算的策略精度；`mip_lp_dual_tolerance` 记录对应 dual 策略。
这些 MIP 内部值根据固定后端策略推导，`mip_precision_source="pinned_backend_policy"`、
`mip_subsolver_options_observed=False` 明确表示未直接观测运行中的内部 LP 对象。
顶层数值选项则由实际 Highs 对象读回。
`backend_repository`、`backend_revision`、`backend_version`、`backend_githash`
记录依赖来源及实际内核身份，避免只凭版本字符串判断是否包含修复。

原模型验证仍失败时抛出 `RuntimeError`，可读取异常的 `.diagnostics`。
其中 `verification_failures` 至多包含 64 个失败项，提供 `kind`、`expression_id`、`name`、
`lhs`、`rhs` 和 `residual`；`compiled_constraint_index` 是编译后的约束序号。
用户原始模型的约束宜通过名称和表达式 ID 定位。

整数取整可能使关联连续变量违反原约束。独立 OptX 可固定取整后的整数，在受限邻域内联合
修正连续变量，然后重新检查全部原约束、布尔条件和变量域；不提高交付容差，也不改变主搜索
选项。修正目标留出数值余量，修正阶段共享剩余预算，最多使用两秒后端运行时间。
`verification` 中的 `completion_attempted`、`completion_succeeded`、`completion_status`、
`completion_time_s` 记录这一步。修正不能通过全量验解时仍拒收候选。

<!-- public-api: optagent.solve_optx -->
<!-- public-api: optagent.OptxConfig -->

## solve_cpsat · OR-Tools CP-SAT

```python
from optagent import CpSatConfig

solution = model.solve_cpsat(
    config=CpSatConfig(num_search_workers=4, random_seed=1),
    time_limit_s=30.0,
)
```

| `CpSatConfig` 参数 | 说明 |
| --- | --- |
| `max_time_in_seconds` | 未显式传入 `time_limit_s` 时的总预算来源 |
| `num_search_workers` | CP-SAT 工作线程设置 |
| `random_seed` | 后端随机种子 |
| `relative_gap_limit` / `absolute_gap_limit` | 相对 / 绝对间隙限制 |
| `log_search_progress` / `log_to_stdout` | 后端日志设置 |
| 其他关键字参数 | 传递给 CP-SAT 原生参数 |

配置中值为 `None` 的选项不传递。显式 `time_limit_s` 优先，未设置时依次使用配置中的时间限制和 30 秒默认值。

<!-- public-api: optagent.solve_cpsat -->
<!-- public-api: optagent.CpSatConfig -->

## solve_mathopt · OR-Tools MathOpt

```python
from optagent import MathOptConfig

solution = model.solve_mathopt(
    config=MathOptConfig(solver_type="GSCIP", threads=4),
    time_limit_s=30.0,
)
```

| `MathOptConfig` 参数 | 说明 |
| --- | --- |
| `solver_type` | 默认 `"GSCIP"`，所选后端须可用且支持模型 |
| `time_limit` | 秒数或 `datetime.timedelta`；显式总预算未设置时采用 |
| `threads` | 后端线程设置 |
| `relative_gap_tolerance` / `absolute_gap_tolerance` | 相对 / 绝对间隙容差 |
| 其他关键字参数 | 传递给 MathOpt 原生求解参数 |

CP-SAT 和 MathOpt 的顶层入口不接受 `objective_threshold`。预算覆盖整个转换、求解与验证流程；验证前超时返回 `UNKNOWN`，没有可读变量值。

<!-- public-api: optagent.solve_mathopt -->
<!-- public-api: optagent.MathOptConfig -->

## 检查配置与结果

`CpSatConfig.to_parameters()`、`MathOptConfig.to_parameters()` 和 `OptxConfig.to_options()` 返回已设置选项的字典。MathOpt 的后端选择单独保存在 `solver_type` 中，不在 `to_parameters()` 字典内。

结果字段与状态处理见[结果与诊断](../guide/results.md)。
