Commit 48800682 authored by Tong Li's avatar Tong Li

MP

parent 74589725
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
<dependency> <dependency>
<groupId>com.google.ortools</groupId> <groupId>com.google.ortools</groupId>
<artifactId>ortools-java</artifactId> <artifactId>ortools-java</artifactId>
<version>9.7.2996</version> <version>9.15.6755</version>
</dependency> </dependency>
<!-- HTTP客户端 (用于调用LLM API) --> <!-- HTTP客户端 (用于调用LLM API) -->
......
...@@ -56,4 +56,23 @@ public class FileHelper { ...@@ -56,4 +56,23 @@ public class FileHelper {
System.err.println("Failed to write log: " + e.getMessage()); System.err.println("Failed to write log: " + e.getMessage());
} }
} }
public static void writeFile(String message,String fileName) {
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))+"-";
// 确保目录存在
java.io.File logDir = new java.io.File(LOG_FILE_PATH);
if (!logDir.exists()) {
logDir.mkdirs(); // 创建目录(包括父目录)
}
String filePath = LOG_FILE_PATH + date + fileName;
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath, true))) {
writer.print(message);
} catch (IOException e) {
System.err.println("Failed to write log: " + e.getMessage());
}
}
} }
\ No newline at end of file
package com.aps.common.util;
import java.io.OutputStream;
import java.io.PrintStream;
/**
* 作者:佟礼
* 时间:2026-07-24
*/
public class TeePrintStream extends PrintStream {
private final PrintStream other;
public TeePrintStream(OutputStream main, PrintStream other) {
super(main);
this.other = other;
}
@Override
public void write(int b) {
super.write(b);
other.write(b);
}
@Override
public void write(byte[] buf, int off, int len) {
super.write(buf, off, len);
other.write(buf, off, len);
}
@Override
public void flush() {
super.flush();
other.flush();
}
@Override
public void close() {
super.close();
other.close();
}
}
This diff is collapsed.
package com.aps.macroplanner;
import com.google.ortools.Loader;
import com.aps.macroplanner.data.RoutingTestDataBuilder;
import com.aps.macroplanner.data.TestDataBuilder;
/**
* 多工序路由测试运行器
*
* <p>验证场景: 3 工序串行生产同一产品, 验证:
* <ol>
* <li>最终产出 = 销售需求 (不是 3×)</li>
* <li>中间 WIP 库存不异常累积</li>
* <li>各工序独立消耗各自设备产能</li>
* </ol>
*/
public class RoutingTestRunner {
public static void main(String[] args) {
Loader.loadNativeLibraries();
System.out.println("===== ROUTING TEST RUNNER START =====");
System.out.println("Scenario: 3-step routing OP_Cut -> OP_Rough -> OP_Finish -> Product P");
System.out.println();
TestDataBuilder data = new RoutingTestDataBuilder();
System.out.println("Data loaded: " + data.getProducts().size() + " products, "
+ data.getOperations().size() + " operations");
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
optimizer.buildModel();
optimizer.solve();
System.out.println("===== ROUTING TEST RUNNER END =====");
}
}
\ No newline at end of file
package com.aps.macroplanner.constraint;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import java.util.Map;
/**
* 需求缺口联动约束 (DemandSlack Linkage)
*
* <p>强制将 SalesDemandQty 与 DemandSlack 关联起来,确保未满足的销售需求
* 必须通过 DemandSlack 变量暴露,从而触发目标函数中的惩罚。</p>
*
* <h3>问题背景</h3>
* <p>在没有此约束时,求解器可以通过降低 SalesDemandQty 来"规避"需求缺口,
* 而不是通过 DemandSlack 来"暴露"需求缺口。因为 DemandSlack 在物料平衡约束中
* 位于流入侧,SalesDemandQty 在流出侧,求解器可以同时降低两者来平衡方程,
* 从而避免触发 DemandSlack 的高额惩罚(权重 100)。</p>
*
* <h3>数学公式</h3>
* <pre>
* 对于每个 PISPIP (Product × StockingPoint × Period):
* Σ SalesDemandQty + DemandSlack >= Σ DemandQuantity
*
* 即: 实际销售量 + 需求缺口 >= 原始需求总量
* 等价于: DemandSlack >= Σ(DemandQuantity - SalesDemandQty)
* </pre>
*
* <h3>效果</h3>
* <p>添加此约束后,当 SalesDemandQty < DemandQuantity 时,
* DemandSlack 必须填补缺口,从而在目标函数中产生惩罚。
* 求解器被激励去增加生产来满足需求,而非简单地降低销售量。</p>
*
* @see BalanceConstraint 物料平衡约束(DemandSlack 在流入侧)
* @see DemandVariableBuilder 需求变量定义(SalesDemandQty, DemandSlack)
*/
public class DemandSlackLinkageConstraint {
/**
* 构建需求缺口联动约束。
*
* <p>对每个 PISPIP 创建一条约束:
* Σ SalesDemandQty + DemandSlack >= Σ DemandQuantity</p>
*
* @param model 模型容器(提供 SalesDemandQty 和 DemandSlack 变量)
* @param data 测试数据(提供 SalesDemand 列表)
*/
public static void build(MacroPlannerModel model, TestDataBuilder data) {
Map<String, MPVariable> sdVars = model.getSalesDemandQtyVars();
Map<String, MPVariable> slackVars = model.getDemandSlackVars();
for (Product prod : data.getProducts()) {
for (StockingPoint sp : data.getStockingPointsForProduct(prod.getId())) {
for (Period p : data.getPeriods()) {
String pispipKey = prod.getId() + "_" + sp.getId() + "_" + p.getIndex();
double totalDemandQty = 0.0;
boolean hasDemand = false;
// 汇总该 PISPIP 的所有销售需求
for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
totalDemandQty += sd.getQuantity();
hasDemand = true;
}
// 只对存在销售需求的 PISPIP 创建约束
if (!hasDemand) continue;
// 约束: Σ SalesDemandQty + DemandSlack >= totalDemandQty
MPConstraint c = model.getSolver().makeConstraint(
totalDemandQty, MPSolver.infinity(),
"DSLink_" + pispipKey);
// + Σ SalesDemandQty
for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
MPVariable sdVar = sdVars.get(sd.getKey());
if (sdVar != null) c.setCoefficient(sdVar, 1.0);
}
// + DemandSlack
MPVariable slackVar = slackVars.get(pispipKey);
if (slackVar != null) c.setCoefficient(slackVar, 1.0);
}
}
}
}
}
\ No newline at end of file
This diff is collapsed.
package com.aps.macroplanner.data;
import java.time.LocalDate;
/**
* 在途供应 (InTransitSupply) — 原材料供应商已发货、将在未来到货的固定供应量。
*
* <p>与初始库存不同,在途供应是计划在未来某个日期到达的固定流入量,
* 不是决策变量,而是作为物料平衡约束中的已知常量。</p>
*
* <p>典型场景: 供应商已承诺在 2026-01-07 交付 100 件原材料 R1</p>
*
* <p>arrivalDate 通过 TestDataBuilder.getPeriodByDate() 映射到对应的周期。</p>
*/
public class InTransitSupply {
private final Product product;
private final StockingPoint stockingPoint;
private final LocalDate arrivalDate;
private final double quantity;
public InTransitSupply(Product product, StockingPoint stockingPoint,
LocalDate arrivalDate, double quantity) {
this.product = product;
this.stockingPoint = stockingPoint;
this.arrivalDate = arrivalDate;
this.quantity = quantity;
}
public Product getProduct() { return product; }
public StockingPoint getStockingPoint() { return stockingPoint; }
public LocalDate getArrivalDate() { return arrivalDate; }
public double getQuantity() { return quantity; }
@Override
public String toString() {
return product.getId() + "@" + stockingPoint.getId()
+ " 到货" + arrivalDate + " " + quantity + "件";
}
}
\ No newline at end of file
package com.aps.macroplanner.data;
/**
* 初始库存 — 定义某个产品在某个库存点的期初库存量。
*
* <p>替代之前用字符串 key ("productId_spId") 的 Map 方式, 更清晰。</p>
*/
public class InitialInventory {
private final Product product;
private final StockingPoint stockingPoint;
private final double quantity;
public InitialInventory(Product product, StockingPoint stockingPoint, double quantity) {
this.product = product;
this.stockingPoint = stockingPoint;
this.quantity = quantity;
}
public Product getProduct() { return product; }
public StockingPoint getStockingPoint() { return stockingPoint; }
public double getQuantity() { return quantity; }
@Override
public String toString() {
return product.getId() + "@" + stockingPoint.getId() + " = " + quantity;
}
}
\ No newline at end of file
package com.aps.macroplanner.data;
/**
* 操作输出 (OperationOutput) — 对应 Quintiq 中的 OperationOutput → PISP
*
* <p>定义某个操作 (Operation) 生产的产品及其产出到的库存点。
* 与 {@link OperationInput} 对称: 输入用 Product + StockingPoint, 输出也用 Product + StockingPoint。</p>
*
* <h3>多工序路由场景</h3>
* <pre>
* 下料 → OperationOutput(P, SP_WIP1)
* 粗加工 → OperationOutput(P, SP_WIP2)
* 精加工 → OperationOutput(P, SP_FG)
* </pre>
* 同一产品经不同工序产出到不同库存点, 通过 {@link OperationOutput} 的 stockingPoint 区分。
*/
public class OperationOutput {
private final Product product; // 产出产品
private final StockingPoint stockingPoint; // 产出到哪个库存点
public OperationOutput(Product product, StockingPoint stockingPoint) {
this.product = product;
this.stockingPoint = stockingPoint;
}
public Product getProduct() { return product; }
public StockingPoint getStockingPoint() { return stockingPoint; }
/** 便捷方法: 产品 ID */
public String getProductId() { return product.getId(); }
/** 便捷方法: 库存点 ID */
public String getSpId() { return stockingPoint.getId(); }
@Override
public String toString() {
return product.getId() + "@" + stockingPoint.getId();
}
}
\ No newline at end of file
package com.aps.macroplanner.data;
/**
* 产品→库存点映射 — 定义一个产品存放在哪个库存点。
*
* <p>替代之前用 Map<String, List<StockingPoint>> 的 productSpMap 方式,
* 声明式地定义产品与库存点的关系。</p>
*
* <p>一个产品可以在多个库存点存放 (通过多个 ProductSpMapping 记录)。</p>
*/
public class ProductSpMapping {
private final Product product;
private final StockingPoint stockingPoint;
public ProductSpMapping(Product product, StockingPoint stockingPoint) {
this.product = product;
this.stockingPoint = stockingPoint;
}
public Product getProduct() { return product; }
public StockingPoint getStockingPoint() { return stockingPoint; }
@Override
public String toString() {
return product.getId() + " → " + stockingPoint.getId();
}
}
\ No newline at end of file
package com.aps.macroplanner.data;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collections;
/**
* 多工序路由测试数据构建器
*
* <h3>测试场景: 3 工序串行生产同一产品 P</h3>
* <pre>
* OP_Cut(下料) ──→ P@SP_WIP1 ──→ OP_Rough(粗加工) ──→ P@SP_WIP2 ──→ OP_Finish(精加工) ──→ P@SP_FG ──→ 销售
* Unit_Cut ↑ Unit_Rough ↑ Unit_Finish ↑
* OP_Rough 消耗 OP_Finish 消耗 销售需求 50/周期
* P@SP_WIP1 (BOM) P@SP_WIP2 (BOM)
*
* 关键验证:
* - 3 个工序各自消耗各自设备的产能 (独立计算)
* - 只有最后一道工序 OP_Finish 的产出计入成品供应
* - 中间工序的产出被 DependentDemand 抵消, 最终产出 = 销售需求, 不是 3×
* </pre>
*/
public class RoutingTestDataBuilder extends TestDataBuilder {
public RoutingTestDataBuilder() {
super(true); // 跳过父类默认 build
build();
}
@Override
protected void build() {
LocalDate baseDate = LocalDate.of(2026, 1, 5);
// === 3 个周期 (每天一个周期) ===
Period p1 = new Period(0, "P1", baseDate);
Period p2 = new Period(1, "P2", baseDate.plusDays(1));
Period p3 = new Period(2, "P3", baseDate.plusDays(2));
periods.addAll(Arrays.asList(p1, p2, p3));
// === 产品: 只有 1 个成品 P ===
Product prodP = new Product("P", "Product-P");
products.add(prodP);
// === 库存点: 2 个 WIP 缓冲区 + 1 个成品库 ===
StockingPoint spWIP1 = new StockingPoint("SP_WIP1", "下料→粗加工缓冲区");
StockingPoint spWIP2 = new StockingPoint("SP_WIP2", "粗加工→精加工缓冲区");
StockingPoint spFG = new StockingPoint("SP_FG", "成品库");
stockingPoints.addAll(Arrays.asList(spWIP1, spWIP2, spFG));
// === 产品→库存点映射 (P 在三个库存点都有) ===
productSpMappings.add(new ProductSpMapping(prodP, spWIP1));
productSpMappings.add(new ProductSpMapping(prodP, spWIP2));
productSpMappings.add(new ProductSpMapping(prodP, spFG));
// === 3 道工序, 都产出产品 P, 但分属不同设备 ===
// 下料: 1.0h/件, 设备 Unit_Cut, 产出到 WIP1 缓冲区
Operation opCut = new Operation("OP_Cut", "下料", "Unit_Cut",
new OperationOutput(prodP, spWIP1), 1.0, 1.0, false, 0, 1.0);
// 粗加工: 1.5h/件, 设备 Unit_Rough, 产出到 WIP2 缓冲区
Operation opRough = new Operation("OP_Rough", "粗加工", "Unit_Rough",
new OperationOutput(prodP, spWIP2), 1.5, 1.0, false, 0, 1.0);
// 精加工: 2.0h/件, 设备 Unit_Finish, 产出到成品库
Operation opFinish = new Operation("OP_Finish", "精加工", "Unit_Finish",
new OperationOutput(prodP, spFG), 2.0, 1.0, false, 0, 1.0);
operations.addAll(Arrays.asList(opCut, opRough, opFinish));
// === BOM / OperationInput: 定义工序间流转 ===
// OP_Rough 消耗 P@SP_WIP1 (即 OP_Cut 的产出), factor=1.0 (1:1 无损耗)
operationInputs.add(new OperationInput(opRough, prodP, spWIP1, 1.0));
// OP_Finish 消耗 P@SP_WIP2 (即 OP_Rough 的产出), factor=1.0
operationInputs.add(new OperationInput(opFinish, prodP, spWIP2, 1.0));
// === 设备产能: 三个设备各有独立产能 ===
// Unit_Cut: 最大 200h/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("Unit_Cut", p, 0.0, 200.0, false));
}
// Unit_Rough: 最大 200h/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("Unit_Rough", p, 0.0, 200.0, false));
}
// Unit_Finish: 最大 200h/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("Unit_Finish", p, 0.0, 200.0, false));
}
// === 初始库存: 全部为 0 (没有初始 WIP) ===
initialInventories.add(new InitialInventory(prodP, spWIP1, 0.0));
initialInventories.add(new InitialInventory(prodP, spWIP2, 0.0));
initialInventories.add(new InitialInventory(prodP, spFG, 0.0));
// === 销售需求: 只在成品库 SP_FG, 每周期 50 ===
for (Period p : periods) {
salesDemands.add(new SalesDemand(prodP, spFG, p, 50.0, 1.0));
}
// === 库存规格: 只在成品库设目标/最小/最大 ===
// WIP 缓冲区不设库存规格 (不约束中间库存)
for (Period p : periods) {
inventorySpecs.add(new InventorySpec(prodP, spFG, p,
80.0, 10.0, 200.0, true, true, true));
}
// === 供应规格: 只统计精加工(最后一道工序)的产出 ===
supplySpecs.add(new SupplySpec("Supply-P", 150.0, 100.0, 300.0,
true, Collections.singletonList(opFinish)));
// === KPI 权重 ===
kpiWeights = new KPIWeights(
100.0, // fulfillmentWeight
10.0, // lotSizeWeight
5.0, // maxInventoryLevelWeight
5.0, // minInventoryLevelWeight
8.0, // targetInventoryLevelWeight
3.0, // unitCapacityWeight
8.0, // supplyTargetWeight
5.0, // minSupplyWeight
5.0, // maxSupplyWeight
1.0, // salesDemandPriorityWeight
20.0, // postponementPenaltyWeight
5.0 // processMaxQuantityWeight
);
}
}
\ No newline at end of file
package com.aps.macroplanner.objective;
import com.google.ortools.linearsolver.MPVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 策略层级配置 — 对应 Quintiq 中的 StrategyLevel
*
* <p>每个层级包含一组 KPI,在分层优化中作为一个整体求解。
* 层级越低(level 值越小),优先级越高,越先求解。</p>
*
* <h3>分层优化流程</h3>
* <pre>
* 第1轮: 只优化 Level 1 的 KPI → 记录最优值 V1
* 第2轮: 约束 Level1 ≤ V1×(1+slack) → 优化 Level 2 的 KPI
* 第3轮: 约束 Level1+2 ≤ 最优×(1+slack) → 优化 Level 3 的 KPI
* ...
* </pre>
*
* <h3>默认层级划分</h3>
* <pre>
* Level 1 (需求满足): Fulfillment (需求缺口 = 硬约束)
* Level 2 (产能): UnitCapacity (产能超载 = 物理约束)
* Level 3 (业务KPI): LotSize, TargetInventory, SupplyTarget, SalesPriority
* Level 4 (软约束): MaxInventory, MinInventory, MinSupply, MaxSupply
* </pre>
*
* @see ObjectiveBuilder
* @see com.aps.macroplanner.MacroPlannerOptimizer
*/
public class StrategyLevel {
/** 层级编号 (1 最高优先, 数值越大优先级越低) */
private final int level;
/** 层级名称 (用于日志输出) */
private final String name;
/**
* 目标松弛比例 — 允许上层最优值退化的比例。
* 例如 0.0 表示不允许退化(严格分层),
* 0.05 表示允许上层目标值恶化 5% 以换取下层优化空间。
* 对应 Quintiq 中的 RelativeGoalSlack。
*/
private final double relativeGoalSlack;
/** 该层级包含的 KPI 条目列表 */
private final List<KPIEntry> kpis = new ArrayList<>();
/**
* 单个 KPI 条目 — 封装变量、权重和方向。
*/
public static class KPIEntry {
/** 该 KPI 的汇总变量 (如 TotalFulfillment) */
public final MPVariable variable;
/** 该 KPI 在当前层级内的权重 */
public final double weight;
/** 是否为负向 KPI (越小越好 = 正常惩罚项; false = 正常惩罚项) */
public final boolean isNegative;
/** KPI 名称 (用于日志) */
public final String name;
public KPIEntry(String name, MPVariable variable, double weight, boolean isNegative) {
this.name = name;
this.variable = variable;
this.weight = weight;
this.isNegative = isNegative;
}
/**
* 计算该 KPI 在目标函数中的实际系数。
* 负向 KPI (如 SalesDemandPriority) 使用负系数实现最大化。
*/
public double effectiveCoefficient() {
return isNegative ? -weight : weight;
}
}
/**
* 创建策略层级。
*
* @param level 层级编号 (1-based, 越小优先级越高)
* @param name 层级名称
* @param relativeGoalSlack 目标松弛比例 (0.0 = 严格分层)
*/
public StrategyLevel(int level, String name, double relativeGoalSlack) {
this.level = level;
this.name = name;
this.relativeGoalSlack = relativeGoalSlack;
}
/**
* 添加一个 KPI 到该层级。
*
* @param name KPI 名称
* @param variable KPI 汇总变量
* @param weight 权重 (0 = 跳过)
*/
public void addKPI(String name, MPVariable variable, double weight) {
addKPI(name, variable, weight, false);
}
/**
* 添加一个 KPI 到该层级。
*
* @param name KPI 名称
* @param variable KPI 汇总变量
* @param weight 权重 (0 = 跳过)
* @param isNegative 是否为负向 KPI (true = 最大化, 使用负系数)
*/
public void addKPI(String name, MPVariable variable, double weight, boolean isNegative) {
if (weight > 0.0 && variable != null) {
kpis.add(new KPIEntry(name, variable, weight, isNegative));
}
}
// ==================== Getters ====================
public int getLevel() { return level; }
public String getName() { return name; }
public double getRelativeGoalSlack() { return relativeGoalSlack; }
public List<KPIEntry> getKpis() { return Collections.unmodifiableList(kpis); }
/**
* 该层级是否有任何有效的 KPI。
*/
public boolean hasKpis() {
return !kpis.isEmpty();
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.aps.service.mp;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
/**
* 作者:佟礼
* 时间:2026-07-29
*/
public class InitVariablesProduct {
/**
* 创建单层产品的决策变量
* @param solver 求解器
* @param config 层配置
* @param numDays 天数
* @param isPurchaseLayer 是否为采购层(原材料层,无生产变量和开关变量)
* @return 层变量
*/
static ProductLayerVariables createVariables(MPSolver solver, ProductLayerConfig config,
int numDays, boolean isPurchaseLayer) {
ProductLayerVariables vars = new ProductLayerVariables();
int n = config.size();
if (isPurchaseLayer) {
// 原材料层:只有采购量和库存变量
vars.purchase = new MPVariable[n][numDays];
vars.inventory = new MPVariable[n][numDays];
for (int i = 0; i < n; i++) {
for (int t = 0; t < numDays; t++) {
vars.purchase[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
"pr_" + config.names[i] + "_d" + (t + 1));
vars.inventory[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
"ir_" + config.names[i] + "_d" + (t + 1));
}
}
} else {
// 生产层:产量、库存、开关变量
vars.production = new MPVariable[n][numDays];
vars.inventory = new MPVariable[n][numDays];
vars.switchVar = new MPVariable[n][numDays];
String prefix = config.names[0].startsWith("P") ? "xp" : "xs";
String invPrefix = config.names[0].startsWith("P") ? "ip" : "is";
String swPrefix = config.names[0].startsWith("P") ? "yp" : "ys";
for (int i = 0; i < n; i++) {
for (int t = 0; t < numDays; t++) {
vars.production[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
prefix + "_" + config.names[i] + "_d" + (t + 1));
vars.inventory[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY,
invPrefix + "_" + config.names[i] + "_d" + (t + 1));
vars.switchVar[i][t] = solver.makeBoolVar(
swPrefix + "_" + config.names[i] + "_d" + (t + 1));
}
}
}
return vars;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package com.aps.service.mp;
/**
* 作者:佟礼
* 时间:2026-07-29
* 产品层配置:封装某一层产品的所有参数
*/
public class ProductLayerConfig {
String[] names; // 产品名称数组
double[] prodCost; // 单位生产成本
double[] prodRate; // 生产效率(件/小时)
double[] setupCost; // 换型成本
double[] holdCost; // 库存持有成本
double[] initInv; // 初始库存
double dailyCapacity; // 产线日产能(小时)
ProductLayerConfig(String[] names, double[] prodCost, double[] prodRate,
double[] setupCost, double[] holdCost, double[] initInv,
double dailyCapacity) {
this.names = names;
this.prodCost = prodCost;
this.prodRate = prodRate;
this.setupCost = setupCost;
this.holdCost = holdCost;
this.initInv = initInv;
this.dailyCapacity = dailyCapacity;
}
int size() { return names.length; }
}
package com.aps.service.mp;
import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
/**
* 作者:佟礼
* 时间:2026-07-29
* 产品层变量:封装某一层的所有决策变量
*/
public class ProductLayerVariables {
MPVariable[][] production; // 产量变量 [item][day]
MPVariable[][] inventory; // 库存变量 [item][day]
MPVariable[][] switchVar; // 生产开关变量 [item][day](半成品/成品生产用)
MPVariable[][] purchase; // 采购变量 [item][day](仅原材料层用)
}
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