2026年6月23日火曜日

UNSATコアとは

 UNSAT Core(充足不能コア)とは、SAT(充当可能性問題)において、問題全体が「満たせない(UNSAT)」状態になっているときに、その矛盾を引き起こしている直接的な原因(節や制約)の最小限の集まりのことです。

たとえるなら、膨大なルールの法律書(CNF問題)を読んだときに、全体として矛盾が生じている原因が、「たった3つの条文(UNSAT Core)が互いに矛盾しているせいだった」と突き止めるようなものです。

🔍 具体例で見る UNSAT Core
次のような 4 つの節(Clauses)からなる CNF 形式の問題があるとします。
  • 節1: x1 である。 (1)
  • 節2: x1 ならば x2 である。(\(\neg x1 \lor x2\))
  • 節3: x2 は偽である。(\(\neg x2\))
  • 節4: x3 または x4 である。(\(x3 \lor x4\))
1. 全体の判定
この問題にすべての条件を満たす真偽値の割り当て(SAT)を探すと、節1・2・3の間で論理的な矛盾が発生するため、結果は UNSAT(充足不能) になります。 [1]
2. 原因(UNSAT Core)の特定
ここで注目すべきは、節4(x3 または x4)は、この矛盾に一切関係していないという点です。
矛盾を発生させている真犯人は、【節1, 節2, 節3】の組み合わせだけです。
このとき、{節1, 節2, 節3} のセットを「UNSAT Core」と呼びます。

💡 なぜ UNSAT Core が重要なのか?
SATソルバーを実務や研究(システムの検証、プログラムのバグ発見、回路設計、スケジューリングなど)で使う際、ただ「この条件では不可能です(UNSAT)」と言われるだけでは、「どこを修正すればいいのか」が分かりません。
UNSAT Core を抽出することで、以下の大きなメリットが得られます。
  • デバッグ・修正の効率化:
    数百万行ある膨大な制約の中から、矛盾に関係している「数十行の制約」だけを絞り込めるため、どこを直せば仕様を満たせるのかがすぐに分かります。
  • 不要なルールの特定:
    システムの仕様同士が衝突している場所(設計ミス)をピンポイントで特定できます。
🛠️ 2つの「UNSAT Core」の種類
ツール(drat-trim など)でコアを抽出する際、文脈によって2つのレベルがあります。
  1. MUS(Minimal Unsatisfiable Subscript / 最小充足不能部分集合)
    • そのコアからどれか1つでも節を外すと、矛盾が消えて(SATに)なってしまうような、極限まで削ぎ落とされた原因のセット。
  2. 一般的な UNSAT Core
    • ソルバーが探索の過程で見つけた「矛盾に関わった節の集まり」。MUSほど厳密に最小化されていない場合もありますが、実用上は十分に小さく絞り込まれています。
Q. drat-trimは、MUSを出力するか?

結論から言うと、drat-trim は「一般的なUNSAT Core」を出力しますが、厳密な「MUS(最小充足不能部分集合)」を必ず出力するとは限りません。 [1]
drat-trim が行う処理は Trimming(トリミング:削減) であり、正確には 「MUSに近いサイズまで大幅に削ぎ落としたUNSAT Core」 を出力するツール です。 [1, 2, 3, 4]
その理由と、MUSを得るための具体的なアプローチについて解説します。

1. drat-trim の内部の仕組み(なぜMUSにならないのか)
drat-trim は、ソルバーが出力した「証明ログ(DRAT)」を、最後の矛盾点(空節)から逆方向にトレース(Backward Checking) します。 [1]
  1. 判定: 矛盾の証明に「実際に使われた節」だけをマークする。
  2. 結果: 探索中に1度も使われなかった節はすべて排除される。 [1]
この時点で、元のCNFから不要な節が90%〜99%以上削られた非常に小さなUNSAT Coreが得られます。しかし、探索中に「たまたま余計に使われてしまった節(なくても矛盾が証明できる節)」が残ってしまうことがあるため、数学的な意味での「これ以上1つも削れない状態(MUS)」の保証には至りません。 [1, 2, 3]
2. drat-trim を何度も回してMUSに近づけるアプローチ [1]
drat-trim の機能を利用して、極限までMUSに近づける(あるいはMUS化する)ために、研究や実務ではループ(反復クレンジング)という手法が使われます。 [1]

2026年6月20日土曜日

Implementation of time constraints

 同じ発想で、時間制約を実装します。


[132:144]なので、1/2で公約して、[66:72]での終端となります。

MDDのDOTグラフです。instance13 staff0の時間制約のMDDになります。係数[8:10:12]時間は、公約して[4:5:6]係数となります。各係数に対してその個数を前述の累計加算器で、加算し、取り得る範囲について、MDDグラフを作成します。各累計加算結果は、グラフ縮小効果により限定されます。初段は、係数6、

