示例 8:设备工时约束下的生产
1. 概览
本有界上下文选择非负整数产量,在设备工时容量约束下最大化产品利润。本文遵循当前 Demo8 的数据与模型;代码标签页是模型构建片段,数据由源码文件提供。
五种产品利润为
| 设备 | 数量 | P1 | P2 | P3 | P4 | P5 |
|---|---|---|---|---|---|---|
| A | 12 | .23 | .44 | .17 | .08 | .36 |
| B | 14 | .13 | – | .20 | .37 | .19 |
| C | 8 | – | .25 | .34 | – | .18 |
| D | 6 | .55 | .72 | – | .61 | – |
每台设备最多提供
1. 依赖上下文
- 无。不需要外部领域上下文。
2. 概念 / 实体
1. 产品(Product)
产品可以按整数数量生产,并为每个生产单位贡献固定利润。
2. 设备类型(Equipment Type)
设备类型具有若干可用设备,并且生产每个产品会消耗该设备类型特定的工时。
3. 变量
1. 决策变量
UIntVariable1,Rust 源码使用 VariableCombination1D<UInteger>。
2. 辅助变量
没有单独声明的辅助决策变量。Profit 和 ManHours_e 是注册的派生符号。
4. 谓词
1. 设备使用谓词
该谓词识别参与某个设备工时表达式的系数。
Uses(e,p):当源码存在非缺失的 e.manHours[p] 系数时为真。缺失映射项不进入求和;Rust 数据用 0.00 表示并过滤零系数。
5. 集合
1. 产品与设备类别
2. 实体对 / 关系
6. 中间值
1. 总利润
说明:所有产品产量产生的总利润。
2. 设备工时
说明:设备类型
3. 设备容量
说明:设备类型
Capacity_e 是文档层面的派生值;当前源码在每条约束中直接写出该乘积。
7. 断言
1. 缺失系数不消耗建模工时
说明:设备映射中不存在的产品不会出现在该设备的工时表达式中。
2. 设备容量非负
说明:设备数量和每台设备最大工时均非负,因此每个设备容量非负。
3. 历史求解观察
说明:此前一次运行报告
8. 约束
1. Equipment Man-Hour Capacity(设备工时容量约束)
说明:某设备类型承担的生产工时不能超过所有可用设备提供的工时。
2. Production Domain(产量定义域约束)
说明:每种产品产量都是非负整数。
9. 目标函数(如适用)
说明:最大化产品总利润。
10. 算法引用
没有引用独立算法文档。模型通过当前 Kotlin LinearMetaModel/SCIP 路径或 Rust MetaModel/求解器路径注册为线性表达式。
| 算法名称 | 文件路径 | 引用位置 | 简要说明 |
|---|---|---|---|
| 无 | — | — | 不需要独立算法。 |
11. 统一语言
| 术语 | 符号 | 定义 |
|---|---|---|
| 产品 | 被选择整数产量的产品。 | |
| 设备 | 具有有限数量和工时的资源类型。 | |
| 利润 | 生产带来的总价值。 | |
| 工时 | 设备类型 | |
| 容量 | 设备类型 |
12. 设计决策
| 决策 | 替代方案 | 理由 | 日期 |
|---|---|---|---|
| 保持产量为整数,且只通过工时约束间接限制上界 | 连续产量或手动添加产品上界 | 与当前 UIntVariable1/UInteger 一致 | 2026-09-08 |
| 忽略缺失的设备系数 | 将短横线视为显式业务惩罚 | Kotlin 映射省略键,Rust 过滤零系数 | 2026-09-08 |
保留 ManHours 命名 | 改名为 Cost | 系数的物理单位是工时,Cost 会错误表达含义 | 2026-09-08 |
当前最小模型构建片段
products、equipments/equipment 来自 Demo8.kt 和 demo8.rs;转换器及求解器设置也由源码提供。以下只展示注册符号和约束。
// `products`、`equipments`、`maxManHours` 和 `flt64Converter` 来自 Demo8.kt。
val metaModel = LinearMetaModel<Flt64>("demo8", converter = flt64Converter)
val x = UIntVariable1("x", Shape1(products.size))
val profit = LinearExpressionSymbol(
sum(products.map { p -> p.profit * x[p] }),
name = "profit"
)
val manHours = LinearIntermediateSymbols1<Flt64>(
"man_hours",
Shape1(equipments.size)
) { i, _ ->
val e = equipments[i]
LinearExpressionSymbol(
sum(products.mapNotNull { p -> e.manHours[p]?.let { it * x[p] } }),
name = "man_hours_${e.index}"
)
}
metaModel.add(x)
metaModel.add(profit)
metaModel.add(manHours)
metaModel.maximize(profit, "profit")
for (e in equipments) {
metaModel.addConstraint(
manHours[e] leq e.amount.toFlt64() * maxManHours,
name = "eq_man_hours_${e.index}"
)
}// `products` 和 `equipments` 来自 demo8.rs。
let mut model = MetaModel::<f64>::new("demo8");
let x: VariableCombination1D<UInteger> =
VariableCombination1D::new(Shape::new([products.len()]), "x");
let x_idx = model.register_combination(&x)?;
let profit = flat_map1_indexed("profit", products, |i, product| {
ospf_rust_core::symbol::flatten::Linear::new(
vec![ospf_rust_core::symbol::flatten::LinearMonomial::new(
product.profit, x_idx[i],
)],
0.0,
)
}, |_, product| product.name.clone());
model.add_symbol_combination(&profit)?;
let man_hours = flat_map1("man_hours", equipments, |equipment| {
let monomials = products.iter().enumerate().filter_map(|(p, _)| {
let value = equipment.man_hours_by_product[p];
(value != 0.0).then(||
ospf_rust_core::symbol::flatten::LinearMonomial::new(value, x_idx[p])
)
}).collect();
ospf_rust_core::symbol::flatten::Linear::new(monomials, 0.0)
}, |_, equipment| equipment.name.clone());
model.add_symbol_combination(&man_hours)?;
let profit_coeffs = extract_coeffs(&profit[0]);
model.add_linear_objective(&profit_coeffs, "profit");
model.set_objective_category(ObjectiveCategory::Maximum);
for (e, equipment) in equipments.iter().enumerate() {
model.add_linear_constraint(
&extract_coeffs(&man_hours[e]), ConstraintRelation::LessEqual,
equipment.amount * max_man_hours, &format!("equipment_{}_{}", e, equipment.name),
)?;
}13. 变更记录
| 版本 | 变更 | 原因 |
|---|---|---|
| 2026-09-08 | 按领域模型模板重组页面;补充带量词的中间值、双语约束名及 Kotlin/Rust 标签页 | 与当前 Demo8 实现保持一致 |