3.9 KiB
3.9 KiB
异步推理并保存结果 (SimInferService 流程解析)
在仿真计算完成后,系统需要将每一步的计算结果发送给 Python 推理服务,以预测关键指标(如 Keff)。这个过程由 SimInferService 和 DeviceInferService 协同完成。
1. 推理数据不是一次性全部发送
核心结论: 并不是把所有结果封装为一个大 Request 调用一次推理接口,而是进行了精细的多级分组后,分别调用推理接口。
因为不同的设备、不同的材料、以及用户为设备配置的不同算法,都对应着不同的模型文件(Pipeline/Model)。一次推理请求只能加载一个模型文件,所以必须将必须使用同一模型的数据分在同一批次发送。
2. 数据转换与分组流程
整个流程分为两部分:在 SimInferService 中进行一级分组,然后在 DeviceInferService 中进行二级和三级分组。
2.1 第一级分组:按设备类型 (DeviceType) 分组
位置: SimInferService.asyncInferAndSave
- 展平时间轴 (Flatten Timeline):仿真引擎返回的
SimContext是按时间步(Step)组织的。代码首先遍历每个时间步,将散落的属性(如液位、浓度)按unitId聚合。 - 注入元数据:通过
SimUnit,将静态属性(如设备固有的直径、高度)以及新加入的materialType(如 U, Pu)合并到设备的属性集合中,生成DeviceStepInfo对象。 - 一级分组:将所有时间步的
DeviceStepInfo根据 设备类型 (deviceType) 放入Map<String, List<DeviceStepInfo>> groupedDevices中。- 例如:所有的 CylindricalTank 数据在一组,AnnularTank 在另一组。
2.2 第二级分组:按算法类型 (AlgorithmType) 分组
位置: DeviceInferService.processDeviceInference
- 读取配置:获取全局配置的算法(如 GPR),以及针对特定设备独立配置的算法(
deviceAlgoConfig)。 - 二级分组:在某一个设备类型(如 CylindricalTank)的数据列表中,进一步按 算法类型 (
algoType) 分组。- 例如:CylindricalTank 组被拆分为 -> 使用 GPR 算法的批次、使用 MLP 算法的批次。
2.3 第三级分组:按材料类型 (MaterialType) 分组
位置: DeviceInferService.processDeviceInference
- 三级分组:在特定的算法批次中,进一步按 材料类型 (
materialType) 分组。- 例如:使用 GPR 的 CylindricalTank 批次被拆分为 -> 材料为 U 的批次、材料为 Pu 的批次。
3. 发起推理请求
经过上述三级分组后,我们得到了一个高度同质化的数据批次。对于每一个这样的最终批次:
- 查询模型:调用
algorithmModelService.getCurrentModelPath(algoType, deviceType, materialType)。因为这三个维度相同,所以它们一定使用同一个物理模型文件(.pkl)。 - 构建 Request:将这批数据(包含所有相关设备在所有时间步的状态)组装成一个
InferRequest。 - 发起调用:向 Python 服务发送 HTTP POST 请求
/v1/infer。 - 入库保存:Python 返回每个设备在每个时间步的预测结果(如
keff),Java 端解析这些结果并批量保存到scenario_result表中。
4. 异常与状态管理
- 整个过程是加上了
@Async异步执行的,不会阻塞前端获取基础仿真数据。 - 如果某一个批次因为找不到模型(例如上述终端日志中
GPR + CylindricalTank + U的模型未配置),代码会记录错误并标记hasAnyError = true,然后跳过该批次(continue)。 - 兜底机制:如果所有的批次都失败或找不到模型(
!hasAnySuccess && hasAnyError),会抛出RuntimeException。这会被外层SimInferService捕获,并将该情景的状态更新为 "3" (失败)。