rb_maxima 0.1.0
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.
- checksums.yaml +7 -0
- data/lib/maxima.rb +22 -0
- data/lib/maxima/boolean.rb +12 -0
- data/lib/maxima/command.rb +179 -0
- data/lib/maxima/complex.rb +85 -0
- data/lib/maxima/core.rb +153 -0
- data/lib/maxima/float.rb +45 -0
- data/lib/maxima/function.rb +91 -0
- data/lib/maxima/helper.rb +63 -0
- data/lib/maxima/histogram.rb +78 -0
- data/lib/maxima/polynomial.rb +50 -0
- data/lib/maxima/rational.rb +39 -0
- data/lib/maxima/unit.rb +124 -0
- data/lib/maxima/version.rb +3 -0
- metadata +189 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: c2001c84271ebb26844cf1217d8e5ae5c7844987c65d4fe14723700996e45d6a
         | 
| 4 | 
            +
              data.tar.gz: 579008bc8d879af91514e27597a6887c9cbcb9aebfac62091b4354ce0e304b0c
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 4bee94b71cc6bb0e107c2f0408d88a6fada488868cdf4c25d005f5275f4ef212e1a75ea8212fea9a301daa703c0fb8849b0e9189a4a4e97cd7baadf702cae969
         | 
| 7 | 
            +
              data.tar.gz: 46afece761a21a62d9d7d018317d2ca74470fe2069028c7af4d767ae2ced93a56ce2dd90da91c2a5c48fd3d0c7dbc830aa6bbe958fbe6d2f9b87df2d66036be2
         | 
    
        data/lib/maxima.rb
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require "securerandom"
         | 
| 2 | 
            +
            require "set"
         | 
| 3 | 
            +
            require "csv"
         | 
| 4 | 
            +
            require "numo/gnuplot"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require "maxima/version"
         | 
| 7 | 
            +
            require "maxima/core"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            require "maxima/command"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require "maxima/unit"
         | 
| 12 | 
            +
            require "maxima/float"
         | 
| 13 | 
            +
            require "maxima/rational"
         | 
| 14 | 
            +
            require "maxima/complex"
         | 
| 15 | 
            +
            require "maxima/boolean"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            require "maxima/function"
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require "maxima/histogram"
         | 
| 20 | 
            +
            require "maxima/polynomial"
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            require "maxima/helper"
         | 
| @@ -0,0 +1,179 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Command
         | 
| 3 | 
            +
                attr_accessor :dependencies, :commands, :assigned_variables, :options
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize
         | 
| 6 | 
            +
                  @dependencies = []
         | 
| 7 | 
            +
                  @assigned_variables = Set.new()
         | 
| 8 | 
            +
                  @commands = []
         | 
| 9 | 
            +
                  @options = {}
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def self.output(*v)
         | 
| 13 | 
            +
                  Command.new()
         | 
| 14 | 
            +
                    .with_options(
         | 
| 15 | 
            +
                      use_fast_arrays: true,
         | 
| 16 | 
            +
                      float: true,
         | 
| 17 | 
            +
                    ).tap do |c|
         | 
| 18 | 
            +
                    yield c
         | 
| 19 | 
            +
                  end.output_variables(*v)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def with_options(options)
         | 
| 23 | 
            +
                  self.tap { @options.merge!(options) }
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def add_variable(variable)
         | 
| 27 | 
            +
                  case variable
         | 
| 28 | 
            +
                  when Enumerable
         | 
| 29 | 
            +
                    @assigned_variables.merge(variable)
         | 
| 30 | 
            +
                  else
         | 
| 31 | 
            +
                    @assigned_variables.add(variable)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def let(variable_or_variables, expression, *unary_operations, **unary_operations_options)
         | 
| 36 | 
            +
                  add_variable(variable_or_variables)
         | 
| 37 | 
            +
                  expression = _apply_unary_operations(expression, *unary_operations, **unary_operations_options)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  variable   = Maxima.mformat(variable_or_variables)
         | 
| 40 | 
            +
                  expression = Maxima.mformat(expression)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  _let(variable, expression)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def let_simplified(variable, expression, *unary_operations, **unary_operations_options)
         | 
| 46 | 
            +
                  unary_operations_options[:expand] = true
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  let(
         | 
| 49 | 
            +
                    variable,
         | 
| 50 | 
            +
                    expression,
         | 
| 51 | 
            +
                    *unary_operations,
         | 
| 52 | 
            +
                    **unary_operations_options
         | 
| 53 | 
            +
                  )
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def _let(variable, expression)
         | 
| 57 | 
            +
                  @commands << "#{variable} : #{expression}"
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def <<(expression)
         | 
| 61 | 
            +
                  @commands << expression
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def _apply_unary_operations(expression, *unary_operations, **unary_operations_options)
         | 
| 65 | 
            +
                  unary_operations = Set.new(unary_operations)
         | 
| 66 | 
            +
                  unary_operations_options.map do |option, is_enabled|
         | 
| 67 | 
            +
                    unary_operations.add(option) if is_enabled
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  [
         | 
| 71 | 
            +
                    unary_operations.map { |unary_operation| "#{unary_operation}(" },
         | 
| 72 | 
            +
                    expression,
         | 
| 73 | 
            +
                    ")" * unary_operations.count
         | 
| 74 | 
            +
                  ].join()
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                OPTIONS = {
         | 
| 78 | 
            +
                  float:           -> (enabled) { "float: #{enabled}" },
         | 
| 79 | 
            +
                  use_fast_arrays: -> (enabled) { "use_fast_arrays: #{enabled}" }
         | 
| 80 | 
            +
                }
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def options_commands()
         | 
