Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

# Natural Language Toolkit: Nonmonotonic Reasoning 

# 

# Author: Daniel H. Garrette <dhgarrette@gmail.com> 

# 

# Copyright (C) 2001-2012 NLTK Project 

# URL: <http://www.nltk.org> 

# For license information, see LICENSE.TXT 

 

""" 

A module to perform nonmonotonic reasoning.  The ideas and demonstrations in 

this module are based on "Logical Foundations of Artificial Intelligence" by 

Michael R. Genesereth and Nils J. Nilsson. 

""" 

from __future__ import print_function 

 

from .prover9 import Prover9, Prover9Command 

from collections import defaultdict 

from functools import reduce 

 

from nltk.sem.logic import (VariableExpression, EqualityExpression, 

                            ApplicationExpression, LogicParser, 

                            AbstractVariableExpression, AllExpression, 

                            BooleanExpression, NegatedExpression, 

                            ExistsExpression, Variable, ImpExpression, 

                            AndExpression, unique_variable, operator) 

 

from nltk.inference.api import Prover, ProverCommandDecorator 

 

class ProverParseError(Exception): pass 

 

def get_domain(goal, assumptions): 

    if goal is None: 

        all_expressions = assumptions 

    else: 

        all_expressions = assumptions + [-goal] 

    return reduce(operator.or_, (a.constants() for a in all_expressions), set()) 

 

class ClosedDomainProver(ProverCommandDecorator): 

    """ 

    This is a prover decorator that adds domain closure assumptions before 

    proving. 

    """ 

    def assumptions(self): 

        assumptions = [a for a in self._command.assumptions()] 

        goal = self._command.goal() 

        domain = get_domain(goal, assumptions) 

        return list([self.replace_quants(ex, domain) for ex in assumptions]) 

 

    def goal(self): 

        goal = self._command.goal() 

        domain = get_domain(goal, self._command.assumptions()) 

        return self.replace_quants(goal, domain) 

 

    def replace_quants(self, ex, domain): 

        """ 

        Apply the closed domain assumption to the expression 

         - Domain = union([e.free()|e.constants() for e in all_expressions]) 

         - translate "exists x.P" to "(z=d1 | z=d2 | ... ) & P.replace(x,z)" OR 

                     "P.replace(x, d1) | P.replace(x, d2) | ..." 

         - translate "all x.P" to "P.replace(x, d1) & P.replace(x, d2) & ..." 

        :param ex: ``Expression`` 

        :param domain: set of {Variable}s 

        :return: ``Expression`` 

        """ 

        if isinstance(ex, AllExpression): 

            conjuncts = [ex.term.replace(ex.variable, VariableExpression(d)) 

                         for d in domain] 

            conjuncts = [self.replace_quants(c, domain) for c in conjuncts] 

            return reduce(lambda x,y: x&y, conjuncts) 

        elif isinstance(ex, BooleanExpression): 

            return ex.__class__(self.replace_quants(ex.first, domain), 

                                self.replace_quants(ex.second, domain) ) 

        elif isinstance(ex, NegatedExpression): 

            return -self.replace_quants(ex.term, domain) 

        elif isinstance(ex, ExistsExpression): 

            disjuncts = [ex.term.replace(ex.variable, VariableExpression(d)) 

                         for d in domain] 

            disjuncts = [self.replace_quants(d, domain) for d in disjuncts] 

            return reduce(lambda x,y: x|y, disjuncts) 

        else: 

            return ex 

 

