Hosted by ИСПРАН Лаборатория «lab22» | Download | Raw |
Kernel: Python 3 (ipykernel)
from __future__ import annotations import typing import random from IPython.core.display import SVG import pyomo.environ as pyo import pyomo.util.infeasible from pysat.solvers import Solver from pysat.formula import CNF import py_svg_combinatorics as psc from ipywidgets import widgets, HBox from collections import Counter from pprint import pprint from random import randint import numpy as np from IPython.display import IFrame import IPython from copy import copy import os from pathlib import Path nbname = '' try: nbname = __vsc_ipynb_file__ except: if 'COCALC_JUPYTER_FILENAME' in os.environ: nbname = os.environ['COCALC_JUPYTER_FILENAME'] title_ = Path(nbname).stem.replace('-', '_').title() IFrame(f'https://discopal.ispras.ru/index.php?title=Hardprob/{title_}&useskin=cleanmonobook', width=1280, height=300)

Генератор тестовых данных

def gen_random_inputs(k: int, n_vars: int, n_terms: int) -> np.ndarray['n_terms,k']: """ Returns C - the matrix of the CNF ter A var is an integer in [1, n_vars] A literal is a var, -var or 0, which means False for disjunctions (i.e. no effect) The set of all vars is implicitly defined as np.arange(n_vars) + 1 """ C: np.ndarray['n_terms,k'] = np.random.randint(n_vars + 1, size=(n_terms, k)) C *= np.random.choice([-1, 1], size=(n_terms, 3)) return C
k = 3 n_vars = 10 n_terms = 50 C = gen_random_inputs(k, n_vars, n_terms) k, n_vars, n_terms, C
(3, 10, 50, array([[ 10, 0, -3], [ -9, -4, -10], [ -7, -9, -9], [ 0, -9, -8], [ 2, 10, 3], [ -2, -5, 9], [ -1, 1, 1], [ -9, 5, -4], [ -7, 9, 3], [ -8, 1, -2], [ 6, -9, 8], [ -3, 9, -2], [-10, 9, 6], [ -9, -2, 7], [ -9, -1, 1], [ 6, -10, 5], [ -9, 2, 10], [ 8, -4, -5], [-10, 1, -7], [ 3, 0, -5], [ -7, -3, -9], [ -4, -8, -3], [ -8, 10, -3], [ -7, -3, 8], [ 0, -4, 0], [ 1, 9, 0], [ 0, 5, -10], [ -9, -8, 0], [ 3, -4, 10], [ -4, -2, 7], [ 8, 3, 0], [ 4, -6, 1], [ -9, -6, 9], [ 9, 6, -3], [ -6, 0, -10], [ 0, 3, -10], [ 2, 4, 6], [ -5, -5, -2], [ 4, -4, 9], [ 3, -10, 2], [ 3, 8, -7], [ -7, 0, -8], [ 0, 2, -10], [-10, -3, -8], [ 9, 10, 5], [ -9, 0, -7], [-10, -3, 1], [ -3, 10, 10], [ -3, 5, -4], [ -4, 2, 10]]))

Pyomo-модель

