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
|
# This program is public domain
# Author: Paul Kienzle
"""
Conditional expression manager
Conditional expressions are formed from combinations of
and (&), or (|), exclusive or (^) and not (!), as well
as primitive tests. The resulting expressions can be
evaluated on particular inputs, with the inputs passed
down to the primitive tests. Primitive tests should be
subclassed from Condition, with the __call__ method
defined to return the truth value of the condition
given the inputs.
For example, the following creates a test whether a
number is in the open interval (0,1)::
>>> class gt(Condition):
... def __init__(self, base): self.base = base
... def __call__(self, test): return test>self.base
... def __str__(self): return "x>%g"%self.base
>>> class lt(Condition):
... def __init__(self, base): self.base = base
... def __call__(self, test): return test<self.base
... def __str__(self): return "x<%g"%self.base
>>> test = lt(1) & gt(0)
>>> print(test)
(x<1 and x>0)
>>> test(0.5)
True
>>> test(1)
False
No assumptions are made about the structure of the arguments
to the primitives, but all primitives in an expression should
accept the same arguments.
The constants true and false are predefined as primitives
which can take any arguments::
>>> true()
True
>>> false("this",string="ignored")
False
You can find the individual terms of the expression using
the method primitives:
>>> (true&false).primitives() == set([true,false])
True
In many instances you not only want to know that an expression
is true or false, but why it is true or false. The status method
on expressions does this, returning not only the truth status as
a boolean, but also a list of primitives and Not(primitives)
saying which conditions are responsible. For example::
>>> class iters(Condition):
... def __init__(self, base): self.base = base
... def __call__(self, state): return state['iters']>self.base
... def __str__(self): return "iters>%d"%self.base
>>> class stepsize(Condition):
... def __init__(self, base): self.base = base
... def __call__(self, state): return state['stepsize']<self.base
... def __str__(self): return "stepsize<%g"%self.base
>>> converge = stepsize(0.001)
>>> diverge = iters(100)
>>> result,why = converge.status(dict(stepsize=21.2,iters=20))
>>> print("%s %s"%(result, ", ".join(str(c) for c in why)))
False not stepsize<0.001
>>> result,why = diverge.status(dict(stepsize=21.2,iters=129))
>>> print("%s %s"%(result, ", ".join(str(c) for c in why)))
True iters>100
Note that status will be less efficient than direct evaluation
because it has to test all branches and construct the result list.
Normally the And and Or calculations an short circuit, and only
compute what they need to guarantee the resulting truth value.
"""
class Condition(object):
"""
Condition abstract base class.
Subclasses should define __call__(self, *args, **kw)
which returns True if the condition is satisfied, and
False otherwise.
"""
def __and__(self, condition):
return And(self, condition)
def __or__(self, condition):
return Or(self, condition)
def __xor__(self, condition):
return Xor(self, condition)
def __invert__(self):
return Not(self)
def __call__(self, *args, **kw):
raise NotImplementedError
def _negate(self):
return _Bar(self)
def status(self, *args, **kw):
"""
Evaluate the condition, returning both the status and a list of
conditions. If the status is true, then the conditions are those
that contribute to the true status. If the status is false, then
the conditions are those that contribute to the false status.
"""
stat = self.__call__(*args, **kw)
if stat:
return stat, [self]
else:
return stat, [Not(self)]
def primitives(self):
"""
Return a list of terms in the condition.
"""
return set([self])
class And(Condition):
"""
True if both conditions are satisfied.
"""
def __init__(self, left, right):
self.left,self.right = left,right
def __call__(self, *args, **kw):
return self.left(*args, **kw) and self.right(*args, **kw)
def __str__(self):
return "(%s and %s)"%(self.left,self.right)
def _negate(self):
return Or(self.left._negate(),self.right._negate())
def status(self, *args, **kw):
lstat,lcond = self.left.status(*args, **kw)
rstat,rcond = self.right.status(*args, **kw)
if lstat and rstat:
return True,lcond+rcond
elif lstat:
return False,rcond
elif rstat:
return False,lcond
else:
return False,lcond+rcond
def primitives(self):
return self.left.primitives() | self.right.primitives()
class Or(Condition):
"""
True if either condition is satisfied
"""
def __init__(self, left, right):
self.left,self.right = left,right
def __call__(self, *args, **kw):
return self.left(*args, **kw) or self.right(*args, **kw)
def __str__(self):
return "(%s or %s)"%(self.left,self.right)
def _negate(self):
return And(self.left._negate(),self.right._negate())
def status(self, *args, **kw):
lstat,lcond = self.left.status(*args, **kw)
rstat,rcond = self.right.status(*args, **kw)
if lstat and rstat:
return True,lcond+rcond
elif lstat:
return True,lcond
elif rstat:
return True,rcond
else:
return False,lcond+rcond
def primitives(self):
return self.left.primitives() | self.right.primitives()
class Xor(Condition):
"""
True if only one condition is satisfied
"""
def __init__(self, left, right):
self.left,self.right = left,right
def __call__(self, *args, **kw):
l,r = self.left(*args, **kw),self.right(*args, **kw)
return (l or r) and not (l and r)
def __str__(self):
return "(%s xor %s)"%(self.left,self.right)
def _negate(self):
return Xor(self.left,self.right._negate())
def status(self, *args, **kw):
lstat,lcond = self.left.status(*args, **kw)
rstat,rcond = self.right.status(*args, **kw)
if lstat ^ rstat:
return True,lcond+rcond
else:
return False,lcond+rcond
def primitives(self):
return self.left.primitives() | self.right.primitives()
class Not(Condition):
"""
True if condition is not satisfied
"""
def __init__(self, condition):
self.condition = condition
def __call__(self, *args, **kw):
return not self.condition(*args, **kw)
def __str__(self):
return "not "+str(self.condition)
def _negate(self):
return self.condition
def status(self, *args, **kw):
stat,cond = self.condition.status()
return not stat, cond
def __eq__(self, other):
return isinstance(other,Not) and self.condition == other.condition
def __ne__(self, other):
return not isinstance(other,Not) or self.condition != other.condition
def primitives(self):
return self.condition.primitives()
class _Bar(Condition):
"""
This is an internal condition structure created solely to handle
negated primitives when computing status. It should not be used
externally.
"""
def __init__(self, condition):
self.condition = condition
def __call__(self, *args, **kw):
return not self.condition(*args, **kw)
def _negate(self):
return self.condition
def status(self, *args, **kw):
stat,cond = self.condition.status(*args,**kw)
return not stat, cond
def __str__(self):
return "not "+str(self.condition)
def primitives(self):
return self.condition.primitives()
class Constant(Condition):
"""
Constants true and false.
"""
def __init__(self, value):
self._value = value
def __call__(self, *args, **kw):
return self._value
def __str__(self):
return str(self._value)
true = Constant(True)
false = Constant(False)
|