class UniqueNamesProver(ProverCommandDecorator): 

    """ 

    This is a prover decorator that adds unique names assumptions before 

    proving. 

    """ 

    def assumptions(self): 

        """ 

         - Domain = union([e.free()|e.constants() for e in all_expressions]) 

         - if "d1 = d2" cannot be proven from the premises, then add "d1 != d2" 

        """ 

        assumptions = self._command.assumptions() 

 

        domain = list(get_domain(self._command.goal(), assumptions)) 

 

        #build a dictionary of obvious equalities 

        eq_sets = SetHolder() 

        for a in assumptions: 

            if isinstance(a, EqualityExpression): 

                av = a.first.variable 

                bv = a.second.variable 

                #put 'a' and 'b' in the same set 

                eq_sets[av].add(bv) 

 

        new_assumptions = [] 

        for i,a in enumerate(domain): 

            for b in domain[i+1:]: 

                #if a and b are not already in the same equality set 

                if b not in eq_sets[a]: 

                    newEqEx = EqualityExpression(VariableExpression(a), 

                                                 VariableExpression(b)) 

                    if Prover9().prove(newEqEx, assumptions): 

                        #we can prove that the names are the same entity. 

                        #remember that they are equal so we don't re-check. 

                        eq_sets[a].add(b) 

                    else: 

                        #we can't prove it, so assume unique names 

                        new_assumptions.append(-newEqEx) 

 

        return assumptions + new_assumptions 

 

class SetHolder(list): 

    """ 

    A list of sets of Variables. 

    """ 

    def __getitem__(self, item): 

        """ 

        :param item: ``Variable`` 

        :return: the set containing 'item' 

        """ 

        assert isinstance(item, Variable) 

        for s in self: 

            if item in s: 

                return s 

        #item is not found in any existing set.  so create a new set 

        new = set([item]) 

        self.append(new) 

        return new 

 

class ClosedWorldProver(ProverCommandDecorator): 

    """ 

    This is a prover decorator that completes predicates before proving. 

 

    If the assumptions contain "P(A)", then "all x.(P(x) -> (x=A))" is the completion of "P". 

    If the assumptions contain "all x.(ostrich(x) -> bird(x))", then "all x.(bird(x) -> ostrich(x))" is the completion of "bird". 

    If the assumptions don't contain anything that are "P", then "all x.-P(x)" is the completion of "P". 

 

    walk(Socrates) 

    Socrates != Bill 

    + all x.(walk(x) -> (x=Socrates)) 

    ---------------- 

    -walk(Bill) 

 

    see(Socrates, John) 

    see(John, Mary) 

    Socrates != John 

    John != Mary 

    + all x.all y.(see(x,y) -> ((x=Socrates & y=John) | (x=John & y=Mary))) 

    ---------------- 

    -see(Socrates, Mary) 

 

    all x.(ostrich(x) -> bird(x)) 

    bird(Tweety) 

    -ostrich(Sam) 

    Sam != Tweety 

    + all x.(bird(x) -> (ostrich(x) | x=Tweety)) 

    + all x.-ostrich(x) 

    ------------------- 

    -bird(Sam) 

    """ 

    def assumptions(self): 

        assumptions = self._command.assumptions() 

 

        predicates = self._make_predicate_dict(assumptions) 

 

        new_assumptions = [] 

        for p in predicates: 

            predHolder = predicates[p] 

            new_sig = self._make_unique_signature(predHolder) 

            new_sig_exs = [VariableExpression(v) for v in new_sig] 

 

            disjuncts = [] 

 

            #Turn the signatures into disjuncts 

            for sig in predHolder.signatures: 

                equality_exs = [] 

                for v1,v2 in zip(new_sig_exs, sig): 

                    equality_exs.append(EqualityExpression(v1,v2)) 

                disjuncts.append(reduce(lambda x,y: x&y, equality_exs)) 

 

            #Turn the properties into disjuncts 

            for prop in predHolder.properties: 

                #replace variables from the signature with new sig variables 

                bindings = {} 

                for v1,v2 in zip(new_sig_exs, prop[0]): 

                    bindings[v2] = v1 

                disjuncts.append(prop[1].substitute_bindings(bindings)) 

 

            #make the assumption 

            if disjuncts: 

                #disjuncts exist, so make an implication 

                antecedent = self._make_antecedent(p, new_sig) 

                consequent = reduce(lambda x,y: x|y, disjuncts) 

                accum = ImpExpression(antecedent, consequent) 

            else: 

                #nothing has property 'p' 

                accum = NegatedExpression(self._make_antecedent(p, new_sig)) 

 

            #quantify the implication 

            for new_sig_var in new_sig[::-1]: 

                accum = AllExpression(new_sig_var, accum) 

            new_assumptions.append(accum) 

 

        return assumptions + new_assumptions 

 

    def _make_unique_signature(self, predHolder): 

        """ 

        This method figures out how many arguments the predicate takes and 

        returns a tuple containing that number of unique variables. 

        """ 

        return tuple([unique_variable() 

                      for i in range(predHolder.signature_len)]) 

 

    def _make_antecedent(self, predicate, signature): 

        """ 

        Return an application expression with 'predicate' as the predicate 

        and 'signature' as the list of arguments. 

        """ 

        antecedent = predicate 

        for v in signature: 

            antecedent = antecedent(VariableExpression(v)) 

        return antecedent 

 

    def _make_predicate_dict(self, assumptions): 

        """ 

        Create a dictionary of predicates from the assumptions. 

 

        :param assumptions: a list of ``Expression``s 

        :return: dict mapping ``AbstractVariableExpression`` to ``PredHolder`` 

        """ 

        predicates = defaultdict(PredHolder) 

        for a in assumptions: 

            self._map_predicates(a, predicates) 

        return predicates 

 

    def _map_predicates(self, expression, predDict): 

        if isinstance(expression, ApplicationExpression): 

            func, args = expression.uncurry() 

            if isinstance(func, AbstractVariableExpression): 

                predDict[func].append_sig(tuple(args)) 

        elif isinstance(expression, AndExpression): 

            self._map_predicates(expression.first, predDict) 

            self._map_predicates(expression.second, predDict) 

        elif isinstance(expression, AllExpression): 

            #collect all the universally quantified variables 

            sig = [expression.variable] 

            term = expression.term 

            while isinstance(term, AllExpression): 

                sig.append(term.variable) 

                term = term.term 

            if isinstance(term, ImpExpression): 

                if isinstance(term.first, ApplicationExpression) and \ 

                   isinstance(term.second, ApplicationExpression): 

                    func1, args1 = term.first.uncurry() 

                    func2, args2 = term.second.uncurry() 

                    if isinstance(func1, AbstractVariableExpression) and \ 

                       isinstance(func2, AbstractVariableExpression) and \ 

                       sig == [v.variable for v in args1] and \ 

                       sig == [v.variable for v in args2]: 

                        predDict[func2].append_prop((tuple(sig), term.first)) 

                        predDict[func1].validate_sig_len(sig) 

 