中段は、5x加算器、終段に係数4x加算器を結果を加算して、[66:72]範囲に終端します。



2026年6月19日金曜日

Daily Cumulative Totalizer

 累積中間和付きトータライザで、前述の実装概念図です。

仕様として入力するのは、Day毎の累積和です。通常のトータライザ仕様は、一番上の黄色で示した終点での[min:max]しかありません。

が、Daily Cumulative Totalizerは、Reduceしたグラフから得たDay毎の累積和を入力とするところが違います。


トータライザは、全体入力を再帰的に2分割していくトップダウンでUnaryCounterの合成は、ボトムアップで行われます。このとき、Daily累計和が与えられれば、分割したセグメントのSpecは、以下で生成することが出来ます。

pair<int, int> segmentTotalRange(int l, int r, const vector<short_range>& constraints) {

int mn_prev = (l == 0) ? 0 : constraints[l - 1].mn;

int mx_prev = (l == 0) ? 0 : constraints[l - 1].mx;


int mn_r = constraints[r].mn;

int mx_r = constraints[r].mx;


int L = mn_r - mx_prev;

int U = mx_r - mn_prev;


if (L < 0) L = 0;

int len = r - l + 1;

if (U > len) U = len;


if (L > U) return { 1, 0 }; // 不可能区間

return { L, U };

}

このようにして、きめ細かく制約することによりPathを出来る限り限定することが出来ます。トータライザの段数は、変わりませんが、通常のトータライザが、分割したセグメントでもトップ段のmax値でしか制約できないのに対し、累計中間和での[min:max]で制約するので、結果的にノード数合計も抑制されるものと期待できます。

2026年6月18日木曜日

Day毎の累積最小・最大の計算法

 SATソルバが鍵となります。

長大のインスタンスにおいて、いきなり年間[1840,1872]時間では、探索範囲が膨大過ぎます。探索範囲をなるべく限定することを考えます。

Power On時、Target Rangeに至る経路は膨大です。そのままでSAT SOLVEすると爆発して求めることができません。



そこで、まずは、LpSolverで、システム全体のLowerBoundまで求めます。LBUBの性質を利用してグラフをReductionします。Reductionしたグラフ上で、Day毎に[最小:最大]を算出して、上図のように探索範囲を限定してやろう、という算段です。最適化という作業は、選択と評価探索の言い換えです。OR世界では、Branch&Boundという最強の手法が存在しますが、残念ながら、今回の規模は大きすぎてそれでも時間がかかりすぎるために、この手法に至っています。

具体的には、次のアルゴリズムで、Day毎の[最小:最大]を確定させます。




グラフ上では、レベルと呼ばれる変数があります。大まかにいえば、各レベルは各シフトに対応します。各シフトは、同一Dayでは連続します。各レベルでの最小、最大を求めてやれば、各Dayでの最小・最大に換算することは出来ます。なので、まずは、各レベルでの最小と最大を求めてやることが必要となります。それには、各レベル上の各ノードで、最小と最大値を得ることになります。それを各レベルにまとめ、さらに各Dayにまとめる、という作業になります。

動的計画法を単に用いただけでは、終端での最小・最大値しか担保されません。全ての中間ノードをScanすることで、はじめて厳密なDay毎の累計最小・最大状態を得ることが出来ます。


このように、ORにおける最適化とは、ほとんど枝刈りのための前処理と枝刈りそのものです。

2026年6月17日水曜日

Q.明け翌日が会議日であるなら日勤、それ以外は休みとなるようにしたい

 Q以下に記述してみましたが、

期待通りに動きません

Ans.

まずは、明けの後、「日勤または休み」を記述します。

シフト集合は、以下で記述します。


これで、明けの後に、「休み」または「日勤」に限定しました。

次に「明けの後の休みパターン」で会議日を禁止します。


これだけでは、明けのあと、会議日以外では、休みになるか日勤になるか特定されません。

そこで、次を追加します。

これを言葉で言うと
■「明けの後の日勤パターン」は、明けの翌日が職員会議日以外では禁止
となります。
ここで、「職員会議ではない」は、以下の最後行のように「でない」演算子を「職員会議」に適用して作成しました。



これで、題意の設定が完了しました。



ちょっと難しかったと思います。一般には、次の順序で考えると良いでしょう。

1)まず、あり得る状態を制約(1/2行目→明けの後のあり得ない状態を排除する) 

2)1)状態のなかで、さらに細かいところを詰めていく(制約していく 3/4行目、全ケースを網羅する)

