# 求解与配置

先阅读[路径选择](../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 -->

## 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 秒。`mip_rel_gap` 必须有限且非负，`threads` 为正整数。其他 HiGHS 选项可通过关键字传入 `OptxConfig`；有效性由求解路径与后端检查。

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

<!-- 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)。
