keisan 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -5
  3. data/README.md +112 -8
  4. data/bin/keisan +7 -0
  5. data/lib/keisan.rb +0 -1
  6. data/lib/keisan/ast.rb +24 -0
  7. data/lib/keisan/ast/assignment.rb +36 -10
  8. data/lib/keisan/ast/builder.rb +7 -1
  9. data/lib/keisan/ast/constant_literal.rb +6 -1
  10. data/lib/keisan/ast/exponent.rb +1 -1
  11. data/lib/keisan/ast/function.rb +7 -3
  12. data/lib/keisan/ast/list.rb +1 -1
  13. data/lib/keisan/ast/node.rb +1 -1
  14. data/lib/keisan/ast/parent.rb +1 -1
  15. data/lib/keisan/ast/plus.rb +11 -1
  16. data/lib/keisan/ast/string.rb +8 -0
  17. data/lib/keisan/ast/unary_operator.rb +1 -1
  18. data/lib/keisan/ast/variable.rb +8 -2
  19. data/lib/keisan/calculator.rb +16 -0
  20. data/lib/keisan/context.rb +39 -8
  21. data/lib/keisan/evaluator.rb +11 -1
  22. data/lib/keisan/function.rb +4 -0
  23. data/lib/keisan/functions/abs.rb +9 -0
  24. data/lib/keisan/functions/cbrt.rb +15 -0
  25. data/lib/keisan/functions/cmath_function.rb +11 -0
  26. data/lib/keisan/functions/cos.rb +15 -0
  27. data/lib/keisan/functions/cosh.rb +15 -0
  28. data/lib/keisan/functions/cot.rb +15 -0
  29. data/lib/keisan/functions/coth.rb +15 -0
  30. data/lib/keisan/functions/csc.rb +15 -0
  31. data/lib/keisan/functions/csch.rb +15 -0
  32. data/lib/keisan/functions/default_registry.rb +95 -1
  33. data/lib/keisan/functions/diff.rb +36 -14
  34. data/lib/keisan/functions/exp.rb +15 -0
  35. data/lib/keisan/functions/expression_function.rb +65 -15
  36. data/lib/keisan/functions/filter.rb +64 -0
  37. data/lib/keisan/functions/if.rb +15 -12
  38. data/lib/keisan/functions/imag.rb +9 -0
  39. data/lib/keisan/functions/log.rb +15 -0
  40. data/lib/keisan/functions/map.rb +57 -0
  41. data/lib/keisan/functions/math_function.rb +34 -0
  42. data/lib/keisan/functions/proc_function.rb +5 -3
  43. data/lib/keisan/functions/real.rb +9 -0
  44. data/lib/keisan/functions/reduce.rb +65 -0
  45. data/lib/keisan/functions/registry.rb +5 -1
  46. data/lib/keisan/functions/sec.rb +15 -0
  47. data/lib/keisan/functions/sech.rb +15 -0
  48. data/lib/keisan/functions/sin.rb +15 -0
  49. data/lib/keisan/functions/sinh.rb +15 -0
  50. data/lib/keisan/functions/sqrt.rb +15 -0
  51. data/lib/keisan/functions/tan.rb +15 -0
  52. data/lib/keisan/functions/tanh.rb +15 -0
  53. data/lib/keisan/repl.rb +105 -0
  54. data/lib/keisan/tokenizer.rb +5 -2
  55. data/lib/keisan/tokens/string.rb +1 -1
  56. data/lib/keisan/variables/registry.rb +14 -3
  57. data/lib/keisan/version.rb +1 -1
  58. data/screenshots/repl.png +0 -0
  59. metadata +30 -6
  60. data/lib/keisan/ast/functions/diff.rb +0 -57
  61. data/lib/keisan/ast/functions/if.rb +0 -47
  62. data/lib/keisan/function_definition_context.rb +0 -34
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Csch < CMathFunction
4
+ def initialize
5
+ super("csch", Proc.new {|arg| 1 / CMath::sinh(arg)})
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ -Keisan::AST::Function.new([argument], "cosh") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sinh"), -2])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,8 +1,32 @@
1
1
  require_relative "if"
2
2
  require_relative "diff"
3
3
  require_relative "replace"
4
+ require_relative "map"
5
+ require_relative "filter"
6
+ require_relative "reduce"
4
7
  require_relative "rand"
5
8
  require_relative "sample"
