Skip to content

运筹学领域语言 ​

OSPF 的建模接口可以从一门内部领域特定语言(DSL)的角度理解:使用 Kotlin 或 Rust 的合法程序表达数学模型,同时让业务概念成为可以命名、组合和复用的对象。本章解释语言的设计,不要求读者先理解求解器内部算法。

1. 基本表达、组合与抽象 ​

一门建模语言需要三个层次。基本表达给出常量、参数和决策变量;组合形成算术表达式、逻辑关系、约束和目标;抽象为重复出现的表达命名,使其可以作为一个整体使用。

元素数学含义不应混淆的对象
参数本次建模前已知的数据求解器决定的变量
变量在给定域中寻找的未知值当前 incumbent 中的数值
表达式参数、变量及运算组成的关系已完成的数值计算
中间值具有业务名称和定义的表达任意独立取值的新变量
约束限制可行方案的条件仅用于输入数据检查的断言
目标可行方案之间的偏好必须满足的可行性条件

线性表达、多项式表达只是语言的一部分。逻辑关系、有限域和 CP 全局约束具有自己的语义,不能都定义为“两个多项式之间的不等式”。

2. 贯穿模型:舱位载重 ​

概述、概念与集合 ​

载重上下文决定货物放在哪个舱位,并对舱位载重量、面积载荷和单位长度载荷施加限制。这里讨论简化的静态均布模型,不替代结构强度分析。

货物集合为 I,舱位集合为 J。货物 i 的质量为 wi>0,单位 kg。舱位 j 的有效面积为 Aj>0(平方米),长度为 Lj>0(米),容量为 Cj(kg);面积载荷上限为 Pj(kg/平方米),线载荷上限为 Dj(kg/米)。面积载荷有时在业务中称为“压强”,但这里不是以 Pa 表示的物理压力。

变量与谓词 ​

xij∈{0,1} 表示是否将货物 i 放入舱位 j,无量纲,∀i∈I,j∈J。兼容关系 R⊆I×J 描述允许的货物—舱位组合。不允许的组合固定 xij=0。本例不需要独立辅助变量。

中间值 ​

舱位载重量是分配到该舱位的货物质量之和:

Wj=∑i∈Iwixij.

面积载荷和线载荷分别由同一个载重量定义:

pj=WjAj,dj=WjLj,j∈J.

它们不是三次互不相关的决策。Wj 确定后,pj 和 dj 也随之确定。

数据断言与约束 ​

面积和长度为正是构建表达式前的输入断言,不能留给求解器“选择”。每件货物至多装载一次,每个舱位同时满足三类限制:

s.t.∑j∈Jxij≤1∀i∈I,Wj≤Cj∀j∈J,pj≤Pj,dj≤Dj∀j∈J,xij=0∀(i,j)∉R.

目标与结果 ​

最大化总装载质量:

max∑j∈JWj.

设一个舱位 A=2、L=2、C=120、P=50、D=60,两件兼容货物质量为 60 和 40 kg。装入两件时 W=100、p=50、d=50,所有限制成立且达到总可用货量,因此最优。面积载荷限制贴边,但仅由这一点不能推出其放宽有收益。

3. 为什么需要中间值 ​

如果所有约束都重复书写 ∑iwixij,读者每次都要重新识别“这代表载重量”。命名 Wj 后,表达式成为业务词汇,容量、面积载荷和线载荷约束可以共享其定义。

中间值在引用位置可以像变量一样参与组合,但数学上仍受定义约束。算术中间值可能直接展开;函数符号可能需要辅助变量和约束实现。不能假设每个名字都创建一列,也不能假设每个名字都只是文本替换。具体转换见类编译器架构与模型转换。

“共享”指明确模型或业务上下文中的共享,不是进程全局对象。将旧模型中的符号引用直接放进新模型,可能产生身份和生命周期错误。

4. 索引、批量表达与宿主语言 ​

xij 是一个变量族,索引描述业务身份;求解器列号只是一次编译后的定位。过滤兼容关系、对舱位聚合、取得某个货物的分配向量,都应保留索引的业务意义。集合为空时,空和为零是否符合业务,需要在建模时明确。

内部 DSL 借助宿主语言的函数、类型、集合和运算符构造表达式。宿主程序中的普通分支在模型构建时执行;依赖未知决策变量的条件必须进入符号逻辑,不能用普通布尔分支提前作出决定。

5. 从语言到业务组件 ​

载重上下文可以暴露 Wj,面积载荷和线载荷规则引用它,而不复制其构造过程。这是从语言抽象走向上下文接口的起点。变量的业务所有权、表达式依赖以及 Kotlin/Rust 的内存管理是不同层次,不应使用一张所有权图替代全部解释。

继续阅读建模与求解的完整流程,了解这些元素怎样组装;阅读使用领域驱动设计架构,了解它们怎样在业务上下文之间协作。