class PredHolder(object): 

    """ 

    This class will be used by a dictionary that will store information 

    about predicates to be used by the ``ClosedWorldProver``. 

 

    The 'signatures' property is a list of tuples defining signatures for 

    which the predicate is true.  For instance, 'see(john, mary)' would be 

    result in the signature '(john,mary)' for 'see'. 

 

    The second element of the pair is a list of pairs such that the first 

    element of the pair is a tuple of variables and the second element is an 

    expression of those variables that makes the predicate true.  For instance, 

    'all x.all y.(see(x,y) -> know(x,y))' would result in "((x,y),('see(x,y)'))" 

    for 'know'. 

    """ 

    def __init__(self): 

        self.signatures = [] 

        self.properties = [] 

        self.signature_len = None 

 

    def append_sig(self, new_sig): 

        self.validate_sig_len(new_sig) 

        self.signatures.append(new_sig) 

 

    def append_prop(self, new_prop): 

        self.validate_sig_len(new_prop[0]) 

        self.properties.append(new_prop) 

 

    def validate_sig_len(self, new_sig): 

        if self.signature_len is None: 

            self.signature_len = len(new_sig) 

        elif self.signature_len != len(new_sig): 

            raise Exception("Signature lengths do not match") 

 

    def __str__(self): 

        return '(%s,%s,%s)' % (self.signatures, self.properties, 

                               self.signature_len) 

 

    def __repr__(self): 

        return str(self) 

 