def get_model(k: int, n_vars: int, n_terms: int, C: np.ndarray['n_terms,k']) -> pyo.ConcreteModel: m = pyo.ConcreteModel(name="maximum k-satisfiability") m.n_vars = n_vars m.n_terms = n_terms m.k = k """ C holds the matrix of the CNF terms. Each row is a term, each column is a literal. A literal is a var, -var or 0, which means False for disjunctions (i.e. no effect) A var is an integer in [1, n_vars] """ assert C.shape == (n_terms, k) m.C = C """ var_values holds the truth values of the variables. """ m.I = range(m.n_vars) m.J = range(m.n_terms) m.K = range(m.k) m.var_values = pyo.Var(m.I, domain=pyo.Binary) """ term_items holds the values of the literals in each term. """ m.term_items = pyo.Var(m.J, m.K, domain=pyo.Binary) @m.Constraint( m.J, m.K ) def evaluate_term_items(m: pyo.ConcreteModel, j: int, k: int) -> bool: if m.C[j, k] == 0: return m.term_items[j, k] == 0 if m.C[j, k] > 0: return m.term_items[j, k] == m.var_values[m.C[j, k] - 1] if m.C[j, k] < 0: return m.term_items[j, k] == 1 - m.var_values[-m.C[j, k] - 1] assert False, "Unreachable" """ term_values holds the values of the terms. Note: every term is a disjunction of literals """ m.term_values = pyo.Var(m.J, domain=pyo.Binary) @m.Constraint(m.J, m.K) def evaluate_terms_low(m: pyo.ConcreteModel, j: int, k: int) -> bool: return m.term_values[j] >= m.term_items[j, k] @m.Constraint(m.J) def evaluate_terms_high(m: pyo.ConcreteModel, j: int) -> bool: return m.term_values[j] <= sum(m.term_items[j, ...]) """ The objective is to maximize the number of satisfied terms. """ m.obj = pyo.Objective(expr=sum(m.term_values[...]), sense=pyo.maximize) return m m: pyo.ConcreteModel = get_model(k, n_vars, n_terms, C)
solver = pyo.SolverFactory('cbc') solver.solve(m).write()
# ========================================================== # = Solver Results = # ========================================================== # ---------------------------------------------------------- # Problem Information # ---------------------------------------------------------- Problem: - Name: unknown Lower bound: 49.0 Upper bound: 49.0 Number of objectives: 1 Number of constraints: 178 Number of variables: 60 Number of binary variables: 210 Number of integer variables: 210 Number of nonzeros: 50 Sense: maximize # ---------------------------------------------------------- # Solver Information # ---------------------------------------------------------- Solver: - Status: ok User time: -1.0 System time: 0.03 Wallclock time: 0.04 Termination condition: optimal Termination message: Model was solved to optimality (subject to tolerances), and an optimal solution is available. Statistics: Branch and bound: Number of bounded subproblems: 0 Number of created subproblems: 0 Black box: Number of iterations: 1 Error rc: 0 Time: 0.07517719268798828 # ---------------------------------------------------------- # Solution Information # ---------------------------------------------------------- Solution: - number of solutions: 0 number of solutions displayed: 0
pyo.value(m.obj)
49.0
m.var_values.extract_values()
{0: 1.0, 1: 1.0, 2: 0.0, 3: 0.0, 4: 0.0, 5: 0.0, 6: 0.0, 7: 1.0, 8: 0.0, 9: 0.0}
m.term_values.extract_values()
{0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0, 6: 1.0, 7: 1.0, 8: 1.0, 9: 1.0, 10: 1.0, 11: 1.0, 12: 1.0, 13: 1.0, 14: 1.0, 15: 1.0, 16: 1.0, 17: 1.0, 18: 1.0, 19: 1.0, 20: 1.0, 21: 1.0, 22: 1.0, 23: 1.0, 24: 1.0, 25: 1.0, 26: 1.0, 27: 1.0, 28: 1.0, 29: 1.0, 30: 1.0, 31: 1.0, 32: 1.0, 33: 1.0, 34: 1.0, 35: 1.0, 36: 1.0, 37: 1.0, 38: 1.0, 39: 1.0, 40: 1.0, 41: 1.0, 42: 1.0, 43: 1.0, 44: 0.0, 45: 1.0, 46: 1.0, 47: 1.0, 48: 1.0, 49: 1.0}
[bool(pyo.value(m.var_values[i])) for i in range(m.n_vars)]
[True, True, False, False, False, False, False, True, False, False]
[[bool(pyo.value(m.term_items[i, j])) for j in range(m.k)] for i in range(m.n_terms)]
[[False, False, True], [True, True, True], [True, True, True], [False, True, False], [True, False, False], [False, True, False], [False, True, True], [True, False, True], [True, False, False], [False, True, False], [False, True, True], [True, False, False], [True, False, False], [True, False, False], [True, False, True], [False, True, False], [True, True, False], [True, True, True], [True, True, True], [False, False, True], [True, True, True], [True, False, True], [False, False, True], [True, True, True], [False, True, False], [True, False, False], [False, False, True], [True, False, False], [False, True, False], [True, False, False], [True, False, False], [False, True, True], [True, True, False], [False, False, True], [True, False, True], [False, False, True], [True, False, False], [True, True, False], [False, True, False], [False, True, True], [False, True, True], [True, False, False], [False, True, True], [True, True, False], [False, False, False], [True, False, True], [True, True, True], [True, False, False], [True, False, True], [True, True, False]]

Вероятностная проверка SAT

def gen_random_3sat(n_vars: int, n_terms: int) -> np.ndarray['n_terms,3']: """ Returns C - the matrix of the CNF terms. A term is a disjunction of 3 literals. A literal is a var or -var. A var is an integer in [1, n_vars] """ C: np.ndarray['n_terms,3'] = 1 + np.random.randint(n_vars, size=(n_terms, 3)) C *= np.random.choice([-1, 1], size=(n_terms, 3)) return C
gen_random_3sat(6, 10)
array([[ 2, -1, 5], [ 5, -4, 6], [-1, -1, 4], [ 6, -4, -4], [ 3, -5, -3], [ 3, 5, 4], [ 4, -2, 3], [-2, -2, -2], [ 6, 2, -2], [ 3, -5, 5]])
def convert_3sat_to_task(C: np.ndarray['n_terms,3']) -> tuple[int, int, int, np.ndarray['n_terms,3']]: n_vars: int = np.max(np.abs(C)) n_terms: int = C.shape[0] k: int = 3 assert C.shape == (n_terms, k) return k, n_vars, n_terms, C
def pysat_solve_3sat(C: np.ndarray['n_terms,3']) -> bool: with Solver() as solver: for i in range(C.shape[0]): clause = list(map(int, C[i, ...])) # print(">", clause) solver.add_clause(clause) return solver.solve()
def my_solve_3sat(C: np.ndarray['n_terms,3']) -> bool: m: pyo.ConcreteModel = get_model(*convert_3sat_to_task(C)) solver = pyo.SolverFactory('cbc') solver.solve(m) return np.isclose(pyo.value(m.obj), m.n_terms)
def test_3sat(n: int = 100) -> bool: for _ in range(n): C = gen_random_3sat(10, 20) matches: bool = pysat_solve_3sat(C) == my_solve_3sat(C) if not matches: print(f"Discrpeancy found for C = {C}!") return False return True assert test_3sat() print("Yay!")
Yay!