3-SAT Solver Python

Oct 22 2020

Ich habe einen 3-SAT- Solver basierend auf dieser Eingabeaufforderung geschrieben:

Alice hat vor kurzem angefangen, für ein Hardware-Design-Unternehmen zu arbeiten. Als Teil ihrer Arbeit muss sie Fehler in hergestellten integrierten Schaltkreisen identifizieren. Ein Ansatz zum Identifizieren dieser Defekte läuft darauf hinaus, eine Erfüllbarkeitsinstanz zu lösen. Sie braucht Ihre Hilfe, um ein Programm für diese Aufgabe zu schreiben.

Eingabe
Die erste Eingabezeile enthält eine einzelne Ganzzahl, nicht mehr als 5, die die Anzahl der folgenden Testfälle angibt. Die erste Zeile jedes Testfalls enthält zwei ganze Zahlen n und m, wobei 1 ≤ n ≤ 20 die Anzahl der Variablen und 1 ≤ m ≤ 100 die Anzahl der Klauseln angibt. Dann folgen m Zeilen entsprechend jeder Klausel. Jede Klausel ist eine Disjunktion von Literalen in der Form Xi oder ~ Xi für einige 1 ≤ i ≤ n, wobei ~ Xi die Negation des Literals Xi angibt. Der Operator "oder" wird durch ein "v" -Zeichen gekennzeichnet und durch ein einzelnes Leerzeichen von Literalen getrennt.

Ausgabe
Zeigen Sie für jeden Testfall in einer einzelnen Zeile zufriedenstellend an, wenn eine zufriedenstellende Zuordnung vorliegt. sonst unbefriedigend anzeigen.

Probeneingabe

2
3 3
X1 v X2
~X1
~X2 v X3
3 5
X1 v X2 v X3
X1 v ~X2
X2 v ~X3
X3 v ~X1
~X1 v ~X2 v ~X3 

Beispielausgabe

satisfiable
unsatisfiable

Dieser Code enthält im Wesentlichen mySetseine Liste von Mengen, die alle mögliche Kombinationen von Literalen darstellen, die die gesamte Aussage wahr machen könnten. Jedes Mal, wenn wir eine neue Klausel analysieren, prüfen wir, ob ihre Negation bereits in einer Menge vorhanden ist. Wenn dies der Fall ist, ist die Menge nicht enthalten.

Das funktioniert, läuft aber etwas langsam.

import sys

cases = int(sys.stdin.readline())


def GetReverse(literal):
    if literal[0] == '~':
    return literal[1:]
    else:
        return '~' + literal


for i in range(cases):
    vars, clauses = map(int, sys.stdin.readline().split())

mySets = []

firstClause = sys.stdin.readline().strip().split(" v ")

for c in firstClause:
    this = set()
    this.add(c)
    mySets.append(this)


for i in range(clauses-1):
    tempSets = []
    currentClause = sys.stdin.readline().strip().split(" v ")

    for s in mySets:
        for literal in currentClause:

            if not s.__contains__(GetReverse(literal)):


                newset = s.copy()
                newset.add(literal)

                tempSets.append(newset)
    mySets = tempSets


if mySets:
    print("satisfiable")
else:
    print("unsatisfiable")

Ich denke, das Problem liegt hier an den eingerückten for-Schleifen. 3-SAT soll exponentiell sein, aber ich möchte es etwas beschleunigen (vielleicht durch Entfernen einer Schleife?)

for i in range(clauses-1):
    tempSets = []
    currentClause = sys.stdin.readline().strip().split(" v ")

    for s in mySets:
        for literal in currentClause:

            if not s.__contains__(GetReverse(literal)):


                newset = s.copy()
                newset.add(literal)

                tempSets.append(newset)
    mySets = tempSets

Antworten

2 RootTwo Oct 23 2020 at 04:47