def closed_domain_demo(): 

    lp = LogicParser() 

 

    p1 = lp.parse(r'exists x.walk(x)') 

    p2 = lp.parse(r'man(Socrates)') 

    c = lp.parse(r'walk(Socrates)') 

    prover = Prover9Command(c, [p1,p2]) 

    print(prover.prove()) 

    cdp = ClosedDomainProver(prover) 

    print('assumptions:') 

    for a in cdp.assumptions(): print('   ', a) 

    print('goal:', cdp.goal()) 

    print(cdp.prove()) 

 

    p1 = lp.parse(r'exists x.walk(x)') 

    p2 = lp.parse(r'man(Socrates)') 

    p3 = lp.parse(r'-walk(Bill)') 

    c = lp.parse(r'walk(Socrates)') 

    prover = Prover9Command(c, [p1,p2,p3]) 

    print(prover.prove()) 

    cdp = ClosedDomainProver(prover) 

    print('assumptions:') 

    for a in cdp.assumptions(): print('   ', a) 

    print('goal:', cdp.goal()) 

    print(cdp.prove()) 

 

    p1 = lp.parse(r'exists x.walk(x)') 

    p2 = lp.parse(r'man(Socrates)') 

    p3 = lp.parse(r'-walk(Bill)') 

    c = lp.parse(r'walk(Socrates)') 

    prover = Prover9Command(c, [p1,p2,p3]) 

    print(prover.prove()) 

    cdp = ClosedDomainProver(prover) 

    print('assumptions:') 

    for a in cdp.assumptions(): print('   ', a) 

    print('goal:', cdp.goal()) 

    print(cdp.prove()) 

 

    p1 = lp.parse(r'walk(Socrates)') 

    p2 = lp.parse(r'walk(Bill)') 

    c = lp.parse(r'all x.walk(x)') 

    prover = Prover9Command(c, [p1,p2]) 

    print(prover.prove()) 

    cdp = ClosedDomainProver(prover) 

    print('assumptions:') 

    for a in cdp.assumptions(): print('   ', a) 

    print('goal:', cdp.goal()) 

    print(cdp.prove()) 

 

    p1 = lp.parse(r'girl(mary)') 

    p2 = lp.parse(r'dog(rover)') 

    p3 = lp.parse(r'all x.(girl(x) -> -dog(x))') 

    p4 = lp.parse(r'all x.(dog(x) -> -girl(x))') 

    p5 = lp.parse(r'chase(mary, rover)') 

    c = lp.parse(r'exists y.(dog(y) & all x.(girl(x) -> chase(x,y)))') 

    prover = Prover9Command(c, [p1,p2,p3,p4,p5]) 

    print(prover.prove()) 

    cdp = ClosedDomainProver(prover) 

    print('assumptions:') 

    for a in cdp.assumptions(): print('   ', a) 

    print('goal:', cdp.goal()) 

    print(cdp.prove()) 

 

def unique_names_demo(): 

    lp = LogicParser() 

 

    p1 = lp.parse(r'man(Socrates)') 

    p2 = lp.parse(r'man(Bill)') 

    c = lp.parse(r'exists x.exists y.(x != y)') 

    prover = Prover9Command(c, [p1,p2]) 

    print(prover.prove()) 

    unp = UniqueNamesProver(prover) 

    print('assumptions:') 

    for a in unp.assumptions(): print('   ', a) 

    print('goal:', unp.goal()) 

    print(unp.prove()) 

 

    p1 = lp.parse(r'all x.(walk(x) -> (x = Socrates))') 

    p2 = lp.parse(r'Bill = William') 

    p3 = lp.parse(r'Bill = Billy') 

    c = lp.parse(r'-walk(William)') 

    prover = Prover9Command(c, [p1,p2,p3]) 

    print(prover.prove()) 

    unp = UniqueNamesProver(prover) 

    print('assumptions:') 

    for a in unp.assumptions(): print('   ', a) 

    print('goal:', unp.goal()) 

    print(unp.prove()) 

 

def closed_world_demo(): 

    lp = LogicParser() 

 

    p1 = lp.parse(r'walk(Socrates)') 

    p2 = lp.parse(r'(Socrates != Bill)') 

    c = lp.parse(r'-walk(Bill)') 

    prover = Prover9Command(c, [p1,p2]) 

    print(prover.prove()) 

    cwp = ClosedWorldProver(prover) 

    print('assumptions:') 

    for a in cwp.assumptions(): print('   ', a) 

    print('goal:', cwp.goal()) 

    print(cwp.prove()) 

 

    p1 = lp.parse(r'see(Socrates, John)') 

    p2 = lp.parse(r'see(John, Mary)') 

    p3 = lp.parse(r'(Socrates != John)') 

    p4 = lp.parse(r'(John != Mary)') 

    c = lp.parse(r'-see(Socrates, Mary)') 

    prover = Prover9Command(c, [p1,p2,p3,p4]) 

    print(prover.prove()) 

    cwp = ClosedWorldProver(prover) 

    print('assumptions:') 

    for a in cwp.assumptions(): print('   ', a) 

    print('goal:', cwp.goal()) 

    print(cwp.prove()) 

 

    p1 = lp.parse(r'all x.(ostrich(x) -> bird(x))') 

    p2 = lp.parse(r'bird(Tweety)') 

    p3 = lp.parse(r'-ostrich(Sam)') 

    p4 = lp.parse(r'Sam != Tweety') 

    c = lp.parse(r'-bird(Sam)') 

    prover = Prover9Command(c, [p1,p2,p3,p4]) 

    print(prover.prove()) 

    cwp = ClosedWorldProver(prover) 

    print('assumptions:') 

    for a in cwp.assumptions(): print('   ', a) 

    print('goal:', cwp.goal()) 

    print(cwp.prove()) 

 

def combination_prover_demo(): 

    lp = LogicParser() 

 

    p1 = lp.parse(r'see(Socrates, John)') 

    p2 = lp.parse(r'see(John, Mary)') 

    c = lp.parse(r'-see(Socrates, Mary)') 

    prover = Prover9Command(c, [p1,p2]) 

    print(prover.prove()) 

    command = ClosedDomainProver( 

                  UniqueNamesProver( 

                      ClosedWorldProver(prover))) 

    for a in command.assumptions(): print(a) 

    print(command.prove()) 

 

def default_reasoning_demo(): 

    lp = LogicParser() 

 

    premises = [] 

 

    #define taxonomy 

    premises.append(lp.parse(r'all x.(elephant(x)        -> animal(x))')) 

    premises.append(lp.parse(r'all x.(bird(x)            -> animal(x))')) 

    premises.append(lp.parse(r'all x.(dove(x)            -> bird(x))')) 

    premises.append(lp.parse(r'all x.(ostrich(x)         -> bird(x))')) 

    premises.append(lp.parse(r'all x.(flying_ostrich(x)  -> ostrich(x))')) 

 

    #default properties 

    premises.append(lp.parse(r'all x.((animal(x)  & -Ab1(x)) -> -fly(x))')) #normal animals don't fly 

    premises.append(lp.parse(r'all x.((bird(x)    & -Ab2(x)) -> fly(x))')) #normal birds fly 

    premises.append(lp.parse(r'all x.((ostrich(x) & -Ab3(x)) -> -fly(x))')) #normal ostriches don't fly 

 

    #specify abnormal entities 

    premises.append(lp.parse(r'all x.(bird(x)           -> Ab1(x))')) #flight 

    premises.append(lp.parse(r'all x.(ostrich(x)        -> Ab2(x))')) #non-flying bird 

    premises.append(lp.parse(r'all x.(flying_ostrich(x) -> Ab3(x))')) #flying ostrich 

 

    #define entities 

    premises.append(lp.parse(r'elephant(E)')) 

    premises.append(lp.parse(r'dove(D)')) 

    premises.append(lp.parse(r'ostrich(O)')) 

 

    #print the assumptions 

    prover = Prover9Command(None, premises) 

    command = UniqueNamesProver(ClosedWorldProver(prover)) 

    for a in command.assumptions(): print(a) 

 

    print_proof('-fly(E)', premises) 

    print_proof('fly(D)', premises) 

    print_proof('-fly(O)', premises) 

 

def print_proof(goal, premises): 

    lp = LogicParser() 

    prover = Prover9Command(lp.parse(goal), premises) 

    command = UniqueNamesProver(ClosedWorldProver(prover)) 

    print(goal, prover.prove(), command.prove()) 

 

def demo(): 

    closed_domain_demo() 

    unique_names_demo() 

    closed_world_demo() 

    combination_prover_demo() 

    default_reasoning_demo() 

 

if __name__ == '__main__': 

    demo()