Splr-0.7.1で発見された決定性誤りバグの一因がどうもvivificationにあるようなので、徹底的に見直してみた。 その結果、バグ修正の副産物として大変更に至りました。
これまではひたすら論文[1]のオリジナル疑似コードに忠実な実装を心がけていた:
ここでconfilctAnalysis
の引数は
見ての通り、節を追加して伝播させて、節を削除して、ということを繰り返している。 そのためsandboxなんてものをサブモジュールに追加したりしていたのだけど、この"clause vivification"とは
というだけのこと。だったらこの通りに実装すればいいんじゃない?
let c = cdb.clause[cid];
for (i, lit) in c.lits.iter().enumerate() { // 順番に
asg.assign_by_decision(!lit); // 否定してみて
if asg.propagate().is_some() // 矛盾した時に
&& i < c.lits.len() // 短くなっていたら
{
cdb.strengthen_by_vivification(cid, i); // iまでのリテラルに縮退
break;
}
}
asg.cancel_until(self.root_level); // クリーンアップ
節の出し入れが一切なくなってclauseDB的な負荷が一切消えてしまった! これで決まりだな。
そしてこれがSplr-0.7.1がさらに1週間リリースできなかった原因になってしまった。 うん、全くダメな考えだった。論外だった。
だめじゃない。ダメなのは矛盾解析の部分で、決定リテラルを積み重ねるこの方法はずっとスマートな気がしてきた。少なくとも、これがSplr-0.7.1におけるvivificationの決定性判定間違いの原因ではない。
というわけでこれで行こう:
let c = cdb.clause[cid];
for lit in c.lits.iter().take(c.lits.len() - 1) { // 順番に
asg.assign_by_decision(!lit); // 否定してみて
if let Some(cc) = asg.propagate() { // 矛盾した時に
let vec = conflict_analyze(cc); // 矛盾解析して
cdb.new_clause(vec); // 学習節を追加
if cc == cid { // それが対象節なら、
cdb.remove_clause(cid); // 対象節を削除
}
break;
}
}
asg.cancel_until(self.root_level); // クリーンアップ
[1] C.-M. Li, F. Xiao, M. Luo, F. Manyà, Z. Lü, and Y. Li, “Clause Vivification by Unit Propagation in CDCL SAT Solvers," Artif. Intell., vol. 279, Jul. 2019.