Wenn Sie Ihren Code mit strategisch platzierten Druckanweisungen instrumentieren, werden Sie feststellen, dass einige wiederholte Berechnungen durchgeführt werden. Im zweiten Testfall bei der Verarbeitung der Klausel X2 v ~X3wird die Menge zweimal {'X1', 'X2'}hinzugefügt mySets. Bei der Verarbeitung der Klausel X3 v ~X1wird die Menge dreimal {'X3', 'X1', 'X2'}hinzugefügt mySets.

In großen Fällen kann es schneller sein, Änderungen mySetsan set()einer Liste vorzunehmen, um die Duplikate zu entfernen. Dann müssen die inneren Sätze sein frozensets.

mySetsist eine Reihe möglicher Lösungen, die alle Klauseln erfüllen, daher habe ich sie in umbenannt candidates.

Wenn Sie initialisieren candidates, um eine einzelne leere Menge zu enthalten, muss die erste Klausel nicht separat behandelt werden.

Ich denke, Sie können jederzeit aufhören, wenn candidateses leer ist.

Teilen Sie den Code auch in Funktionen auf.

def is_satisfiable(n_vars, clauses):
    candidates = {frozenset()}

    for clause in clauses:
        temp = set()

        for s in candidates:
            for literal in clause:

                if GetReverse(literal) not in s:

                    temp.add(s | {literal})

        candidates = temp
        
        if len(candidates) == 0:
            return False

    return True
        
        
def load_case(f):
    n_vars, n_clauses = f.readline().split()
    clauses = [f.readline().strip().split(' v ') for _ in range(int(n_clauses))]
    return int(n_vars), clauses
    
    
def main(f=sys.stdin):
    num_cases = int(f.readline())

    for i in range(num_cases):
        n_vars, clauses = load_case(f)
        result = is_satisfiable(n_vars, clauses)
        
        print(f"{'satisfiable' if result else 'unsatisfiable'}")

Genannt wie:

import io

data = """
2
3 3
X1 v X2
~X1
~X2 v X3
3 5
X1 v X2 v X3
X1 v ~X2
X2 v ~X3
X3 v ~X1
~X1 v ~X2 v ~X3 
""".strip()

main(io.StringIO(data))

oder

import sys

main(sys.stdin)        
3 Reinderien Oct 23 2020 at 01:58

Hier ist eine vorgeschlagene Implementierung, die im Grunde nichts an Ihrem Algorithmus ändert, aber

  • hat die richtige Einrückung
  • verwendet ein wenig Typhinweis
  • verwendet Set-Literale und Generatoren
  • wird _für "nicht verwendete" Variablen verwendet
  • fügt ein hinzu, parse_clause()weil der Klauselcode wiederholt wird
  • verwendet a StringIOfür diese Zwecke, stdinum die von Ihnen gezeigte Beispieleingabe effektiv zu verspotten und zu verwenden
  • verwendet PEP8-kompatible Namen (mit Unterstrichen)
from io import StringIO
from typing import List

stdin = StringIO('''2
3 3
X1 v X2
~X1
~X2 v X3
3 5
X1 v X2 v X3
X1 v ~X2
X2 v ~X3
X3 v ~X1
~X1 v ~X2 v ~X3
'''
)


def get_reverse(literal: str) -> str:
    if literal[0] == '~':
        return literal[1:]
    return '~' + literal


def parse_clause() -> List[str]:
    return stdin.readline().strip().split(' v ')


n_cases = int(stdin.readline())
for _ in range(n_cases):
    n_vars, n_clauses = (int(s) for s in stdin.readline().split())
    my_sets = [{c} for c in parse_clause()]

    for _ in range(n_clauses - 1):
        temp_sets = []
        current_clause = parse_clause()

        for s in my_sets:
            for literal in current_clause:
                if get_reverse(literal) not in s:
                    new_set = s.copy()
                    new_set.add(literal)
                    temp_sets.append(new_set)

        my_sets = temp_sets

    if my_sets:
        print('satisfiable')
    else:
        print('unsatisfiable')