rucas 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ require 'rucas/symbolic'
2
+
3
+ module Rucas
4
+ module Extensions
5
+ #
6
+ # Avoid collisions with other patches; this maps operators (+,-, etc.) to
7
+ # normal method names, so they can be called without the additional overhead
8
+ # of Kernel#send.
9
+ #
10
+ PATCH_HASH = Hash.new {|h,k| k} # return key by default
11
+ PATCH_HASH[:+] = :add
12
+ PATCH_HASH[:-] = :sub
13
+ PATCH_HASH[:*] = :mul
14
+ PATCH_HASH[:/] = :div
15
+ PATCH_HASH[:**] = :pow
16
+ PATCH_HASH[:=~] = :eqs
17
+ PATCH_HASH[:<] = :lt
18
+ PATCH_HASH[:<=] = :lte
19
+ PATCH_HASH[:>] = :gt
20
+ PATCH_HASH[:>=] = :gte
21
+ PATCH_HASH[:&] = :and
22
+ PATCH_HASH[:|] = :or
23
+ raise "PATCH_HASH does not match BINARY_OPS" unless
24
+ Symbolic::BINARY_OPS.keys.all?{|k| PATCH_HASH.member?(k)}
25
+
26
+ # See PATCH_HASH.
27
+ def self.without_rucas_name original
28
+ "#{PATCH_HASH[original]}__without_rucas"
29
+ end
30
+
31
+ # See PATCH_HASH.
32
+ def self.with_rucas_name original
33
+ "#{PATCH_HASH[original]}__with_rucas"
34
+ end
35
+
36
+ # Add rucas methods constant classes, but don't alias_method them yet; that
37
+ # is handled in the apply and unapply methods.
38
+ for c in Symbolic::CONST_CLASSES
39
+ for op in Symbolic::BINARY_OPS.keys
40
+ c.class_eval %Q{
41
+ def #{with_rucas_name(op)} rhs
42
+ if rhs.is_a?(Rucas::Symbolic::Expr)
43
+ Rucas::Symbolic::ConstExpr.new(self) #{op} rhs
44
+ else
45
+ self.#{without_rucas_name(op)}(rhs)
46
+ end
47
+ end
48
+ }
49
+ end
50
+
51
+ # Try to convert to ConstExpr if can't find method (e.g. +simplify+).
52
+ c.module_eval do
53
+ def method_missing_with_rucas method, *args, &block
54
+ method_missing_without_rucas(method *args, &block)
55
+ rescue NoMethodError
56
+ error = $!
57
+ begin
58
+ return Symbolic::Expr.make(self).send(method, *args, &block)
59
+ rescue NoMethodError
60
+ nil
61
+ end
62
+ raise error
63
+ end
64
+ end
65
+ end
66
+
67
+ # Patch system classes to make constants work with vars (e.g. 0 + x).
68
+ def self.apply
69
+ for c in Symbolic::CONST_CLASSES
70
+ for op in Symbolic::BINARY_OPS.keys
71
+ c.class_eval %Q{
72
+ alias_method :#{without_rucas_name(op)}, :#{op}
73
+ alias_method :#{op}, :#{with_rucas_name(op)}
74
+ }
75
+ end
76
+ c.module_eval do
77
+ alias_method :method_missing_without_rucas, :method_missing
78
+ alias_method :method_missing, :method_missing_with_rucas
79
+ end
80
+ end
81
+ end
82
+
83
+ # Get rid of patches.
84
+ def self.unapply
85
+ for c in Symbolic::CONST_CLASSES
86
+ for op in Symbolic::BINARY_OPS.keys
87
+ c.class_eval %Q{
88
+ alias_method :#{with_rucas_name(op)}, :#{op}
89
+ alias_method :#{op}, :#{without_rucas_name(op)}
90
+ }
91
+ end
92
+ c.module_eval do
93
+ alias_method :method_missing_with_rucas, :method_missing
94
+ alias_method :method_missing, :method_missing_without_rucas
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,181 @@
1
+ require 'rucas/symbolic'
2
+ require 'facets/dictionary'
3
+
4
+ module Rucas
5
+ #
6
+ # Tools for recursively rewriting expressions based on rules.
7
+ #
8
+ module Rewrite
9
+ #
10
+ # Construct an ordered hash of rewrite rules.
11
+ #
12
+ # Example:
13
+ # my_rules = make_rules {
14
+ # var :x
15
+ # rule x + 0 => x
16
+ # rule 0 + x => x
17
+ # }
18
+ #
19
+ def self.make_rules &block
20
+ s = Scope.new
21
+ class <<s
22
+ def dict
23
+ @dict ||= Dictionary[]
24
+ end
25
+ def rule rule
26
+ raise unless rule.size == 1
27
+ dict[rule.keys.first] = Symbolic::Expr.make(rule.values.first)
28
+ end
29
+ end
30
+ s.rucas(&block)
31
+ s.dict
32
+ end
33
+ end
34
+
35
+ module Symbolic
36
+ module Expr
37
+ #
38
+ # If this expression matches +pattern+, return +output+ (with appropriate
39
+ # bindings of variables in pattern applied to output); otherwise, return
40
+ # self (or an object == to self).
41
+ #
42
+ def rewrite pattern, output
43
+ bindings = pattern.match(self)
44
+ return output.with(bindings) if bindings
45
+ self
46
+ end
47
+
48
+ #
49
+ # Non-recursive matching. Here, self is a pattern and +expr+ is the input
50
+ # to be matched against this pattern. Returns bindings for the free
51
+ # variables in self that make the match succeed, or nil if there is no
52
+ # such match.
53
+ #
54
+ def match expr, bindings = {}
55
+ nil # see subclasses
56
+ end
57
+
58
+ #
59
+ # If this expression involves only constants, evaluate it and return the
60
+ # result; if it is not, return nil.
61
+ #
62
+ def value
63
+ nil # see subclasses
64
+ end
65
+ end
66
+
67
+ module OpExpr
68
+ def rewrite pattern, output
69
+ # Rewrite children first.
70
+ new_children = self.children.map{|c| c.rewrite(pattern, output)}
71
+ new_self = self.class.new(*new_children)
72
+ # See if it's a constant.
73
+ v = new_self.value
74
+ if v
75
+ Symbolic::Expr.make(v)
76
+ else
77
+ # It's not a constant; try to rewrite using the rule.
78
+ bindings = pattern.match(new_self)
79
+ return output.with(bindings) if bindings
80
+ new_self
81
+ end
82
+ end
83
+
84
+ def with bindings
85
+ self.class.new(*self.children.map{|c| c.with(bindings)})
86
+ end
87
+ end
88
+
89
+ class ConstExpr
90
+ def match expr, bindings = {}
91
+ return bindings if self == expr
92
+ return nil
93
+ end
94
+
95
+ def with bindings
96
+ self
97
+ end
98
+ end
99
+
100
+ class VarExpr
101
+ def match expr, bindings = {}
102
+ binding = bindings[self]
103
+ return bindings.merge(self => expr) if !binding
104
+ return bindings if binding == expr
105
+ return nil
106
+ end
107
+
108
+ def with bindings
109
+ bindings[self] || self
110
+ end
111
+ end
112
+
113
+ class UnaryOpExpr
114
+ def match expr, bindings = {}
115
+ return nil unless expr.is_a?(UnaryOpExpr)
116
+ return nil unless self.op == expr.op
117
+ rhs.match(expr.rhs, bindings)
118
+ end
119
+
120
+ def value
121
+ v = rhs.value
122
+ eval "(#{v}).#{self.op}" if v
123
+ end
124
+ end
125
+
126
+ class BinaryOpExpr
127
+ def match expr, bindings = {}
128
+ if expr.is_a?(BinaryOpExpr) && self.op == expr.op
129
+ lb = self.lhs.match(expr.lhs, bindings)
130
+ rb = self.rhs.match(expr.rhs, lb) if lb
131
+ return rb if rb
132
+ end
133
+ nil
134
+ end
135
+
136
+ def value
137
+ lv = lhs.value
138
+ rv = rhs.value
139
+ eval "(#{lv})#{self.op}(#{rv})" if lv && rv
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ # module Expr
146
+ # def simplify bindings={}, rules=DEFAULT_SIMPLIFY_RULES
147
+ # for rule in rules
148
+ # new_bindings = self.match(rule.lhs, bindings)
149
+ # return rule.rhs.with(new_bindings).simplify(bindings,rules) if
150
+ # new_bindings
151
+ # end
152
+ # end
153
+
154
+ # rule: pair of expressions, which may refer to matchers
155
+ # matcher: name, predicate block
156
+ # bindings: map from matcher to expression
157
+ # the result of matching an expression with another expression should be
158
+ # false if the match fails, or it should return bindings for the matchers
159
+ # that make the match succeed.
160
+ # note: match is not commutative; it's probably easiest if we return
161
+ # bindings for the lhs (self), since most of the tricky stuff then goes
162
+ # into VarExpr. This isn't maximally rubyish: "foo" =~ /foo/ -- the
163
+ # pattern comes last -- but /foo/.match("foo") is also valid.
164
+ # note: a convention is also required for handling things like
165
+ # (x + y).match(a - b) -- this could succeed with x => a, y => -b, but it
166
+ # requires more checks -- can't just check op of root.
167
+ # def match expr
168
+ #
169
+ # end
170
+ # end
171
+ #
172
+ # Simplification isn't really recursive (in the tree structure) -- we have
173
+ # to apply a list of rules, and we restart when the current rule matches
174
+ # any part of the expression tree.
175
+ # The simplification process for one rewrite rule could be recursive: do a
176
+ # post-order traversal, rewriting children; at the top level, we just
177
+ # compare the whole resulting expression with the original to see if
178
+ # anything's changed.
179
+ # The match is then non-recursive.
180
+ #
181
+
@@ -0,0 +1,102 @@
1
+ require 'rucas/rewrite'
2
+
3
+ module Rucas
4
+ #
5
+ # Simplification; this is still fairly primitive.
6
+ #
7
+ # things that don't simplify:
8
+ # Examples from PAIP 8.3: commutativity and associativity
9
+ # {3*(2*x)}
10
+ # {2 * x * x * 3}
11
+ # {2 * x * 3 * y * 4 * z * 5 * 6}
12
+ # {3 + x + 4 + x}
13
+ # {2 * x * 3 * x * 4 * (1 / x) * 5 * 6}
14
+ #
15
+ module Simplify
16
+ #
17
+ # Ordered hash of simplification rules.
18
+ #
19
+ # The order of the rules is very important for avoiding unsoundness due to
20
+ # division by zero, for example.
21
+ #
22
+ # Note that division by zero always results in a NaN. If it were possible to
23
+ # prove that x was strictly positive (negative), x / 0 should arguably
24
+ # evaluate to +Inf (-Inf), but this is substantially beyond what we can do
25
+ # at this point.
26
+ #
27
+ RULES = Rewrite.make_rules {
28
+ var :w
29
+ var :x
30
+ var :y
31
+ z = Symbolic::Expr.make(0)
32
+ nan = Symbolic::Expr.make(0.0/0.0)
33
+
34
+ # These rules are taken from Norvig's Paradigms of AI Programming
35
+ # (chapter 8). The source for that book is freely available.
36
+ rule( x + 0 => x )
37
+ rule( 0 + x => x )
38
+ rule( x + x => 2*x )
39
+ rule( x - 0 => x )
40
+ rule( 0 - x => -x )
41
+ rule( x - x => 0 )
42
+ rule( +x => x )
43
+ rule( --x => x )
44
+ rule( x * 1 => x )
45
+ rule( 1 * x => x )
46
+ rule( x * 0 => 0 )
47
+ rule( 0 * x => 0 )
48
+ rule( x * x => x**2)
49
+ rule( x / 0 => nan )
50
+ rule( 0 / x => 0 )
51
+ rule( x / 1 => x )
52
+ rule( x / x => 1 )
53
+ rule( z ** 0 => 1 ) # note: Ruby says 0 ** 0 is 1; be consistent
54
+ rule( x ** 0 => 1 )
55
+ rule( 0 ** x => 0 )
56
+ rule( 1 ** x => 1 )
57
+ rule( x ** 1 => x )
58
+ rule( x ** -1 => 1 / x)
59
+ rule( x * (y / x) => y )
60
+ rule( (y / x) * x => y )
61
+ rule( (y * x) / x => y )
62
+ rule( (x * y) / x => y )
63
+ rule( x + -x => 0 )
64
+ rule( -x + x => 0 )
65
+ rule( x + y - x => y )
66
+ rule( x - y - x => -y )
67
+
68
+ # These rules are helpful for "rebalancing" long trees, which can make
69
+ # some of the rules above effective. This is because we don't actually
70
+ # handle associativity properly. These rules do slow things down a bit,
71
+ # but hopefully it doesn't break anything too badly.
72
+ #
73
+ # Interestingly, Norvig's "infix->prefix" functions used right-
74
+ # associative grouping, which made things go more smoothly. For example,
75
+ # x^2 + x + x + 1 simplifies to x^2 + 2*x + 1 without the rebalancing
76
+ # rule, if you group from the right (but the reverse doesn't simplify).
77
+ rule( (w + x) + y => w + (x + y) )
78
+ rule( (w * x) * y => w * (x * y) )
79
+ }
80
+ end
81
+
82
+ module Symbolic
83
+ module Expr
84
+ #
85
+ # Return expression after algebraic simplification. Note that this isn't a
86
+ # very smart simplifier.
87
+ #
88
+ def simplify
89
+ new_self = self
90
+ changed = false
91
+ for pattern, output in Simplify::RULES
92
+ new_self = new_self.rewrite(pattern, output)
93
+ #puts "#{pattern}\t#{new_self.to_s_paren}"
94
+ changed = (new_self != self)
95
+ break if changed
96
+ end
97
+ if changed then new_self.simplify else self end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
@@ -0,0 +1,238 @@
1
+ module Rucas
2
+ #
3
+ # Translate Ruby expressions into expression trees for symbolic manipulation.
4
+ #
5
+ module Symbolic
6
+ include Utility
7
+
8
+ #
9
+ # Define variable in this scope and return it.
10
+ #
11
+ def var name
12
+ var = VarExpr.new(name)
13
+ meta_def var.name do
14
+ var
15
+ end
16
+ var
17
+ end
18
+
19
+ CONST_CLASSES = [Fixnum, Float, Bignum]
20
+
21
+ #
22
+ # Symbolic expression; subclasses represent various kinds of expressions.
23
+ #
24
+ module Expr
25
+ # Children in the expression tree, if any. For example, an AddExpr returns
26
+ # its left and right operands (x and y in x + y).
27
+ def children; [] end
28
+
29
+ # Operator precedence; this reflects Ruby's built-in order-of-operations
30
+ # rules. You should not rely on the particular values; only the ordering
31
+ # they define is guaranteed.
32
+ def precedence; 0 end
33
+
34
+ # String representation including all parentheses; by default, +to_s+
35
+ # omits parentheses when operator precedence rules allow.
36
+ def to_s_paren; to_s end
37
+
38
+ def self.make e
39
+ return e if e.is_a?(Expr)
40
+ return ConstExpr.new(e) if CONST_CLASSES.any? {|t| e.is_a?(t)}
41
+ raise "#{e} is not a symbolic expression"
42
+ end
43
+ end
44
+
45
+ # Numeric constant.
46
+ ConstExpr = Struct.new(:value)
47
+ class ConstExpr
48
+ include Expr
49
+
50
+ def to_s; value.to_s end
51
+ def constant?; true end
52
+ end
53
+
54
+ # Variable (literal).
55
+ VarExpr = Struct.new(:name)
56
+ class VarExpr
57
+ include Expr
58
+
59
+ def to_s; name.to_s end
60
+ def constant?; false end
61
+ end
62
+
63
+ #
64
+ # Expression with an operator and operand(s).
65
+ #
66
+ module OpExpr
67
+ include Expr
68
+
69
+ #
70
+ # The operators on the same line have the same precedence; the list is
71
+ # from highest precedence to lowest precedence. This list is from the
72
+ # Pickaxe book ("The Ruby Language").
73
+ #
74
+ op_precedence_table =
75
+ [%w(**),
76
+ %w(~@ +@ -@),
77
+ %w(* / %),
78
+ %w(+ -)]
79
+
80
+ #
81
+ # Operator precedence; you shouldn't rely on the numbers, but (barring
82
+ # changes to Ruby), the order shouldn't change.
83
+ #
84
+ OP_PRECEDENCE = Hash[*op_precedence_table.
85
+ zip((1..op_precedence_table.size).to_a).
86
+ map{|ops,prec| ops.map{|op| [op.to_sym, -prec]}}.flatten]
87
+
88
+ #
89
+ # Precedence (in Ruby) of this operator; this affects how expressions are
90
+ # parenthesized.
91
+ #
92
+ def precedence
93
+ OP_PRECEDENCE[self.op]
94
+ end
95
+
96
+ def constant?; children.all {|c| c.constant?} end
97
+ end
98
+
99
+ #
100
+ # Unary operations.
101
+ #
102
+ UnaryOpExpr = Struct.new(:op, :rhs)
103
+ class UnaryOpExpr
104
+ include OpExpr
105
+
106
+ def children; [rhs] end
107
+
108
+ def to_s_paren
109
+ "#{self.op}(#{self.rhs.to_s_paren})"
110
+ end
111
+ end
112
+
113
+ class PosExpr < UnaryOpExpr
114
+ def to_s; rhs.to_s end
115
+ end
116
+
117
+ class NegExpr < UnaryOpExpr
118
+ def to_s; rhs.precedence < self.precedence ? "-(#{rhs})" : "-#{rhs}" end
119
+ end
120
+
121
+ UNARY_OPS = {
122
+ :+@ => :PosExpr,
123
+ :-@ => :NegExpr,
124
+ :~@ => :NotExpr,
125
+ }
126
+
127
+ for op, op_class in UNARY_OPS
128
+ class_eval %Q{
129
+ class #{op_class}
130
+ def initialize(rhs)
131
+ self.op, self.rhs = :#{op}, rhs
132
+ end
133
+ end
134
+ }
135
+ end
136
+
137
+ module Expr
138
+ for op, op_class in UNARY_OPS
139
+ class_eval %Q{
140
+ def #{op} ; #{op_class}.new(self) end
141
+ }
142
+ end
143
+ end
144
+
145
+ #
146
+ # Binary operations.
147
+ #
148
+ BinaryOpExpr = Struct.new(:op, :lhs, :rhs)
149
+ class BinaryOpExpr
150
+ include OpExpr
151
+
152
+ def children; [lhs, rhs] end
153
+
154
+ def to_s_paren
155
+ op_string = " #{self.op} "
156
+ "(#{self.children.map{|c| c.to_s_paren}.join(op_string)})"
157
+ end
158
+
159
+ def to_s
160
+ inner = self.children.map{|c|
161
+ c.precedence < self.precedence ? "(#{c})" : c.to_s}
162
+ "#{inner.join(self.op_string)}"
163
+ end
164
+
165
+ protected
166
+ # Allow for control of spacing between operands.
167
+ def op_string; " #{self.op} " end
168
+ end
169
+
170
+ # Arithmetic
171
+ class ArithmeticOpExpr < BinaryOpExpr ; end
172
+ class AddExpr < ArithmeticOpExpr; end
173
+ class SubExpr < ArithmeticOpExpr; end
174
+ class MulExpr < ArithmeticOpExpr; end
175
+ class DivExpr < ArithmeticOpExpr; end
176
+ class PowExpr < ArithmeticOpExpr; end
177
+
178
+ # Comparison
179
+ class CompareOpExpr < BinaryOpExpr ; end
180
+ class EqExpr < CompareOpExpr ; end
181
+ class LTExpr < CompareOpExpr ; end
182
+ class LTEqExpr < CompareOpExpr ; end
183
+ class GTExpr < CompareOpExpr ; end
184
+ class GTEqExpr < CompareOpExpr ; end
185
+
186
+ # Logic -- see comments below
187
+ #class BooleanOpExpr < BinaryOpExpr ; end
188
+ #class AndExpr < BooleanOpExpr ; end
189
+ #class OrExpr < BooleanOpExpr ; end
190
+
191
+ BINARY_OPS = {
192
+ :+ => :AddExpr,
193
+ :- => :SubExpr,
194
+ :* => :MulExpr,
195
+ :/ => :DivExpr,
196
+ :** => :PowExpr,
197
+ :=~ => :EqExpr, # =, == and === are all taken!
198
+ :< => :LTExpr,
199
+ :<= => :LTEqExpr,
200
+ :> => :GTExpr,
201
+ :>= => :GTEqExpr}
202
+
203
+ # Note: can use & and | for logic, but there are problems with
204
+ # precedence: it binds before the (in)equality symbols. Also need to
205
+ # change the simplification code to treat them specially. This needs more
206
+ # thinking.
207
+ # :& => :AndExpr,
208
+ # :| => :OrExpr}
209
+
210
+ for op, op_class in BINARY_OPS
211
+ class_eval %Q{
212
+ class #{op_class}
213
+ def initialize(lhs, rhs)
214
+ self.lhs, self.op, self.rhs = lhs, :#{op}, rhs
215
+ end
216
+ end
217
+ }
218
+ end
219
+
220
+ module Expr
221
+ for op, op_class in BINARY_OPS
222
+ class_eval %Q{
223
+ def #{op} rhs ; #{op_class}.new(self, Expr.make(rhs)) end
224
+ }
225
+ end
226
+ end
227
+
228
+ class MulExpr
229
+ # Reduce spacing between factors.
230
+ def op_string; self.op.to_s end
231
+ end
232
+
233
+ class PowExpr
234
+ # Reduce spacing between base and exponent.
235
+ def op_string; self.op.to_s end
236
+ end
237
+ end
238
+ end