9
+ require_relative "math_function"
10
+ require_relative "cmath_function"
11
+ require_relative "exp"
12
+ require_relative "log"
13
+ require_relative "sin"
14
+ require_relative "cos"
15
+ require_relative "sec"
16
+ require_relative "tan"
17
+ require_relative "cot"
18
+ require_relative "csc"
19
+ require_relative "sinh"
20
+ require_relative "cosh"
21
+ require_relative "sech"
22
+ require_relative "tanh"
23
+ require_relative "coth"
24
+ require_relative "csch"
25
+ require_relative "sqrt"
26
+ require_relative "cbrt"
27
+ require_relative "abs"
28
+ require_relative "real"
29
+ require_relative "imag"
6
30
 
7
31
  module Keisan
8
32
  module Functions
@@ -19,10 +43,40 @@ module Keisan
19
43
  registry.register!(:if, Keisan::Functions::If.new, force: true)
20
44
  registry.register!(:diff, Keisan::Functions::Diff.new, force: true)
21
45
  registry.register!(:replace, Keisan::Functions::Replace.new, force: true)
46
+ registry.register!(:map, Keisan::Functions::Map.new, force: true)
47
+ registry.register!(:collect, Keisan::Functions::Map.new, force: true)
48
+ registry.register!(:filter, Keisan::Functions::Filter.new, force: true)
49
+ registry.register!(:select, Keisan::Functions::Filter.new, force: true)
50
+ registry.register!(:reduce, Keisan::Functions::Reduce.new, force: true)
51
+ registry.register!(:inject, Keisan::Functions::Reduce.new, force: true)
22
52
 
23
53
  register_builtin_math!(registry)
24
54
  register_array_methods!(registry)
25
55
  register_random_methods!(registry)
56
+
57
+ registry.register!(:exp, Keisan::Functions::Exp.new, force: true)
58
+ registry.register!(:log, Keisan::Functions::Log.new, force: true)
59
+
60
+ registry.register!(:sin, Keisan::Functions::Sin.new, force: true)
61
+ registry.register!(:cos, Keisan::Functions::Cos.new, force: true)
62
+ registry.register!(:tan, Keisan::Functions::Tan.new, force: true)
63
+ registry.register!(:cot, Keisan::Functions::Cot.new, force: true)
64
+ registry.register!(:sec, Keisan::Functions::Sec.new, force: true)
65
+ registry.register!(:csc, Keisan::Functions::Csc.new, force: true)
66
+
67
+ registry.register!(:sinh, Keisan::Functions::Sinh.new, force: true)
68
+ registry.register!(:cosh, Keisan::Functions::Cosh.new, force: true)
69
+ registry.register!(:tanh, Keisan::Functions::Tanh.new, force: true)
70
+ registry.register!(:coth, Keisan::Functions::Coth.new, force: true)
71
+ registry.register!(:sech, Keisan::Functions::Sech.new, force: true)
72
+ registry.register!(:csch, Keisan::Functions::Csch.new, force: true)
73
+
74
+ registry.register!(:sqrt, Keisan::Functions::Sqrt.new, force: true)
75
+ registry.register!(:cbrt, Keisan::Functions::Cbrt.new, force: true)
76
+
77
+ registry.register!(:abs, Keisan::Functions::Abs.new, force: true)
78
+ registry.register!(:real, Keisan::Functions::Real.new, force: true)
79
+ registry.register!(:imag, Keisan::Functions::Imag.new, force: true)
26
80
  end
27
81
 
28
82
  def self.register_builtin_math!(registry)
