File: condition.py

package info (click to toggle)
python-bumps 0.7.11-2
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 10,264 kB
  • sloc: python: 22,226; ansic: 4,973; cpp: 4,849; xml: 493; makefile: 163; perl: 108; sh: 101
file content (249 lines) | stat: -rw-r--r-- 8,373 bytes parent folder | download | duplicates (3)
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)