| 83 | 
            +
                  [].each do |commands|
         | 
| 84 | 
            +
                    @options.each do |option, configuration|
         | 
| 85 | 
            +
                      next unless OPTIONS[option]
         | 
| 86 | 
            +
                      commands << OPTIONS[option].call(configuration)
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                def run_shell(extract_variables = nil, debug: ENV["DEBUG"])
         | 
| 92 | 
            +
                  inputs = [*dependencies_input, *options_commands(), *@commands]
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  inputs << "grind(#{extract_variables.join(', ')})" if extract_variables
         | 
| 95 | 
            +
                  input = inputs.join("$\n") + "$\n"
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  output = with_debug(debug, input) do
         | 
| 98 | 
            +
                    Helper.spawn_silenced_shell_process("maxima --quiet --run-string '#{input}'")
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  {
         | 
| 102 | 
            +
                    input:  input,
         | 
| 103 | 
            +
                    output: output
         | 
| 104 | 
            +
                  }
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def with_debug(debug, input)
         | 
| 108 | 
            +
                  return yield unless debug
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  uuid = SecureRandom.uuid[0..6]
         | 
| 111 | 
            +
                  puts input.lines.map { |s| "#{uuid}>>>\t#{s}" }.join
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  yield.tap do |output|
         | 
| 114 | 
            +
                    puts output.lines.map { |s| "#{uuid}<<<\t#{s}" }.join
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                MATCH_REGEX = -> (eol_maxima_characters) { /(?<=\(%i#{eol_maxima_characters}\)).*(?=\$|\Z)/m.freeze }
         | 
| 119 | 
            +
                GSUB_REGEX = Regexp.union(/\s+/, /\(%(i|o)\d\)|done/)
         | 
| 120 | 
            +
                def self.extract_outputs(output, eol_maxima_characters)
         | 
| 121 | 
            +
                  MATCH_REGEX.call(eol_maxima_characters)
         | 
| 122 | 
            +
                    .match(output)[0]
         | 
| 123 | 
            +
                    .gsub(GSUB_REGEX, "")
         | 
| 124 | 
            +
                    .split("$")
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def self.convert_output_to_variables(output_variable_map, raw_output)
         | 
| 128 | 
            +
                  {}.tap do |result|
         | 
| 129 | 
            +
                    output_variable_map.each_with_index do |(variable, klazz), index|
         | 
| 130 | 
            +
                      output = raw_output[index]
         | 
| 131 | 
            +
                      output = klazz.respond_to?(:parse) ? klazz.parse(output) : klazz.new(output)
         | 
| 132 | 
            +
                      result[variable] = output
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                def self.expand_output_variable_map(output_variable_map)
         | 
| 138 | 
            +
                  {}.tap do |expanded_output_variable_map|
         | 
| 139 | 
            +
                    add_key = -> (k,v) {
         | 
| 140 | 
            +
                      if expanded_output_variable_map.has_key?(k)
         | 
| 141 | 
            +
                        throw :key_used_twice
         | 
| 142 | 
            +
                      else
         | 
| 143 | 
            +
                        expanded_output_variable_map[k] = v
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
                    }
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    output_variable_map.each do |output_key, parsed_into_class|
         | 
| 148 | 
            +
                      case output_key
         | 
| 149 | 
            +
                      when Array
         | 
| 150 | 
            +
                        output_key.each do |output_subkey|
         | 
| 151 | 
            +
                          add_key.call(output_subkey, parsed_into_class)
         | 
| 152 | 
            +
                        end
         | 
| 153 | 
            +
                      else
         | 
| 154 | 
            +
                        add_key.call(output_key, parsed_into_class)
         | 
| 155 | 
            +
                      end
         | 
| 156 | 
            +
                    end
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                def self.eol_maxima_characters(input)
         | 
| 161 | 
            +
                  input.count("$") + input.count(";")
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def output_variables(output_variable_map)
         | 
| 165 | 
            +
                  output_variable_map = Command.expand_output_variable_map(output_variable_map)
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  input, output = run_shell(output_variable_map.keys).values_at(:input, :output)
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  eol_maxima_characters = Command.eol_maxima_characters(input)
         | 
| 170 | 
            +
                  extracted_outputs = Command.extract_outputs(output, eol_maxima_characters)
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  Command.convert_output_to_variables(output_variable_map, extracted_outputs)
         | 
| 173 | 
            +
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                def dependencies_input
         | 
| 176 | 
            +
                  @dependencies.map { |s| "load(#{s})" }
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
            end
         | 
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              def self.Complex(real, imaginary)
         | 
| 4 | 
            +
                Complex.new(real, imaginary)
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class Complex < Unit
         | 
| 8 | 
            +
                attr_accessor :real, :imaginary
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(real, imaginary, **options)
         | 
| 11 | 
            +
                  super(**options)
         | 
| 12 | 
            +
                  @real = real
         | 
| 13 | 
            +
                  @imaginary = imaginary
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                WHITESPACE_OR_PARENTHESES_REGEX = /(\s|\(|\))/
         | 
| 17 | 
            +
                COMPLEX_REGEX = /(-?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)((?:\*)?(?:%)?i)?/
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def self.parse(maxima_output)
         | 
| 20 | 
            +
                  maxima_output = maxima_output.to_s unless maxima_output.is_a?(String)
         | 
| 21 | 
            +
                  string = maxima_output.gsub(WHITESPACE_OR_PARENTHESES_REGEX, "")
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  real = 0
         | 
| 24 | 
            +
                  imaginary = 0
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  string.scan(COMPLEX_REGEX) do |(float, is_imaginary)|
         | 
| 27 | 
            +
                    if is_imaginary
         | 
| 28 | 
            +
                      imaginary += float.to_f
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      real += float.to_f
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  if imaginary == 0
         | 
| 35 | 
            +
                    Float.new(real, maxima_output: maxima_output)
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    Complex.new(real, imaginary, maxima_output: maxima_output)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def pretty_to_s
         | 
| 42 | 
            +
                  if real == 0
         | 
| 43 | 
            +
                    "#{@imaginary}i"
         | 
| 44 | 
            +
                  else
         | 
| 45 | 
            +
                    operand = @real.positive? ? '+' : '-'
         | 
| 46 | 
            +
                    "#{@imaginary}i #{operand} #{@real.abs}"
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def to_maxima_input
         | 
| 51 | 
            +
                  return "#{@imaginary} * %i" if real == 0
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  operand = @real.positive? ? '+' : '-'
         | 
| 54 | 
            +
                  "(#{@imaginary} * %i #{operand} #{@real.abs})"
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def ==(other)
         | 
| 58 | 
            +
                  @real == other.real && @imaginary == other.imaginary
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Definitions are somewhat contrived and not per se mathematically accurate.
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def positive?
         | 
| 64 | 
            +
                  !negative?
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                # At least one scalar must be negative & the others non positive
         | 
| 68 | 
            +
                def negative?
         | 
| 69 | 
            +
                  (@real < 0 && @imaginary <= 0) ||
         | 
| 70 | 
            +
                  (@imaginary < 0 && @real <= 0)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def zero?
         | 
| 74 | 
            +
                  @real == 0 && @imaginary == 0
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def imaginary?
         | 
| 78 | 
            +
                  @imaginary != 0
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def real?
         | 
| 82 | 
            +
                  @imaginary == 0
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
    
        data/lib/maxima/core.rb
    ADDED
    
    | @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              def self.bin_op(e_1, e_2, bin_op)
         | 
| 3 | 
            +
                Maxima::Function.new("((#{Maxima.mformat(e_1)}) #{bin_op} (#{Maxima.mformat(e_2)}))")
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def self.cobyla(minimize_function, variables, constraint_function, initial_guess)
         | 
| 7 | 
            +
                Command.output(variables => Unit) do |c|
         | 
| 8 | 
            +
                  initial_guess = mformat(initial_guess)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  c.dependencies << "cobyla"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  c.let :output, "fmin_cobyla(#{minimize_function}, #{mformat(variables)}, #{initial_guess},constraints = #{constraint_function})"
         | 
| 13 | 
            +
                  c.let variables, "sublis(output[1], #{variables})"
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def self.interpolate(array)
         | 
| 18 | 
            +
                Command.output(lagrangian: Function) do |c|
         | 
| 19 | 
            +
                  c.dependencies << "interpol"
         | 
| 20 | 
            +
                  c.let :array, array.to_a
         | 
| 21 | 
            +
                  c.let_simplified :lagrangian, "lagrange(array)"
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              def self.integrate(function, t0 = nil, t1 = nil, v: "x")
         | 
| 26 | 
            +
                expression = (t0 && t1) ? "integrate(function, #{v}, #{t0}, #{t1})" : "integrate(function, #{v})"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                Command.output(integral: Function) do |c|
         | 
| 29 | 
            +
                  c.let :function, function
         | 
| 30 | 
            +
                  c.let :integral, expression
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              def self.diff(function, v: "x")
         | 
| 35 | 
            +
                Command.output(diff: Function) do |c|
         | 
| 36 | 
            +
                  c.let :function, function
         | 
| 37 | 
            +
                  c.let :diff, "derivative(function, #{v})"
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def self.plot(*maxima_objects, from_x: nil, from_y: nil)
         | 
| 42 | 
            +
                maxima_objects << [[from_x.min, 0], [from_x.max, 0]] if from_x
         | 
| 43 | 
            +
                maxima_objects << [[0, from_y.min], [0, to_y.max]] if from_y
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                maxima_objects = maxima_objects.map do |k|
         | 
| 46 | 
            +
                  if k.respond_to?(:to_gnu_plot)
         | 
| 47 | 
            +
                    k.to_gnu_plot
         | 
| 48 | 
            +
                  elsif k.is_a?(Array) && !k.first.is_a?(String)
         | 
| 49 | 
            +
                    [*k.transpose, w: "points"]
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    k
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                Helper.stfu do
         | 
| 56 | 
            +
                  Numo.gnuplot do |c|
         | 
| 57 | 
            +
                    c.debug_on
         | 
| 58 | 
            +
                    c.set title: "Maxima Plot"
         | 
| 59 | 
            +
                    c.plot(*maxima_objects)
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
              def self.lsquares_estimation(points, variables, equation, outputs, equation_for_mse: equation)
         | 
| 65 | 
            +
                Command.output(outputs => Complex, :mse => Float) do |c|
         | 
| 66 | 
            +
                  formatted_points    = points.map { |a| mformat(a) }.join(",")
         | 
| 67 | 
            +
                  formatted_variables = mformat(variables)
         | 
| 68 | 
            +
                  formatted_outputs   = mformat(outputs)
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  c.dependencies << "lsquares"
         | 
| 71 | 
            +
                  c.let :M, "matrix(#{formatted_points})"
         | 
| 72 | 
            +
                  c.let :lsquares_estimation, "lsquares_estimates(M, #{formatted_variables}, #{equation}, #{formatted_outputs})"
         | 
| 73 | 
            +
                  if outputs.count == 1
         | 
| 74 | 
            +
                    c << "map (lhs, lsquares_estimation) :: map (rhs, lsquares_estimation)"
         | 
| 75 | 
            +
                  else
         | 
| 76 | 
            +
                    c << "map (lhs, lsquares_estimation[1]) :: map (rhs, lsquares_estimation[1])"
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                  c.let :mse, "lsquares_residual_mse(M, #{formatted_variables}, #{equation_for_mse}, first (lsquares_estimation))"
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              def self.equivalence(expression_one, expression_two)
         | 
| 83 | 
            +
                Command.output(:is_equal => Boolean) do |c|
         | 
| 84 | 
            +
                  formatted_expression_one = mformat(expression_one)
         | 
| 85 | 
            +
                  formatted_expression_two = mformat(expression_two)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  c.let :is_equal, "is(equal(#{formatted_expression_one}, #{formatted_expression_two}))"
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              def self.lagrangian(minimize_function, variables, constraint_function, initial_guess, iterations: 5)
         | 
| 92 | 
            +
                Command.output(variables => Unit) do |c|
         | 
| 93 | 
            +
                  initial_guess       = mformat(initial_guess)
         | 
| 94 | 
            +
                  constraint_function = mformat(Array(constraint_function))
         | 
| 95 | 
            +
                  optional_args       = mformat(niter: iterations)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  c.dependencies << "lbfgs"
         | 
| 98 | 
            +
                  c.dependencies << "augmented_lagrangian"
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  c.let :output, "augmented_lagrangian_method(#{minimize_function}, #{variables}, #{constraint_function}, #{initial_guess}, #{optional_args})"
         | 
| 101 | 
            +
                  c.let variables, "sublis(output[1], #{variables})"
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
              end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
              def self.mformat(variable)
         | 
| 106 | 
            +
                case variable
         | 
| 107 | 
            +
                when String, Symbol
         | 
| 108 | 
            +
                  variable # the only truly `valid` input is a string
         | 
| 109 | 
            +
                when Hash
         | 
| 110 | 
            +
                  variable.map do |key,value|
         | 
| 111 | 
            +
                    "#{mformat(key)} = #{mformat(value)}"
         | 
| 112 | 
            +
                  end.join(", ")
         | 
| 113 | 
            +
                when Array
         | 
| 114 | 
            +
                  "[" + variable.map { |v| mformat(v) }.join(",") + "]"
         | 
| 115 | 
            +
                when ::Complex
         | 
| 116 | 
            +
                  mformat Complex.new(variable, variable.real, variable.imag)
         | 
| 117 | 
            +
                when Numeric
         | 
| 118 | 
            +
                  mformat Float(variable)
         | 
| 119 | 
            +
                when Complex, Float, Function
         | 
| 120 | 
            +
                  variable.to_maxima_input
         | 
| 121 | 
            +
                when nil
         | 
| 122 | 
            +
                  throw :cannot_format_nil
         | 
| 123 | 
            +
                else
         | 
| 124 | 
            +
                  variable
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
              def self.solve_polynomial(equations, variables_to_solve_for, ignore: nil, real_only: false)
         | 
| 129 | 
            +
                # regex match extract
         | 
| 130 | 
            +
                output = Command
         | 
| 131 | 
            +
                           .with_options(real_only: real_only)
         | 
| 132 | 
            +
                           .output(output: Unit) do |c|
         | 
| 133 | 
            +
                  variables = mformat(Array(variables_to_solve_for))
         | 
| 134 | 
            +
                  equations = mformat(Array(equations))
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                  c.let :output, "algsys(#{equations},#{variables})"
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                output = output[:output]
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                variables_to_solve_for -= Array(ignore)
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                variable_regexes = variables_to_solve_for.map do |variable|
         | 
| 144 | 
            +
                  "#{variable}=(-?.*?)(?:\,|\\])"
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                regex = Regexp.new(variable_regexes.reduce(&:+))
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                output = output.to_s.gsub(" ", "")
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                output.scan(regex).map { |row| variables_to_solve_for.zip(row.map { |v| Unit.parse(v) }).to_h }
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
    
        data/lib/maxima/float.rb
    ADDED
    
    | @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              def self.Float(real)
         | 
| 4 | 
            +
                Float.new(real)
         | 
| 5 | 
            +
              end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              class Float < Unit
         | 
| 8 | 
            +
                ZERO = Float.new(0).freeze
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                attr_accessor :real
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(real = nil, **options)
         | 
| 13 | 
            +
                  options[:maxima_output] ||= real&.to_s
         | 
| 14 | 
            +
                  super(**options)
         | 
| 15 | 
            +
                  @real = (real || @maxima_output).to_f
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def <=>(other)
         | 
| 19 | 
            +
                  case other
         | 
| 20 | 
            +
                  when ::Float
         | 
| 21 | 
            +
                    @real <=> other
         | 
| 22 | 
            +
                  when Float
         | 
| 23 | 
            +
                    @real <=> other.real
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    -1
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def to_f
         | 
| 30 | 
            +
                  @real
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def real?
         | 
| 34 | 
            +
                  true
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def imaginary?
         | 
| 38 | 
            +
                  false
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def derivative(v: nil)
         | 
| 42 | 
            +
                  ZERO
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
| @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Function < Unit
         | 
| 3 | 
            +
                attr_accessor :string, :variables
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(string, variables = nil, **options)
         | 
| 6 | 
            +
                  string = string.to_s
         | 
| 7 | 
            +
                  options[:maxima_output] ||= string
         | 
| 8 | 
            +
                  super(**options)
         | 
| 9 | 
            +
                  @variables = variables || Function.variables_in_string(string)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # This strategy fails for functions (cos etc.). However, that does not impact it's actual usage.
         | 
| 13 | 
            +
                VARIABLE_REGEX = /[%|a-z|A-Z]+/.freeze
         | 
| 14 | 
            +
                IGNORE_VARIABLES = %w(%e %i).freeze
         | 
| 15 | 
            +
                def self.variables_in_string(string)
         | 
| 16 | 
            +
                  (string.scan(VARIABLE_REGEX) - IGNORE_VARIABLES).to_set
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def integral(t0 = nil, t1 = nil, v: "x")
         | 
| 20 | 
            +
                  if t0 && t1
         | 
| 21 | 
            +
                    Maxima.integrate(to_maxima_input, t0, t1, v: v)[:integral]
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    Maxima.integrate(to_maxima_input, v: v)[:integral]
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def definite_integral(t0, t1, v: "x")
         | 
| 28 | 
            +
                  i_v = self.integral(v: v)
         | 
| 29 | 
            +
                  i_v.at(v => t1) - i_v.at(v => t0)
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def derivative(variable = nil, v: "x")
         | 
| 33 | 
            +
                  Maxima.diff(to_maxima_input, v: (variable || v))[:diff]
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def between(min, max, steps)
         | 
| 37 | 
            +
                  step = (max - min).fdiv(steps)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  Command.output(r: Histogram) do |c|
         | 
| 40 | 
            +
                    c.let :r, "makelist([x,float(#{self})],x, #{min}, #{max}, #{step})"
         | 
| 41 | 
            +
                  end[:r]
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                # Assume what we get is what we need
         | 
| 45 | 
            +
                def self.parse(string)
         | 
| 46 | 
            +
                  variables = variables_in_string(string)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  if variables.any?
         | 
| 49 | 
            +
                    Function.new(string, variables)
         | 
| 50 | 
            +
                  else
         | 
| 51 | 
            +
                    Unit.parse_float(string)
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                rescue
         | 
| 54 | 
            +
                  nil
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def gnu_plot_text
         | 
| 58 | 
            +
                  super.gsub("^", "**")
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def gnu_plot_w
         | 
| 62 | 
            +
                  "lines"
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def to_maxima_input
         | 
| 66 | 
            +
                  self.to_s
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def at(v)
         | 
| 70 | 
            +
                  s = self.to_s.dup
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  case v
         | 
| 73 | 
            +
                  when Hash
         | 
| 74 | 
            +
                    v.each do |k,t|
         | 
| 75 | 
            +
                      k = k.to_s
         | 
| 76 | 
            +
                      if @variables.include?(k)
         | 
| 77 | 
            +
                        s.gsub!(k, "(#{t})")
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
                  else
         | 
| 81 | 
            +
                    throw :must_specify_variables_in_hash if @variables.length != 1
         | 
| 82 | 
            +
                    s.gsub!(@variables.first, "(#{v})")
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                  Function.parse(s).simplified
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def ==(other)
         | 
| 88 | 
            +
                  to_s == other.to_s
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            $interrupted = false
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Signal.trap("INT") {
         | 
| 4 | 
            +
              $interrupted = true
         | 
| 5 | 
            +
            }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Maxima
         | 
| 8 | 
            +
              module Helper
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def self.stfu
         | 
| 11 | 
            +
                  result = nil
         | 
| 12 | 
            +
                  begin
         | 
| 13 | 
            +
                    orig_stderr = $stderr.clone
         | 
| 14 | 
            +
                    orig_stdout = $stdout.clone
         | 
| 15 | 
            +
                    $stderr.reopen File.new('/dev/null', 'w')
         | 
| 16 | 
            +
                    $stdout.reopen File.new('/dev/null', 'w')
         | 
| 17 | 
            +
                    result = yield
         | 
| 18 | 
            +
                  rescue Exception => e
         | 
| 19 | 
            +
                    if $interrupted
         | 
| 20 | 
            +
                      throw :interrupted
         | 
| 21 | 
            +
                    else
         | 
| 22 | 
            +
                      $stdout.reopen orig_stdout
         | 
| 23 | 
            +
                      $stderr.reopen orig_stderr
         | 
| 24 | 
            +
                      raise e
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  ensure
         | 
| 27 | 
            +
                    if $interrupted
         | 
| 28 | 
            +
                      throw :interrupted
         | 
| 29 | 
            +
                    else
         | 
| 30 | 
            +
                      $stdout.reopen orig_stdout
         | 
| 31 | 
            +
                      $stderr.reopen orig_stderr
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                  if $interrupted
         | 
| 35 | 
            +
                    throw :interrupted
         | 
| 36 | 
            +
                  else
         | 
| 37 | 
            +
                    result
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def self.spawn_silenced_shell_process(shell_command)
         | 
| 42 | 
            +
                  stfu do
         | 
| 43 | 
            +
                    rout, wout = IO.pipe
         | 
| 44 | 
            +
                    result = nil
         | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                      pid = Process.spawn(shell_command, out: wout)
         | 
| 47 | 
            +
                      _, _ = Process.wait2(pid)
         | 
| 48 | 
            +
                    ensure
         | 
| 49 | 
            +
                      wout.close
         | 
| 50 | 
            +
                      if $interrupted
         | 
| 51 | 
            +
                        rout.close
         | 
| 52 | 
            +
                        throw :interrupted
         | 
| 53 | 
            +
                      else
         | 
| 54 | 
            +
                        result = rout.readlines.join("\n")
         | 
| 55 | 
            +
                        rout.close
         | 
| 56 | 
            +
                      end
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                    result
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         | 
| @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Histogram < Unit
         | 
| 3 | 
            +
                attr_accessor :points
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def self.between(min, max, function = ->(x) { x }, steps = 100)
         | 
| 6 | 
            +
                  Histogram.new(
         | 
| 7 | 
            +
                    *[].tap do |points|
         | 
| 8 | 
            +
                      (min..max).step((max - min).fdiv(steps)).each do |x|
         | 
| 9 | 
            +
                        points.push([x, function.call(x)])
         | 
| 10 | 
            +
                      end
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  )
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def polynomial_fit(degrees)
         | 
| 16 | 
            +
                  Polynomial.fit(self, degrees)[:function]
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def self.from_csv(csv)
         | 
| 20 | 
            +
                  Histogram.new(
         | 
| 21 | 
            +
                    *CSV.read(csv).map { |array| array.map(&:to_i) }
         | 
| 22 | 
            +
                  )
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def self.parse(s)
         | 
| 26 | 
            +
                  Histogram.new((eval s), maxima_output: s)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def initialize(*points, **options)
         | 
| 30 | 
            +
                  super(**options)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  while points.is_a?(Array) && points.first.is_a?(Array) && points.first.first.is_a?(Array)
         | 
| 33 | 
            +
                    points = points.flatten(1)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  unless points.is_a?(Array) && points.first.is_a?(Array) && points.first.length == 2
         | 
| 37 | 
            +
                    throw :invalid_histogram_points
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  @points = points
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def to_percentage()
         | 
| 44 | 
            +
                  @to_percentage ||=
         | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                      sum = points.sum(&:x)
         | 
| 47 | 
            +
                      Histogram.new(
         | 
| 48 | 
            +
                        points.map do |point|
         | 
| 49 | 
            +
                          Point.new(
         | 
| 50 | 
            +
                            point.x,
         | 
| 51 | 
            +
                            point.y.fdiv(sum)
         | 
| 52 | 
            +
                          )
         | 
| 53 | 
            +
                        end
         | 
| 54 | 
            +
                      )
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def to_a
         | 
| 59 | 
            +
                  @points
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def integral()
         | 
| 63 | 
            +
                  begin
         | 
| 64 | 
            +
                    sum = 0
         | 
| 65 | 
            +
                    Histogram.new(
         | 
| 66 | 
            +
                      points.map do |(one, two)|
         | 
| 67 | 
            +
                        sum += two
         | 
| 68 | 
            +
                        [one, sum]
         | 
| 69 | 
            +
                      end
         | 
| 70 | 
            +
                    )
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def to_gnu_plot()
         | 
| 75 | 
            +
                  [*points.map(&:to_a).transpose, w: "points"]
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
            end
         | 
| @@ -0,0 +1,50 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Polynomial < Unit
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def self.fit(histogram, degrees)
         | 
| 5 | 
            +
                  throw :degrees_must_be_zero_or_positive if degrees < 0
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  equation_string, variables = polynomial_equation(degrees)
         | 
| 8 | 
            +
                  results = Maxima.lsquares_estimation(histogram.to_a, [:x, :y], "y = #{equation_string}", variables)
         | 
| 9 | 
            +
                  mse = results.delete(:mse)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  results.each do |variable, value|
         | 
| 12 | 
            +
                    equation_string.gsub!("(#{variable})", value.to_s)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  {
         | 
| 16 | 
            +
                    function: Maxima::Function.new(equation_string),
         | 
| 17 | 
            +
                    mse: mse
         | 
| 18 | 
            +
                  }
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def self.fit_function(min, max, x: "x")
         | 
| 22 | 
            +
                  Enumerator.new do |e|
         | 
| 23 | 
            +
                    (min..max).each do |degrees|
         | 
| 24 | 
            +
                      equation_string, variables = polynomial_equation(degrees, f_of: x)
         | 
| 25 | 
            +
                      e.<<(equation_string, variables)
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def self.polynomial_equation(degrees, f_of: "x")
         | 
| 31 | 
            +
                  constant_variables = degrees.times.map { |degree| "c#{degree}" }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  free_polynomial = constant_variables.each do |degree|
         | 
| 34 | 
            +
                    case degree
         | 
| 35 | 
            +
                    when 0
         | 
| 36 | 
            +
                      "(#{constant_variable})"
         | 
| 37 | 
            +
                    when 1
         | 
| 38 | 
            +
                      "(#{constant_variable}) * #{f_of}"
         | 
| 39 | 
            +
                    else
         | 
| 40 | 
            +
                      "(#{constant_variable}) * #{f_of} ^ #{degree}"
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  [
         | 
| 45 | 
            +
                    free_polynomial.join(" + "),
         | 
| 46 | 
            +
                    variables
         | 
| 47 | 
            +
                  ]
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Rational < Unit
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                attr_accessor :numerator, :denominator
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def initialize(string, numerator, denominator, title = nil)
         | 
| 7 | 
            +
                  super(string, title)
         | 
| 8 | 
            +
                  @numerator = numerator
         | 
| 9 | 
            +
                  @denominator = denominator
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                REGEX = /(\d+)\/(\d+)/
         | 
| 13 | 
            +
                def self.parse(input_string)
         | 
| 14 | 
            +
                  _, numerator, denominator = REGEX.match(input_string).to_a
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  return nil if numerator.nil? || denominator.nil?
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  if numerator == 0
         | 
| 19 | 
            +
                    Float.new(input_string, 0)
         | 
| 20 | 
            +
                  else
         | 
| 21 | 
            +
                    Rational.new(input_string, numerator.to_i, denominator.to_i)
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                rescue StandardError
         | 
| 24 | 
            +
                  nil
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def real?
         | 
| 28 | 
            +
                  true
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def imaginary?
         | 
| 32 | 
            +
                  false
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def to_f
         | 
| 36 | 
            +
                  @to_f ||= numerator.fdiv(denominator)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
    
        data/lib/maxima/unit.rb
    ADDED
    
    | @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            module Maxima
         | 
| 2 | 
            +
              class Unit
         | 
| 3 | 
            +
                attr_accessor :maxima_output, :plot_title
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(inline_maxima_output = nil, plot_title: nil, maxima_output: nil)
         | 
| 6 | 
            +
                  @maxima_output = inline_maxima_output || maxima_output
         | 
| 7 | 
            +
                  @plot_title    = plot_title
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def inspect
         | 
| 11 | 
            +
                  if plot_title.nil? || plot_title == ""
         | 
| 12 | 
            +
                    "#{self.class}(#{self})"
         | 
| 13 | 
            +
                  else
         | 
| 14 | 
            +
                    "#{self.class}[#{plot_title}](#{self})"
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def self.parse(m)
         | 
| 19 | 
            +
                  Function.parse(m)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def self.parse_float(m)
         | 
| 23 | 
            +
                  Rational.parse(m) || Complex.parse(m)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def to_s
         | 
| 27 | 
            +
                  @maxima_output
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def with_plot_title(plot_title)
         | 
| 31 | 
            +
                  self.class.new(@maxima_output, plot_title)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                %w(* / ** + -).each do |operation|
         | 
| 35 | 
            +
                  define_method(operation) do |other|
         | 
| 36 | 
            +
                    Maxima.bin_op(self, other, operation)
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                # def absolute_difference(other)
         | 
| 41 | 
            +
                #   Function.new("abs(#{self - other})")
         | 
| 42 | 
            +
                # end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def simplified
         | 
| 45 | 
            +
                  @simplified ||= through_maxima(:expand)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # ~~ *unary_operations, **unary_operations_options
         | 
| 49 | 
            +
                def through_maxima(*array_options, **options)
         | 
| 50 | 
            +
                  @after_maxima ||= Command.output(itself: Unit) do |c|
         | 
| 51 | 
            +
                    c.let :itself, self.to_s, *array_options, **options
         | 
| 52 | 
            +
                  end[:itself]
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def simplified!
         | 
| 56 | 
            +
                  simplified.to_s
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def to_maxima_input
         | 
| 60 | 
            +
                  to_s
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def to_gnu_plot
         | 
| 64 | 
            +
                  [gnu_plot_text, gnu_plot_options]
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def at(_)
         | 
| 68 | 
            +
                  self
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def to_pdf(t0, t1, v: "x")
         | 
| 72 | 
            +
                  (self / integral(t0, t1)).definite_integral(t0, v)
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                # private
         | 
| 76 | 
            +
                def gnu_plot_text
         | 
| 77 | 
            +
                  to_s
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def gnu_plot_options
         | 
| 81 | 
            +
                  { w: gnu_plot_w }.tap do |options|
         | 
| 82 | 
            +
                    options[:plot_title] = @plot_title if @plot_title
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def gnu_plot_w
         | 
| 87 | 
            +
                  "points"
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def to_f
         | 
| 91 | 
            +
                  throw "cannot_cast_#{self.class}_to_float"
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def real?
         | 
| 95 | 
            +
                  throw "real_is_undecidable_for_#{self.class}"
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def imaginary?
         | 
| 99 | 
            +
                  throw "imaginary_is_undecidable_for_#{self.class}"
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def positive?
         | 
| 103 | 
            +
                  to_f > 0
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def zero?
         | 
| 107 | 
            +
                  to_f == 0
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def negative?
         | 
| 111 | 
            +
                  to_f < 0
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                # Basic string identity
         | 
| 115 | 
            +
                def ==(other)
         | 
| 116 | 
            +
                  (self <=> other) == 0
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                # True mathematical equivalence
         | 
| 120 | 
            +
                def ===(other)
         | 
| 121 | 
            +
                  (self == other) || Maxima.equivalence(self, other)[:is_equal]
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,189 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: rb_maxima
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Daniel Ackerman
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2018-06-18 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: bundler
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '1.16'
         | 
| 20 | 
            +
              type: :development
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '1.16'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: rake
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '10.0'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '10.0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: pry
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0.10'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0.10'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: rspec
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '3.0'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '3.0'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: guard
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '2.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '2.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: guard-rspec
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - "~>"
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '4.7'
         | 
| 90 | 
            +
              type: :development
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - "~>"
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '4.7'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: pry-nav
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0.2'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '0.2'
         | 
| 111 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 112 | 
            +
              name: simplecov
         | 
| 113 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 114 | 
            +
                requirements:
         | 
| 115 | 
            +
                - - "~>"
         | 
| 116 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 117 | 
            +
                    version: '0.16'
         | 
| 118 | 
            +
              type: :development
         | 
| 119 | 
            +
              prerelease: false
         | 
| 120 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 121 | 
            +
                requirements:
         | 
| 122 | 
            +
                - - "~>"
         | 
| 123 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 124 | 
            +
                    version: '0.16'
         | 
| 125 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 126 | 
            +
              name: numo-gnuplot
         | 
| 127 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 128 | 
            +
                requirements:
         | 
| 129 | 
            +
                - - "~>"
         | 
| 130 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 131 | 
            +
                    version: '0.2'
         | 
| 132 | 
            +
              type: :runtime
         | 
| 133 | 
            +
              prerelease: false
         | 
| 134 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 135 | 
            +
                requirements:
         | 
| 136 | 
            +
                - - "~>"
         | 
| 137 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 138 | 
            +
                    version: '0.2'
         | 
| 139 | 
            +
            description: Ruby developers have, for as long as I can remember, had a disheveled
         | 
| 140 | 
            +
              heap of scientific and mathematical libraries - many of which operate in pure ruby
         | 
| 141 | 
            +
              code. Given a problem we either kludge together some cobbled mess or turn to Python/R/etc.
         | 
| 142 | 
            +
              And to this I say no more! `rb_maxima` allows a ruby developer to directly leverage
         | 
| 143 | 
            +
              the unbridled power of the open source, lisp powered, computer algebra system that
         | 
| 144 | 
            +
              is `Maxima`!
         | 
| 145 | 
            +
            email:
         | 
| 146 | 
            +
            - daniel.joseph.ackerman@gmail.com
         | 
| 147 | 
            +
            executables: []
         | 
| 148 | 
            +
            extensions: []
         | 
| 149 | 
            +
            extra_rdoc_files: []
         | 
| 150 | 
            +
            files:
         | 
| 151 | 
            +
            - lib/maxima.rb
         | 
| 152 | 
            +
            - lib/maxima/boolean.rb
         | 
| 153 | 
            +
            - lib/maxima/command.rb
         | 
| 154 | 
            +
            - lib/maxima/complex.rb
         | 
| 155 | 
            +
            - lib/maxima/core.rb
         | 
| 156 | 
            +
            - lib/maxima/float.rb
         | 
| 157 | 
            +
            - lib/maxima/function.rb
         | 
| 158 | 
            +
            - lib/maxima/helper.rb
         | 
| 159 | 
            +
            - lib/maxima/histogram.rb
         | 
| 160 | 
            +
            - lib/maxima/polynomial.rb
         | 
| 161 | 
            +
            - lib/maxima/rational.rb
         | 
| 162 | 
            +
            - lib/maxima/unit.rb
         | 
| 163 | 
            +
            - lib/maxima/version.rb
         | 
| 164 | 
            +
            homepage: 
         | 
| 165 | 
            +
            licenses:
         | 
| 166 | 
            +
            - MIT
         | 
| 167 | 
            +
            metadata: {}
         | 
| 168 | 
            +
            post_install_message: 
         | 
| 169 | 
            +
            rdoc_options: []
         | 
| 170 | 
            +
            require_paths:
         | 
| 171 | 
            +
            - lib
         | 
| 172 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 173 | 
            +
              requirements:
         | 
| 174 | 
            +
              - - ">="
         | 
| 175 | 
            +
                - !ruby/object:Gem::Version
         | 
| 176 | 
            +
                  version: '0'
         | 
| 177 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 178 | 
            +
              requirements:
         | 
| 179 | 
            +
              - - ">="
         | 
| 180 | 
            +
                - !ruby/object:Gem::Version
         | 
| 181 | 
            +
                  version: '0'
         | 
| 182 | 
            +
            requirements: []
         | 
| 183 | 
            +
            rubyforge_project: 
         | 
| 184 | 
            +
            rubygems_version: 2.7.3
         | 
| 185 | 
            +
            signing_key: 
         | 
| 186 | 
            +
            specification_version: 4
         | 
| 187 | 
            +
            summary: A gem that allows for mathematical calculations using the open source `Maxima`
         | 
| 188 | 
            +
              library!
         | 
| 189 | 
            +
            test_files: []
         |