def todo(fn):
def skip(reason):
def _log(fn, message):
def canonical_cnf(be):
def current_directory(what):
    def __init__(self, testcase, location, model):
    def __enter__(self):
    def __exit__(self, type_, value, traceback):
class Configure: def init_nusmv(collecting=True):
    """
    Initialize NuSMV. Must be called only once before calling
    :func:`deinit_nusmv`.

    :param collecting: Whether or not collecting pointer wrappers to free them
                       before deiniting nusmv.

    .. warning: Deactivating the collection of pointer wrappers may provoke
                segmentation faults when deiniting nusmv without correctly
                freeing all pointer wrappers in advance.
                On the other hand, collection may blow memory.

    """
    global __collector, __collecting
    if __collector is not None:
        raise NuSMVInitError("Cannot initialize NuSMV twice.")
    else:
        __collecting = collecting
        __collector = set()
        nscinit.NuSMVCore_init_data()
        nscinit.NuSMVCore_init(None, 0)  # No addons specified
        nscinit.NuSMVCore_init_cmd_options()

        # Set NuSMV in interactive mode to avoid fast termination when errors
        # nsopt.unset_batch(nsopt.OptsHandler_get_instance())

        # Initialize option commands (set, unset)
        # to be able to set parser_is_lax
        nsopt.init_options_cmd()
        nscmd.Cmd_SecureCommandExecute("set parser_is_lax")

        return _PyNuSMVContext() def deinit_nusmv(ddinfo=False):
    """
    Quit NuSMV. Must be called only once, after calling :func:`init_nusmv`.

    :param ddinfo: Whether or not display Decision Diagrams statistics.

    """
    global __collector
    if __collector is None:
        raise NuSMVInitError(
            "Cannot deinitialize NuSMV before initialization.")
    else:
        # Apply Python garbage collection first,
        # then collect every pointer wrapper
        # that is not yet collected by Python GC
        from . import glob

        # Print statistics on stdout about DDs handled by the main DD manager.
        if ddinfo:
            try:
                manager = glob.prop_database().master.bddFsm.bddEnc.DDmanager
                nsdd.dd_print_stats(manager._ptr, nscinit.cvar.nusmv_stdout)
            except PyNuSMVError:
                pass

        glob._reset_globals()

        # First garbage collect with Python
        gc.collect()
        # Then garbage collect with PyNuSMV
        for elem in __collector:
            elem._free()
        __collector = None
        nscinit.NuSMVCore_quit() def load(*model):
    """
    Load the given model. This model can be of several forms:

    * a file path; in this case, the model is loaded from the file;
    * NuSMV modelling code; in this case, `model` is the code for the model;
    * a list of modules (list of :class:`Module <pynusmv.model.Module>`
      subclasses); in this case, the model is represented by the set of
      modules.

    """
    if len(model) == 1 and isinstance(model[0], str):
        if os.path.isfile(model[0]):
            load_from_file(model[0])
        else:
            load_from_string(model[0])
    else:  # model must be a list of modules
        load_from_modules(*model) def go_bmc(force=False):
    """
    Performs all the necessary steps to use loaded model and be able to perform
    bmc related operations on it.
    
    :raises NuSMVNoReadModelError: if no module was read (:func:`pynusmv.glob.load`) 
      before this method was called. 
    """
    cmp = glob.global_compile_cmps() 
    if not _compile.cmp_struct_get_read_model(cmp):
        raise NuSMVNoReadModelError("No read model.")

    # Check cmps and perform what is needed
    if not _compile.cmp_struct_get_flatten_hrc(cmp):
        glob.flatten_hierarchy()
    if not _compile.cmp_struct_get_encode_variables(cmp):
        glob.encode_variables()
    if not _compile.cmp_struct_get_build_bool_model(cmp):
        glob.build_boolean_model(force=force)
    if not _compile.cmp_struct_get_bmc_setup(cmp):
        bmc_setup(force=force) def bmc_exit():
    """
    Releases all resources associated to the bmc model manager. 
    If you want to do bmc again after calling this, you will have to call
    :func:`bmc_setup` or :func:`go_bmc` again.
    """  
    global __be_fsm
    _bmc.Bmc_Quit()
    __be_fsm = None
    
    _lower.MEMOIZER_clear() class BeFsm(PointerWrapper):
    """
    This class wraps the public interface of a BE FSM as defined in NuSMV
    with the BeFsm_ptr type.
    
    Its role is to give an access to the properties of FSM as represented with
    boolean expressions (BE). For instance, it gives access to the initial states
    the invariants and the transition relation encoded in terms of BE. Moreover
    it gives the possibility to compute the synchronous product of two FSMs.  
    """
    
    def __init__(self, ptr, freeit=False):
        """
        Creates an instance of the BeFsm, using the NuSMV pointer. 
        :param ptr: the NuSMV pointer of type BeFsm_ptr
        :param freeit: a flag indicating whether or not the system resources 
          should be freed upon garbage collection.
        """
        super().__init__(ptr, freeit)
        
    def _free(self):
        """Frees the system resources associated with this object"""
        if self._freeit and self._ptr is not None:
            _be.BeFsm_destroy(self._ptr)
            self._freeit = False
            self._ptr    = None
    
    @staticmethod
    def global_master_instance():
        """
        :return: the boolean FSM in BE stored in the master prop. 
        :raises NuSMVBeFsmMasterInstanceNotInitializedError:
             when the global BE FSM is null in the global properties database 
             (ie when coi is enabled).
        """
        _pd  = _prop.PropPkg_get_prop_database()
        _fsm = _prop.PropDb_master_get_be_fsm(_pd)
        if _fsm is None:
            raise NuSMVBeFsmMasterInstanceNotInitializedError("""
                            BE fsm stored in master prop is not initialized.
                            You must either initialize the BE model (ie. go_bmc)
                            or disable COI.
                            """)
        return BeFsm(_fsm)
    
    @staticmethod
    def create(enc, init, invar, trans, fairness, freeit=False):
        """
        Creates a new BeFsm instance using the given encoder, initial states, 
        invariants, transition relation and list of fairness.
        
        :param enc: the encoder used to represent the variables of the model.
        :param init: the boolean expression representing the initial states of 
          the FSM
        :param invar: the boolean expression representing the invariants of the 
          model encoded in this FSM
        :pqram trans: the boolean expression representing the transition relation
          of the model encoded in this FSM
        :param fairness: a list of boolean expression representing the fairness
          constraints of the model. 
        :param freeit: a flag indicating whether or not the system resources 
          should be freed upon garbage collection.
          
        :return: a new instance of BeFsm that gets its init, invar, transition 
          relation and the list of fairness in Boolean Expression format
        """
        # very unlikely to be used directly
        return BeFsm(_be.BeFsm_create(enc._ptr, 
                                      init._ptr, 
                                      invar._ptr,
                                      trans._ptr,
                                      fairness._ptr))
    
    @staticmethod
    def create_from_sexp(enc, sexp_fsm):
        """
        Creates a new instance of BeFsm extracting the necessary information
        from the given `sexp_fsm` of type :class:`BoolSexpFsm <pynusmv.sexp.BoolSexpFsm>`
        
        :param enc: the encoder used to represent the variables of the model.
        :param sexp_fsm: the BoolSexpFsm which contains the automaton information
        """
        return BeFsm(_be.BeFsm_create_from_sexp_fsm(enc._ptr, sexp_fsm._ptr)) 
    
    def copy(self, freeit=True):
        """
        Creates a new independent copy of the FSM.
        :param freeit: a flag indicating whether or not the system resources 
        should be freed upon garbage collection.
        """
        return BeFsm(_be.BeFsm_copy(self._ptr), freeit=freeit)
    
    def __copy__(self):
        """Creates a new independent copy of the FSM"""
        return self.copy()
    
    @property
    def encoding(self):
        """The BE encoding of this FSM"""
        if not hasattr(self, "__encoding"):
            setattr(self, '__encoding', BeEnc(_be.BeFsm_get_be_encoding(self._ptr), freeit=False))
        return getattr(self, '__encoding')
    
    @property
    def init(self):
        """The BE representing the initial states of this FSM"""
        _expr = _be.BeFsm_get_init(self._ptr)
        return Be(_expr, self.encoding.manager) if _expr is not None else None
    
    @property
    def invariants(self):
        """The boolean expression representing the invariants of this FSM"""
        _expr = _be.BeFsm_get_invar(self._ptr)
        return Be(_expr, self.encoding.manager) if _expr is not None else None
    
    @property
    def trans(self):
        """
        The boolean expression representing the transition relation of the FSM
        
        .. note::
        
            Transition expression shifted at time zero is what brings you to 
            state one. Hence::
            
                shift_to_time(init, 0) & shift_to_time(trans, 0) == STATE_1 
        """
        _expr = _be.BeFsm_get_trans(self._ptr)
        return Be(_expr, self.encoding.manager) if _expr is not None else None
    
    @property
    def fairness_list(self):
        """
        The list of fairness constraints of this model encoded in BE format.
        
        .. note:: accessing this property is not free: use fairness_iterator
           instead if you don't need to manipulate the list as a list.
        """
        return [ i for i in self.fairness_iterator() ]
    
    def fairness_iterator(self):
        """:return: an iterator to iterate over the fairness list"""
        _ptr  = _be.BeFsm_get_fairness_list(self._ptr)
        _iter = NodeIterator.from_pointer(_ptr)
        for fairness in _iter:
            yield self._fairness_conversion(fairness)
    
    def _fairness_conversion(self, fairness):
        """
        Converts the given `fairness` into a Be representation.
        
        .. note::
            This function is present for purely technical reason: under the 
            hood, NuSMV encodes the fairness list as a NodeList however the
            'car' (value) of these nodes is nothing that can be understood 
            AST-wise. Indeed, the values are opaque pointers (be_ptr that is 
            to say void*) to a structure representing a Be. 
        """
        beptr = _be.node_ptr_to_be_ptr(_node.car(fairness._ptr))
        bexpr = Be(beptr, self.encoding.manager)
        return bexpr
    
    def apply_synchronous_product(self, other):
        """
        Apply the synchronous product between self and other modifying self.
        :param other: the other to compute the synchronous product with
        """
        _be.BeFsm_apply_synchronous_product(self._ptr, other._ptr)
        
    def __mul__(self, other):
        """
        :return: a new fsm corresponding to the synchronous product of self and
        other.
        :param other: the other to compute the synchronous product with
        """
        cpy = self.copy()
        cpy.apply_synchronous_product(other)
        return cpy
    
    def __imul__(self, other):
        """
        Apply the synchronous product between self and other modifying self.
        :param other: the other to compute the synchronous product with
        """
        self.apply_synchronous_product(other) class SatSolverFactory():
    # =========================================================================
    # ========== Utility functions ============================================
    # =========================================================================
    @staticmethod
    def normalize_name(name):
        """
        :return: a normalized solver name corresponding to the given `name`.
          (Only case should be changed)
        :param name: the name (string) of the solver to normalize.
        :raise: ValueError whenever the name corresponds to none of the 
          available solvers
        """
        normalized = _sat.Sat_NormalizeSatSolverName(name)
        if normalized is None:
            raise ValueError("No such SAT solver available : {}".format(name))
        else:
            return normalized

    @staticmethod
    def available_solvers():
        """
        :return: a list with the name of the solvers that can be instantiated
        """
        return set(_sat.Sat_GetAvailableSolversString().split())
    
    @staticmethod 
    def print_available_solvers(stdio_file=utils.StdioFile.stdout()):
        """
        prints the list of available SAT solvers to the given stdio file 
        """
        _sat.Sat_PrintAvailableSolvers(stdio_file.handle)
        
    @staticmethod
    def create(name='MiniSat', incremental=True, proof=True):
        """
        Creates a new sat solver corresponding to the given name and capabilities
        :param name: the name of the solver to instanciate 
        :param incremental: a flag indicating whether the instanciated solver
        should have incremental capabilities
        :param proof: a flag indicating whether the instanciated solver should
        have proof logging capability.
        :return: a new sat solver corresponding to the given name and capabilities
        
        :raise: Given that ZChaff does not support proof logging, this method
          raises a ValueError when prooflogging is turned on and zchaff is passed
          as name parameter.
        """
        if SatSolverFactory.normalize_name(name) == 'ZChaff' and proof:
            raise ValueError("""
                Proof logging not supported when using the zchaff  SAT Solver. 
                Please retry using MiniSat
                """)
        
        if incremental:
            if proof:
                return SatIncProofSolver(
                                _sat.Sat_CreateIncProofSolver(name))
            else:
                return SatIncSolver(_sat.Sat_CreateIncSolver(name))
        else:
            if proof:
                return SatProofSolver(
                                _sat.Sat_CreateNonIncProofSolver(name))
            else:
                return SatSolver(
                                _sat.Sat_CreateNonIncSolver(name))
        raise Exception("Could not create solver") class Polarity(IntEnum):
    """
    In general, a polarity is assigned to a formula and its variables. If this
    were not done, this would potentially slow down the solver because the 
    solver would unnecessarily try to assign values to many variables.
    """
    POSITIVE =  1
    NEGATIVE = -1
    NOT_SET  =  0 class SatSolverResult(IntEnum):
    """
    This result represents the possible outcomes of a sat solving. 
    """
    INTERNAL_ERROR        = _sat.SAT_SOLVER_INTERNAL_ERROR
    TIMEOUT               = _sat.SAT_SOLVER_TIMEOUT
    MEMOUT                = _sat.SAT_SOLVER_MEMOUT
    SATISFIABLE           = _sat.SAT_SOLVER_SATISFIABLE_PROBLEM
    UNSATISFIABLE         = _sat.SAT_SOLVER_UNSATISFIABLE_PROBLEM 
    UNAVAILABLE           = _sat.SAT_SOLVER_UNAVAILABLE
