Hide keyboard shortcuts

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

"""Logic expressions handling 

 

NOTE 

---- 

 

at present this is mainly needed for facts.py , feel free however to improve 

this stuff for general purpose. 

""" 

from __future__ import print_function, division 

 

from sympy.core.compatibility import range 

 

 

def _torf(args): 

"""Return True if all args are True, False if they 

are all False, else None. 

 

>>> from sympy.core.logic import _torf 

>>> _torf((True, True)) 

True 

>>> _torf((False, False)) 

False 

>>> _torf((True, False)) 

""" 

sawT = sawF = False 

for a in args: 

if a is True: 

if sawF: 

return 

sawT = True 

elif a is False: 

if sawT: 

return 

sawF = True 

else: 

return 

return sawT 

 

 

def _fuzzy_group(args, quick_exit=False): 

"""Return True if all args are True, None if there is any None else False 

unless ``quick_exit`` is True (then return None as soon as a second False 

is seen. 

 

``_fuzzy_group`` is like ``fuzzy_and`` except that it is more 

conservative in returning a False, waiting to make sure that all 

arguments are True or False and returning None if any arguments are 

None. It also has the capability of permiting only a single False and 

returning None if more than one is seen. For example, the presence of a 

single transcendental amongst rationals would indicate that the group is 

no longer rational; but a second transcendental in the group would make the 

determination impossible. 

 

 

Examples 

======== 

 

>>> from sympy.core.logic import _fuzzy_group 

 

By default, multiple Falses mean the group is broken: 

 

>>> _fuzzy_group([False, False, True]) 

False 

 

If multiple Falses mean the group status is unknown then set 

`quick_exit` to True so None can be returned when the 2nd False is seen: 

 

>>> _fuzzy_group([False, False, True], quick_exit=True) 

 

But if only a single False is seen then the group is known to 

be broken: 

 

>>> _fuzzy_group([False, True, True], quick_exit=True) 

False 

 

""" 

saw_other = False 

for a in args: 

if a is True: 

continue 

if a is None: 

return 

if quick_exit and saw_other: 

return 

saw_other = True 

return not saw_other 

 

 

def fuzzy_bool(x): 

"""Return True, False or None according to x. 

 

Whereas bool(x) returns True or False, fuzzy_bool allows 

for the None value and non-false values (which become None), too. 

 

Examples 

======== 

 

>>> from sympy.core.logic import fuzzy_bool 

>>> from sympy.abc import x 

>>> fuzzy_bool(x), fuzzy_bool(None) 

(None, None) 

>>> bool(x), bool(None) 

(True, False) 

 

""" 

if x is None: 

return None 

if x in (True, False): 

return bool(x) 

 

 

def fuzzy_and(args): 

"""Return True (all True), False (any False) or None. 

 

Examples 

======== 

 

>>> from sympy.core.logic import fuzzy_and 

>>> from sympy import Dummy 

 

If you had a list of objects to test the commutivity of 

and you want the fuzzy_and logic applied, passing an 

iterator will allow the commutativity to only be computed 

as many times as necessary. With this list, False can be 

returned after analyzing the first symbol: 

 

>>> syms = [Dummy(commutative=False), Dummy()] 

>>> fuzzy_and(s.is_commutative for s in syms) 

False 

 

That False would require less work than if a list of pre-computed 

items was sent: 

 

>>> fuzzy_and([s.is_commutative for s in syms]) 

False 

""" 

 

rv = True 

for ai in args: 

ai = fuzzy_bool(ai) 

if ai is False: 

return False 

if rv: # this will stop updating if a None is ever trapped 

rv = ai 

return rv 

 

 

def fuzzy_not(v): 

""" 

Not in fuzzy logic 

 

Return None if `v` is None else `not v`. 

 

Examples 

======== 

 

>>> from sympy.core.logic import fuzzy_not 

>>> fuzzy_not(True) 

False 

>>> fuzzy_not(None) 

>>> fuzzy_not(False) 

True 

 

""" 

if v is None: 

return v 

else: 

return not v 

 

 

def fuzzy_or(args): 

""" 

Or in fuzzy logic. Returns True (any True), False (all False), or None 

 

See the docstrings of fuzzy_and and fuzzy_not for more info. fuzzy_or is 

related to the two by the standard De Morgan's law. 

 

>>> from sympy.core.logic import fuzzy_or 

>>> fuzzy_or([True, False]) 

True 

>>> fuzzy_or([True, None]) 

True 

>>> fuzzy_or([False, False]) 

False 

>>> print(fuzzy_or([False, None])) 

None 

 

""" 

return fuzzy_not(fuzzy_and(fuzzy_not(i) for i in args)) 

 

 

class Logic(object): 

"""Logical expression""" 

# {} 'op' -> LogicClass 

op_2class = {} 

 

def __new__(cls, *args): 

obj = object.__new__(cls) 

obj.args = args 

return obj 

 

