Commit e3a2cdde authored by Tong Li's avatar Tong Li

MP

parent 8dc6c378
=== MacroPlanner 优化器运行日志 === ===== MACROPLANNER DATA CONVERTER RUNNER START =====
=== 运行时间: 2026-08-17 15:49:44 === SceneId: B288477F1A594DB584C87EEA77880AA3
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.18)
16:20:33.307 [main] INFO c.a.m.MacroPlannerDataConverterRunner - Starting MacroPlannerDataConverterRunner using Java 1.8.0_201 on DESKTOP-MH00R44 with PID 25004 (F:\product\MES\hyh.apsj\target\classes started by Thinkpad in F:\product\MES\hyh.apsj)
16:20:33.311 [main] INFO c.a.m.MacroPlannerDataConverterRunner - No active profile set, falling back to 1 default profile: "default"
16:20:34.942 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode
16:20:34.949 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
16:20:35.050 [main] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 72 ms. Found 0 Redis repository interfaces.
16:20:35.854 [main] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'spring.datasource.dynamic-com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties' of type [com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
16:20:35.857 [main] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration' of type [com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration$$EnhancerBySpringCGLIB$$d9e9b6f5] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
16:20:35.879 [main] INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'dsProcessor' of type [com.baomidou.dynamic.datasource.processor.DsHeaderProcessor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
16:20:37.383 [main] INFO o.s.b.w.e.t.TomcatWebServer - Tomcat initialized with port(s): 8181 (http)
16:20:37.395 [main] INFO o.a.c.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8181"]
16:20:37.399 [main] INFO o.a.c.core.StandardService - Starting service [Tomcat]
16:20:37.400 [main] INFO o.a.c.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.83]
16:20:37.680 [main] INFO o.a.c.c.C.[.[localhost].[/] - Initializing Spring embedded WebApplicationContext
16:20:37.680 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 4290 ms
16:20:38.135 [main] INFO c.z.hikari.HikariDataSource - oracle - Starting...
16:20:39.856 [main] INFO c.z.hikari.HikariDataSource - oracle - Start completed.
16:20:39.857 [main] INFO c.b.d.d.DynamicRoutingDataSource - dynamic-datasource - add a datasource named [oracle] success
16:20:39.858 [main] INFO c.b.d.d.DynamicRoutingDataSource - dynamic-datasource initial loaded [1] datasource,primary datasource named [oracle]
16:20:41.489 [main] WARN c.b.m.c.i.DefaultSqlInjector - class com.aps.entity.ApsTimeConfig ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
_ _ |_ _ _|_. ___ _ | _
| | |\/|_)(_| | |_\ |_)||_|_\
/ |
3.5.6
16:20:44.683 [main] WARN c.b.m.c.i.DefaultSqlInjector - class com.aps.entity.Algorithm.OrderMaterialRequirement ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.
16:20:44.825 [main] INFO c.a.c.g.FileUploadController - 使用已存在的上传目录: F:\product\MES\hyh.apsj\uploads
16:20:46.168 [main] INFO o.s.b.a.w.s.WelcomePageHandlerMapping - Adding welcome page template: index
16:20:46.784 [main] INFO o.a.c.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8181"]
16:20:46.805 [main] INFO o.s.b.w.e.t.TomcatWebServer - Tomcat started on port(s): 8181 (http) with context path ''
16:20:46.828 [main] INFO c.a.m.MacroPlannerDataConverterRunner - Started MacroPlannerDataConverterRunner in 14.371 seconds (JVM running for 15.52)
数据库连接成功!
数据库: Oracle
16:20:46.952 [main] INFO c.a.m.d.MacroPlannerDataConverter - 开始转换场景数据到 macroplanner: sceneId=B288477F1A594DB584C87EEA77880AA3
16:20:47.598 [main] INFO c.a.m.d.MacroPlannerDataConverter - 排产周期: dimension=WEEK, periodCount=6, baseTime=2026-09-28T00:00, horizonEnd=2026-11-09
16:20:47.670 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载需求订单: 6 条 (时间范围过滤)
16:20:47.843 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载工艺路线: 9 条
16:20:47.993 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载工序: 9 条
16:20:48.230 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载工艺物料消耗: 91 条
16:20:48.460 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载物料主数据: 47 条
16:20:48.972 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载库存: 0, 采购: 0, ERP采购订单: 0, 待验: 18, 半成品在途: 39
16:20:49.024 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载工序设备关联: 9 条
16:20:49.152 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载设备资源: 7, 设备信息: 7
16:20:49.522 [main] INFO c.a.m.d.MacroPlannerDataConverter - 加载设备产能日历(EquipShiftCapacity): 294 条 (时间范围过滤)
16:20:49.534 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 Material 业务对象: 47 个
16:20:49.534 [main] INFO c.a.m.d.MacroPlannerDataConverter - 排产周期: dimension=WEEK, periodCount=6, horizonEnd=2026-11-09
16:20:49.581 [main] INFO c.a.m.d.MacroPlannerDataConverter - 设备日产能(EquipShiftCapacity): 7 台设备, 1 台有节假日, 默认=16.0h
16:20:49.584 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 Product: 47, StockingPoint: 1
16:20:49.587 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_644735 '104车间-5号线灭菌水50支10ml' → 产品=A24001 最终库存点=SP_472 工序数=1
16:20:49.588 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_644735_660272' 产出 → A24001@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_643441 '10%葡萄糖注射液' → 产品=D61001U 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_643441_659747' 产出 → D61001U@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_643736 '甘露醇注射液' → 产品=D53014 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_643736_659348' 产出 → D53014@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_643663 '0.9%氯化钠注射液' → 产品=D89004U 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_643663_659422' 产出 → D89004U@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_643686 '乳酸左氧氟沙星氯化钠注射液' → 产品=D66546U 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_643686_659399' 产出 → D66546U@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_643717 '10%葡萄糖注射液' → 产品=D61001U 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_643717_659369' 产出 → D61001U@SP_472 (产出数: 0→1)
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 开始: R_644161 '甘露醇注射液' → 产品=D53014 最终库存点=SP_472 工序数=1
16:20:49.589 [main] INFO c.a.macroplanner.data.Routing - [路由展开] 单步路由: 'OP_644161_656815' 产出 → D53014@SP_472 (产出数: 0→1)
16:20:49.590 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 Routing: 7, Operation: 7
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 c7f9de08-358b-2247-8e45-07bdeecbe626 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-982b-ef49-8b11-f42c13f76058 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-98e4-de4b-8466-a0063eb17440 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ccd0dd08-b389-7c43-881b-3617e270e0c1 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-212d-9041-8008-534b9a5c5116 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-5a2b-fb4f-8e58-35c78327c069 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-abc0-564d-8fcf-07440af92534 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-9c2c-ba4a-8483-12dd4b747d64 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ccd0dd08-ab85-a84f-824b-68a9ad8078e9 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ced0dd08-3c6b-db40-86b7-09cc87bc86cf 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-8120-3f4e-8c4e-1f25bf980000 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-d0b9-8141-8e26-811f9a47d167 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.591 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-5752-a14c-8b95-43b8c1879d7b 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-8fe3-774e-8aa3-11fcd7c566b3 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-4aa1-4141-890f-b65b39cee506 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ced0dd08-1ff4-5541-8688-b667e88fdfb6 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-5d98-794b-82aa-fd0fa7a5ae55 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-a27e-dc47-8976-ec9ae348c2b1 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 d0d0dd08-275c-e24b-8267-3db6b56ec68f 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-b70d-134f-85c6-40cb6a8c07fd 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.592 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-e0e9-254b-8908-ed292fc0b2b0 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-092f-d44e-8504-f70fc6b8d214 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-e02c-3b4d-8710-a11570027450 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-36e2-d04b-8b0b-2ef4550eb809 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-d398-ec4c-8e50-e6365a3ea831 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ccd0dd08-278f-9e47-862a-061ccbadad7d 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 d0d0dd08-7a56-5b47-8046-ddebc3c82c32 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-5c2c-6f47-88fa-9c12f6423db9 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.593 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-2666-4f4a-8ef3-f4a2c3fce8dc 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-0c15-1b43-8bff-c81b34b6e5fe 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-6c88-db4a-85ff-8749f8dce853 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-73da-0d47-8f89-9b1e37b7dfbd 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-3922-1248-859b-6108cf0b47f0 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ccf9de08-be3d-7545-8eab-2dc71fee1294 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ced0dd08-35f5-a444-882a-e7ae29bb27cc 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-8608-ba49-8e97-db7c85c1b84d 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-a696-1544-89ca-8e41292db881 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.594 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cdd0dd08-9b80-714b-808c-9a90a588a8c7 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.595 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ced0dd08-a33d-d64b-88d3-7df77ee6c023 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.595 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 ccd0dd08-7b84-5a42-8650-79e5f044251f 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.595 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-0121-4b44-85f6-777dee3d1eba 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.595 [main] INFO c.a.m.d.MacroPlannerDataConverter - 为 MP 原材料 cfd0dd08-5b6a-e446-8dc2-4fa39855a2cb 创建通用采购 Operation (无供应商, 无限产能)
16:20:49.595 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 OperationInput: 75
16:20:49.596 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 InitialInventory: 0, InTransitSupply: 57
16:20:49.598 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 Period: 6 (dimension=WEEK, horizonEnd=2026-11-09), UnitPeriod: 48
16:20:49.598 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 SalesDemand: 16
16:20:49.600 [main] INFO c.a.m.d.MacroPlannerDataConverter - 构建 InventorySpec: 282, SupplySpec: 49
16:20:49.600 [main] INFO c.a.m.d.MacroPlannerDataConverter - 转换完成: products=47, stockingPoints=1, operations=49, routings=7, operationInputs=75, initialInventories=0, inTransitSupplies=57, salesDemands=16
Data converted:
Products: 47
StockingPoints: 1
Operations: 49
Routings: 7
OperationInputs: 75
InitialInventories: 0
InTransitSupplies: 57
SalesDemands: 16
Periods: 6
UnitPeriods: 48
16:20:49.603 [main] INFO c.a.m.data.DataValidator - ✅ 数据检查通过: 无错误, 无警告
DataValidator: valid=true
=== 开始构建 MacroPlanner 优化模型 === === 开始构建 MacroPlanner 优化模型 ===
[警告] ========== 数据检查: 4 个警告 ==========
[警告] ⚠️ 工序 OP_R1 leadTimeDays=1 天, 但第一个周期前无对应生产周期, 第1周期该产品无到货
[警告] ⚠️ 工序 OP_R1_Alt leadTimeDays=2 天, 但第一个周期前无对应生产周期, 第1周期该产品无到货
[警告] ⚠️ 工序 OP_R2 leadTimeDays=1 天, 但第一个周期前无对应生产周期, 第1周期该产品无到货
[警告] ⚠️ 工序 OP_R2_Alt leadTimeDays=3 天, 但第一个周期前无对应生产周期, 第1周期该产品无到货
[信息] ✅ 数据检查通过: 无错误, 4 个警告
[OK] 数据检查通过 [OK] 数据检查通过
[信息] ✅ 数据检查通过: 无错误, 无警告
[OK] 决策变量创建完成 [OK] 决策变量创建完成
[信息] ========== 开始构建约束 ========== [信息] ========== 开始构建约束 ==========
[信息] [1. 物料平衡] 确保每个 PISPIP 流入 = 流出 | +15约束 [信息] [1. 物料平衡] 确保每个 PISPIP 流入 = 流出 | +282约束
[信息] [2. BOM依赖需求] OperationDemandQty = PTQty × BOM因子 | +27约束 [信息] [2. BOM依赖需求] OperationDemandQty = PTQty × BOM因子 | +732约束
[信息] [3. 需求满足量汇总] DemandFulfillment = SalesDemandQty + OperationDemandQty | +15约束 [信息] [3. 需求满足量汇总] DemandFulfillment = SalesDemandQty + OperationDemandQty | +282约束
[信息] [4. 需求缺口联动] SalesDemandQty + DemandSlack >= DemandQuantity | +6约束 [信息] [4. 需求缺口联动] SalesDemandQty + DemandSlack >= DemandQuantity | +14约束
[警告] [4.5 工序产量一致] Routing 内相邻工序 PTQty 相等, 防止 WIP 堆积 → 未创建任何约束或变量! (可能数据为空) [警告] [4.5 工序产量一致] Routing 内相邻工序 PTQty 相等, 防止 WIP 堆积 → 未创建任何约束或变量! (可能数据为空)
[信息] [库存规格] 构建完成: 0 个使用安全库存天数, 45 个使用绝对数量 [信息] [库存规格] 构建完成: 0 个使用安全库存天数, 846 个使用绝对数量
[信息] [5. 库存规格] InvQty + Slack ≥ Min/Max/Target (支持天数模式) | +45约束 [信息] [5. 库存规格] InvQty + Slack ≥ Min/Max/Target (支持天数模式) | +846约束
[信息] [6. 产能约束] Σ(PTQty×coeff) ∈ [MinCapacity, MaxCapacity] | +9约束 [信息] [6. 产能约束] Σ(PTQty×coeff) ∈ [MinCapacity, MaxCapacity] | +42约束
[信息] [7. 供应规格] ΣPTQty + Slack ≥ Target/Min/Max | +14约束 [信息] [7. 供应规格] ΣPTQty + Slack ≥ Target/Min/Max | +98约束
[信息] [8. 批次大小] PTQty×QTPFactor + Slack ∈ [LotSize] | +24约束 [警告] [8. 批次大小] PTQty×QTPFactor + Slack ∈ [LotSize] → 未创建任何约束或变量! (可能数据为空)
[信息] [9. KPI汇总] TotalKPI = Σ 松弛变量 (12个KPI) | +10约束 | +12变量 [信息] [9. KPI汇总] TotalKPI = Σ 松弛变量 (12个KPI) | +10约束 | +12变量
[信息] ========== 约束构建完成: 总计 165 约束, 218 变量 ========== [信息] ========== 约束构建完成: 总计 2306 约束, 2928 变量 ==========
[OK] 约束与KPI汇总创建完成 [OK] 约束与KPI汇总创建完成
[OK] 目标函数创建完成 [OK] 目标函数创建完成
[OK] LP模型文件已导出: F:\product\MES\hyh.apsj\mp\lp\model.lp [OK] LP模型文件已导出: F:\product\MES\hyh.apsj\mp\lp\model.lp
模型统计: 变量=218, 约束=165 模型统计: 变量=2928, 约束=2306
=== 开始分层求解 === === 开始分层求解 ===
--- 第 1/4 层: 需求满足 (松弛=0%) --- --- 第 1/4 层: 需求满足 (松弛=0%) ---
SCIP Status : OPTIMAL presolving:
Solving Time (sec) : 0.01 (round 1, fast) 1616 del vars, 1668 del conss, 0 add conss, 631 chg bounds, 0 chg sides, 0 chg coeffs, 0 upgd conss, 0 impls, 0 clqs, 0 implints
Primal Bound : +1.950000e+04 presolving (2 rounds: 2 fast, 0 medium, 0 exhaustive):
需求缺口: 195.00 (系数=100.0, 惩罚=19500.00) 2193 deleted vars, 1694 deleted constraints, 0 added constraints, 715 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
已添加边界约束: 上层目标 ≤ 19500.00 0 implications, 0 cliques, 0 implied integral variables (0 bin, 0 int, 0 cont)
presolving detected infeasibility
Presolving Time: 0.00
SCIP Status : problem is solved [infeasible]
Solving Time (sec) : 0.00
Solving Nodes : 0
Primal Bound : +1.00000000000000e+20 (0 solutions)
Dual Bound : +1.00000000000000e+20
Gap : 0.00 %
SCIP Status : INFEASIBLE
Solving Time (sec) : 0.02
Primal Bound : +0.000000e+00
需求缺口: 0.00 (系数=100.0, 惩罚=0.00)
已添加边界约束: 上层目标 ≤ 0.00
--- 第 2/4 层: 产能约束 (松弛=0%) --- --- 第 2/4 层: 产能约束 (松弛=0%) ---
SCIP Status : OPTIMAL presolving:
Solving Time (sec) : 0.01 presolving (1 rounds: 1 fast, 0 medium, 0 exhaustive):
1616 deleted vars, 1668 deleted constraints, 0 added constraints, 631 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
0 implications, 0 cliques, 0 implied integral variables (0 bin, 0 int, 0 cont)
presolving detected infeasibility
Presolving Time: 0.00
SCIP Status : problem is solved [infeasible]
Solving Time (sec) : 0.00
Solving Nodes : 0
Primal Bound : +1.00000000000000e+20 (0 solutions)
Dual Bound : +1.00000000000000e+20
Gap : 0.00 %
SCIP Status : INFEASIBLE
Solving Time (sec) : 0.02
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
E0000 00:00:1787127650.835447 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.835997 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.854022 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.854375 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
Primal Bound : +0.000000e+00 Primal Bound : +0.000000e+00
产能超载: 0.00 (系数=20.0, 惩罚=0.00) 产能超载: 0.00 (系数=3.0, 惩罚=0.00)
已添加边界约束: 上层目标 ≤ 0.00 已添加边界约束: 上层目标 ≤ 0.00
--- 第 3/4 层: 业务KPI (松弛=5%) --- --- 第 3/4 层: 业务KPI (松弛=5%) ---
SCIP Status : OPTIMAL presolving:
presolving (1 rounds: 1 fast, 0 medium, 0 exhaustive):
1616 deleted vars, 1668 deleted constraints, 0 added constraints, 631 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
0 implications, 0 cliques, 0 implied integral variables (0 bin, 0 int, 0 cont)
presolving detected infeasibility
Presolving Time: 0.00
SCIP Status : problem is solved [infeasible]
Solving Time (sec) : 0.00
Solving Nodes : 0
Primal Bound : +1.00000000000000e+20 (0 solutions)
Dual Bound : +1.00000000000000e+20
Gap : 0.00 %
SCIP Status : INFEASIBLE
Solving Time (sec) : 0.01 Solving Time (sec) : 0.01
Primal Bound : +6.405000e+03 Primal Bound : +0.000000e+00
批次偏差: 466.67 (系数=10.0, 惩罚=4666.67) 批次偏差: 0.00 (系数=10.0, 惩罚=0.00)
目标库存偏差: 195.00 (系数=8.0, 惩罚=1560.00) 目标库存偏差: 0.00 (系数=8.0, 惩罚=0.00)
供应目标偏差: 81.67 (系数=8.0, 惩罚=653.33) 供应目标偏差: 0.00 (系数=8.0, 惩罚=0.00)
销售优先级: 475.00 (系数=-1.0, 惩罚=-475.00) 销售优先级: 0.00 (系数=-1.0, 惩罚=-0.00)
已添加边界约束: 上层目标 ≤ 6725.25 已添加边界约束: 上层目标 ≤ 0.00
--- 第 4/4 层: 软约束 (松弛=10%) --- --- 第 4/4 层: 软约束 (松弛=10%) ---
SCIP Status : OPTIMAL presolving:
Solving Time (sec) : 0.01 E0000 00:00:1787127650.869440 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
Primal Bound : +1.900000e+02 E0000 00:00:1787127650.869813 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.869967 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.870092 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.870214 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
presolving (1 rounds: 1 fast, 0 medium, 0 exhaustive):
1616 deleted vars, 1667 deleted constraints, 0 added constraints, 631 tightened bounds, 0 added holes, 0 changed sides, 0 changed coefficients
0 implications, 0 cliques, 0 implied integral variables (0 bin, 0 int, 0 cont)
presolving detected infeasibility
Presolving Time: 0.00
SCIP Status : problem is solved [infeasible]
Solving Time (sec) : 0.00
Solving Nodes : 0
Primal Bound : +1.00000000000000e+20 (0 solutions)
Dual Bound : +1.00000000000000e+20
Gap : 0.00 %
SCIP Status : INFEASIBLE
Solving Time (sec) : 0.02
Primal Bound : +0.000000e+00
超库存: 0.00 (系数=5.0, 惩罚=0.00) 超库存: 0.00 (系数=5.0, 惩罚=0.00)
欠库存: 38.00 (系数=5.0, 惩罚=190.00) 欠库存: 0.00 (系数=5.0, 惩罚=0.00)
最小供应不足: 0.00 (系数=5.0, 惩罚=0.00) 最小供应不足: 0.00 (系数=5.0, 惩罚=0.00)
最大供应超出: 0.00 (系数=5.0, 惩罚=0.00) 最大供应超出: 0.00 (系数=5.0, 惩罚=0.00)
📋 BOM关系验证: 求解失败! 状态: INFEASIBLE
P1 → 消耗: [S1@WH_Semi×2, R1@WH_RM×3], 被消耗于: [] ===== MACROPLANNER DATA CONVERTER RUNNER END =====
P2 → 消耗: [S1@WH_Semi×1], 被消耗于: [] E0000 00:00:1787127650.891040 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
S1 → 消耗: [R2@WH_RM×2], 被消耗于: [P1@WH_FG×2, P2@WH_FG×1] E0000 00:00:1787127650.891428 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
R1 → 消耗: [], 被消耗于: [P1@WH_FG×3] E0000 00:00:1787127650.891629 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
R2 → 消耗: [], 被消耗于: [S1@WH_Semi×2] E0000 00:00:1787127650.891779 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
E0000 00:00:1787127650.891958 31160 linear_solver.cc:1891] No solution exists. MPSolverInterface::result_status_ = MPSOLVER_INFEASIBLE
========== 网状BOM + MRP 排产求解 ========== [信息] Pausing ProtocolHandler ["http-nio-8181"]
成品(根节点) 2 种:P1 P2 [警告] Failed to unlock acceptor for [http-nio-8181] because the local address was not available
生产品 5 种:P1 P2 S1 R1 R2 [信息] Stopping service [Tomcat]
周期 3 天 [严重] Socket accept failed
[信息] Stopping ProtocolHandler ["http-nio-8181"]
[信息] Destroying ProtocolHandler ["http-nio-8181"]
═══════════════════ 第 1 天 ═══════════════════ 16:20:53.719 [main] INFO c.b.d.d.DynamicRoutingDataSource - dynamic-datasource start closing ....
▶ 产线视图(按产线分组) 16:20:53.719 [main] INFO c.z.hikari.HikariDataSource - oracle - Shutdown initiated...
【L1】产能10h 16:20:54.287 [main] INFO c.z.hikari.HikariDataSource - oracle - Shutdown completed.
Make-P1(P1): 40件 (耗时4.0h) 16:20:54.287 [main] INFO c.b.d.d.DynamicRoutingDataSource - dynamic-datasource all closed success,bye
利用率: 4/10 h (40%)
【L2】产能10h
Make-S1(S1): 50件 (耗时1.7h)
利用率: 2/10 h (17%)
【L3】产能10h
Make-P2(P2): 20件 (耗时3.3h)
利用率: 3/10 h (33%)
【SuA】产能无限 (供应商)
Procure-R1(R1): 200件 (耗时24.0h)
【SuB】产能无限 (供应商)
Procure-R1-Alt(R1): 150件 (耗时36.0h)
【Su1】产能无限 (供应商)
Procure-R2(R2): 446件 (耗时71.4h)
【Su2】产能无限 (供应商)
Procure-R2-Alt(R2): 150件 (耗时45.0h)
▶ 产品视图(汇总各产线产量)
P1@WH_FG: 期初库存20件 → 期末库存0件 [目标20/最小5/最大150] | 到货共40件 [L1(到货40件)] 生产中40件 ⚠需求缺口195件 | 销售255/450件 ⚠未满足195件 [库存耗尽(可用60件, 需求450件)]
P2@WH_FG: 期初库存10件 → 期末库存0件 [目标15/最小3/最大120] | 到货共20件 [L3(到货20件)] 生产中20件 | 销售30/30件
S1@WH_Semi: 期初库存50件 → 期末库存0件 [目标30/最小10/最大200] | 到货共50件 [L2(到货50件)] 生产中50件 | 销售0/0件
R1@WH_RM: 期初库存200件 → 期末库存80件 [目标80/最小50/最大500] | 无到货 | 销售0/0件
R2@WH_RM: 期初库存100件 → 期末库存0件 [目标40/最小20/最大400] | 无到货 | 销售0/0件
▶ 物料需求展开(按BOM逐级展开)
Make-P1(P1)生产40件 →
─── 物料需求汇总 ───
S1(生产): 期初库存50件, 今日生产50件, 今日需求80件, 期末库存0件
└ S1需求80件(由P1×2系数)
─── 物料需求汇总 ───
R1(采购): 期初库存200件, 今日生产350件, 今日需求120件, 期末库存80件
└ R1需求120件(由P1×3系数)
Make-P2(P2)生产20件 →
─── 物料需求汇总 ───
S1(生产): 期初库存50件, 今日生产50件, 今日需求20件, 期末库存0件
└ S1需求20件(由P2×1系数)
Make-S1(S1)生产50件 →
─── 物料需求汇总 ───
R2(采购): 期初库存100件, 今日生产596件, 今日需求100件, 期末库存0件
└ R2需求100件(由S1×2系数)
Procure-R1(R1)生产200件 →
Procure-R1-Alt(R1)生产150件 →
Procure-R2(R2)生产446件 →
Procure-R2-Alt(R2)生产150件 →
▶ 期末库存 P1@WH_FG=0 P2@WH_FG=0 S1@WH_Semi=0 R1@WH_RM=80 R2@WH_RM=0
═══════════════════ 第 2 天 ═══════════════════
▶ 产线视图(按产线分组)
【L1】产能10h
Make-P1(P1): 67件 (耗时6.7h)
利用率: 7/10 h (67%)
Make-P2(P2): 27件 (耗时3.3h)
利用率: 3/10 h (33%)
【L2】产能10h
Make-S1(S1): 203件 (耗时6.8h)
利用率: 7/10 h (68%)
【L3】产能10h
Make-P2(P2): 16件 (耗时2.7h)
利用率: 3/10 h (27%)
【SuA】产能无限 (供应商)
(休息)
【SuB】产能无限 (供应商)
Procure-R1-Alt(R1): 150件 (耗时36.0h)
【Su1】产能无限 (供应商)
Procure-R2(R2): 266件 (耗时42.6h)
【Su2】产能无限 (供应商)
Procure-R2-Alt(R2): 150件 (耗时45.0h)
▶ 产品视图(汇总各产线产量)
P1@WH_FG: 上期库存0件 → 期末库存7件 [目标20/最小5/最大150] | 到货共66.7件 [L1(到货67件)] 生产中66.7件 | 销售60/60件
P2@WH_FG: 上期库存0件 → 期末库存3件 [目标15/最小3/最大120] | 到货共43件 [L1(到货27件), L3(到货16件)] 生产中43件 | 销售40/40件
S1@WH_Semi: 上期库存0件 → 期末库存27件 [目标30/最小10/最大200] | 到货共203.1件 [L2(到货203件)] 生产中203.1件 | 销售0/0件
R1@WH_RM: 上期库存80件 → 期末库存80件 [目标80/最小50/最大500] | 到货共200件 [SuA(到货200件)] 生产中150件 | 销售0/0件
R2@WH_RM: 上期库存0件 → 期末库存40件 [目标40/最小20/最大400] | 到货共446.2件 [Su1(到货446件)] 生产中416.3件 | 销售0/0件
▶ 物料需求展开(按BOM逐级展开)
Make-P1(P1)生产67件 →
─── 物料需求汇总 ───
S1(生产): 期初库存0件, 今日生产203件, 今日需求133件, 期末库存27件
└ S1需求133件(由P1×2系数)
─── 物料需求汇总 ───
R1(采购): 期初库存80件, 今日生产150件, 今日需求200件, 期末库存80件
└ R1需求200件(由P1×3系数)
Make-P2(P2)生产43件 →
─── 物料需求汇总 ───
S1(生产): 期初库存0件, 今日生产203件, 今日需求43件, 期末库存27件
└ S1需求43件(由P2×1系数)
Make-S1(S1)生产203件 →
─── 物料需求汇总 ───
R2(采购): 期初库存0件, 今日生产416件, 今日需求406件, 期末库存40件
└ R2需求406件(由S1×2系数)
Procure-R1-Alt(R1)生产150件 →
Procure-R2(R2)生产266件 →
Procure-R2-Alt(R2)生产150件 →
▶ 期末库存 P1@WH_FG=7 P2@WH_FG=3 S1@WH_Semi=27 R1@WH_RM=80 R2@WH_RM=40
═══════════════════ 第 3 天 ═══════════════════
▶ 产线视图(按产线分组)
【L1】产能10h
Make-P1(P1): 50件 (耗时5.0h)
利用率: 5/10 h (50%)
【L2】产能10h
Make-S1(S1): 143件 (耗时4.8h)
利用率: 5/10 h (48%)
【L3】产能10h
Make-P2(P2): 60件 (耗时10.0h)
利用率: 10/10 h (100%)
【SuA】产能无限 (供应商)
Procure-R1(R1): 200件 (耗时24.0h)
【SuB】产能无限 (供应商)
Procure-R1-Alt(R1): 150件 (耗时36.0h)
【Su1】产能无限 (供应商)
Procure-R2(R2): 200件 (耗时32.0h)
【Su2】产能无限 (供应商)
Procure-R2-Alt(R2): 150件 (耗时45.0h)
▶ 产品视图(汇总各产线产量)
P1@WH_FG: 上期库存7件 → 期末库存17件 [目标20/最小5/最大150] | 到货共50件 [L1(到货50件)] 生产中50件 | 销售40/40件
P2@WH_FG: 上期库存3件 → 期末库存13件 [目标15/最小3/最大120] | 到货共59.9件 [L3(到货60件)] 生产中59.9件 | 销售50/50件
S1@WH_Semi: 上期库存27件 → 期末库存10件 [目标30/最小10/最大200] | 到货共143.1件 [L2(到货143件)] 生产中143.1件 | 销售0/0件
R1@WH_RM: 上期库存80件 → 期末库存80件 [目标80/最小50/最大500] | 到货共150件 [SuB(到货150件)] 生产中350件 | 销售0/0件
R2@WH_RM: 上期库存40件 → 期末库存20件 [目标40/最小20/最大400] | 到货共266.3件 [Su1(到货266件)] 生产中350件 | 销售0/0件
▶ 物料需求展开(按BOM逐级展开)
Make-P1(P1)生产50件 →
─── 物料需求汇总 ───
S1(生产): 期初库存27件, 今日生产143件, 今日需求100件, 期末库存10件
└ S1需求100件(由P1×2系数)
─── 物料需求汇总 ───
R1(采购): 期初库存80件, 今日生产350件, 今日需求150件, 期末库存80件
└ R1需求150件(由P1×3系数)
Make-P2(P2)生产60件 →
─── 物料需求汇总 ───
S1(生产): 期初库存27件, 今日生产143件, 今日需求60件, 期末库存10件
└ S1需求60件(由P2×1系数)
Make-S1(S1)生产143件 →
─── 物料需求汇总 ───
R2(采购): 期初库存40件, 今日生产350件, 今日需求286件, 期末库存20件
└ R2需求286件(由S1×2系数)
Procure-R1(R1)生产200件 →
Procure-R1-Alt(R1)生产150件 →
Procure-R2(R2)生产200件 →
Procure-R2-Alt(R2)生产150件 →
▶ 期末库存 P1@WH_FG=17 P2@WH_FG=13 S1@WH_Semi=10 R1@WH_RM=80 R2@WH_RM=20
═══════════════════ KPI 汇总 ═══════════════════
目标函数值 (最小总惩罚): 190.00
需求缺口惩罚: 19500.00 (权重100 × 195.00)
批次偏差惩罚: 5124.27 (权重10 × 512.43)
超库存惩罚: 0.00 (权重5 × 0.00)
欠库存惩罚: 190.00 (权重5 × 38.00)
目标库存偏差: 1432.35 (权重8 × 179.04)
产能超载惩罚: 0.00 (权重20 × 0.00)
供应目标偏差: 643.62 (权重8 × 80.45)
最小供应不足: 0.00 (权重5 × 0.00)
最大供应超出: 0.00 (权重5 × 0.00)
销售优先级(负): 475.00 (权重1 × 475.00)
⚠ 注意:存在需求缺口,部分订单未满足。
═══════════════════ 求解统计 ═══════════════════
变量数:218
约束数:168
耗时:0.085 秒
═══════════════════ 分层优化汇总 ═══════════════════
层级 名称 松弛比例 最优值 包含KPI
1 需求满足 0% 19500.00 需求缺口
2 产能约束 0% 0.00 产能超载
3 业务KPI 5% 6405.00 批次偏差, 目标库存偏差, 供应目标偏差, 销售优先级
4 软约束 10% 190.00 超库存, 欠库存, 最小供应不足, 最大供应超出
--- 最终 KPI 值 (加权总惩罚) ---
加权总惩罚: 26415.25
需求缺口: 19500.00 (权重100 × 195.00)
产能超载: 0.00 (权重20 × 0.00)
批次偏差: 5124.27 (权重10 × 512.43)
目标库存偏差: 1432.35 (权重8 × 179.04)
供应目标偏差: 643.62 (权重8 × 80.45)
超库存: 0.00 (权重5 × 0.00)
欠库存: 190.00 (权重5 × 38.00)
最小供应不足: 0.00 (权重5 × 0.00)
最大供应超出: 0.00 (权重5 × 0.00)
销售优先级(负): 475.00 (权重1 × 475.00)
15:49:47.930 [main] INFO com.aps.service.plan.SceneService - 保存成功,场景ID: 1, 文件: F:\product\MES\hyh.apsj\mp\result\optimization_result_1.json
[OK] 优化结果JSON已导出: true Process finished with exit code 0
=== 运行结束 ===
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
package com.aps.macroplanner; package com.aps.macroplanner;
import com.aps.common.util.FileHelper;
import com.google.ortools.Loader; import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPSolver; import com.google.ortools.linearsolver.MPSolver;
import com.aps.macroplanner.constraint.ConstraintFactory; import com.aps.macroplanner.constraint.ConstraintFactory;
...@@ -89,7 +90,7 @@ public class MacroPlannerOptimizer { ...@@ -89,7 +90,7 @@ public class MacroPlannerOptimizer {
private static final String LP_FILE_PATH = LOG_DIR + "/lp/model.lp"; private static final String LP_FILE_PATH = LOG_DIR + "/lp/model.lp";
/** 运行日志文件路径 */ /** 运行日志文件路径 */
private static final String LOG_FILE_PATH = LOG_DIR + "/log/log.txt"; private static final String LOG_FILE_PATH = LOG_DIR + "/log/";
/** 获取模型容器 (供 ResultWriter 等外部组件使用)。 */ /** 获取模型容器 (供 ResultWriter 等外部组件使用)。 */
public MacroPlannerModel getModel() { return model; } public MacroPlannerModel getModel() { return model; }
...@@ -148,6 +149,11 @@ public class MacroPlannerOptimizer { ...@@ -148,6 +149,11 @@ public class MacroPlannerOptimizer {
rootLogger.addHandler(handler); rootLogger.addHandler(handler);
} }
private void writeLog(String msg)
{
FileHelper.writeFile(msg,LOG_FILE_PATH,"log.txt");
}
// ==================== 模型构建流程 ==================== // ==================== 模型构建流程 ====================
/** /**
...@@ -163,32 +169,32 @@ public class MacroPlannerOptimizer { ...@@ -163,32 +169,32 @@ public class MacroPlannerOptimizer {
// 设为 FINE 可输出每个周期的折算详情 // 设为 FINE 可输出每个周期的折算详情
configureLogging(); configureLogging();
System.out.println("=== 开始构建 MacroPlanner 优化模型 ===\n"); writeLog("=== 开始构建 MacroPlanner 优化模型 ===\n");
// 0. 数据完整性检查 (在构建模型前验证) // 0. 数据完整性检查 (在构建模型前验证)
DataValidator validator = new DataValidator(data); DataValidator validator = new DataValidator(data);
if (!validator.validate()) { if (!validator.validate()) {
System.out.println(" ⚠️ 数据检查发现错误, 求解结果可能不可靠\n"); writeLog(" ⚠️ 数据检查发现错误, 求解结果可能不可靠\n");
} else { } else {
System.out.println(" [OK] 数据检查通过\n"); writeLog(" [OK] 数据检查通过\n");
} }
// 1. 决策变量 // 1. 决策变量
VariableFactory.createAll(model, data); VariableFactory.createAll(model, data);
System.out.println(" [OK] 决策变量创建完成"); writeLog(" [OK] 决策变量创建完成");
// 2. 约束 + KPI 汇总 (由 ConstraintFactory 统一调度) // 2. 约束 + KPI 汇总 (由 ConstraintFactory 统一调度)
ConstraintFactory.buildAll(model, data); ConstraintFactory.buildAll(model, data);
System.out.println(" [OK] 约束与KPI汇总创建完成"); writeLog(" [OK] 约束与KPI汇总创建完成");
// 3. 目标函数 (加权求和) // 3. 目标函数 (加权求和)
ObjectiveBuilder.build(model, data); ObjectiveBuilder.build(model, data);
System.out.println(" [OK] 目标函数创建完成"); writeLog(" [OK] 目标函数创建完成");
// 4. 导出 LP 模型文件 // 4. 导出 LP 模型文件
exportLpModel(); exportLpModel();
System.out.println("\n模型统计: 变量=" + model.getSolver().numVariables() writeLog("\n模型统计: 变量=" + model.getSolver().numVariables()
+ ", 约束=" + model.getSolver().numConstraints() + "\n"); + ", 约束=" + model.getSolver().numConstraints() + "\n");
} }
...@@ -211,9 +217,9 @@ public class MacroPlannerOptimizer { ...@@ -211,9 +217,9 @@ public class MacroPlannerOptimizer {
String lpContent = model.getSolver().exportModelAsLpFormat(); String lpContent = model.getSolver().exportModelAsLpFormat();
Path lpPath = Paths.get(LP_FILE_PATH).toAbsolutePath(); Path lpPath = Paths.get(LP_FILE_PATH).toAbsolutePath();
Files.write(lpPath, lpContent.getBytes(StandardCharsets.UTF_8)); Files.write(lpPath, lpContent.getBytes(StandardCharsets.UTF_8));
System.out.println(" [OK] LP模型文件已导出: " + lpPath); writeLog(" [OK] LP模型文件已导出: " + lpPath);
} catch (Exception e) { } catch (Exception e) {
System.err.println(" [WARN] LP模型导出失败: " + e.getMessage()); writeLog(" [WARN] LP模型导出失败: " + e.getMessage());
} }
} }
...@@ -255,7 +261,7 @@ public class MacroPlannerOptimizer { ...@@ -255,7 +261,7 @@ public class MacroPlannerOptimizer {
* 每个层级独立求解, 上层最优值作为下层约束, 确保严格优先级顺序。</p> * 每个层级独立求解, 上层最优值作为下层约束, 确保严格优先级顺序。</p>
*/ */
public void solve() { public void solve() {
System.out.println("=== 开始分层求解 ===\n"); writeLog("=== 开始分层求解 ===\n");
startTimeMs = System.currentTimeMillis(); startTimeMs = System.currentTimeMillis();
KPIWeights w = data.getKpiWeights(); KPIWeights w = data.getKpiWeights();
...@@ -300,17 +306,20 @@ public class MacroPlannerOptimizer { ...@@ -300,17 +306,20 @@ public class MacroPlannerOptimizer {
if (i < levels.size() - 1 && level.getRelativeGoalSlack() >= 0.0) { if (i < levels.size() - 1 && level.getRelativeGoalSlack() >= 0.0) {
ObjectiveBuilder.addLevelBoundConstraint(model, level, optimalValue); ObjectiveBuilder.addLevelBoundConstraint(model, level, optimalValue);
System.out.printf(" 已添加边界约束: 上层目标 ≤ %.2f%n", System.out.printf(" 已添加边界约束: 上层目标 ≤ %.2f%n",
optimalValue * (1.0 + level.getRelativeGoalSlack())); ObjectiveBuilder.computeUpperBound(optimalValue, level.getRelativeGoalSlack()));
} }
System.out.println(); writeLog("-----------------------------------");
} }
// 输出最终结果 // 输出最终结果
MPSolver.ResultStatus finalStatus = model.getSolver().solve(); MPSolver.ResultStatus finalStatus = model.getSolver().solve();
if (finalStatus == MPSolver.ResultStatus.OPTIMAL if (finalStatus == MPSolver.ResultStatus.OPTIMAL
|| finalStatus == MPSolver.ResultStatus.FEASIBLE) { || finalStatus == MPSolver.ResultStatus.FEASIBLE) {
SolutionPrinter printer = new SolutionPrinter(model, data, startTimeMs); SolutionPrinter printer = new SolutionPrinter(model, data, startTimeMs,LOG_FILE_PATH);
printer.printAll(); printer.printAll();
// 构建层级最优值列表 // 构建层级最优值列表
List<Double> levelObjValues = new ArrayList<>(); List<Double> levelObjValues = new ArrayList<>();
...@@ -323,10 +332,10 @@ public class MacroPlannerOptimizer { ...@@ -323,10 +332,10 @@ public class MacroPlannerOptimizer {
ResultWriter rw = new ResultWriter(model, data, startTimeMs); ResultWriter rw = new ResultWriter(model, data, startTimeMs);
boolean jsonPath = rw.saveResultToFile("1"); boolean jsonPath = rw.saveResultToFile("1");
if (jsonPath) { if (jsonPath) {
System.out.println("\n[OK] 优化结果JSON已导出: " + jsonPath); writeLog("\n[OK] 优化结果JSON已导出: " + jsonPath);
} }
} else { } else {
System.out.println("求解失败! 状态: " + finalStatus); writeLog("求解失败! 状态: " + finalStatus);
} }
} }
......
...@@ -135,7 +135,7 @@ public class InventorySpecConstraint { ...@@ -135,7 +135,7 @@ public class InventorySpecConstraint {
} else { } else {
double rhs = spec.getTargetLevel(); double rhs = spec.getTargetLevel();
MPConstraint c = model.getSolver().makeConstraint( MPConstraint c = model.getSolver().makeConstraint(
rhs, rhs, "TargetInv_" + invKey); rhs, MPSolver.infinity(), "TargetInv_" + invKey);
c.setCoefficient(invVar, 1.0); c.setCoefficient(invVar, 1.0);
MPVariable u = targetUnderVars.get(invKey); MPVariable u = targetUnderVars.get(invKey);
if (u != null) c.setCoefficient(u, 1.0); if (u != null) c.setCoefficient(u, 1.0);
......
...@@ -5,6 +5,7 @@ import com.aps.entity.*; ...@@ -5,6 +5,7 @@ import com.aps.entity.*;
import com.aps.entity.basic.Material; import com.aps.entity.basic.Material;
import com.aps.entity.basic.MaterialSupply; import com.aps.entity.basic.MaterialSupply;
import com.aps.mapper.EquipShiftCapacityMapper; import com.aps.mapper.EquipShiftCapacityMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.aps.mapper.EquipinfoMapper; import com.aps.mapper.EquipinfoMapper;
...@@ -190,6 +191,16 @@ public class MacroPlannerDataConverter { ...@@ -190,6 +191,16 @@ public class MacroPlannerDataConverter {
new LambdaQueryWrapper<ApsDemandOrder>() new LambdaQueryWrapper<ApsDemandOrder>()
.ge(ApsDemandOrder::getDeliverytime, ctx.baseTime) .ge(ApsDemandOrder::getDeliverytime, ctx.baseTime)
.lt(ApsDemandOrder::getDeliverytime, horizonEndDateTime)); .lt(ApsDemandOrder::getDeliverytime, horizonEndDateTime));
// ctx.apsDemandOrders = apsDemandOrderMapper.selectList(
//
// new LambdaQueryWrapper<ApsDemandOrder>()
// .ge(ApsDemandOrder::getDeliverytime, ctx.baseTime)
// .eq(ApsDemandOrder::getCode,"XQDD_20260812_6")
// .lt(ApsDemandOrder::getDeliverytime, horizonEndDateTime)
// );
log.info("加载需求订单: {} 条 (时间范围过滤)", ctx.apsDemandOrders.size()); log.info("加载需求订单: {} 条 (时间范围过滤)", ctx.apsDemandOrders.size());
// 2. 收集 materialIds (从 ApsDemandOrder.mmid) // 2. 收集 materialIds (从 ApsDemandOrder.mmid)
...@@ -215,6 +226,40 @@ public class MacroPlannerDataConverter { ...@@ -215,6 +226,40 @@ public class MacroPlannerDataConverter {
// 4. 工序 (通过 LanuchService 批量查询) // 4. 工序 (通过 LanuchService 批量查询)
if (!routingIds.isEmpty()) { if (!routingIds.isEmpty()) {
List<Integer> routingIds1=routingIds;
// while (routingIds1!=null&&routingIds1.size()>0) {
// List<Routingsupporting> rss = routingsupportingMapper.selectList(
// new LambdaQueryWrapper<Routingsupporting>()
// .in(Routingsupporting::getRoutingHeaderId, routingIds)
// .eq(Routingsupporting::getIsdeleted, 0));
// if (rss != null && rss.size() > 0) {
// Set<String> materialIdrss = rss.stream()
// .map(Routingsupporting::getMaterialId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toSet());
// materialIds.addAll(materialIdrss);
// ctx.routingsupportings.addAll(rss);
//
// List<RoutingHeader> rhs = routingHeaderMapper.selectList(
// new LambdaQueryWrapper<RoutingHeader>()
// .in(RoutingHeader::getMaterialId, materialIdrss));
//
// if (rhs != null && rhs.size() > 0) {
// routingIds1 = rhs.stream()
// .map(RoutingHeader::getId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toList());
// routingIds.addAll(routingIds1);
// ctx.routingHeaders.addAll(rhs);
// } else {
// routingIds1 = null;
// }
// } else {
// routingIds1 = null;
// }
// }
List<Long> routingIdsLong = routingIds.stream() List<Long> routingIdsLong = routingIds.stream()
.map(Long::valueOf) .map(Long::valueOf)
.collect(Collectors.toList()); .collect(Collectors.toList());
...@@ -237,9 +282,12 @@ public class MacroPlannerDataConverter { ...@@ -237,9 +282,12 @@ public class MacroPlannerDataConverter {
// 6. 收集所有 materialId (订单 + 工艺物料消耗 + 工艺路线头表) // 6. 收集所有 materialId (订单 + 工艺物料消耗 + 工艺路线头表)
ctx.routingsupportings.forEach(rs -> { ctx.routingsupportings.forEach(rs -> {
if (rs.getMaterialId() != null) materialIds.add(rs.getMaterialId()); if (rs.getMaterialId() != null) materialIds.add(rs.getMaterialId());
}); });
// 7. 物料主数据 // 7. 物料主数据
...@@ -641,7 +689,7 @@ public class MacroPlannerDataConverter { ...@@ -641,7 +689,7 @@ public class MacroPlannerDataConverter {
// 3.1 为每个 Material 创建 Product // 3.1 为每个 Material 创建 Product
for (Material m : ctx.materialByMaterialId.values()) { for (Material m : ctx.materialByMaterialId.values()) {
String name = pickName(m.getName(), m.getCode(), m.getId()); String name = pickName(m.getName(), m.getCode(), m.getId());
Product p = new Product(m.getId(), name); Product p = new Product(m.getCode(), name);
products.add(p); products.add(p);
ctx.productByMaterialId.put(m.getId(), p); ctx.productByMaterialId.put(m.getId(), p);
} }
...@@ -892,10 +940,10 @@ public class MacroPlannerDataConverter { ...@@ -892,10 +940,10 @@ public class MacroPlannerDataConverter {
if (inputMaterial == null) { if (inputMaterial == null) {
continue; continue;
} }
if (!"MP".equals(inputMaterial.getMaterialTypeName())) { // if (!"MP".equals(inputMaterial.getMaterialTypeName())) {
// 半成品/成品不作为投料, 由各自 Routing 产出 // // 半成品/成品不作为投料, 由各自 Routing 产出
continue; // continue;
} // }
if (rs.getMainQty() == null || rs.getMainQty().compareTo(BigDecimal.ZERO) == 0) { if (rs.getMainQty() == null || rs.getMainQty().compareTo(BigDecimal.ZERO) == 0) {
log.warn("跳过主量为0的BOM项: routingDetailId={}, materialId={}", log.warn("跳过主量为0的BOM项: routingDetailId={}, materialId={}",
...@@ -919,9 +967,11 @@ public class MacroPlannerDataConverter { ...@@ -919,9 +967,11 @@ public class MacroPlannerDataConverter {
// - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle // - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle
// - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限 // - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限
for (Material m : ctx.materialByMaterialId.values()) { for (Material m : ctx.materialByMaterialId.values()) {
if (!"MP".equals(m.getMaterialTypeName())) { long headercount= ctx.routingHeaders.stream().filter(t->t.getMaterialId()==m.getId()).count();
continue; if(headercount>0)
} {
continue;
}
Product p = ctx.productByMaterialId.get(m.getId()); Product p = ctx.productByMaterialId.get(m.getId());
if (p == null) { if (p == null) {
continue; continue;
...@@ -1287,7 +1337,7 @@ public class MacroPlannerDataConverter { ...@@ -1287,7 +1337,7 @@ public class MacroPlannerDataConverter {
String key = m.getProduct().getId() + "_" + m.getStockingPoint().getId() + "_" + p.getIndex(); String key = m.getProduct().getId() + "_" + m.getStockingPoint().getId() + "_" + p.getIndex();
if (invSpecKeys.add(key)) { if (invSpecKeys.add(key)) {
inventorySpecs.add(new InventorySpec(m.getProduct(), m.getStockingPoint(), p, inventorySpecs.add(new InventorySpec(m.getProduct(), m.getStockingPoint(), p,
0.0, 0.0, LOOSE_MAX, true, true, true)); 0.0, 0.0, LOOSE_MAX, false, true, true));
} }
} }
} }
...@@ -1305,7 +1355,7 @@ public class MacroPlannerDataConverter { ...@@ -1305,7 +1355,7 @@ public class MacroPlannerDataConverter {
for (Operation op : operations) { for (Operation op : operations) {
if (op.getId().startsWith("OP_PROCURE_")) { if (op.getId().startsWith("OP_PROCURE_")) {
supplySpecs.add(new SupplySpec("Supply-" + op.getId(), supplySpecs.add(new SupplySpec("Supply-" + op.getId(),
0.0, 0.0, LOOSE_MAX, false, Collections.singletonList(op))); 0.0, 0.0, LOOSE_MAX, true, Collections.singletonList(op)));
} }
} }
log.info("构建 InventorySpec: {}, SupplySpec: {}", inventorySpecs.size(), supplySpecs.size()); log.info("构建 InventorySpec: {}, SupplySpec: {}", inventorySpecs.size(), supplySpecs.size());
......
...@@ -112,7 +112,21 @@ public class ObjectiveBuilder { ...@@ -112,7 +112,21 @@ public class ObjectiveBuilder {
// 设置为最小化目标 // 设置为最小化目标
objective.setMinimization(); objective.setMinimization();
} }
/**
* 计算层级边界约束的上界。
*
* <p>松弛比例应允许上层目标值<b>变差</b>(对最小化而言即数值增大)。
* 由于目标值可能为负(负系数 KPI 导致),直接使用
* {@code optimalValue × (1 + slack)} 会在负值时反向收紧约束,
* 因此改用 {@code optimalValue + |optimalValue| × slack}。</p>
*
* @param optimalValue 上层最优目标值
* @param relativeGoalSlack 松弛比例 (>= 0)
* @return 放宽后的上界
*/
public static double computeUpperBound(double optimalValue, double relativeGoalSlack) {
return optimalValue + Math.abs(optimalValue) * relativeGoalSlack;
}
// ==================== 分层优化方法 ==================== // ==================== 分层优化方法 ====================
/** /**
...@@ -165,7 +179,7 @@ public class ObjectiveBuilder { ...@@ -165,7 +179,7 @@ public class ObjectiveBuilder {
* *
* <h3>数学公式</h3> * <h3>数学公式</h3>
* <pre> * <pre>
* Σ (effectiveCoeff × KPI_variable) ≤ optimalValue × (1 + relativeGoalSlack) * Σ (effectiveCoeff × KPI_variable) ≤ optimalValue + |optimalValue| × relativeGoalSlack
* </pre> * </pre>
* *
* @param model 模型容器 * @param model 模型容器
...@@ -178,7 +192,7 @@ public class ObjectiveBuilder { ...@@ -178,7 +192,7 @@ public class ObjectiveBuilder {
if (level.getRelativeGoalSlack() < 0.0) return; // 负松弛表示不约束 if (level.getRelativeGoalSlack() < 0.0) return; // 负松弛表示不约束
MPSolver solver = model.getSolver(); MPSolver solver = model.getSolver();
double upperBound = optimalValue * (1.0 + level.getRelativeGoalSlack()); double upperBound = computeUpperBound(optimalValue, level.getRelativeGoalSlack());
MPConstraint bound = solver.makeConstraint( MPConstraint bound = solver.makeConstraint(
-MPSolver.infinity(), upperBound, -MPSolver.infinity(), upperBound,
......
package com.aps.macroplanner.output; package com.aps.macroplanner.output;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.*; import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel; import com.aps.macroplanner.model.MacroPlannerModel;
import com.aps.macroplanner.objective.StrategyLevel; import com.aps.macroplanner.objective.StrategyLevel;
...@@ -30,12 +31,25 @@ public class SolutionPrinter { ...@@ -30,12 +31,25 @@ public class SolutionPrinter {
private final TestDataBuilder data; private final TestDataBuilder data;
private final long startTimeMs; private final long startTimeMs;
public SolutionPrinter(MacroPlannerModel model, TestDataBuilder data, long startTimeMs) { private final String LOG_FILE_PATH;
public SolutionPrinter(MacroPlannerModel model, TestDataBuilder data, long startTimeMs,String logfilepath) {
this.model = model; this.model = model;
this.data = data; this.data = data;
this.startTimeMs = startTimeMs; this.startTimeMs = startTimeMs;
this.LOG_FILE_PATH=logfilepath;
}
private void writeLog(String msg)
{
FileHelper.writeFile(msg,LOG_FILE_PATH,"log.txt");
} }
private void writeLog(String format, Object ... args)
{
writeLog(String.format(format,args));
}
// ==================== 主入口 ==================== // ==================== 主入口 ====================
/** 输出完整求解结果 */ /** 输出完整求解结果 */
...@@ -50,14 +64,14 @@ public class SolutionPrinter { ...@@ -50,14 +64,14 @@ public class SolutionPrinter {
// ==================== BOM 关系验证 ==================== // ==================== BOM 关系验证 ====================
private void printBomStructure() { private void printBomStructure() {
System.out.println("📋 BOM关系验证:"); writeLog("📋 BOM关系验证:");
for (Product prod : data.getProducts()) { for (Product prod : data.getProducts()) {
System.out.printf(" %s → 消耗: %s, 被消耗于: %s%n", writeLog(" %s → 消耗: %s, 被消耗于: %s",
prod.getId(), prod.getId(),
formatConsumes(prod), formatConsumes(prod),
formatConsumedBy(prod)); formatConsumedBy(prod));
} }
System.out.println(); writeLog("");
} }
/** 某产品消耗了哪些物料 (BOM输入, 含库存点) */ /** 某产品消耗了哪些物料 (BOM输入, 含库存点) */
...@@ -73,7 +87,7 @@ public class SolutionPrinter { ...@@ -73,7 +87,7 @@ public class SolutionPrinter {
inputCount++; inputCount++;
} }
if (inputCount > 0) { if (inputCount > 0) {
LOG.fine(String.format("[结果输出] BOM消耗: '%s'(产出%s) 消耗 %d 种物料", writeLog(String.format("[结果输出] BOM消耗: '%s'(产出%s) 消耗 %d 种物料",
op.getId(), formatOutputProducts(op), inputCount)); op.getId(), formatOutputProducts(op), inputCount));
} }
} }
...@@ -88,7 +102,7 @@ public class SolutionPrinter { ...@@ -88,7 +102,7 @@ public class SolutionPrinter {
// 遍历消费者工序的所有产出 (支持联产品/副产品) // 遍历消费者工序的所有产出 (支持联产品/副产品)
List<OperationOutput> outputs = input.getOperation().getOutputs(); List<OperationOutput> outputs = input.getOperation().getOutputs();
if (outputs.size() > 1) { if (outputs.size() > 1) {
LOG.fine(String.format("[结果输出] 多产出消耗: '%s' 被工序 '%s' 消耗, 该工序有 %d 个产出", writeLog(String.format("[结果输出] 多产出消耗: '%s' 被工序 '%s' 消耗, 该工序有 %d 个产出",
prod.getId(), input.getOperation().getId(), outputs.size())); prod.getId(), input.getOperation().getId(), outputs.size()));
} }
for (OperationOutput oo : outputs) { for (OperationOutput oo : outputs) {
...@@ -106,7 +120,7 @@ public class SolutionPrinter { ...@@ -106,7 +120,7 @@ public class SolutionPrinter {
ids.add(oo.getProductId()); ids.add(oo.getProductId());
} }
if (ids.size() > 1) { if (ids.size() > 1) {
LOG.fine(String.format("[结果输出] 多产出工序 '%s': 产出=%s", writeLog(String.format("[结果输出] 多产出工序 '%s': 产出=%s",
op.getId(), String.join("+", ids))); op.getId(), String.join("+", ids)));
} }
return ids.isEmpty() ? "-" : String.join("+", ids); return ids.isEmpty() ? "-" : String.join("+", ids);
...@@ -148,25 +162,25 @@ public class SolutionPrinter { ...@@ -148,25 +162,25 @@ public class SolutionPrinter {
} }
} }
System.out.println("========== 网状BOM + MRP 排产求解 =========="); writeLog("========== 网状BOM + MRP 排产求解 ==========");
System.out.printf("成品(根节点) %d 种:%s%n", finishedGoods.size(), writeLog("成品(根节点) %d 种:%s", finishedGoods.size(),
String.join(" ", finishedGoods)); String.join(" ", finishedGoods));
System.out.printf("生产品 %d 种:%s%n", producedGoods.size(), writeLog("生产品 %d 种:%s", producedGoods.size(),
String.join(" ", producedGoods)); String.join(" ", producedGoods));
if (!rawMaterials.isEmpty()) { if (!rawMaterials.isEmpty()) {
System.out.printf("采购品 %d 种:%s%n", rawMaterials.size(), writeLog("采购品 %d 种:%s", rawMaterials.size(),
String.join(" ", rawMaterials)); String.join(" ", rawMaterials));
} }
System.out.printf("周期 %d 天%n", data.getPeriods().size()); writeLog("周期 %d 天", data.getPeriods().size());
System.out.println(); writeLog("-------------");
} }
// ==================== 每日视图 ==================== // ==================== 每日视图 ====================
private void printDailyViews() { private void printDailyViews() {
for (Period p : data.getPeriods()) { for (Period p : data.getPeriods()) {
System.out.println(); writeLog(" ");
System.out.printf("═══════════════════ 第 %d 天 ═══════════════════%n", writeLog("═══════════════════ 第 %d 天 ═══════════════════",
p.getIndex() + 1); p.getIndex() + 1);
printLineView(p); printLineView(p);
printProductView(p); printProductView(p);
...@@ -178,7 +192,7 @@ public class SolutionPrinter { ...@@ -178,7 +192,7 @@ public class SolutionPrinter {
// --- 产线视图 --- // --- 产线视图 ---
private void printLineView(Period p) { private void printLineView(Period p) {
System.out.println(" ▶ 产线视图(按产线分组)"); writeLog(" ▶ 产线视图(按产线分组)");
// 收集所有 unit // 收集所有 unit
Set<String> unitIds = new LinkedHashSet<>(); Set<String> unitIds = new LinkedHashSet<>();
...@@ -193,10 +207,10 @@ public class SolutionPrinter { ...@@ -193,10 +207,10 @@ public class SolutionPrinter {
if (up == null) continue; if (up == null) continue;
if (up.isUnlimited()) { if (up.isUnlimited()) {
System.out.printf(" 【%s】产能无限 (供应商)%n", unitId); writeLog(" 【%s】产能无限 (供应商)", unitId);
} else { } else {
String minStr = up.hasMinCapacity() ? String.format("最小%.0fh/", up.getMinCapacity()) : ""; String minStr = up.hasMinCapacity() ? String.format("最小%.0fh/", up.getMinCapacity()) : "";
System.out.printf(" 【%s】产能%s%.0fh %s%n", unitId, minStr, up.getMaxCapacity(), writeLog(" 【%s】产能%s%.0fh %s", unitId, minStr, up.getMaxCapacity(),
up.hasMinCapacity() ? "(强制最小)" : ""); up.hasMinCapacity() ? "(强制最小)" : "");
} }
...@@ -210,22 +224,22 @@ public class SolutionPrinter { ...@@ -210,22 +224,22 @@ public class SolutionPrinter {
hasProduction = true; hasProduction = true;
double capacityUsed = ptQty * uo.getCapacityCoeff(); double capacityUsed = ptQty * uo.getCapacityCoeff();
if (up.isUnlimited()) { if (up.isUnlimited()) {
System.out.printf(" %s(%s): %.0f件 (耗时%.1fh)%n", writeLog(" %s(%s): %.0f件 (耗时%.1fh)",
op.getName(), formatOutputProducts(op), ptQty, capacityUsed); op.getName(), formatOutputProducts(op), ptQty, capacityUsed);
} else { } else {
double utilPercent = up.getMaxCapacity() > 0 double utilPercent = up.getMaxCapacity() > 0
? capacityUsed / up.getMaxCapacity() * 100 : 0; ? capacityUsed / up.getMaxCapacity() * 100 : 0;
System.out.printf(" %s(%s): %.0f件 (耗时%.1fh) %s%n", writeLog(" %s(%s): %.0f件 (耗时%.1fh) %s",
op.getName(), formatOutputProducts(op), ptQty, capacityUsed, op.getName(), formatOutputProducts(op), ptQty, capacityUsed,
uo.hasLotSize() ? String.format(", 批次%.0f", uo.getLotSize()) : ""); uo.hasLotSize() ? String.format(", 批次%.0f", uo.getLotSize()) : "");
System.out.printf(" 利用率: %.0f/%.0f h (%.0f%%)%n", writeLog(" 利用率: %.0f/%.0f h (%.0f%%)",
capacityUsed, up.getMaxCapacity(), utilPercent); capacityUsed, up.getMaxCapacity(), utilPercent);
} }
} }
} }
} }
if (!hasProduction) { if (!hasProduction) {
System.out.println(" (休息)"); writeLog(" (休息)");
} }
} }
} }
...@@ -233,7 +247,7 @@ public class SolutionPrinter { ...@@ -233,7 +247,7 @@ public class SolutionPrinter {
// --- 产品视图 --- // --- 产品视图 ---
private void printProductView(Period p) { private void printProductView(Period p) {
System.out.println(" ▶ 产品视图(汇总各产线产量)"); writeLog(" ▶ 产品视图(汇总各产线产量)");
for (Product prod : data.getProducts()) { for (Product prod : data.getProducts()) {
for (StockingPoint sp : data.getStockingPointsForProduct(prod.getId())) { for (StockingPoint sp : data.getStockingPointsForProduct(prod.getId())) {
...@@ -284,7 +298,7 @@ public class SolutionPrinter { ...@@ -284,7 +298,7 @@ public class SolutionPrinter {
} }
} }
if (matchedUnitOpCount > 1) { if (matchedUnitOpCount > 1) {
LOG.fine(String.format("[结果输出] 多Unit产出: %s@%s P%d 有 %d 个UnitOp产出到该库存点 (到货=%.0f, 生产中=%.0f)", writeLog(String.format("[结果输出] 多Unit产出: %s@%s P%d 有 %d 个UnitOp产出到该库存点 (到货=%.0f, 生产中=%.0f)",
prod.getId(), sp.getId(), p.getIndex(), matchedUnitOpCount, prod.getId(), sp.getId(), p.getIndex(), matchedUnitOpCount,
totalArrived, totalInProduction)); totalArrived, totalInProduction));
} }
...@@ -318,7 +332,7 @@ public class SolutionPrinter { ...@@ -318,7 +332,7 @@ public class SolutionPrinter {
? String.format("期初库存%.0f件 → ", openingInv) ? String.format("期初库存%.0f件 → ", openingInv)
: String.format("上期库存%.0f件 → ", openingInv); : String.format("上期库存%.0f件 → ", openingInv);
System.out.printf(" %s@%s: %s期末库存%.0f件 %s | %s%s%s | 销售%.0f/%.0f件", writeLog(" %s@%s: %s期末库存%.0f件 %s | %s%s%s | 销售%.0f/%.0f件",
prod.getId(), sp.getId(), openingStr, invQty, specStr, prod.getId(), sp.getId(), openingStr, invQty, specStr,
totalArrived > 0.001 totalArrived > 0.001
? "到货共" + formatNum(totalArrived) + "件 [" ? "到货共" + formatNum(totalArrived) + "件 ["
...@@ -333,11 +347,11 @@ public class SolutionPrinter { ...@@ -333,11 +347,11 @@ public class SolutionPrinter {
double unmet = totalDemand - totalSales; double unmet = totalDemand - totalSales;
if (unmet > 0.001) { if (unmet > 0.001) {
System.out.printf(" ⚠未满足%.0f件", unmet); writeLog(" ⚠未满足%.0f件", unmet);
// 分析未满足原因 // 分析未满足原因
System.out.print(" " + analyzeUnmetReason(prod, sp, p, openingInv, totalArrived, unmet)); writeLog(" " + analyzeUnmetReason(prod, sp, p, openingInv, totalArrived, unmet));
} }
System.out.println();
} }
} }
} }
...@@ -459,7 +473,7 @@ public class SolutionPrinter { ...@@ -459,7 +473,7 @@ public class SolutionPrinter {
// --- 物料需求展开 --- // --- 物料需求展开 ---
private void printBomExplosion(Period p) { private void printBomExplosion(Period p) {
System.out.println(" ▶ 物料需求展开(按BOM逐级展开)"); writeLog(" ▶ 物料需求展开(按BOM逐级展开)");
for (Operation op : data.getOperations()) { for (Operation op : data.getOperations()) {
// 跨 UnitOperations 求和总产量 // 跨 UnitOperations 求和总产量
...@@ -471,7 +485,7 @@ public class SolutionPrinter { ...@@ -471,7 +485,7 @@ public class SolutionPrinter {
if (ptQty < 0.001) continue; if (ptQty < 0.001) continue;
String outputLabel = formatOutputProducts(op); String outputLabel = formatOutputProducts(op);
System.out.printf(" %s(%s)生产%.0f件 →%n", op.getName(), outputLabel, ptQty); writeLog(" %s(%s)生产%.0f件 →", op.getName(), outputLabel, ptQty);
// 找到该操作的所有输入物料 // 找到该操作的所有输入物料
int inputMatched = 0; int inputMatched = 0;
...@@ -508,7 +522,7 @@ public class SolutionPrinter { ...@@ -508,7 +522,7 @@ public class SolutionPrinter {
} }
} }
if (matchedInputOps > 1) { if (matchedInputOps > 1) {
LOG.fine(String.format("[结果输出] 物料多源生产: %s@%s P%d 有 %d 个工序产出 (总量=%.0f)", writeLog(String.format("[结果输出] 物料多源生产: %s@%s P%d 有 %d 个工序产出 (总量=%.0f)",
inputProd.getId(), inputSp.getId(), p.getIndex(), inputProd.getId(), inputSp.getId(), p.getIndex(),
matchedInputOps, inputProdQty)); matchedInputOps, inputProdQty));
} }
...@@ -536,18 +550,18 @@ public class SolutionPrinter { ...@@ -536,18 +550,18 @@ public class SolutionPrinter {
String inTransitNote = inTransit > 0.001 String inTransitNote = inTransit > 0.001
? String.format("(+在途%.0f)", inTransit) : ""; ? String.format("(+在途%.0f)", inTransit) : "";
System.out.printf(" ─── 物料需求汇总 ───%n"); writeLog(" ─── 物料需求汇总 ───");
System.out.printf(" %s(%s): 期初库存%.0f件, 今日生产%.0f件%s, 今日需求%.0f件, 期末库存%.0f件%n", writeLog(" %s(%s): 期初库存%.0f件, 今日生产%.0f件%s, 今日需求%.0f件, 期末库存%.0f件",
inputProd.getId(), inputProd.getId(),
inputProd.getId().startsWith("R") ? "采购" : "生产", inputProd.getId().startsWith("R") ? "采购" : "生产",
prevInv, inputProdQty, inTransitNote, prevInv, inputProdQty, inTransitNote,
opDemand + externalSales, endInv); opDemand + externalSales, endInv);
System.out.printf(" └ %s需求%.0f件(由%s×%s系数)%n", writeLog(" └ %s需求%.0f件(由%s×%s系数)",
inputProd.getId(), opDemand, inputProd.getId(), opDemand,
formatOutputProducts(op), formatFactor(input.getFactor())); formatOutputProducts(op), formatFactor(input.getFactor()));
} }
if (inputMatched == 0) { if (inputMatched == 0) {
LOG.fine(String.format("[结果输出] 无BOM输入: '%s'(产出=%s) P%d 生产%.0f件, 无物料消耗", writeLog(String.format("[结果输出] 无BOM输入: '%s'(产出=%s) P%d 生产%.0f件, 无物料消耗",
op.getId(), outputLabel, p.getIndex(), ptQty)); op.getId(), outputLabel, p.getIndex(), ptQty));
} }
} }
...@@ -556,7 +570,7 @@ public class SolutionPrinter { ...@@ -556,7 +570,7 @@ public class SolutionPrinter {
// --- 期末库存 --- // --- 期末库存 ---
private void printEndingInventory(Period p) { private void printEndingInventory(Period p) {
System.out.print(" ▶ 期末库存"); writeLog(" ▶ 期末库存");
// 收集所有产品在本周期的库存 // 收集所有产品在本周期的库存
List<String> items = new ArrayList<>(); List<String> items = new ArrayList<>();
for (Product prod : data.getProducts()) { for (Product prod : data.getProducts()) {
...@@ -566,16 +580,16 @@ public class SolutionPrinter { ...@@ -566,16 +580,16 @@ public class SolutionPrinter {
items.add(String.format("%s@%s=%.0f", prod.getId(), sp.getId(), invQty)); items.add(String.format("%s@%s=%.0f", prod.getId(), sp.getId(), invQty));
} }
} }
System.out.println(" " + String.join(" ", items)); writeLog(" " + String.join(" ", items));
} }
// ==================== KPI 汇总 ==================== // ==================== KPI 汇总 ====================
private void printKpiSummary() { private void printKpiSummary() {
System.out.println();
System.out.println("═══════════════════ KPI 汇总 ═══════════════════"); writeLog("═══════════════════ KPI 汇总 ═══════════════════");
double objValue = model.getSolver().objective().value(); double objValue = model.getSolver().objective().value();
System.out.printf("目标函数值 (最小总惩罚): %.2f%n", objValue); writeLog("目标函数值 (最小总惩罚): %.2f", objValue);
double fulfillment = model.getTotalFulfillment().solutionValue(); double fulfillment = model.getTotalFulfillment().solutionValue();
double lotSize = model.getTotalLotSize().solutionValue(); double lotSize = model.getTotalLotSize().solutionValue();
...@@ -589,31 +603,31 @@ public class SolutionPrinter { ...@@ -589,31 +603,31 @@ public class SolutionPrinter {
double salesPriority = model.getTotalSalesDemandPriority().solutionValue(); double salesPriority = model.getTotalSalesDemandPriority().solutionValue();
KPIWeights w = data.getKpiWeights(); KPIWeights w = data.getKpiWeights();
System.out.printf("需求缺口惩罚: %.2f (权重%.0f × %.2f)%n", writeLog("需求缺口惩罚: %.2f (权重%.0f × %.2f)",
fulfillment * w.getFulfillmentWeight(), w.getFulfillmentWeight(), fulfillment); fulfillment * w.getFulfillmentWeight(), w.getFulfillmentWeight(), fulfillment);
System.out.printf("批次偏差惩罚: %.2f (权重%.0f × %.2f)%n", writeLog("批次偏差惩罚: %.2f (权重%.0f × %.2f)",
lotSize * w.getLotSizeWeight(), w.getLotSizeWeight(), lotSize); lotSize * w.getLotSizeWeight(), w.getLotSizeWeight(), lotSize);
System.out.printf("超库存惩罚: %.2f (权重%.0f × %.2f)%n", writeLog("超库存惩罚: %.2f (权重%.0f × %.2f)",
maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv); maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv);
System.out.printf("欠库存惩罚: %.2f (权重%.0f × %.2f)%n", writeLog("欠库存惩罚: %.2f (权重%.0f × %.2f)",
minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv); minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv);
System.out.printf("目标库存偏差: %.2f (权重%.0f × %.2f)%n", writeLog("目标库存偏差: %.2f (权重%.0f × %.2f)",
targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv); targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv);
System.out.printf("产能超载惩罚: %.2f (权重%.0f × %.2f)%n", writeLog("产能超载惩罚: %.2f (权重%.0f × %.2f)",
capacity * w.getUnitCapacityWeight(), w.getUnitCapacityWeight(), capacity); capacity * w.getUnitCapacityWeight(), w.getUnitCapacityWeight(), capacity);
System.out.printf("供应目标偏差: %.2f (权重%.0f × %.2f)%n", writeLog("供应目标偏差: %.2f (权重%.0f × %.2f)",
supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget); supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget);
System.out.printf("最小供应不足: %.2f (权重%.0f × %.2f)%n", writeLog("最小供应不足: %.2f (权重%.0f × %.2f)",
minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply); minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply);
System.out.printf("最大供应超出: %.2f (权重%.0f × %.2f)%n", writeLog("最大供应超出: %.2f (权重%.0f × %.2f)",
maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply); maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply);
System.out.printf("销售优先级(负): %.2f (权重%.0f × %.2f)%n", writeLog("销售优先级(负): %.2f (权重%.0f × %.2f)",
salesPriority * w.getSalesDemandPriorityWeight(), salesPriority * w.getSalesDemandPriorityWeight(),
w.getSalesDemandPriorityWeight(), salesPriority); w.getSalesDemandPriorityWeight(), salesPriority);
if (fulfillment > 0.001) { if (fulfillment > 0.001) {
System.out.println();
System.out.println("⚠ 注意:存在需求缺口,部分订单未满足。"); writeLog("⚠ 注意:存在需求缺口,部分订单未满足。");
} }
} }
...@@ -621,12 +635,12 @@ public class SolutionPrinter { ...@@ -621,12 +635,12 @@ public class SolutionPrinter {
private void printStatistics() { private void printStatistics() {
long elapsedMs = System.currentTimeMillis() - startTimeMs; long elapsedMs = System.currentTimeMillis() - startTimeMs;
System.out.println(); writeLog("");
System.out.println("═══════════════════ 求解统计 ═══════════════════"); writeLog("═══════════════════ 求解统计 ═══════════════════");
System.out.printf("变量数:%d%n", model.getSolver().numVariables()); writeLog("变量数:%d", model.getSolver().numVariables());
System.out.printf("约束数:%d%n", model.getSolver().numConstraints()); writeLog("约束数:%d", model.getSolver().numConstraints());
System.out.printf("耗时:%.3f 秒%n", elapsedMs / 1000.0); writeLog("耗时:%.3f 秒", elapsedMs / 1000.0);
System.out.println();
} }
// ==================== 分层优化汇总 ==================== // ==================== 分层优化汇总 ====================
...@@ -643,11 +657,11 @@ public class SolutionPrinter { ...@@ -643,11 +657,11 @@ public class SolutionPrinter {
*/ */
public void printHierarchicalSummary(List<StrategyLevel> levels, public void printHierarchicalSummary(List<StrategyLevel> levels,
List<Double> levelObjValues) { List<Double> levelObjValues) {
System.out.println(); writeLog("");
System.out.println("═══════════════════ 分层优化汇总 ═══════════════════"); writeLog("═══════════════════ 分层优化汇总 ═══════════════════");
// 表头 // 表头
System.out.printf("%-6s %-14s %-12s %-12s %s%n", writeLog("%-6s %-14s %-12s %-12s %s",
"层级", "名称", "松弛比例", "最优值", "包含KPI"); "层级", "名称", "松弛比例", "最优值", "包含KPI");
double totalWeightedPenalty = 0.0; double totalWeightedPenalty = 0.0;
...@@ -663,7 +677,7 @@ public class SolutionPrinter { ...@@ -663,7 +677,7 @@ public class SolutionPrinter {
kpiNames.add(kpi.name); kpiNames.add(kpi.name);
} }
System.out.printf("%-6d %-14s %-12s %-12.2f %s%n", writeLog("%-6d %-14s %-12s %-12.2f %s",
level.getLevel(), level.getLevel(),
level.getName(), level.getName(),
String.format("%.0f%%", level.getRelativeGoalSlack() * 100), String.format("%.0f%%", level.getRelativeGoalSlack() * 100),
...@@ -672,8 +686,8 @@ public class SolutionPrinter { ...@@ -672,8 +686,8 @@ public class SolutionPrinter {
} }
// 计算并输出加权总惩罚 (等价于单目标函数值) // 计算并输出加权总惩罚 (等价于单目标函数值)
System.out.println(); writeLog("");
System.out.println("--- 最终 KPI 值 (加权总惩罚) ---"); writeLog("--- 最终 KPI 值 (加权总惩罚) ---");
double fulfillment = model.getTotalFulfillment().solutionValue(); double fulfillment = model.getTotalFulfillment().solutionValue();
double lotSize = model.getTotalLotSize().solutionValue(); double lotSize = model.getTotalLotSize().solutionValue();
double maxInv = model.getTotalMaxInventoryLevel().solutionValue(); double maxInv = model.getTotalMaxInventoryLevel().solutionValue();
...@@ -696,26 +710,26 @@ public class SolutionPrinter { ...@@ -696,26 +710,26 @@ public class SolutionPrinter {
+ maxSupply * w.getMaxSupplyWeight() + maxSupply * w.getMaxSupplyWeight()
- salesPriority * w.getSalesDemandPriorityWeight(); - salesPriority * w.getSalesDemandPriorityWeight();
System.out.printf(" 加权总惩罚: %.2f%n", totalPenalty); writeLog(" 加权总惩罚: %.2f", totalPenalty);
System.out.printf(" 需求缺口: %.2f (权重%.0f × %.2f)%n", writeLog(" 需求缺口: %.2f (权重%.0f × %.2f)",
fulfillment * w.getFulfillmentWeight(), w.getFulfillmentWeight(), fulfillment); fulfillment * w.getFulfillmentWeight(), w.getFulfillmentWeight(), fulfillment);
System.out.printf(" 产能超载: %.2f (权重%.0f × %.2f)%n", writeLog(" 产能超载: %.2f (权重%.0f × %.2f)",
capacity * w.getUnitCapacityWeight(), w.getUnitCapacityWeight(), capacity); capacity * w.getUnitCapacityWeight(), w.getUnitCapacityWeight(), capacity);
System.out.printf(" 批次偏差: %.2f (权重%.0f × %.2f)%n", writeLog(" 批次偏差: %.2f (权重%.0f × %.2f)",
lotSize * w.getLotSizeWeight(), w.getLotSizeWeight(), lotSize); lotSize * w.getLotSizeWeight(), w.getLotSizeWeight(), lotSize);
System.out.printf(" 目标库存偏差: %.2f (权重%.0f × %.2f)%n", writeLog(" 目标库存偏差: %.2f (权重%.0f × %.2f)",
targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv); targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv);
System.out.printf(" 供应目标偏差: %.2f (权重%.0f × %.2f)%n", writeLog(" 供应目标偏差: %.2f (权重%.0f × %.2f)",
supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget); supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget);
System.out.printf(" 超库存: %.2f (权重%.0f × %.2f)%n", writeLog(" 超库存: %.2f (权重%.0f × %.2f)",
maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv); maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv);
System.out.printf(" 欠库存: %.2f (权重%.0f × %.2f)%n", writeLog(" 欠库存: %.2f (权重%.0f × %.2f)",
minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv); minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv);
System.out.printf(" 最小供应不足: %.2f (权重%.0f × %.2f)%n", writeLog(" 最小供应不足: %.2f (权重%.0f × %.2f)",
minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply); minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply);
System.out.printf(" 最大供应超出: %.2f (权重%.0f × %.2f)%n", writeLog(" 最大供应超出: %.2f (权重%.0f × %.2f)",
maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply); maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply);
System.out.printf(" 销售优先级(负): %.2f (权重%.0f × %.2f)%n", writeLog(" 销售优先级(负): %.2f (权重%.0f × %.2f)",
salesPriority * w.getSalesDemandPriorityWeight(), salesPriority * w.getSalesDemandPriorityWeight(),
w.getSalesDemandPriorityWeight(), salesPriority); w.getSalesDemandPriorityWeight(), salesPriority);
} }
......
package com.aps.service.Algorithm;
/**
* 作者:佟礼
* 时间:2026-08-18
*/
public class POAOrToolsModel {
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment