# 求解与配置

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

## solve

自动协调适用的启发式与精确求解能力，通常从此入口开始。模块函数 `solve(model, ...)` 使用相同参数；以下参数均按名称传入。

```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,
    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` |
| `threads` | `None` | 可选正整数，范围为 `1` 到 `1024`；默认采用机器报告的硬件并发数 |

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

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

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

默认启用确定性模式。包含外部 Python 回调时，需显式传入 `deterministic=False`；
其他模型也可以用该设置切换到吞吐模式。两种模式的耗时和解质量可能不同，可用业务模型在相同预算下比较。

默认根据系统和容器可用内存调整搜索资源。
例如 `model.solve(memory_limit_bytes=2 * 1024**3)` 将受管内存的目标限制在 2 GiB 以内；系统余量不足时实际目标可能更低。

该参数覆盖搜索工作区、候选存储和算法辅助空间，是协作式估算目标，不是整个进程的硬内存上限。
模型转换、Python、求解后端和单次临时分配仍可能产生额外占用。资源不足时会保留已有的已验证结果，
通过 `solution.diagnostics["termination_reason"]` 报告 `memory_limit`，不表示模型无解。
可通过 `solution.diagnostics.get("memory_policy")` 查看本次实际使用的内存策略。

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

`solution.feasible=False` 表示本次没有交付可行解。到达时间限制且没有可行解时，
`solution.status` 为 `deadline`；其他终止情况保留其停止原因。
只有取得全局不可行证明时才返回 `infeasible`，并在 diagnostics 中标记 `globally_infeasible=True`。

## 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` 除外。
模型声明在整个会话中冻结；外部函数对相同输入的含义也必须保持稳定。
此会话接口使用异步吞吐搜索。`solve_heuristic()` 和 `solve_optx()` 仍使用一次性调用方式；
`session.finish()` 的 `solver_name` 当前为 `search`。

最终 `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_heuristic

```python
from optagent import GaOptions