@@ -30,6 +84,7 @@ module Keisan
30
84
  registry.register!(
31
85
  method,
32
86
  Proc.new {|*args|
87
+ args = args.map(&:value)
33
88
  Math.send(method, *args)
34
89
  },
35
90
  force: true
@@ -38,9 +93,48 @@ module Keisan
38
93
  end
39
94
 
40
95
  def self.register_array_methods!(registry)
41
- %i(min max size).each do |method|
96
+ %i(min max size flatten reverse).each do |method|
42
97
  registry.register!(method, Proc.new {|a| a.send(method)}, force: true)
43
98
  end
99
+
100
+ # range(10) => Integers from 0 inclusive to 10 exclusively
101
+ # range(5, 15) => Integers from 5 inclusive to 15 exclusive
102
+ # range(10, -1, -2) => Integers from 10 inclusive to -1 exclusive, decreasing by twos
103
+ # i.e.: [10, 8, 6, 4, 2, 0]
104
+ registry.register!("range", Proc.new {|*args|
105
+ case args.count
106
+ when 1
107
+ (0...args[0]).to_a
108
+ when 2
109
+ (args[0]...args[1]).to_a
110
+ when 3
111
+ current = args[0]
112
+ final = args[1]
113
+ shift = args[2]
114
+
115
+ if shift == 0 or !shift.is_a?(Integer)
116
+ raise Keisan::Exceptions::InvalidFunctionError.new("range's 3rd argument must be non-zero integer")
117
+ end
118
+
119
+ result = []
120
+
121
+ if shift > 0
122
+ while current < final
123
+ result << current
124
+ current += shift
125
+ end
126
+ else
127
+ while current > final
128
+ result << current
129
+ current += shift
130
+ end
131
+ end
132
+
133
+ result
134
+ else
135
+ raise Keisan::Exceptions::InvalidFunctionError.new("range takes 1 to 3 arguments")
136
+ end
137
+ }, force: true)
44
138
  end
45
139
 
46
140
  def self.register_random_methods!(registry)
@@ -17,29 +17,46 @@ module Keisan
17
17
  end
18
18
 
19
19
  def evaluate(ast_function, context = nil)
20
- context ||= Context.new
21
- function, vars = function_and_vars(ast_function)
20
+ context ||= Keisan::Context.new
21
+ function, variables = function_and_variables(ast_function)
22
+ local = context_from(variables, context)
22
23
 
23
- vars.inject(function.evaluate(context)) do |result, variable|
24
- result = differentiate(result, variable, context)
24
+ result = variables.inject(function.evaluate(local)) do |result, variable|
25
+ result = differentiate(result, variable, local)
25
26
  if !is_ast_derivative?(result)
26
- result = result.evaluate(context)
27
+ result = result.evaluate(local)
27
28
  end
28
29
  result
29
30
  end
31
+
32
+ case result
33
+ when Keisan::AST::Function
34
+ result.name == "diff" ? result : result.simplify(context)
35
+ else
36
+ result.simplify(context)
37
+ end
30
38
  end
31
39
 
32
40
  def simplify(ast_function, context = nil)
33
- context ||= Context.new
34
- function, vars = function_and_vars(ast_function)
41
+ raise Keisan::Exceptions::InternalError.new("received non-diff function") unless ast_function.name == "diff"
42
+ function, variables = function_and_variables(ast_function)
43
+ context ||= Keisan::Context.new
44
+ local = context_from(variables, context)
35
45
 
36
- vars.inject(function.simplify(context)) do |result, variable|
37
- result = differentiate(result, variable, context)
46
+ result = variables.inject(function.simplify(local)) do |result, variable|
47
+ result = differentiate(result, variable, local)
38
48
  if !is_ast_derivative?(result)
39
- result = result.simplify(context)
49
+ result = result.simplify(local)
40
50
  end
41
51
  result
42
52
  end
53
+
54
+ case result
55
+ when Keisan::AST::Function
56
+ result.name == "diff" ? result : result.simplify(context)
57
+ else
58
+ result.simplify(context)
59
+ end
43
60
  end
44
61
 
45
62
  private
@@ -61,22 +78,27 @@ module Keisan
61
78
  )
62
79
  end
63
80
 
64
- def function_and_vars(ast_function)
81
+ def function_and_variables(ast_function)
65
82
  unless ast_function.is_a?(Keisan::AST::Function) && ast_function.name == name
66
83
  raise Keisan::Exceptions::InvalidFunctionError.new("Must receive diff function")
67
84
  end
68
85
 
69
- vars = ast_function.children[1..-1]
86
+ variables = ast_function.children[1..-1]
70
87
 
71
- unless vars.all? {|var| var.is_a?(AST::Variable)}
88
+ unless variables.all? {|var| var.is_a?(AST::Variable)}
72
89
  raise Keisan::Exceptions::InvalidFunctionError.new("Diff must differentiate with respect to variables")
73
90
  end
74
91
 
75
92
  [
76
93
  ast_function.children.first,
77
- vars
94
+ variables
78
95
  ]
79
96
  end
97
+
98
+ def context_from(variables, context = nil)
99
+ context ||= Keisan::Context.new(shadowed: variables.map(&:name))
100
+ context.spawn_child(shadowed: variables.map(&:name))
101
+ end
80
102
  end
