如何在Z3中以Max-SMT形式编码频率分配问题及相关疑问
频率分配Max-SMT编码问题解答
先直接回应你的两个疑问,再分析代码结果偏离最优值的原因,最后给出修正思路:
问题1:软约束的转化是否正确?
是的,你理解的核心逻辑是对的——把原软约束C转化为C ∨ p(p是新增的违反标记命题变量),再通过p关联违反成本。不过不用手动写这么繁琐:Z3的Optimize.add_soft()方法已经封装了这个逻辑,它会自动引入辅助变量、处理成本累加,你只需要传入原软约束表达式和对应的权重即可,和你代码里用add_soft()的方式是一致的,不用额外手动构造(C ∨ p)的子句。
问题2:如何编码实现成本最小化并查看结果?
Z3的Optimize模块默认会把所有add_soft()传入的权重作为目标,自动最小化违反软约束的权重总和。要查看最终成本,只需要在s.check()返回sat后,通过s.objectives()获取目标表达式,再用模型evaluate()计算具体数值即可。不过你的代码结果偏离最优值,核心问题出在约束编码错误和变量重复定义上,而非成本计算的逻辑。
代码结果偏离最优值的核心问题
1. 硬约束编码错误
你处理硬约束|fi - fj|=d时,错误地使用了软约束的上下界逻辑(比如mi - mj >= floor(...) +1),但硬约束需要的是精确等式:
- 比如
ti ∧ ¬tj场景下,fi=2+14mi,fj=8+14mj,所以|(2+14mi)-(8+14mj)|=d等价于|14(mi-mj)-6|=d,也就是14(mi-mj)-6 = d或14(mi-mj)-6 = -d,对应整数解mi-mj=(d+6)/14或mi-mj=(-d+6)/14,而不是你写的大于等于/小于等于的或条件。 - 其他
ti/tj组合的硬约束也存在类似的错误,这会导致硬约束被错误放宽,进而让求解器找到不符合原始问题要求的解,最终软约束违反成本偏高。
2. 变量重复定义
你在循环每个约束时,都重新定义ti = Bool('t_%d' % ctr.first_var)和mi = Int('m_%d' % ctr.first_var),这会导致同一个变量被多次声明,虽然Z3不会报错,但可能出现约束叠加冲突或赋值混乱的问题。正确的做法是提前初始化所有变量,用字典存储每个变量的Z3对象,避免重复定义。
修正后的核心代码示例
import mongoengine as me from models import Graph from z3 import * from math import ceil, floor me.connect(db='tcc') instance = next(Graph.objects(name='scen06')) Vars = instance.var Doms = instance.dom Ctrs = instance.ctr weights = { 1 : 1000, 2 : 100, 3 : 10, 4 : 1 } # 提前初始化所有变量,避免重复定义 t_vars = {v: Bool(f't_{v}') for v in Vars} m_vars = {v: Int(f'm_{v}') for v in Vars} s = Optimize() # 先添加所有变量的域约束(只执行一次) for v in Vars: ti = t_vars[v] mi = m_vars[v] s.add(Implies(ti, Or(And(1 <= mi, mi <= 11), And(18 <= mi, mi <= 28)))) s.add(Implies(Not(ti), Or(And(29 <= mi, mi <= 39), And(46 <= mi, mi <= 56)))) # 处理每个约束 for ctr in Ctrs: v1 = ctr.first_var v2 = ctr.second_var ti = t_vars[v1] tj = t_vars[v2] mi = m_vars[v1] mj = m_vars[v2] k = ctr.deviation current_weight = weights.get(ctr.weight, 0) if ctr.operator == '=': # 硬约束:|fi - fj| = k,分场景编码精确等式 # ti ∧ ¬tj: |2+14mi - (8+14mj)| = k → |14(mi-mj) -6| = k eq1 = 14*(mi - mj) - 6 == k eq2 = 14*(mi - mj) - 6 == -k s.add(Implies(And(ti, Not(tj)), Or(eq1, eq2))) # ¬ti ∧ tj: |(8+14mi) - (2+14mj)| = k → |14(mi-mj) +6| = k eq3 = 14*(mi - mj) + 6 == k eq4 = 14*(mi - mj) + 6 == -k s.add(Implies(And(Not(ti), tj), Or(eq3, eq4))) # ti ∧ tj 或 ¬ti ∧ ¬tj: |14(mi-mj)| = k eq5 = 14*(mi - mj) == k eq6 = 14*(mi - mj) == -k s.add(Implies(And(ti, tj), Or(eq5, eq6))) s.add(Implies(And(Not(ti), Not(tj)), Or(eq5, eq6))) elif ctr.operator == '>': # 软约束:|fi - fj| > k,分场景编码线性约束 # ti ∧ ¬tj场景 cond1 = mi - mj >= floor((k + 6)/14) + 1 cond2 = mi - mj <= ceil((-k + 6)/14) - 1 s.add_soft(Implies(And(ti, Not(tj)), Or(cond1, cond2)), current_weight) # ¬ti ∧ tj场景 cond3 = mi - mj >= floor((k - 6)/14) + 1 cond4 = mi - mj <= ceil((-k - 6)/14) - 1 s.add_soft(Implies(And(Not(ti), tj), Or(cond3, cond4)), current_weight) # ti ∧ tj 和 ¬ti ∧ ¬tj场景 cond5 = mi - mj >= floor(k/14) + 1 cond6 = mi - mj <= ceil(-k/14) - 1 s.add_soft(Implies(And(ti, tj), Or(cond5, cond6)), current_weight) s.add_soft(Implies(And(Not(ti), Not(tj)), Or(cond5, cond6)), current_weight) # 求解并输出结果 result = s.check() print(f"求解结果: {result}") if result == sat: m = s.model() # 获取最小总成本(违反软约束的权重总和) total_cost = m.evaluate(s.objectives()[0]) print(f"最小总成本: {total_cost}") # 可选:查看每个变量的赋值 for v in Vars: print(f"变量{v}: t={m.evaluate(t_vars[v])}, m={m.evaluate(m_vars[v])}")
内容的提问来源于stack exchange,提问作者Yuri Carvalho
相关产品推荐
相关产品推荐