solution = model.solve_heuristic(
    time_limit_s=30.0,
    threads=4,
    ga=GaOptions(population_size=64, search_definition="automatic"),
    objective_threshold={0: 100},
)
```

通用运行和停止参数直接按名称传入；GA 专用参数可通过 `ga=GaOptions(...)` 单独配置。模块函数 `solve_heuristic(model, ...)` 使用相同参数。返回 `Solution`，
`solver_name` 为 `"heuristic"`，原决策与已计算表达式的值写回模型。该入口只运行 GA，
不自动切换其他求解引擎，也不要求提供初始可行解。

`search_definition="automatic"` 在 GA 内部选择适用的搜索方案；可显式指定
`decision`、`cvrp_tour`、`schedule_priority` 或 `machine_dispatch`。
显式方案不适用时抛出 `HeuristicUnsupportedError`，其 `search_definition` 和 `diagnostics`
保留请求方案与准备原因。耗时、内存或准备额度耗尽单独报告，不作为数学不支持或不可行证明。

### 基础运行参数

以下参数直接传给 `solve_heuristic()`。不接受 `config` 包装；可选的 `ga` 仅配置遗传算法。

| 参数 | 默认值 | 含义与范围 |
| --- | --- | --- |
| `time_limit_s` | `30.0` | 正数且有限；总协作式预算，单位秒 |
| `threads` | `None` | 自动采用机器硬件并发数；可指定 1～1024 |
| `random_seed` | `1` | 0～`2**64 - 1` |
| `deterministic` | `True` | 固定逻辑顺序；`False` 使用吞吐执行 |
| `evaluation_limit` | `None` | 可选正整数；包括 GA 自行生成初值及子代内部试算；外部函数所需的共享初始化不计入此项 |
| `memory_limit_bytes` | `None` | 可选正整数，最大 `sys.maxsize`；协作式受管内存目标，不是进程 RSS 硬限制 |
| `external_evaluation_limit` | `None` | 可选正整数；限制实际启动的外部评价次数 |
| `log_level` | `"on"` | `"off"`、`"on"` 或 `"debug"` |
| `ga` | `None` | 可选 `GaOptions`；省略时使用默认遗传参数 |

### GaOptions

GA 的搜索表示、种群及遗传操作通过 `ga=GaOptions(...)` 设置。该对象不可变；`to_options()` 返回独立字典。

| 参数 | 默认值 | 含义与范围 |
| --- | --- | --- |
| `search_definition` | `"automatic"` | 上述五种 GA 搜索方案选择 |
| `population_size` | `32` | 每个搜索方案的目标种群大小，2～65536 |
| `minimum_decision_distance` | `1` | 正整数；初始种群的最小决策距离 |
| `mutation_enabled` | `True` | 是否安排独立变异任务 |
| `mutations_per_parent` | `1` | 每个父代的独立变异任务数，0～1024；启用时与种群大小之积不超过 65536 |
| `crossover_enabled` | `True` | 是否安排交叉任务 |
| `crossovers_per_round` | `None` | 默认当轮实际父代数的一半；可指定 0～65536，0 关闭交叉任务 |
| `crossover_mutation_rate` | `None` | 交叉子代再变异的概率，0～1；未配置时为 0，不控制独立变异任务 |
| `restart_after` | `64` | 正整数；停滞与重启阈值，不是总迭代上限 |
| `preparation_work_limit` | `None` | 可选非负整数；额外限制准备工作，仍受总预算约束 |
| `generation_evaluation_limit` | `None` | 单个子代任务的内部试算上限，0～65536；默认 16，不含最终评价确认，耗尽后放弃该任务 |
| `max_in_flight` | `None` | 可选正整数；同时进行的任务数上限，默认自动确定 |
| `max_resident_populations` | `4` | 正整数；同时保留的种群数上限 |
| `max_pending_preparations` | `16` | 正整数；等待准备的搜索方案数上限 |
| `diversity_archive_size` | `8` | 非负整数；多样性档案容量 |

总时间包含模型准备、搜索、结果校验和返回。正在执行的操作和清理可能使调用稍晚返回。
结果校验未完成时，不提供可读取的解；读取变量值前仍须检查 `solution.feasible`。

确定性复现还要求相同模型、运行版本、种子、线程配置和逻辑评价预算，且未受时间或资源压力中断。
外部 Python 回调要求 `deterministic=False`，其调用次数统计规则与 `solve()` 相同。
整数目标阈值保留 int64 精度。达到阈值或搜索停滞不意味着已证明最优；
`feasible=False` 只表示本次没有取得可用的可行解，不能据此判断模型无解。

<!-- public-api: optagent.GaOptions -->
<!-- public-api: optagent.GaOptions.to_options -->
<!-- public-api: optagent.solve_heuristic -->
<!-- public-api: optagent.HeuristicUnsupportedError -->

## solve_optx

```python
from optagent import HighsConfig

solution = model.solve_optx(
    config=HighsConfig(time_limit_s=30.0, relative_gap_limit=0.01, threads=4),
    objective_threshold=None,
)
```

`config=None` 使用 `HighsConfig()`。传入 `HighsConfig` 选择 HiGHS，传入 `CpSatConfig` 选择原生 CP-SAT。
配置类型就是后端声明，不需要 `backend` 参数或其他包装对象。
其他类型、字典或多个配置组成的容器在 Python 端立即触发 `TypeError`。

两个具体配置都直接接收以下运行字段：

| 参数 | 默认值 | 含义 |
| --- | --- | --- |
| `time_limit_s` | `30.0` | 正数且有限；覆盖序列化、准备、搜索、验解和交付的协作式总预算 |
| `threads` | `None` | 原生自动选择，或指定 1～1024 的总物理线程额度 |
| `random_seed` | `1` | 整数，范围为 0 到 `2**31 - 1` |
| `log_level` | `"off"` | `off`、`on` 或 `debug` |
| `relative_gap_limit` / `absolute_gap_limit` | `0.0` | 非负有限数；原生停止公式依后端解释，不等于严格最优证明 |

返回结果的 `solver_name` 为 `optx`，`diagnostics["backend"]` 记录实际后端 `highs` 或 `cp_sat`。
所选后端不支持模型时，统一抛出 `OptxUnsupportedError`，`.issues` 保留原表达式位置、实际后端和原因，不自动切换。

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

### HighsConfig

使用 HiGHS 求解线性、混合整数线性或受支持的连续凸二次模型。模型条件见[求解指南](../guide/solvers.md)。

常用调优参数可直接作为 `HighsConfig` 的命名参数传入。例如，使用四个线程并开启 MIP 并行搜索：

```python
from optagent import HighsConfig

