def test_passing_empty_list_assumptions(self): """Test passing an empty list as assumptions causes an error.""" with self.assertRaises(InvalidArgumentValueError): sat_one([[1, 2]], assumptions=[]) with self.assertRaises(InvalidArgumentValueError): sat_all([[1, 2]], assumptions=[])
def test_passing_empty_list_clauses(self): """Test passing an empty list as clauses causes an error.""" with self.assertRaises(InvalidArgumentValueError): sat_one([]) with self.assertRaises(InvalidArgumentValueError): sat_all([])
def test_passing_list_containing_zeroes_assumptions(self): """Test an error is rasied when zeroes are passed in assumptions.""" with self.assertRaises(InvalidArgumentValueError): sat_one([[1, -2], [-1]], assumptions=[1, 2, 3, 0]) with self.assertRaises(InvalidArgumentValueError): sat_all([[1, -2], [-1]], assumptions=[1, 2, 3, 0])
def test_passing_non_list_clauses(self): """Test passing a non-list as the clauses argument.""" with self.assertRaises(InvalidArgumentTypeError): sat_one(None) with self.assertRaises(InvalidArgumentTypeError): sat_all(None)
def test_passing_non_list_assumptions(self): """Test passing an invalid non-list as the assumptions argument.""" with self.assertRaises(InvalidArgumentTypeError): sat_one([[1, 2, 3]], assumptions=float()) with self.assertRaises(InvalidArgumentTypeError): sat_all([[1, 2, 3]], assumptions=float())
def test_passing_list_of_non_ints_assumptions(self): """Test an error is raised when non-ints are passed in assumptions.""" with self.assertRaises(InvalidArgumentTypeError): sat_one([[1, -2], [-1]], assumptions=[1, 'string']) with self.assertRaises(InvalidArgumentTypeError): sat_all([[1, -2], [-1]], assumptions=[1, 'string'])
def test_passing_zeroes_inner_list_clauses(self): """Test an error is raised when zeroes are passed in clauses.""" with self.assertRaises(InvalidArgumentValueError): sat_one([[1, 0, 3], [2], [3]]) with self.assertRaises(InvalidArgumentValueError): sat_all([[1, 0, 3], [2], [3]])
def test_passing_non_int_inner_list_clauses(self): """Test an error is raised when passing a non-int in clauses.""" with self.assertRaises(InvalidArgumentTypeError): sat_one([[1, 2, 3], [-2], ['string']]) with self.assertRaises(InvalidArgumentTypeError): sat_all([[1, 2, 3], [-2], ['string']])
def test_passing_empty_inner_list_clauses(self): """Test an error is raised when passing an empty list of clauses.""" with self.assertRaises(InvalidArgumentValueError): sat_one([[1], [], [2, 3]]) with self.assertRaises(InvalidArgumentValueError): sat_all([[1], [], [2, 3]])
def sat_one(self): """Find a combination of inputs that satisfies this expression. Under the hood, this method is using the functionality exposed in tt's :mod:`satisfiability.picosat <tt.satisfiability.picosat>` module. Here's a simple example of satisfying an expression:: >>> from tt import BooleanExpression >>> b = BooleanExpression('A xor 1') >>> b.sat_one() <BooleanValues [A=0]> Don't forget about the utility provided by the :func:`constrain` context manager:: >>> b = BooleanExpression('(A nand B) iff C') >>> with b.constrain(A=1, C=1): ... b.sat_one() ... <BooleanValues [A=1, B=0, C=1]> Finally, here's an example when the expression cannot be satisfied:: >>> with BooleanExpression('A xor 1').constrain(A=1) as b: ... b.sat_one() is None ... True :returns: :func:`namedtuple <python:collections.namedtuple>`-like object representing a satisfying set of values (see :func:`boolean_variables_factory \ <tt.definitions.operands.boolean_variables_factory>` for more information about the type of object returned); ``None`` will be returned if no satisfiable set of inputs exists. :rtype: :func:`namedtuple <python:collections.namedtuple>`-like object or ``None`` :raises NoEvaluationVariationError: If this is an expression of only constants. """ if not self._symbols: raise NoEvaluationVariationError( 'Cannot attempt to satisfy an expression of only constants') if not (self._symbol_set - self._constrained_symbol_set): # shortcut if all symbols are constrained if self.evaluate_unchecked(**self._constraints): return self._symbol_vals_factory(**self._constraints) else: return None clauses, assumptions, symbol_to_index_map, index_to_symbol_map = \ self._to_picosat_clauses_assumptions_and_symbol_mappings() if not assumptions: # cannot pass empty list of assumptions to picosat assumptions = None picosat_result = picosat.sat_one(clauses, assumptions=assumptions) if picosat_result is None: return None result_dict = self._picosat_result_as_dict(picosat_result, symbol_to_index_map, index_to_symbol_map) return self._symbol_vals_factory(**result_dict)
def test_sat_one_assumptions_not_in_clauses(self): """Test that assumptions not in clauses are still in the solution.""" self.assertEqual([1, -2, 3, 4], sat_one([[1, 2], [-2]], assumptions=[3, 4]))
def test_sat_one_multi_clauses_assumptions_not_satisfiable(self): """Test multi-clause sat with assumptions without a solution.""" self.assertEqual(None, sat_one([[-1, -2], [1]], [2]))
def test_sat_one_multi_clauses_assumptions_satisfiable(self): """Test multi-clause sat with assumptions with a single solution.""" self.assertEqual([-1, 2, 3], sat_one([[1, 2, 3], [-1, 2, 3]], [-1]))
def test_sat_one_multi_clauses_not_satisfiable(self): """Test multi-clause satisfiability without a solution.""" self.assertEqual(None, sat_one([[1, 2], [-1, 2], [-2]]))
def test_sat_one_multi_clauses_satisfiable(self): """Test multi-clause satisfiability with a single solution.""" self.assertEqual([-1, 2, 3], sat_one([[1, 2, 3], [-1, 2], [3]]))
def test_sat_one_assumptions_single_clause_not_satisfiable(self): """Assert no solution found for an infeasible assumption.""" self.assertEqual(None, sat_one([[1]], [-1])) self.assertEqual(None, sat_one([[-1]], [1]))
def test_sat_one_single_clause_assumptions_satisfiable(self): """Test a single clause with assumptions, ensuring solution found.""" self.assertEqual([1, 2, -3, -4], sat_one([[1, 4], [2, 3], [1, -3], [-3]], assumptions=[-4]))
def test_sat_one_single_clause_satisfiable(self): """Test a single clause and look for a satisfiable solution.""" self.assertEqual([1], sat_one([[1]]))