def __getnewargs__(self): 

return self.args 

 

def __hash__(self): 

return hash( (type(self).__name__,) + tuple(self.args) ) 

 

def __eq__(a, b): 

if not isinstance(b, type(a)): 

return False 

else: 

return a.args == b.args 

 

def __ne__(a, b): 

if not isinstance(b, type(a)): 

return True 

else: 

return a.args != b.args 

 

def __lt__(self, other): 

if self.__cmp__(other) == -1: 

return True 

return False 

 

def __cmp__(self, other): 

if type(self) is not type(other): 

a = str(type(self)) 

b = str(type(other)) 

else: 

a = self.args 

b = other.args 

return (a > b) - (a < b) 

 

def __str__(self): 

return '%s(%s)' % (self.__class__.__name__, ', '.join(str(a) for a in self.args)) 

 

__repr__ = __str__ 

 

@staticmethod 

def fromstring(text): 

"""Logic from string with space around & and | but none after !. 

 

e.g. 

 

!a & b | c 

""" 

lexpr = None # current logical expression 

schedop = None # scheduled operation 

for term in text.split(): 

# operation symbol 

if term in '&|': 

if schedop is not None: 

raise ValueError( 

'double op forbidden: "%s %s"' % (term, schedop)) 

if lexpr is None: 

raise ValueError( 

'%s cannot be in the beginning of expression' % term) 

schedop = term 

continue 

if '&' in term or '|' in term: 

raise ValueError('& and | must have space around them') 

if term[0] == '!': 

if len(term) == 1: 

raise ValueError('do not include space after "!"') 

term = Not(term[1:]) 

 

# already scheduled operation, e.g. '&' 

if schedop: 

lexpr = Logic.op_2class[schedop](lexpr, term) 

schedop = None 

continue 

 

# this should be atom 

if lexpr is not None: 

raise ValueError( 

'missing op between "%s" and "%s"' % (lexpr, term)) 

 

lexpr = term 

 

# let's check that we ended up in correct state 

if schedop is not None: 

raise ValueError('premature end-of-expression in "%s"' % text) 

if lexpr is None: 

raise ValueError('"%s" is empty' % text) 

 

# everything looks good now 

return lexpr 

 

 

class AndOr_Base(Logic): 

 

def __new__(cls, *args): 

bargs = [] 

for a in args: 

if a == cls.op_x_notx: 

return a 

elif a == (not cls.op_x_notx): 

continue # skip this argument 

bargs.append(a) 

 

args = sorted(set(cls.flatten(bargs)), key=hash) 

 

for a in args: 

if Not(a) in args: 

return cls.op_x_notx 

 

if len(args) == 1: 

return args.pop() 

elif len(args) == 0: 

return not cls.op_x_notx 

 

return Logic.__new__(cls, *args) 

 

@classmethod 

def flatten(cls, args): 

# quick-n-dirty flattening for And and Or 

args_queue = list(args) 

res = [] 

 

while True: 

try: 

arg = args_queue.pop(0) 

except IndexError: 

break 

if isinstance(arg, Logic): 

if isinstance(arg, cls): 

args_queue.extend(arg.args) 

continue 

res.append(arg) 

 

args = tuple(res) 

return args 

 

 

class And(AndOr_Base): 

op_x_notx = False 

 

def _eval_propagate_not(self): 

# !(a&b&c ...) == !a | !b | !c ... 

return Or( *[Not(a) for a in self.args] ) 

 

# (a|b|...) & c == (a&c) | (b&c) | ... 

def expand(self): 

 

# first locate Or 

for i in range(len(self.args)): 

arg = self.args[i] 

if isinstance(arg, Or): 

arest = self.args[:i] + self.args[i + 1:] 

 

orterms = [And( *(arest + (a,)) ) for a in arg.args] 

for j in range(len(orterms)): 

if isinstance(orterms[j], Logic): 

orterms[j] = orterms[j].expand() 

 

res = Or(*orterms) 

return res 

 

else: 

return self 

 

 

class Or(AndOr_Base): 

op_x_notx = True 

 

def _eval_propagate_not(self): 

# !(a|b|c ...) == !a & !b & !c ... 

return And( *[Not(a) for a in self.args] ) 

 

 

class Not(Logic): 

 

def __new__(cls, arg): 

if isinstance(arg, str): 

return Logic.__new__(cls, arg) 

 

elif isinstance(arg, bool): 

return not arg 

elif isinstance(arg, Not): 

return arg.args[0] 

 

elif isinstance(arg, Logic): 

# XXX this is a hack to expand right from the beginning 

arg = arg._eval_propagate_not() 

return arg 

 

else: 

raise ValueError('Not: unknown argument %r' % (arg,)) 

 

@property 

def arg(self): 

return self.args[0] 

 

 

Logic.op_2class['&'] = And 

Logic.op_2class['|'] = Or 

Logic.op_2class['!'] = Not