81
103
  end
82
104
  end
@@ -0,0 +1,15 @@
1
+ module Keisan
2
+ module Functions
3
+ class Exp < CMathFunction
4
+ def initialize
5
+ super("exp")
6
+ end
7
+
8
+ protected
9
+
10
+ def self.derivative(argument)
11
+ Keisan::AST::Function.new([argument], "exp")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,19 +3,17 @@ module Keisan
3
3
  class ExpressionFunction < Keisan::Function
4
4
  attr_reader :arguments, :expression
5
5
 
6
- def initialize(name, arguments, expression, local_context)
6
+ def initialize(name, arguments, expression, transient_definitions)
7
7
  super(name)
8
8
  @expression = expression.deep_dup
9
9
  @arguments = arguments
10
- @local_context = local_context
10
+ @transient_definitions = transient_definitions
11
11
  end
12
12
 
13
13
  def call(context, *args)
14
- unless @arguments.count == args.count
15
- raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
16
- end
14
+ verify_argument_size!(args.count)
17
15
 
18
- local = @local_context.spawn_child
16
+ local = local_context_for(context)
19
17
  arguments.each.with_index do |arg_name, i|
20
18
  local.register_variable!(arg_name, args[i])
21
19
  end
@@ -24,28 +22,30 @@ module Keisan
24
22
  end
25
23
 
26
24
  def value(ast_function, context = nil)
25
+ verify_argument_size!(ast_function.children.count)
26
+
27
27
  context ||= Keisan::Context.new
28
28
  argument_values = ast_function.children.map {|child| child.value(context)}
29
29
  call(context, *argument_values)
30
30
  end
31
31
 
32
32
  def evaluate(ast_function, context = nil)
33
+ verify_argument_size!(ast_function.children.count)
34
+
33
35
  context ||= Keisan::Context.new
36
+ local = local_context_for(context)
34
37
 
35
- ast_function.instance_variable_set(
36
- :@children,
37
- ast_function.children.map {|child| child.evaluate(context)}
38
- )
38
+ argument_values = ast_function.children.map {|child| child.evaluate(context)}
39
39
 
40
- if ast_function.children.all? {|child| child.well_defined?(context)}
41
- value(ast_function, context).to_node.evaluate(context)
42
- else
43
- ast_function
40
+ arguments.each.with_index do |arg_name, i|
41
+ local.register_variable!(arg_name, argument_values[i].evaluate(context))
44
42
  end
43
+
44
+ expression.evaluate(local)
45
45
  end
46
46
 
47
47
  def simplify(ast_function, context = nil)
48
- context ||= Context.new
48
+ verify_argument_size!(ast_function.children.count)
49
49
 