結果的に全ケースを網羅している、ことに気づかれたでしょうか? 最初から全ケースを網羅しようとすると、考えること自体が面倒になってしまいますが、このように一行一行、パターン作成→解確認 で確認しながら作成するやり方もアリです。制約を言葉にしながら進むことがポイントです。

他に、
■通常パターンをソフト制約にして、
■イレギュラーを予定でハード制約する、
方が実装は簡単かとは思います。あまり美しくはないですがこれもアリです。


2026年6月16日火曜日

SAT Hard制約中に得たFixed LitsをSAT SOFT制約に投入してもよいか?

 Q.SATでHARD制約のみでSAT解を得た時のレベル0STACKをFIXEDLITSとして得る。SOFT制約を追加して、SAT SOLVEするが、前FIXEDLITSをADDしても厳密性は失われないか?

結論から言うと:

HARD 制約だけで得たレベル 0 の FIXED LITS(=レベル 0 で強制される単位伝播の結果)を、 SOFT 制約を追加した後の SAT SOLVE にそのまま ADD しても、 厳密性(soundness)は一切失われません。

むしろ レベル 0 の FIXED LITS は「HARD 制約から論理的に必然な真理」なので、 SOFT 制約を追加しても常に保持すべき情報 です。

🎯 結論(最重要)

  • レベル 0 の FIXED LITS は HARD 制約から論理的に導かれる必然の事実

  • SOFT 制約は目的関数を変えるだけで、HARD 制約の論理的帰結を壊さない

  • よって レベル 0 の FIXED LITS を再投入しても soundness は完全に保たれる

🔍 なぜ soundness が保たれるのか(論理的理由)

◆ 1. レベル 0 の FIXED LITS とは何か?

レベル 0 の割り当ては:

  • HARD 制約だけで単位伝播した結果

  • どんな SAT 解でも必ず満たさなければならない真理

  • つまり HARD 制約の論理的帰結(logical consequences)

形式的には:

HARDl

という形で表せる。

◆ 2. SOFT 制約は「目的関数」または「優先度」を追加するだけ

SOFT 制約は:

  • 満たされなくてもよい(ただしペナルティがある)

  • HARD 制約の論理的帰結を壊すことはない

つまり SOFT 制約を追加しても:

HARDl

は変わらない。

◆ 3. よって FIXED LITS を再投入しても soundness は壊れない

なぜなら:

  • FIXED LITS は HARD 制約の論理的帰結

  • SOFT 制約は HARD 制約を弱めない

  • したがって FIXED LITS を追加しても 本来存在する最適解を排除しない

つまり soundness が保たれる。

2026年6月15日月曜日

RC Fixingにより、目的関数値は変化するか?

Q.RC FixingによりRMPのOBJが変化することはあるか?

 Iterativeに解いていくこのアルゴリズムは、自然にOBJが上昇することを期待しています。一応、AIの意見を聞きました。


結論から言うと:

Reduced Cost Fixing(RCF)そのものは RMP の OBJ 値を変化させません。 ただし、RCF によって “変数の上限・下限が固定される” ことで、 RMP を解き直したときの LB(=RMP の OBJ)は変化することがあります。

つまり:

  • RCF = OBJ を直接いじらない(安全)

  • RCF の結果として RMP の feasible set が縮む → LB が上がることはある

  • LB が下がることは絶対にない(soundness の理由)

この 3 点が本質です。

◆ Reduced Cost Fixing が「OBJ を変えない」理由

RCF は次のような操作です:

  • ある変数 x を 0 に固定(UB=0)

  • または 1 に固定(LB=1)

これは 目的関数の係数(重み)を変更する操作ではない

したがって:

  • OBJ の式は変わらない

  • 目的関数の係数も変わらない

  • RMP の線形構造も変わらない

RCF は 制約(bounds)を tighten するだけ です。

◆ ではなぜ LB(RMP の OBJ)が変わることがあるのか?

理由はシンプルで:

変数を固定すると、RMP の feasible region が縮むから。

LP の性質として:

  • feasible region を縮める → LB は 上がるか同じ

  • feasible region を広げる → LB は 下がるか同じ

RCF は feasible region を 縮めるだけ なので、

LBnewLBold

が常に成立します。

これが soundness の根拠でもある。

◆ 重要:LB が下がることは絶対にない

RCF は「増やす方向の Fixing」しか行わないため、

  • 最適解を排除しない(sound)

  • LB が下がることはない(安全)

LB が下がる Fixing は 危険(unsound) であり、 あなたの方式では絶対に起きません。

◆ まとめ(最重要)

  • RCF は OBJ の式を変えない → OBJ は変化しない

  • ただし、変数固定により feasible region が縮む → LB が上がることはある

  • LB が下がることは絶対にない(soundness)