Skip to content

mike-bourgeous/mb-math

Repository files navigation

mb-math

Tests

Mathematical functions such as range-clamping, interpolation, extrapolation, etc. This is companion code to my educational video series about code and sound.

You might also be interested in mb-sound, mb-geometry, and mb-util.

This code is reasonably well-tested, but I recommend using it for non-critical tasks, not for making important decisions or for mission-critical data modeling. Most of the function implementations target 4 to 6 decimals of accuracy, which may be too low for some applications.

Quick start

Clone the repo, follow the standalone installation instructions below, and run bin/console. Use Pry's ls command to get a list of what's available, and the show-source -d command to see a function's documentation).

bin/console
ls
show-source -d clamp

Examples

For typing convenience, and to avoid conflicting with Ruby's top-level Math module, everything lives under MB::M instead of MB::Math.

Smooth interpolation

The interp method blends between two Numerics, Arrays, Hashes, or Numo::NArrays.

Numbers

MB::M.interp(1, 2, 0.5)
# => 1.5

Hashes

a = { x: 0.5, y: 1.5 }
b = { x: 1.0, y: -1.0 }

MB::M.interp(a, b, 0)
# => { x: 0.5, y: 1.5 }

MB::M.interp(a, b, 1)
# => { x: 1.0, y: -1.0 }

MB::M.interp(a, b, 0.5)
# => { x: 0.75, y: 0.25 }

Smoothed interpolation

The :func keyword argument accepts a tweening function. Anything that responds to :call and returns 0.0 if given 0.0 and 1.0 if given 1.0 can be used here.

a = [-1, -1]
b = [1, 2]
steps = [0, 0.25, 0.5, 0.75, 1]
MB::M.interp(a, b, steps, func: MB::M.method(:smootherstep))
# => [[-1, -1], [-0.79296875, -0.689453125], [0.0, 0.5], [0.79296875, 1.689453125], [1, 2]]

Linear extrapolation

Note: extrapolation doesn't work as one might expect with smoothstep or smootherstep.

a = 1
b = 2
MB::M.interp(a, b, 2)
# => 3

Plotting

A simple wrapper around GNUPlot (or any compatible plotter) is provided that can plot to an image file, a graphical window, or a text console.

The MB::M::Plot#plot method takes a Hash mapping dataset names to data values.

# Standard plot
p = MB::M::Plot.terminal(width_fraction: 1, height_fraction: 1, width: 40, height: 15)
p.plot({noise: Numo::SFloat.zeros(10).rand(-0.9, 0.9)}, columns: 1, rows: 1)
   1 +----------------------------+
     |  +  +   +  +  +  +   +  +  |
 0.5 |-+            noise *******-|
     |         *               *  |
     |  *     * *              ** |
   0 |-* *   *  *             * +*|
     | *  *  *   *            *  *|
-0.5 |*+  * *    *           *  +-|
     |     *      *          *    |
     |  +  +   +  ***********  +  |
  -1 +----------------------------+
     0  1  2   3  4  5  6   7  8  9
# Scatter plot
p = MB::M::Plot.terminal(width_fraction: 1, height_fraction: 1, width: 40, height: 20)
points = (0..(Math::PI * 2)).step(Math::PI / 8).map { |a| [ 0.9 * Math.cos(a), 0.9 * Math.sin(a) ] }
p.xrange(-1, 1)
p.plot({ circle: points })
    1 +----------------------------+
      |      +       +      +      |
      |             circle ******* |
      |                            |
  0.5 |-+       *********        +-|
      |        *         **        |
      |       *            *       |
    0 |-+    *              *    +-|
      |       *             *      |
      |       *            *       |
      |        ***       **        |
 -0.5 |-+         *******        +-|
      |                            |
      |                            |
      |      +       +      +      |
   -1 +----------------------------+
     -1    -0.5      0     0.5     1

Quadratic roots

# f(x) = x^2 + 4
MB::M.quadratic_roots(1, 0, 4)
# => [(0.0+2.0i), (0.0-2.0i)]

Scaling ranges

Scales values or Numo::NArrays from one linear range to another, extrapolating for values beyond the end of the range.

MB::M.scale(2, 0..4, 10..12)
# => 11

MB::M.scale(-2, 0..4, 10..12)
# => 9

# Reverse ranges work too
MB::M.scale(Numo::SFloat[0, 1, 2, 3, 4], 1..3, 6..2)
# => Numo::SFloat[8, 6, 4, 2, 0]

Installation and usage

This project contains some useful programs of its own, or you can use it as a Gem (with Git source) in your own projects.

Standalone usage and development

First, install a Ruby version manager like RVM. Using the system's Ruby is not recommended -- that is only for applications that come with the system. You should follow the instructions from https://rvm.io, but here are the basics:

gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
\curl -sSL https://get.rvm.io | bash -s stable

Next, install Ruby. RVM binary rubies are still broken on Ubuntu 20.04.x, so use the --disable-binary option if you are running Ubuntu 20.04.x.

rvm install --disable-binary 2.7.3

You can tell RVM to isolate all your projects and switch Ruby versions automatically by creating .ruby-version and .ruby-gemset files (already present in this project):

cd mb-math
cat .ruby-gemset
cat .ruby-version

Now install dependencies:

bundle install

# Ubuntu/Debian
sudo apt-get install gnuplot-qt

# macOS
brew install gnuplot

Using the project as a Gem

To use mb-math in your own Ruby projects, add this Git repo to your Gemfile:

# your-project/Gemfile
gem 'mb-math', git: 'https://github.com/mike-bourgeous/mb-math.git

Testing

Run rspec to run all tests.

Contributing

Pull requests welcome, though development is focused specifically on the needs of my video series.

License

This project is released under a 2-clause BSD license. See the LICENSE file.

See also

Dependencies

About

Mathematical functions shared by mb-sound, mb-geometry, and other personal projects

Resources

License

Stars

Watchers

Forks