50
50
  ast_function.instance_variable_set(
51
51
  :@children,
@@ -58,6 +58,56 @@ module Keisan
58
58
  ast_function
59
59
  end
60
60
  end
61
+
62
+ # Multi-argument functions work as follows:
63
+ # Given f(x, y), in general we will take the derivative with respect to t,
64
+ # and x = x(t), y = y(t). For instance d/dt f(2*t, t+1).
65
+ # In this case, chain rule gives derivative:
66
+ # dx(t)/dt * f_x(x(t), y(t)) + dy(t)/dt * f_y(x(t), y(t)),
67
+ # where f_x and f_y are the x and y partial derivatives respectively.
68
+ def differentiate(ast_function, variable, context = nil)
69
+ verify_argument_size!(ast_function.children.count)
70
+
71
+ local = local_context_for(context)
72
+
73
+ # expression.differentiate(variable, context)
74
+
75
+ argument_values = ast_function.children.map {|child| child.evaluate(local)}
76
+
77
+ argument_derivatives = ast_function.children.map do |child|
78
+ child.differentiate(variable, context)
79
+ end
80
+
81
+ Keisan::AST::Plus.new(
82
+ argument_derivatives.map.with_index {|argument_derivative, i|
83
+ partial_derivative = partial_derivatives[i].replace(argument_variables[i], argument_values[i])
84
+ Keisan::AST::Times.new([argument_derivative, partial_derivative])
85
+ }
86
+ )
87
+ end
88
+
89
+ private
90
+
91
+ def argument_variables
92
+ @argument_variables ||= arguments.map {|argument| Keisan::AST::Variable.new(argument)}
93
+ end
94
+
95
+ def partial_derivatives
96
+ @partial_derivatives ||= argument_variables.map.with_index do |variable, i|
97
+ partial_derivative = expression.differentiate(variable)
98
+ end
99
+ end
100
+
101
+ def verify_argument_size!(argument_size)
102
+ unless @arguments.count == argument_size
103
+ raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
104
+ end
105
+ end
106
+
107
+ def local_context_for(context = nil)
108
+ context ||= Keisan::Context.new
109
+ context.spawn_child(definitions: @transient_definitions, shadowed: @arguments, transient: true)
110
+ end
61
111
  end
62
112
  end
63
113
  end
@@ -0,0 +1,64 @@
1
+ module Keisan
2
+ module Functions
3
+ class Filter < Keisan::Function
4
+ # Filters (list, variable, expression)
5
+ # e.g. filter([1,2,3,4], x, x % 2 == 0)
6
+ # should give [2,4]
7
+ def initialize
8
+ @name = "filter"
9
+ end
10
+
11
+ def value(ast_function, context = nil)
12
+ evaluate(ast_function, context = nil)
13
+ end
14
+
15
+ def evaluate(ast_function, context = nil)
16
+ context ||= Context.new
17
+ simplify(ast_function, context).evaluate(context)
18
+ end
19
+
20
+ def simplify(ast_function, context = nil)
21
+ list, variable, expression = list_variable_expression_for(ast_function, context)
22
+
23
+ context ||= Keisan::Context.new
24
+ local = context.spawn_child(transient: false, shadowed: [variable.name])
25
+
26
+ Keisan::AST::List.new(
27
+ list.children.select do |element|
28
+ local.register_variable!(variable, element)
29
+ result = expression.evaluate(local)
30
+
31
+ case result
32
+ when Keisan::AST::Boolean
33
+ result.value
34
+ else
35
+ raise Keisan::Exceptions::InvalidFunctionError.new("Filter requires expression to be a logical expression")
36
+ end
37
+ end
38
+ )
39
+ end
40
+
41
+ private
42
+
43
+ def list_variable_expression_for(ast_function, context)
44
+ unless ast_function.children.size == 3
45
+ raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to filter")
46
+ end
47
+
48
+ list = ast_function.children[0].simplify(context)
49
+ variable = ast_function.children[1]
50
+ expression = ast_function.children[2]
51
+
52
+ unless list.is_a?(Keisan::AST::List)
53
+ raise Keisan::Exceptions::InvalidFunctionError.new("First argument to filter must be a list")
54
+ end
55
+
56
+ unless variable.is_a?(Keisan::AST::Variable)
57
+ raise Keisan::Exceptions::InvalidFunctionError.new("Second argument to filter must be a variable")
58
+ end
59
+
60
+ [list, variable, expression]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -27,17 +27,8 @@ module Keisan
27
27
  bool = ast_function.children[0].evaluate(context)
28
28
 
29
29
  if bool.is_a?(Keisan::AST::Boolean)
30
- if bool.value
31
- ast_function.children[1].evaluate(context)
32
- else
33
- ast_function.children[2].to_node.evaluate(context)
34
- end
35
- else
36
- ast_function
37
- end
38
-
39
- if ast_function.children.all? {|child| child.well_defined?(context)}
40
- value(ast_function, context).to_node.evaluate(context)
30
+ node = bool.value ? ast_function.children[1] : ast_function.children[2]
31
+ node.to_node.evaluate(context)
41
32
  else
42
33
  ast_function
43
34
  end
@@ -49,7 +40,7 @@ module Keisan
49
40
 
50
41
  if bool.is_a?(Keisan::AST::Boolean)
51
42
  if bool.value
52
- ast_function.children[1].simplify(context)
43
+ ast_function.children[1].to_node.simplify(context)
53
44
  else
54
45
  ast_function.children[2].to_node.simplify(context)
55
46
  end
@@ -57,6 +48,18 @@ module Keisan
57
48
  ast_function
58
49
  end
59
50
  end
51
+
52
+ def differentiate(ast_function, variable, context = nil)
53
+ context ||= Context.new
54
+ Keisan::AST::Function.new(
55
+ [
56
+ ast_function.children[0],
57
+ ast_function.children[1].differentiate(variable, context),
58
+ ast_function.children[2].differentiate(variable, context)
59
+ ],
60
+ @name
61
+ )
62
+ end
60
63
  end
61
64
  end
62
65
  end