solution = model.solve_optx(
    config=HighsConfig(time_limit_s=30.0, threads=4, parallel="on"),
)
```

省略 `threads` 时由后端自动选择。`parallel="choose"` 是原生默认值，不自动开启 MIP 树并行；
需要该能力时显式传入 `parallel="on"`，并提供大于 1 的可用线程数。并行效果取决于模型和机器。

#### 数值与结果校验

| 参数 | 默认值 | 含义 |
| --- | --- | --- |
| `numerical_recheck` | `False` | 是否允许在剩余预算内，以更严格精度重试不可行报告或未通过校验的候选 |
| `optimality_abs_tol` | `1e-8` | 非负有限数；原模型最优性确认的绝对允许间隙 |
| `optimality_rel_tol` | `1e-10` | 非负有限数；原模型最优性确认的相对允许间隙 |

可用原生选项调整数值容差，例如 `HighsConfig(mip_feasibility_tolerance=1e-7)`。
默认 primal、dual 和 MIP 可行性容差分别为 `1e-7`、`1e-7` 和 `1e-6`；
LP 容差不直接决定 MIP 候选的验解容差。`mip_strict_numerics` 默认开启，控制 MIP 内部数值策略；
改变此策略也不会自动改变交付可行解的容差。设置影响数值策略时，求解器可能发出 `RuntimeWarning`。

交付候选始终经过原模型约束和变量范围校验。小幅数值修正后，目标值按最终赋值重新计算。
支持原模型最优性确认的情形中，允许间隙为绝对精度与
`optimality_rel_tol * max(1, abs(目标值), abs(全局界))` 中的较大者；这两项不放宽可行性要求。
无法确认最优但已有可行解时返回 `feasible`。未经确认的后端不可行报告也不会直接作为原模型无解的结论。

连续凸二次目标默认自动选择算法，必要时在剩余预算内尝试备选算法。
这类切换保持原精度，不受 `numerical_recheck` 控制。显式指定 `solver` 时遵循该选择。

#### 模型转换资源

| 参数 | 默认值 | 含义 |
| --- | --- | --- |
| `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 | 每个二次目标的展开和凸性检查工作量 |

以上均为正整数；`max_compile_bytes` 最大为 `sys.maxsize`，其他计数最大为 `2**31 - 1`。
例如 `HighsConfig(max_variables=500000, max_compile_bytes=3 * 1024**3)`。
转换内存预算不包含后端搜索内存，也不是进程内存的硬限制。
超出转换能力或上限时抛出 `OptxUnsupportedError`，`.issues` 提供原因和位置。

#### 原生参数与诊断

`presolve="on"`、`parallel="on"` 等 HiGHS 选项直接传入配置对象。
时间、线程、随机种子、日志和停止间隙使用上方统一字段，例如
`relative_gap_limit=0.01`，不能再用 `mip_rel_gap` 覆盖。
未知原生选项或无效值会在求解时抛出 `ValueError`。

