"""便利な関数群.
cspsat モジュールの便利な関数群をまとめている.
.. csv-table:: 命題論理式の記法 (Pは命題変数.A1, A2, ..., Anは命題論理式)
:header: "名称など", "命題論理式の記法"
:widths: 20, 40
"真", "TRUE"
"偽", "FALSE"
"命題変数 p", "Bool(""p"")"
"補リテラル", "~P"
"否定", "[""not"", A1], [""!"", A1]"
"論理積", "[""and"", A1, ..., An], [""&&"", A1, ..., An]"
"論理和", "[""or"", A1, ..., An], [""||"", A1, ..., An]"
"含意", "[""imp"", A1, A2], [""=>"", A1, A2]"
"同値", "[""equ"", A1, A2], [""<=>"", A1, A2]"
"排他的論理和", "[""xor"", A1, ..., An], [""^"", A1, ..., An]"
Note:
本プログラムは学習用の目的で作成されている.
実用上の問題への適用は想定していない.
"""
[ドキュメント]
def variables(f):
"""命題論理式に含まれている命題変数の集合を返す.
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
Returns:
:obj:`.Bool` オブジェクトの集合.
Examples:
>>> p = Bool("p")
>>> variables(["and", p(1), ~p(2), p(2)])
{p(1), p(2)}
"""
if isinstance(f, Bool) and f != TRUE and f != FALSE:
return { abs(f) }
if isinstance(f, (list,tuple,set)):
return { v for g in f for v in variables(g) }
return set()
[ドキュメント]
def assignments(vs):
"""与えられた命題変数のリストに対し,可能な値割当てをyieldするジェネレータ関数.
各値割当ては,命題変数(:obj:`.Bool` オブジェクト)をキーとする辞書(dict)で,値は0か1.
Args:
vs: 命題変数(:obj:`.Bool` オブジェクト)のリスト.
Yields:
値割当てを表す辞書(dict).
Examples:
>>> p = Bool("p")
>>> list(assignments([p(1),p(2)]))
[{p(1): 0, p(2): 0}, {p(1): 0, p(2): 1}, {p(1): 1, p(2): 0}, {p(1): 1, p(2): 1}]
"""
if len(vs) == 0:
yield {}
else:
vs = vs.copy()
v = vs.pop()
for a in assignments(vs):
yield { **a, v: 0 }
yield { **a, v: 1 }
[ドキュメント]
def value(f, a):
"""与えられた命題論理式 f と値割当て a に対し,f の真理値を返す関数.
命題論理式 f はいったん :obj:`toNF` 関数で否定・論理積・論理和のみの式に変換され,その後,真理値を計算する.
値割当て中に含まれない命題変数の真理値はNoneになる.
否定の引数の式の真理値がNoneの場合,否定の真理値はNoneになる.
論理積の引数の真理値に0がある場合,論理積の真理値は0になる.
論理積の引数の真理値に0がなくNoneがある場合,論理積の真理値はNoneになる.
論理和の引数の真理値に1がある場合,論理和の真理値は1になる.
論理和の引数の真理値に1がなくNoneがある場合,論理和の真理値はNoneになる.
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
a (dict): 値割当ての辞書(dict).キーは :obj:`.Bool` オブジェクトで表される命題変数,値は0か1.
Returns:
f の真理値 (0か1かNone).
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> value(["not",p], {p: 1})
0
>>> value(["not",p], {}) # Noneが返される
"""
def _value(f):
match f:
case Bool() if f == FALSE:
return 0
case Bool() if f == TRUE:
return 1
case Bool():
if f.positive:
return a.get(f, None)
t1 = a.get(~f, None)
return 1 - t1 if t1 is not None else None
case ["not", g1]:
t1 = _value(g1)
return 1 - _value(g1) if t1 is not None else None
case ["and", *gs]:
t = 1
for g in gs:
match _value(g):
case 0:
return 0
case None:
t = None
return t
case ["or", *gs]:
t = 0
for g in gs:
match _value(g):
case 1:
return 1
case None:
t = None
return t
raise CspsatException(f"論理式の構文エラー: {f}")
return _value(toNF(f))
[ドキュメント]
def truthTable(*fs):
"""与えられた命題論理式 f1, f2, ... の真理値表を出力する関数.
Args:
*fs: 命題論理式 f1, f2, ... の列.命題論理式の記法については :obj:`.functions` 参照.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> truthTable(["and",p(1),p(2)], ["or",p(1),p(2)])
| p(1) | p(2) | ['and', p(1), p(2)] | ['or', p(1), p(2)] |
|------|------|---------------------|--------------------|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 1 |
"""
def row(xs, ws):
ss = []
for i, x in enumerate(xs):
s = str(x)
s = " "*(ws[i]>>1) + s + " "*((ws[i]-len(s))>>1)
ss.append(s[-ws[i]:])
return "| " + " | ".join(ss) + " |"
fs = list(fs)
vs = sorted(variables(fs))
ws = [ len(str(x)) for x in vs + fs ]
print(row(vs + fs, ws), flush=True)
print("|" + "|".join([ "-"*(w+2) for w in ws ]) + "|", flush=True)
for a in assignments(vs):
xs1 = [ a[v] for v in vs ]
xs2 = [ value(f, a) for f in fs ]
print(row(xs1 + xs2, ws), flush=True)
[ドキュメント]
def isValid(f):
"""与えられた命題論理式 f が恒真かどうかを判定する関数.
命題論理式 f に対するすべての値割当てで f の真理値が1ならば恒真としてTrueを返す.
f に含まれる命題変数の個数を :math:`n` とすると :math:`2^n` の時間がかかる.
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
Returns:
f が恒真ならTrue.そうでなければFalse.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> isValid(["or",p,~p])
True
"""
vs = variables(f)
if len(vs) == 0:
return value(f, {}) == 1
for a in assignments(vs):
if value(f, a) == 0:
return False
return True
[ドキュメント]
def isSat(f):
"""与えられた命題論理式 f が充足可能かどうかを判定する関数.
命題論理式 f に対するある値割当てで f の真理値が1ならば充足可能としてTrueを返す.
f に含まれる命題変数の個数を :math:`n` とすると :math:`2^n` の時間がかかる.
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
Returns:
f が充足可能ならTrue.そうでなければFalse.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> isSat(["or",p(1),p(2)])
True
>>> isSat(["and",p,~p])
False
"""
vs = variables(f)
if len(vs) == 0:
return value(f, {}) == 1
for a in assignments(vs):
if value(f, a) == 1:
return True
return False
[ドキュメント]
def isEquiv(f1, f2):
"""与えられた命題論理式 f1, f2 が論理的に同値かどうかを判定する関数.
命題論理式 ["equ",f1,f2] が恒真なら f1 と f2 が論理的に同値としてTrueを返す.
f1, f2 に含まれる命題変数の個数を :math:`n` とすると :math:`2^n` の時間がかかる.
Args:
f1: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
f2: 命題論理式.
Returns:
f1 と f2 が論理的に同値ならTrue.そうでなければFalse.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> isEquiv(p, ["and",p,p])
True
"""
return isValid(["equ", f1, f2])
[ドキュメント]
def models(f, num=1):
"""与えられた命題論理式 f の複数のモデルを探索し,それらをyieldするジェネレータ関数.
命題論理式 f のすべての値割当て a に対し,a が f のモデルなら a をyieldする.
探索するモデルの最大個数を num で指定できる.
f に含まれる命題変数の個数を :math:`n` とすると :math:`2^n` の時間がかかる.
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
num (int): 探索するモデルの最大個数.0なら全解を探索する (デフォルト値は1).
Yields:
見つかったモデル.キーが :obj:`.Bool` オブジェクトで値が0か1の辞書(dict)である.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> for model in models(["or",p(1),p(2)], num=0): print(model)
{p(1): 0, p(2): 1}
{p(1): 1, p(2): 0}
{p(1): 1, p(2): 1}
"""
vs = variables(f)
count = 0
for a in assignments(vs):
if num != 0 and count >= num:
break
if value(f, a) == 1:
yield a
count += 1
[ドキュメント]
def toNF(f):
"""与えられた命題論理式 f を,論理演算子として否定・論理積・論理和だけを含む同値な式に変換する.
含意 :math:`A \\Rightarrow B` は :math:`(\\lnot A) \\lor B` に変換する.
同値 :math:`A \\Leftrightarrow B` は :math:`((\\lnot A) \\lor B) \\land (A \\lor (\\lnot B))` に変換する.
排他的論理和 :math:`A \\oplus B` は :math:`(A \\lor B) \\land ((\\lnot A) \\lor (\\lnot B))` に変換する.
Args:
f: 命題論理式.
Returns:
論理演算子として否定・論理積・論理和だけを含む命題論理式.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> toNF(["not",["xor",p(1),p(2)]])
['not', ['and', ['or', p(1), p(2)], ['or', ['not', p(1)], ['not', p(2)]]]]
"""
match f:
case Bool():
return f
case ["imp", g1, g2] | ["=>", g1, g2]:
return toNF(["or", ["not", g1], g2])
case ["equ", g1, g2] | ["<=>", g1, g2]:
return toNF(["and", ["or", ["not", g1], g2], ["or", g1, ["not", g2]]])
case ["xor", *gs] | ["^", *gs]:
if len(gs) == 0:
return FALSE
if len(gs) == 1:
return gs[0]
(g1, g2) = (gs[0], toNF(["xor", *gs[1:]]))
return toNF(["and", ["or", g1, g2], ["or", ["not", g1], ["not", g2]]])
case ["not", g1] | ["!", g1]:
return ["not", toNF(g1)]
case ["and", *gs] | ["&&", *gs]:
return ["and", *[ toNF(g) for g in gs]]
case ["or", *gs] | ["||", *gs]:
return ["or", *[ toNF(g) for g in gs]]
case _:
raise CspsatException(f"論理式の構文エラー: {f}")
[ドキュメント]
def toNNF(f, positive=True):
"""与えられた命題論理式 f を否定標準形に変換する.
否定標準形は,リテラル(:obj:`.Bool` オブジェクト)および論理積と論理和のみが現れる命題論理式である.
いったん f を :obj:`toNF` 関数で否定・論理積・論理和だけを含む式に変換したのち,
:math:`\\lnot \\lnot A` を :math:`A` に,
:math:`\\lnot (A \\land B)` を :math:`(\\lnot A) \\lor (\\lnot B)` に,
:math:`\\lnot (A \\lor B)` を :math:`(\\lnot A) \\land (\\lnot B)` に置き換える操作を再帰的に繰り返している.
Args:
f: 命題論理式.
positive (bool, optional): Falseなら ["not",f] を否定標準形に変換する(デフォルト値はTrue).
Returns:
否定標準形の命題論理式.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> toNNF(["not",["xor",p(1),p(2)]])
['or', ['and', ~p(1), ~p(2)], ['and', p(1), p(2)]]
"""
def _toNNF(f, positive):
match f:
case Bool():
return f if positive else ~f
case ["not", g1]:
return _toNNF(g1, not positive)
case ["and", *gs]:
op = "and" if positive else "or"
return [op, *[ _toNNF(g, positive) for g in gs ]]
case ["or", *gs]:
op = "or" if positive else "and"
return [op, *[ _toNNF(g, positive) for g in gs ]]
return _toNNF(toNF(f), positive)
[ドキュメント]
def toCNF(f, simplify=True, formula=False):
"""与えられた命題論理式 f を論理積標準形(CNF)に変換する.
CNF式は複数の節の論理積であり,各節は複数のリテラルの論理和である.
いったん f を :obj:`toNNF` 関数で否定標準形に変換したのち,
分配法則を用いて :math:`A \\lor (B \\land C)` を :math:`(A \\lor B) \\land (A \\lor C)` に置き換える操作を再帰的に繰り返している.
simplify=True の場合,以下の方法でCNF式を簡単化する.
* 節から命題定数 FALSE を削除する
* 節に命題定数 TRUE が含まれていれば,その節をCNF式から削除する
* 節にリテラル p と補リテラル ~p が含まれていれば,その節をCNF式から削除する
* 節中に同じリテラルが重複して現れていれば,1つにまとめる
formula=True を指定すれば,結果が節のリストではなく,命題論理式を返す.
f に含まれる命題変数の個数を :math:`n` とすると最悪の場合CNF式のサイズは :math:`2^n` になる
(たとえば :math:`(p_1 \\land q_1)\\lor(p_2 \\land q_2)\\lor\\cdots\\lor(p_n \\land q_n)` の場合).
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
simplify (bool, optional): Falseなら得られたCNF式を簡単化しない(デフォルト値はTrue).
formula (bool, optional): 節のリストでなく,命題論理式を返す.
Returns:
CNF式.CNF式は節のリストであり,各節はリテラルのリストである.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> toCNF(["not",["xor",p(1),p(2)]])
[[~p(1), p(2)], [p(1), ~p(2)]]
>>> toCNF(["not",["xor",p(1),p(2)]], simplify=False)
[[~p(1), p(1)], [~p(1), p(2)], [~p(2), p(1)], [~p(2), p(2)]]
>>> toCNF(["not",["xor",p(1),p(2)]], formula=True)
['and', ['or', ~p(1), p(2)], ['or', p(1), ~p(2)]]
"""
def simplifyClause(clause):
clause = { lit for lit in clause if lit != FALSE }
for lit in clause:
if lit == TRUE or ~lit in clause:
return TRUE
return sorted(list(clause))
def _toCNF(f):
match f:
case Bool() if f == FALSE:
return [ [] ]
case Bool() if f == TRUE:
return []
case Bool():
return [ [f] ]
case ["and", *gs]:
cnf = [ clause for g in gs for clause in _toCNF(g) ]
if simplify and [] in cnf:
cnf = [ [] ]
return cnf
case ["or"]:
return [ [] ]
case ["or", g1, *gs]:
cnf = []
for clause1 in _toCNF(g1):
for clause2 in _toCNF(["or", *gs]):
clause = clause1 + clause2
if simplify:
clause = simplifyClause(clause)
if clause == []:
return [ [] ]
if clause != TRUE:
cnf.append(clause)
return cnf
def simplifyCNF(cnf):
tuples = { tuple(c) for c in cnf }
clauses = [ set(c) for c in tuples ]
cnf = []
for c in clauses:
if any(c >= c1 for c1 in clauses if c != c1):
continue
cnf.append(sorted(list(c)))
return sorted(cnf)
cnf = _toCNF(toNNF(f))
if simplify:
cnf = simplifyCNF(cnf)
if formula:
cnf = ["and", *[ ["or",*clause] for clause in cnf ]]
return cnf
[ドキュメント]
def toDNF(f, simplify=True, formula=False):
"""与えられた命題論理式 f を論理和標準形(DNF)に変換する.
DNF式は複数の連言節の論理和であり,各連言節は複数のリテラルの論理積である.
いったん ["not",f] を :obj:`toCNF` 関数でCNF式に変換したのち,
CNF式中の各リテラルを否定することでDNF式を求めている.
simplify=True の場合,簡単化したDNF式を返す.
formula=True を指定すれば,命題論理式を返す.
f に含まれる命題変数の個数を :math:`n` とすると最悪の場合DNF式のサイズは :math:`2^n` になる
Args:
f: 命題論理式.命題論理式の記法については :obj:`.functions` 参照.
simplify (bool, optional): Falseなら得られたDNF式を簡単化しない(デフォルト値はTrue).
formula (bool, optional): 連言節のリストでなく,命題論理式を返す.
Returns:
DNF式.DNF式は連言節のリストであり,各連言節はリテラルのリストである.
Raises:
CspsatException: 論理式の構文エラー.
Examples:
>>> p = Bool("p")
>>> toDNF(["not",["xor",p(1),p(2)]])
[[p(1), p(2)], [~p(1), ~p(2)]]
>>> toDNF(["not",["xor",p(1),p(2)]], formula=True)
['or', ['and', p(1), p(2)], ['and', ~p(1), ~p(2)]]
"""
dnf = [ [ ~lit for lit in clause ] for clause in toCNF(["not",f], simplify) ]
if formula:
dnf = ["or", *[ ["and",*clause] for clause in dnf ]]
return dnf
[ドキュメント]
def ge1(xx):
"""at-least-one基数制約の節をyieldするジェネレータ関数.
xx = [x1, x2, ..., xn] のとき,節 {x1, x2, ..., xn} をyieldする.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in ge1([p(1), p(2), p(3)]): print(clause)
[p(1), p(2), p(3)]
"""
yield xx
[ドキュメント]
def le1(xx):
"""at-most-one基数制約の節をyieldするジェネレータ関数.
xx = [x1, x2, ..., xn] のとき,すべての xi, xj の組合せに対し節 {xi, xj} をyieldする.
この方法はペアワイズ法と呼ばれる.
一般には :obj:`.Encoder` クラスに実装されている逐次カウンタ法のほうが良い.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in le1([p(1), p(2), p(3)]): print(clause)
[~p(1), ~p(2)]
[~p(1), ~p(3)]
[~p(2), ~p(3)]
"""
for i in range(len(xx)):
for j in range(i+1, len(xx)):
yield [~xx[i], ~xx[j]]
[ドキュメント]
def eq1(xx):
"""exact-one基数制約の節をyieldするジェネレータ関数.
:obj:`ge1` 関数,:obj:`le1` 関数を用いて必要な節をyieldする.
一般には :obj:`.Encoder` クラスに実装されている逐次カウンタ法のほうが良い.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in eq1([p(1), p(2), p(3)]): print(clause)
[p(1), p(2), p(3)]
[~p(1), ~p(2)]
[~p(1), ~p(3)]
[~p(2), ~p(3)]
"""
yield from ge1(xx)
yield from le1(xx)
[ドキュメント]
def geK(xx, k):
"""at-least-k基数制約の節をyieldするジェネレータ関数.
xx = [x1, x2, ..., xn] のとき,xi から n-k+1 個のリテラルを選択するすべての組合せに対し,選んだリテラルからなる節をyieldする.
すなわち n-k+1 個のリテラルがすべて偽になる場合を排除している.
この方法はバイノミナル法と呼ばれる.
一般には :obj:`.Encoder` クラスに実装されている逐次カウンタ法のほうが良い.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
k (int): kの値.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in geK([p(1), p(2), p(3)], 2): print(clause)
[p(1), p(2)]
[p(1), p(3)]
[p(2), p(3)]
"""
for yy in itertools.combinations(xx, len(xx)-k+1):
yield list(yy)
[ドキュメント]
def leK(xx, k):
"""at-most-k基数制約の節をyieldするジェネレータ関数.
xx = [x1, x2, ..., xn] のとき,xi から k+1 個のリテラルを選択するすべての組合せに対し,選んだリテラルの否定からなる節をyieldする.
すなわち k+1 個のリテラルがすべて真になる場合を排除している.
この方法はバイノミナル法と呼ばれる.
一般には :obj:`.Encoder` クラスに実装されている逐次カウンタ法のほうが良い.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
k (int): kの値.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in leK([p(1), p(2), p(3)], 2): print(clause)
[~p(1), ~p(2), ~p(3)]
"""
for yy in itertools.combinations(xx, k+1):
yield [ ~x for x in yy ]
[ドキュメント]
def eqK(xx, k):
"""exact-k基数制約の節をyieldするジェネレータ関数.
:obj:`geK` 関数,:obj:`leK` 関数を用いて必要な節をyieldする.
一般には :obj:`.Encoder` クラスに実装されている逐次カウンタ法のほうが良い.
Args:
xx (list): リテラル(:obj:`.Bool` オブジェクト)リスト.
k (int): kの値.
Yields:
基数制約を符号化した節.
Examples:
>>> p = Bool("p")
>>> for clause in eqK([p(1), p(2), p(3)], 2): print(clause)
[p(1), p(2)]
[p(1), p(3)]
[p(2), p(3)]
[~p(1), ~p(2), ~p(3)]
"""
yield from geK(xx, k)
yield from leK(xx, k)
statData = None
"""ソルバー実行の統計データを保存するグローバル変数
"""
[ドキュメント]
def status(lastOnly=True):
"""SATソルバー (:obj:`.SAT`)またはCSPソルバー (:obj:`.Solver`)実行の統計データ.
:obj:`.SAT.getStat`, :obj:`.Solver.getStat` 参照.
Returns:
ソルバー実行の統計データ.
"""
if not lastOnly:
return statData
infos = statData["sat"]
info = infos[-1] if len(infos) > 0 else {}
return { **statData, "sat":info }
from contextlib import contextmanager
defaultTimeout = 600
"""タイムアウト秒数のデフォルト値を保存するグローバル変数
"""
[ドキュメント]
def getTimeout():
"""タイムアウト秒数のデフォルト値を返す.
Returns:
タイムアウト秒数のデフォルト値.
>>> getTimeout()
600
"""
return defaultTimeout
[ドキュメント]
def setTimeout(timeout):
"""タイムアウト秒数のデフォルト値を設定する.
Args:
timeout (int): タイムアウト秒数.
>>> setTimeout(100)
>>> getTimeout()
100
"""
global defaultTimeout
defaultTimeout = timeout
@contextmanager
def _cspsatTimer(timeout, verbose=0):
"""timeout秒を超えると,メインのスレッドにSIGINTのシグナルを送り,:obj:`.CspsatTimeout` 例外をraiseする.
Args:
timeout (int): タイムアウト秒.
"""
timer = threading.Timer(timeout, _thread.interrupt_main)
timer.start()
if verbose >= 1:
print(f"# タイムアウト{timeout}秒でプログラム開始", file=sys.stderr)
try:
yield
except KeyboardInterrupt as e:
if timer.is_alive():
raise
raise CspsatTimeout(f"プログラムの実行時間が{timeout}秒を超えた") from e
finally:
timer.cancel()
[ドキュメント]
def solutionsSAT(cnf, command=None, num=1, positiveOnly=False, includeAux=False, verbose=0, timeout=None, tempdir=None):
"""与えられたCNF式のモデルをSATソルバーで探索しyieldするジェネレータ関数.
:obj:`.SAT.solutions` 参照.
Args:
cnf (list of list of Bool): CNF式 (節のリスト).
command (str, optional): 利用するSATソルバーのコマンド.
:obj:`.SAT` のcommand参照.
num (int, optional): 探索するモデルの最大個数.0なら全解を探索する (デフォルト値は1).
:obj:`.SAT.solutions` のnum参照.
positiveOnly (bool, optional): Trueならモデルに正リテラルのみを含める (デフォルト値はFalse).
:obj:`.SAT` のpositveOnly参照.
includeAux (bool, optional): Trueならモデルに補助変数を含める (デフォルト値はFalse).
:obj:`.SAT` のincludeAux参照.
verbose (int, optional): 負なら結果を出力しない.正ならSATソルバーの情報を表示する (デフォルト値は0).
:obj:`.SAT` のverobse参照.
timeout (int, optional): この関数の実行時間のタイムアウト秒数を指定する (デフォルト値は600秒).
:obj:`setTimeout` でデフォルト値を変更できる.
tempdir (str, optional): 一時ファイルのディレクトリ名 (デフォルト値はNone).
Yields:
CNF式のモデル.命題変数(:obj:`.Bool` オブジェクト)をキーとする辞書(dict)で,値は0か1.
Examples:
>>> p = Bool("p")
>>> cnf = [ [p(1), p(2)] ]
>>> for sol in solutionsSAT(cnf, num=0): print(sol)
{p(1): 0, p(2): 1}
{p(1): 1, p(2): 0}
{p(1): 1, p(2): 1}
>>> for sol in solutionsSAT(cnf, num=0): print(f"p(1)={sol[p(1)]}, p(2)={sol[p(2)]}")
p(1)=0, p(2)=1
p(1)=1, p(2)=0
p(1)=1, p(2)=1
>>> for sol in solutionsSAT(cnf, num=0): print((sol, status()))
({p(1): 0, p(2): 1}, {'variables': 2, 'clauses': 1, 'conflicts': 0, 'decisions': 1, 'propagations': 2, 'solving': 0.709282636642456})
({p(1): 1, p(2): 0}, {'variables': 2, 'clauses': 2, 'conflicts': 1, 'decisions': 2, 'propagations': 3, 'solving': 0.30728840827941895})
({p(1): 1, p(2): 1}, {'variables': 2, 'clauses': 3, 'conflicts': 1, 'decisions': 1, 'propagations': 3, 'solving': 0.33997058868408203})
"""
global statData
timeout = timeout or getTimeout()
sat = SAT(command=command, positiveOnly=positiveOnly, includeAux=includeAux, verbose=verbose, tempdir=tempdir)
try:
with _cspsatTimer(timeout, verbose):
sat.add(*cnf)
for sol in sat.solutions(num=num):
statData = sat.stats
yield sol
except CspsatTimeout as e:
sat.stats["result"] = "TIMEOUT"
raise e
finally:
statData = sat.stats
sat.__del__()
[ドキュメント]
def solveSAT(cnf, command=None, num=1, positiveOnly=False, includeAux=False, verbose=0, timeout=None, stat=False, tempdir=None):
"""与えられたCNF式のモデルをSATソルバーで探索し出力する.
:obj:`.SAT.solve` 参照.
Args:
cnf (list of list of Bool): CNF式 (節のリスト).
command (str, optional): 利用するSATソルバーのコマンド.
:obj:`.SAT` のcommand参照.
num (int, optional): 探索するモデルの最大個数.0なら全解を探索する (デフォルト値は1).
:obj:`.SAT.solve` のnum参照.
positiveOnly (bool, optional): Trueならモデルに正リテラルのみを含める (デフォルト値はFalse).
:obj:`.SAT` のpositveOnly参照.
includeAux (bool, optional): Trueならモデルに補助変数を含める (デフォルト値はFalse).
:obj:`.SAT` のincludeAux参照.
verbose (int, optional): 負なら結果を出力しない.正ならSATソルバーの情報を表示する (デフォルト値は0).
:obj:`.SAT` のverobse参照.
timeout (int, optional): この関数の実行時間のタイムアウト秒数を指定する (デフォルト値は600秒).
:obj:`setTimeout` でデフォルト値を変更できる.
stat (bool, optional): Trueなら統計データも表示する (デフォルト値はFalse).
:obj:`.SAT.getStat` 参照.
tempdir (str, optional): 一時ファイルのディレクトリ名 (デフォルト値はNone).
Examples:
>>> p = Bool("p")
>>> cnf = [ [p(1), p(2)] ]
>>> solveSAT(cnf, num=0)
SATISFIABLE
Model 1: {p(1): 0, p(2): 1}
Model 2: {p(1): 1, p(2): 0}
Model 3: {p(1): 1, p(2): 1}
>>> solveSAT(cnf, num=0, stat=True)
SATISFIABLE
Model 1: {p(1): 0, p(2): 1}
Stat: {'variables': 2, 'clauses': 1, 'conflicts': 0, 'decisions': 1, 'propagations': 2, 'solving': 0.30565834045410156}
Model 2: {p(1): 1, p(2): 0}
Stat: {'variables': 2, 'clauses': 2, 'conflicts': 1, 'decisions': 2, 'propagations': 3, 'solving': 0.34708118438720703}
Model 3: {p(1): 1, p(2): 1}
Stat: {'variables': 2, 'clauses': 3, 'conflicts': 1, 'decisions': 1, 'propagations': 3, 'solving': 0.3393073081970215}
Stat: {'variables': 2, 'clauses': 4, 'conflicts': 2, 'decisions': 1, 'propagations': 2, 'solving': 0.303924560546875}
"""
global statData
timeout = timeout or getTimeout()
sat = SAT(command=command, positiveOnly=positiveOnly, includeAux=includeAux, verbose=verbose, tempdir=tempdir)
try:
with _cspsatTimer(timeout, verbose):
sat.add(*cnf)
sat.solve(num=num, stat=stat)
except CspsatTimeout as e:
sat.stats["result"] = "TIMEOUT"
raise e
finally:
statData = sat.stats
sat.__del__()
[ドキュメント]
def solutionsCSP(csp, encoder=None, command=None, num=1, positiveOnly=False, includeAux=False, verbose=0, timeout=None, tempdir=None):
"""与えられたCSP(制約充足問題)をCNF式にSAT符号化し,得られたCNF式の解(モデル)をSATソルバーで探索し,
CSPの解(モデル)に変換してyieldするジェネレータ関数.
:obj:`.Solver.solutions` 参照.
Args:
csp: CSP.制約のシーケンス.制約の記法については :obj:`cspsat.csp` 参照.
encoder (Encoder | str, optional): 使用するSAT符号化.Encoderのインスタンスならそれを用いる.
文字列の場合,"d"から始まるなら :obj:`.DirectEncoder`, "l"から始まるなら :obj:`.LogEncoder`, それ以外なら :obj:`.OrderEncoder` を用いる.
command (str, optional): 利用するSATソルバーのコマンド.
:obj:`.SAT` のcommand参照.
num (int, optional): 探索するモデルの最大個数.0なら全解を探索する (デフォルト値は1).
:obj:`.Solver.solutions` のnum参照.
positiveOnly (bool, optional): Trueならモデルに正リテラルのみを含める (デフォルト値はFalse).
:obj:`.Solver` のpositveOnly参照.
includeAux (bool, optional): Trueならモデルに補助変数を含める (デフォルト値はFalse).
:obj:`.Solver` のincludeAux参照.
verbose (int, optional): 負なら結果を出力しない.正ならSATソルバーの情報を表示する (デフォルト値は0).
:obj:`.Solver` のverbose参照.
timeout (int, optional): この関数の実行時間のタイムアウト秒数を指定する (デフォルト値は600秒).
:obj:`setTimeout` でデフォルト値を変更できる.
tempdir (str, optional): 一時ファイルのディレクトリ名 (デフォルト値はNone).
Yields:
CSPのモデル.命題変数(:obj:`.Bool` オブジェクト)あるいは整数変数(:obj:`.Var` オブジェクト)をキーとする辞書(dict)で,値は整数.
Examples:
>>> x = Var("x")
>>> csp = [ ["int",x(1),1,3], ["int",x(2),1,3], [">",x(1),x(2)] ]
>>> for sol in solutionsCSP(csp, num=0): print(sol)
{x(1): 2, x(2): 1}
{x(1): 3, x(2): 1}
{x(1): 3, x(2): 2}
"""
global statData
if isinstance(encoder, Encoder):
pass
elif isinstance(encoder, str) and encoder.lower().startswith("d"):
encoder = DirectEncoder(verbose=verbose)
elif isinstance(encoder, str) and encoder.lower().startswith("l"):
encoder = LogEncoder(verbose=verbose)
else:
encoder = OrderEncoder(verbose=verbose)
encoder.verbose = verbose
if verbose >= 1:
print(f"# Encoderは{encoder.__class__.__name__}", file=sys.stderr)
sat = SAT(command=command, verbose=verbose, tempdir=tempdir)
solver = Solver(encoder, sat=sat, positiveOnly=positiveOnly, includeAux=includeAux, verbose=verbose)
try:
timeout = timeout or getTimeout()
with _cspsatTimer(timeout, verbose):
for sol in solver.solutions(csp, num=num):
statData = solver.stats
yield sol
except CspsatTimeout as e:
solver.stats["result"] = "TIMEOUT"
raise e
finally:
statData = solver.stats
sat.__del__()
[ドキュメント]
def solveCSP(csp, encoder=None, command=None, num=1, positiveOnly=False, includeAux=False, verbose=0, timeout=None, stat=False, tempdir=None):
"""与えられたCSP(制約充足問題)をCNF式にSAT符号化し,得られたCNF式の解(モデル)をSATソルバーで探索し,
CSPの解(モデル)に変換して出力する.
:obj:`.Solver.solve` 参照.
Args:
csp: CSP.制約のシーケンス.制約の記法については :obj:`cspsat.csp` 参照.
encoder (Encoder | str, optional): 使用するSAT符号化.Encoderのインスタンスならそれを用いる.
文字列の場合,"d"から始まるなら :obj:`.DirectEncoder`, "l"から始まるなら :obj:`.LogEncoder`, それ以外なら :obj:`.OrderEncoder` を用いる.
command (str, optional): 利用するSATソルバーのコマンド.
:obj:`.SAT` のcommand参照.
num (int, optional): 探索するモデルの最大個数.0なら全解を探索する (デフォルト値は1).
:obj:`.Solver.solutions` のnum参照.
positiveOnly (bool, optional): Trueならモデルに正リテラルのみを含める (デフォルト値はFalse).
:obj:`.Solver` のpositveOnly参照.
includeAux (bool, optional): Trueならモデルに補助変数を含める (デフォルト値はFalse).
:obj:`.Solver` のincludeAux参照.
verbose (int, optional): 負なら結果を出力しない.正ならSATソルバーの情報を表示する (デフォルト値は0).
:obj:`.Solver` のverbose参照.
timeout (int, optional): この関数の実行時間のタイムアウト秒数を指定する (デフォルト値は600秒).
:obj:`setTimeout` でデフォルト値を変更できる.
stat (bool, optional): Trueなら統計データも表示する (デフォルト値はFalse).
:obj:`.Solver.getStat` 参照.
tempdir (str, optional): 一時ファイルのディレクトリ名 (デフォルト値はNone).
Examples:
>>> x = Var("x")
>>> csp = [ ["int",x(1),1,3], ["int",x(2),1,3], [">",x(1),x(2)] ]
>>> solveCSP(csp, num=0)
SATISFIABLE
Model 1: {x(1): 2, x(2): 1}
Model 2: {x(1): 3, x(2): 1}
Model 3: {x(1): 3, x(2): 2}
>>> solveCSP(csp, num=0, stat=True) # 順序符号化 (OrderEncoder)
SATISFIABLE
Model 1: {x(1): 2, x(2): 1}
Stat: {'variables': 4, 'clauses': 5, 'conflicts': 0, 'decisions': 2, 'propagations': 4, 'solving': 0.3538937568664551, 'encoding': 0.0009377002716064453}
Model 2: {x(1): 3, x(2): 1}
Stat: {'variables': 4, 'clauses': 6, 'conflicts': 0, 'decisions': 1, 'propagations': 4, 'solving': 0.36089038848876953, 'encoding': 0.0009377002716064453}
Model 3: {x(1): 3, x(2): 2}
Stat: {'variables': 4, 'clauses': 7, 'conflicts': 1, 'decisions': 1, 'propagations': 5, 'solving': 0.39816951751708984, 'encoding': 0.0009377002716064453}
Stat: {'variables': 4, 'clauses': 7, 'conflicts': 1, 'decisions': 1, 'propagations': 5, 'solving': 0.39816951751708984, 'encoding': 0.0009377002716064453}
>>> solveCSP(csp, num=0, stat=True, encoder="direct") # 直接符号化 (DirectEncoder)
SATISFIABLE
Model 1: {x(1): 2, x(2): 1}
Stat: {'variables': 6, 'clauses': 11, 'conflicts': 0, 'decisions': 3, 'propagations': 6, 'solving': 0.40071797370910645, 'encoding': 0.0010752677917480469}
Model 2: {x(1): 3, x(2): 1}
Stat: {'variables': 6, 'clauses': 12, 'conflicts': 0, 'decisions': 2, 'propagations': 6, 'solving': 0.39261484146118164, 'encoding': 0.0010752677917480469}
Model 3: {x(1): 3, x(2): 2}
Stat: {'variables': 6, 'clauses': 13, 'conflicts': 1, 'decisions': 2, 'propagations': 10, 'solving': 0.38961291313171387, 'encoding': 0.0010752677917480469}
Stat: {'variables': 6, 'clauses': 13, 'conflicts': 1, 'decisions': 2, 'propagations': 10, 'solving': 0.38961291313171387, 'encoding': 0.0010752677917480469}
"""
global statData
if isinstance(encoder, Encoder):
pass
elif isinstance(encoder, str) and encoder.lower().startswith("d"):
encoder = DirectEncoder(verbose=verbose)
elif isinstance(encoder, str) and encoder.lower().startswith("l"):
encoder = LogEncoder(verbose=verbose)
else:
encoder = OrderEncoder(verbose=verbose)
encoder.verbose = verbose
if verbose >= 1:
print(f"# Encoderは{encoder.__class__.__name__}", file=sys.stderr)
sat = SAT(command=command, verbose=verbose, tempdir=tempdir)
solver = Solver(encoder, sat=sat, positiveOnly=positiveOnly, includeAux=includeAux, verbose=verbose)
try:
timeout = timeout or getTimeout()
with _cspsatTimer(timeout, verbose):
solver.solve(csp, num=num, stat=stat)
statData = solver.stats
except CspsatTimeout as e:
solver.stats["result"] = "TIMEOUT"
raise e
finally:
statData = solver.stats
sat.__del__()
[ドキュメント]
def saveSAT(cnf, fileName):
"""CNF式をファイルに保存する.
CNF式は節のシーケンス(リストなど)であり,各節はリテラル(:obj:`.Bool` オブジェクト)のリストである.
節が文字列の場合はコメントとして扱われ,そのままファイルに書き込まれる
(文字列の先頭が "#" でない場合は先頭に "# " を追加する).
保存したCNF式はloadSAT関数でロードできる.
Args:
cnf: CNF式.リテラルのリストのシーケンス.
fileName (str): ファイル名.
Examples:
>>> p = Bool("p")
>>> cnf = [ [ p(1), p(2) ]]
>>> saveSAT(cnf, "/tmp/foo.sat")
>>> cnf = loadSAT("/tmp/foo.sat")
>>> solveSAT(cnf)
SATISFIABLE
Model 1: {p(1): 0, p(2): 1}
"""
with open(fileName, mode="w", encoding="utf-8") as file:
for clause in cnf:
if isinstance(clause, list):
file.write(" ".join(map(str, clause)))
else:
comment = str(clause)
if not comment.startswith("#"):
file.write("# ")
file.write(comment)
file.write("\n")
[ドキュメント]
def loadSAT(fileName):
"""ファイルからCNF式を読み込み,各節をyieldするジェネレータ関数.
各行が節を表すが,空行および ``#`` から始まる行はコメントとして処理される.
各リテラルは1つ以上の空白文字で区切られているとし,負リテラルは ``~`` から始まるとする.
命題変数名には ``#`` あるいは ``~`` から始まらない任意の文字列を使用できる.
Args:
fileName (str): ファイル名.
Yields:
読み込んだCNF式.
"""
with open(fileName, encoding="utf-8") as file:
for line in file:
line = re.sub(r"\s+", " ", line).strip()
if line == "":
continue
if line.startswith("#"):
yield line
continue
clause = []
for s in line.split(" "):
if s.startswith("~"):
clause.append(Bool(s[1:], False))
else:
clause.append(Bool(s))
yield clause
[ドキュメント]
def saveDimacs(cnf, fileName):
"""CNF式をDIMACS CNF形式に変換しファイルに保存する.
Args:
cnf: CNF式.リテラルのリストのシーケンス.
fileName (str): ファイル名.
Examples:
>>> p = Bool("p")
>>> cnf = [ [ p(1), p(2) ], [ ~p(1), ~p(2) ] ]
>>> saveDimacs(cnf, "/tmp/foo.cnf")
"""
sat = SAT(cnfFile=fileName, delete=False)
sat.add(*cnf)
sat.updateDimacsHeader()
sat.cnf.close()
sat.__del__()
[ドキュメント]
def saveCSP(csp, fileName):
"""CSPをJSON形式でファイルに保存する.
Boolオブジェクトは先頭に "$" を付け加えた文字列に変換する.
Varオブジェクトは先頭に "@" を付け加えた文字列に変換する.
Args:
cnf: CSP.
fileName (str): ファイル名.
"""
def serialize(data):
if isinstance(data, Bool):
n = data.name
return "$" + n if data.positive else "$~" + n
if isinstance(data, Var):
return "@" + data.name
if isinstance(data, (int,float,bool,str)):
return data
try:
return [ serialize(d) for d in iter(data) ]
except TypeError as e:
raise CspsatException(f"制約の構文エラー: {data}") from e
with open(fileName, mode="w", encoding="utf-8") as file:
file.write("[\n")
for c in csp:
s = json.JSONEncoder().encode(serialize(c))
file.write(s + ",\n")
file.write(json.JSONEncoder().encode(["#", "END of CSP"]) + "\n")
file.write("]\n")
[ドキュメント]
def loadCSP(fileName):
"""JSON形式でCSPが保存されたファイルからCSPを読み込みyieldするジェネレータ関数.
先頭に "$" がある文字列はBoolオブジェクトに変換する.
先頭に "@" がある文字列はVarオブジェクトに変換する.
Args:
fileName (str): ファイル名.
Yield:
読み込んだCSPのデータ.
"""
def deserialize(data):
if isinstance(data, (int,float,bool)):
return data
if isinstance(data, str):
if data.startswith("$"):
return Bool(data[1:]) if data[1] != "~" else ~Bool(data[2:])
if data.startswith("@"):
return Var(data[1:])
return data
return [ deserialize(d) for d in iter(data) ]
with open(fileName, encoding="utf-8") as file:
for c in deserialize(json.load(file)):
yield c
import sys
import re
import itertools
import json
import threading
import _thread
from .sat import SAT
from .csp import Solver, Encoder, DirectEncoder, OrderEncoder, LogEncoder
from .util import CspsatException, CspsatTimeout, Bool, TRUE, FALSE, Var