import unittest
from tests                import utils as tests
from pynusmv.init         import init_nusmv, deinit_nusmv
from pynusmv.glob         import load 
from pynusmv.bmc.glob     import go_bmc, bmc_exit
from pynusmv.be.fsm       import BeFsm  
from pynusmv.sat          import SatSolverFactory, Polarity, SatSolverResult


class TestSatSolver(unittest.TestCase):
      
    def model(self):
        return tests.current_directory(__file__)+"/models/flipflops_explicit_relation.smv"

    def setUp(self):
        init_nusmv()
        load(self.model())
        go_bmc()
        self.fsm = BeFsm.global_master_instance()
 
    def tearDown(self):
        bmc_exit()
        deinit_nusmv()
         
    def test_name(self):
        solver = SatSolverFactory.create('MiniSat')
        self.assertEqual('MiniSat', solver.name)
     
    def test_last_solving_time(self):
        solver = SatSolverFactory.create('MiniSat')
        # didn't solve anything yet
        self.assertEqual(0, solver.last_solving_time)
        # any trivial problem is solved so fast that the last solving time
        # would be 0. This is why that second case is not tested.
        # (giving in a non trvial problem makes imho the test harder to follow)
     
    def test_permanent_group(self):
        solver = SatSolverFactory.create('MiniSat')
        # note: Zchaff returns 0 for its perm. group id
        self.assertEqual(-1, solver.permanent_group)
     
    def test_random_mode(self):
        solver= SatSolverFactory.create('MiniSat')
        solver.random_mode = 0.1
        # this property is write only: nothing can be verified except that the
        # solver did not crash
     
    def test_solve(self):
        """
        This function tests the following functionalities::
         
        - add (and __iadd__)
        - solve
        - polarity
         
        """
        v   = self.fsm.encoding.by_name['v'].boolean_expression
        ########################################################################
        # THIS IS THE INCREMENTAL WAY OF DOING IT
        ########################################################################
        # solver.add_to_group(unsat, solver.permanent_group)
        # solver.group_polarity(unsat, Polarity.POSITIVE, solver.permanent_group)
        # solution = solver.solve_all_groups()
        ########################################################################
        # Tests with a satisfiable problem
        solver= SatSolverFactory.create('MiniSat')
        sat   = (v + -v).to_cnf()
        solver.add(sat)

        solver.polarity(sat, Polarity.POSITIVE)