`diagnostics["backend_attempts"]` 记录各次后端运行的状态、目标、界限、间隙、耗时和生效参数。
`backend_status` 表示最后一次后端运行的原始状态；`optimality_certified` 表示是否通过原模型最优性确认。
这些诊断可能随执行阶段而缺失，使用 `.get()` 按需读取。
原模型校验失败且不能交付候选时会抛出 `RuntimeError`，可从异常的 `.diagnostics` 定位原因。
结果字段见[结果与诊断](../guide/results.md)。

<!-- public-api: optagent.HighsConfig -->
<!-- public-api: optagent.HighsConfig.to_options -->

### CpSatConfig

```python
from optagent import CpSatConfig

solution = model.solve_optx(config=CpSatConfig(time_limit_s=30.0, threads=4))
```

使用内置 OR-Tools CP-SAT 求解整数兼容模型，无须安装 Python `ortools`。
继续使用 `OptModel` 建模，通过 `CpSatConfig()` 设置上方通用运行参数及以下资源选项。
模型不受支持时抛出 `OptxUnsupportedError`，可读取 `.issues` 查看原因和对应位置；不会自动切换到其他入口。
因时间或资源限制停止时，是否取得可行解以 `solution.feasible` 为准。

| `CpSatConfig` 参数 | 默认值与说明 |
| --- | --- |
| `max_variables` / `max_constraints` | `None`，按编译内存额度估算变量与约束上限 |
| `max_nonzeros` | `None`，按编译内存额度估算标量项上限 |
| `max_compile_bytes` | `None`，按可用内存选择模型转换的估算内存预算；不包含后端搜索占用 |
| `max_collection_slots` | `None`，按编译内存额度估算通用有限容器展开上限 |
| `max_native_bytes` | `None`，按可用内存选择搜索软预算，并为系统保留余量 |
| 开放的 CP-SAT 原生选项 | 直接作为命名参数传入，例如 `cp_model_presolve=False`；求解时检查名称和取值 |

六项资源参数均可传入正整数，固定对应上限；未指定的参数继续自动选择。
`max_variables`、`max_constraints`、`max_collection_slots` 最大为 `2**31 - 1`，其余最大为 `2**63 - 1`。
`solution.diagnostics.get("cp_sat", {})` 中的 `limit_compile_bytes`、
`limit_native_bytes`、`limit_collection_slots` 和 `limit_threads` 可用于核对实际设置。
例如 `CpSatConfig(max_collection_slots=4096, max_native_bytes=512 * 1024**2)`
保留这两项固定上限，其余参数自动选择。

`max_native_bytes` 是搜索期间的软内存预算，不是整个进程的硬内存上限。
同一进程中的其他内存占用也可能影响停止时机。资源不足时可能提前结束；已通过校验的可行解仍可保留。

原生参数仅开放部分调优项，例如 `cp_model_presolve`、`linearization_level` 和 `stop_after_first_solution`。
时间、线程和日志仍须使用统一字段；不接受 `max_time_in_seconds`、`num_search_workers` 或 `log_search_progress` 覆盖，也不支持枚举全部解。

`objective_threshold` 与其他入口一样使用从零开始的目标索引，整数阈值限于 `[-2^53, 2^53]`。
达到阈值返回可行候选，不升级为最优。多目标逐级求解，前级确认最优后才继续；后级中止保留已验证候选。
CP-SAT 不隐式离散化连续变量。整数转换当前仍受精确数值范围与有限展开约束。

`solver_name` 为 `"optx"`，`diagnostics["backend"]` 为 `"cp_sat"`。`diagnostics` 可查询终止原因与各阶段耗时；时间到期或取消后，结果清理可能使调用稍晚返回。

<!-- public-api: optagent.CpSatConfig -->
<!-- public-api: optagent.CpSatConfig.to_options -->
<!-- public-api: optagent.OptxCompatibilityIssue -->
<!-- public-api: optagent.OptxUnsupportedError -->

## 检查配置与结果

`HighsConfig` 与 `CpSatConfig` 的 `to_options()` 返回独立字典；原生选项在配置中不可变，修改导出的嵌套字典或列表不回写配置。

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