backdoer / warehouse

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Warehouse

In the Manufacturing floor, we have a bunch of machines that run off of mechanical wind power. We routinely move our machines around the manufacturing floor as new machines come out and old ones get retired. We need to get mechanical power from the output of our windmills to our machines through a complex network of gears. Not only does the power have to get to the machine, but it also needs to double the RPM of the input power by the last gear in the system.

This application provides you with the functionality to be able to determine the necessary gear sizes.

Dependencies

  • Python 3 (only for python implementation on master branch)
  • Elixir 1.7
  • Erlang 21

A .tool-versions has been included for installing the necessary dependencies with asdf.

Implementations

Both implementations create a matrix from the inputs and then utilize linear algebra to solve for a system of linear equations.

Porting to Python

Branch: master
This approach ports to a python process and then utilizes python's numpy library to handle the linear algebra. It then uses python's fraction library to resolve the floating decimal point to the closest fraction within a denominator of 10. This implementation works very well with large datasets (4x faster than the native elixir implementation and over 100x more memory efficient; see benchmarking)

Setup

Run mix deps.get && pip3 install --user --requirement requirements.txt

Native Elixir

Branch: use-fractions
This approach utilizes linear algebra functions and fraction functions written in Native Elixir. Specifically, it reduces the matrix into row echelon form utilizing Gaussian elimination. This implementation works well with small datasets (4x faster than the python implementation, although stil uses over 10x the memory; see benchmarking)

Credit

Fractions Code: https://github.com/lermannen/elixir-fraction
Linear Algebra Algorithms: https://github.com/SebastianCallh/elixir-linear-algebra

Setup

Run mix deps.get

All IO is handled with IO.gets/1 and IO.puts/1. I considered using Logger for things like user warnings but it didn't feel like the right use case because Logger is more for application monitoring.

Benchmarking Both Implementations

To run the benchmarks, go to the both-solutions branch and run mix deps.get and mix benchmark

Operating System: macOS
CPU Information: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
Number of Available Cores: 12
Available memory: 16 GB
Elixir 1.7.4
Erlang 20.1

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
parallel: 1
inputs: Bigger (20), Medium (10), Small (5)
Estimated total run time: 54 s

Benchmarking native_elixir with input Bigger (20)...
Benchmarking native_elixir with input Medium (10)...
Benchmarking native_elixir with input Small (5)...
Benchmarking python with input Bigger (20)...
Benchmarking python with input Medium (10)...
Benchmarking python with input Small (5)...

##### With input Bigger (20) #####
Name                    ips        average  deviation         median         99th %
python               1.10 K        0.91 ms     ±2.59%        0.91 ms        1.00 ms
native_elixir        0.28 K        3.59 ms     ±7.48%        3.51 ms        4.86 ms

Comparison:
python               1.10 K
native_elixir        0.28 K - 3.96x slower +2.69 ms

Memory usage statistics:

Name             Memory usage
python              0.0226 MB
native_elixir         2.42 MB - 107.09x memory usage +2.40 MB

**All measurements for memory usage were the same**

##### With input Medium (10) #####
Name                    ips        average  deviation         median         99th %
python               2.40 K      416.49 μs     ±8.95%         422 μs         517 μs
native_elixir        2.33 K      428.45 μs     ±5.80%         420 μs         543 μs

Comparison:
python               2.40 K
native_elixir        2.33 K - 1.03x slower +11.96 μs

Memory usage statistics:

Name             Memory usage
python                7.39 KB
native_elixir       299.91 KB - 40.58x memory usage +292.52 KB

**All measurements for memory usage were the same**

##### With input Small (5) #####
Name                    ips        average  deviation         median         99th %
native_elixir       15.03 K       66.55 μs     ±9.33%          65 μs          94 μs
python               3.67 K      272.72 μs    ±11.82%         271 μs      370.28 μs

Comparison:
native_elixir       15.03 K
python               3.67 K - 4.10x slower +206.17 μs

Memory usage statistics:

Name             Memory usage
native_elixir        46.24 KB
python                3.34 KB - 0.07x memory usage -42.90625 KB

**All measurements for memory usage were the same**

It's interesting to note that the python implementation is slower with smaller datasets but faster with larger datasets. Also, Python always seems to outperform the pure elixir implementation in memory usage.

Test and Lint

Format: mix format
Run tests: mix test
Lint: bin/lint

Run

run mix first_gear_radius

You will be prompted for the positions of the pegs. Your input will be validated against the following constraints:

  • Peg positions are greater than 1 and less than 10,000
  • At least 2 pegs are inserted
  • The pegs input are in ascending order (if they aren't, you will be warned, the list will be sorted, and the operation will continue)

Input will be collected until either you input a value of 0 or you reach 20 pegs, whichever comes first.

If a gear sequence is found for your peg input, the size of the first gear in the sequence will be returned as a fraction: [a, b] where the size of the gear is a/b. If no sequence is found, [-1, -1] will be returned.

Next things on the horizon

  • Compile Python code to C (in python implementation)
  • Only use fractions where necessary (in pure elixir implementation)
  • Crate mock for IO.gets and test end to end

About


Languages

Language:Elixir 96.4%Language:Python 3.2%Language:Shell 0.3%