def test_axiom_to_pcnf(self): a = Symbol.Predicate('A', ['x']) b = Symbol.Predicate('B', ['y']) c = Symbol.Predicate('C', ['z']) # Simple test of disjunction over conjunction axi_one = Axiom.Axiom(Quantifier.Universal(['x', 'y', 'z'], a | b & c)) axi_one = axi_one.to_pcnf() self.assertEqual('∀(z,y,x)[((A(z) | B(y)) & (A(z) | C(x)))]', repr(axi_one)) # Simple sanity check, it's already FF-PCNF axi_two = Axiom.Axiom( Quantifier.Universal(['x', 'y', 'z'], (a | b) & c)) axi_two = axi_two.to_pcnf() self.assertEqual('∀(z,y,x)[(C(x) & (A(z) | B(y)))]', repr(axi_two)) # Sanity check we remove functions c = Symbol.Predicate('C', ['z', Symbol.Function('F', ['z'])]) axi_three = Axiom.Axiom( Quantifier.Universal(['x', 'y', 'z'], a | b & c)) axi_three = axi_three.to_pcnf() self.assertEqual( '∀(z,y,x,w)[((A(z) | C(x,w)) & (A(z) | F(x,w)) & (A(z) | B(y)))]', repr(axi_three))
def test_axiom_connecive_rescoping(self): a = Symbol.Predicate('A', ['x']) b = Symbol.Predicate('B', ['y']) universal = Quantifier.Universal(['x'], a) existential = Quantifier.Existential(['y'], b) conjunction = universal & existential disjunction = universal | existential # Ensure we handle single quantifier case self.assertEqual(repr((universal & b).rescope()), '∀(x)[(A(x) & B(y))]') self.assertEqual(repr((existential & a).rescope()), '∃(y)[(B(y) & A(x))]') self.assertEqual(repr((universal | b).rescope()), '∀(x)[(A(x) | B(y))]') self.assertEqual(repr((existential | a).rescope()), '∃(y)[(B(y) | A(x))]') # Ensure we catch error condition where lookahead is needed self.assertRaises(ValueError, (existential | universal).rescope) # Ensure that we can promote Universals when a conjunction lives above us top = a & disjunction self.assertEqual(repr(disjunction.rescope(top)), '∀(x)[∃(y)[(A(x) | B(y))]]') # Ensure that we can promote Existentials when a conjunction lives above us top = a | conjunction self.assertEqual(repr(conjunction.rescope(top)), '∃(y)[∀(x)[(B(y) & A(x))]]')
def test_cnf_negation(self): ''' Ensure we can get into conjunctive normal form ''' alpha = Symbol.Predicate('A', ['x']) beta = Symbol.Predicate('B', ['y']) delta = Symbol.Predicate('D', ['z']) s = ~(Quantifier.Universal(['x', 'y', 'z'], (~(alpha | beta) & delta))) self.assertEqual(repr(s.push_complete()), "∃(x,y,z)[(A(x) | B(y) | ~D(z))]") s = ~(Quantifier.Universal(['x', 'y', 'z'], ~((alpha | beta) & delta))) self.assertEqual(repr(s.push_complete()), "∃(x,y,z)[((A(x) | B(y)) & D(z))]") s = ~((~alpha | ~beta) & ~delta) self.assertEqual(repr(s.push_complete()), "((A(x) & B(y)) | D(z))") ## Test to make sure the recursino into nested stuff actually work s = (~~~~~~~~~alpha).push_complete() self.assertEqual(repr(s), '~A(x)') s = (~~~~~~~~alpha).push_complete() self.assertEqual(repr(s), 'A(x)')
def test_cnf_quantifier_simplfy(self): alpha = Symbol.Predicate('A', ['x']) uni_one = Quantifier.Universal(['x'], alpha) mixer = uni_one | alpha uni_two = Quantifier.Universal(['y'], mixer) self.assertEqual(repr(uni_two), "∀(y)[(∀(x)[A(x)] | A(x))]") self.assertEqual(repr(uni_two.simplify()), "∀(y,x)[(A(x) | A(x))]")
def test_axiom_function_replacement(self): f = Symbol.Function('f', ['x']) t = Symbol.Function('t', ['y']) a = Symbol.Predicate('A', [f]) b = Symbol.Predicate('B', [f, t]) axi = Axiom.Axiom(Quantifier.Universal(['x'], a | a & a)) self.assertEqual(repr(axi), '∀(x)[(A(f(x)) | (A(f(x)) & A(f(x))))]') axi = Axiom.Axiom(Quantifier.Universal(['x', 'y'], b)) self.assertEqual( repr(axi.substitute_functions()), '∀(x,y)[∀(t1)[(∀(f1)[(B(f1,t1) & f(x,f1))] & t(y,t1))]]')
def substitute_function(self): ''' Find a function that's nested and replace it by adding a new variable and term ''' # TODO This a dirty hack because cyclic imports are painful import macleod.logical.Quantifier as Quantifier if not self.has_functions(): return self for idx, var in enumerate(self.variables): if isinstance(var, Function): function = var pos = idx # TODO Get a global variable singleton n_var = function.name.lower()[0] + '1' f_vars = copy.deepcopy(function.variables) f_vars.append(n_var) n_pred = Predicate(function.name, f_vars) e_vars = copy.deepcopy(self.variables) e_vars[pos] = n_var e_pred = Predicate(self.name, e_vars) return Quantifier.Universal([n_var], [e_pred & n_pred]), None
def test_axiom_variable_standardize(self): a = Symbol.Predicate('A', ['x']) b = Symbol.Predicate('B', ['y', 'x']) c = Symbol.Predicate('C', ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) axi = Axiom.Axiom(Quantifier.Universal(['x'], a | a & a)) self.assertEqual(repr(axi.standardize_variables()), '∀(z)[(A(z) | (A(z) & A(z)))]') axi = Axiom.Axiom(Quantifier.Universal(['x', 'y'], b)) self.assertEqual(repr(axi.standardize_variables()), '∀(z,y)[B(y,z)]') axi = Axiom.Axiom( Quantifier.Existential( ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], c)) self.assertEqual(repr(axi.standardize_variables()), '∃(z,y,x,w,v,u,t,s,r)[C(z,y,x,w,v,u,t,s,r)]')
def test_quantifiers(self): alpha = Symbol.Predicate('A', ['x']) beta = Symbol.Predicate('B', ['y']) delta = Symbol.Predicate('D', ['z']) uni = Quantifier.Universal(['x', 'y', 'z'], alpha | beta | delta) exi = Quantifier.Existential(['x', 'y', 'z'], alpha & beta & delta) self.assertEqual(repr(uni), "∀(x,y,z)[(A(x) | B(y) | D(z))]") self.assertEqual(repr(exi), "∃(x,y,z)[(A(x) & B(y) & D(z))]") self.assertEqual(repr(~uni), "~∀(x,y,z)[(A(x) | B(y) | D(z))]") self.assertEqual(repr(~exi), "~∃(x,y,z)[(A(x) & B(y) & D(z))]") self.assertEqual(repr((~uni).push()), "∃(x,y,z)[~(A(x) | B(y) | D(z))]") self.assertEqual(repr((~exi).push()), "∀(x,y,z)[~(A(x) & B(y) & D(z))]")
def test_axiom_quantifier_coalesence(self): a = Symbol.Predicate('A', ['x']) b = Symbol.Predicate('B', ['y']) universal = Quantifier.Universal(['x'], a) universal_two = Quantifier.Universal(['y'], b) existential = Quantifier.Existential(['y'], b) existential_two = Quantifier.Existential(['x'], a) # Coalescence over conjunction should merge Universals conjunction = universal & universal_two & existential & existential_two self.assertEqual(repr(conjunction.coalesce()), '(∃(y)[B(y)] & ∃(x)[A(x)] & ∀(x)[(B(x) & A(x))])') # Coalescence over disjunction should merge Existentials disjunction = universal | universal_two | existential | existential_two self.assertEqual(repr(disjunction.coalesce()), '(∀(x)[A(x)] | ∀(y)[B(y)] | ∃(y)[(A(y) | B(y))])')
def push(self): ''' Push negation inwards and apply to all children ''' # Can be a conjunction or disjunction # Can be a single predicate # Can be a quantifier if isinstance(self.term(), Connective.Conjunction): ret = Connective.Disjunction( [Negation(x) for x in self.term().get_term()]) elif isinstance(self.term(), Connective.Disjunction): ret = Connective.Conjunction( [Negation(x) for x in self.term().get_term()]) elif isinstance(self.term(), Symbol.Predicate): ret = self elif isinstance(self.term(), Quantifier.Existential): ret = Quantifier.Universal(self.term().variables, Negation(self.term().get_term())) elif isinstance(self.term(), Quantifier.Universal): ret = Quantifier.Existential(self.term().variables, Negation(self.term().get_term())) elif isinstance(self.term(), Negation): ret = self.term().term() else: raise ValueError("Negation onto unknown type!", self.term) return copy.deepcopy(ret)
def coalesce(self): ''' Coalesce or merge any like quantifiers held as terms of this connective. Disjunctions will coalesce existentials and will merge universals. ''' obj = copy.deepcopy(self) existentials = [ x for x in obj.terms if isinstance(x, Quantifier.Existential) ] universals = [ x for x in obj.terms if isinstance(x, Quantifier.Universal) ] if len(existentials) != 0: LOGGER.debug("Coalescing existentials") new_existential = functools.reduce(lambda x, y: x.coalesce(y), existentials) if len(existentials) == len(obj.terms): return new_existential else: for term in existentials: obj.remove_term(term) obj.set_term(new_existential) return obj elif len(universals) != 0 and len(existentials) == 0: LOGGER.debug("Merging nested Universals") variables = [] terms = [] for quant in universals: variables += quant.variables terms.append(quant.terms[0]) for term in obj.terms: LOGGER.debug("Adding term: " + repr(term)) if term not in universals: terms.append(term) terms = [type(self)(terms)] return Quantifier.Universal(variables, terms) else: return obj
def test_onf_detection(self): alpha = Symbol.Predicate('A', ['x']) beta = Symbol.Predicate('B', ['y']) delta = Symbol.Predicate('D', ['z']) uni = Quantifier.Universal(['x', 'y', 'z'], alpha | beta | delta) exi = Quantifier.Existential(['x', 'y', 'z'], alpha & beta | delta) self.assertEqual(alpha.is_onf(), True) self.assertEqual((alpha | beta).is_onf(), True) self.assertEqual((alpha & beta).is_onf(), True) self.assertEqual((alpha | (beta & delta)).is_onf(), False) self.assertEqual((alpha & (beta | delta)).is_onf(), True) self.assertEqual((~(alpha | beta)).is_onf(), False) self.assertEqual((~(alpha & beta)).is_onf(), False) self.assertEqual(uni.is_onf(), True) self.assertEqual(exi.is_onf(), False) # Note that is_onf() is not a recursive call, it's a top level feature # If will actually if you need an ONF axiom then create a Logical.Axiom and to_onf() self.assertEqual((alpha & (alpha | (beta & delta)) & delta).is_onf(), True)
def test_cnf_quantifier_scoping(self): alpha = Symbol.Predicate('A', ['x']) beta = Symbol.Predicate('B', ['y']) delta = Symbol.Predicate('D', ['z']) uni_one = Quantifier.Universal(['x', 'y', 'z'], alpha | beta | delta) exi_one = Quantifier.Existential(['x', 'y', 'z'], alpha & beta | delta) term = exi_one | beta term_two = exi_one | beta | uni_one self.assertEqual( repr(Quantifier.Universal(['x', 'y', 'z'], uni_one).simplify()), "∀(x,y,z)[(A(x) | B(y) | D(z))]") self.assertEqual( repr(Quantifier.Universal(['x', 'y', 'z'], term_two).simplify()), "∀(x,y,z)[(∃(x,y,z)[((A(x) & B(y)) | D(z))] | B(y) | (A(x) | B(y) | D(z)))]" ) self.assertEqual( repr( Quantifier.Universal(['x', 'y', 'z'], term_two).simplify().rescope()), "∀(x,y,z)[∃(x,y,z)[(B(y) | (A(x) | B(y) | D(z)) | ((A(x) & B(y)) | D(z)))]]" )
def coalesce(self): ''' Coalesce or merge any like quantifiers held as terms of this connective. Conjunctions will coalesce universals and will merge existentials. precondition: Must not have both universals and existentials ''' obj = copy.deepcopy(self) LOGGER.debug("Attempting to coalesce: " + repr(obj)) universals = [ x for x in obj.terms if isinstance(x, Quantifier.Universal) ] existentials = [ x for x in obj.terms if isinstance(x, Quantifier.Existential) ] if len(universals) != 0: LOGGER.debug("Coalescing Universals") new_universal = functools.reduce(lambda x, y: x.coalesce(y), universals) LOGGER.debug("Coalesced Universal: " + repr(new_universal)) if len(universals) == len(obj.terms): LOGGER.debug("Returning plain: " + repr(new_universal)) return new_universal else: for term in universals: obj.remove_term(term) obj.set_term(new_universal) LOGGER.debug("Returning added: " + repr(obj)) return obj elif len(existentials) != 0 and len(universals) == 0: LOGGER.debug("Merging nested existentials") variables = [] terms = [] for quant in existentials: variables += quant.variables terms.append(quant.terms[0]) for term in obj.terms: if term not in existentials: terms.append(term) terms = [type(self)(terms)] return Quantifier.Existential(variables, terms) else: return obj
def p_universal(p): """ universal : LPAREN FORALL LPAREN nonlogicals RPAREN axiom RPAREN """ p[0] = Quantifier.Universal(p[4], p[6])
def p_existential(p): """ existential : LPAREN EXISTS LPAREN nonlogicals RPAREN axiom RPAREN """ p[0] = Quantifier.Existential(p[4], p[6])