Numy is LAPACK based scientific computing library. Online API documentation is here.
- Example
- Comparison
- Installation
- Mutable internal state
- Vector operations
- Set operations
- Simple Linear Regression
See this example in LAPACK reference documentation.
iex(1)> a = Numy.Lapack.new_tensor([3,5])
iex(2)> Numy.Tz.assign(a, [
...(2)> [1,1,1],
...(2)> [2,3,4],
...(2)> [3,5,2],
...(2)> [4,2,5],
...(2)> [5,4,3]])
:ok
iex(3)> b = Numy.Lapack.new_tensor([2,5])
iex(4)> Numy.Tz.assign(b, [
...(4)> [-10,-3],
...(4)> [12,14],
...(4)> [14,12],
...(4)> [16,16],
...(4)> [18,16]])
:ok
iex(5)> Numy.Lapack.solve_lls(a,b)
0
iex(6)> solution = Numy.Lapack.data(b,2*3)
[1.9999999999999982, 0.9999999999999983, 0.9999999999999991, 0.9999999999999997,
1.0000000000000024, 2.0000000000000018]
iex(7)> Numy.Float.equal?(solution, [[2,1], [1,1], [1,2]])
true
The closest to Numy project (that I am aware of) is Matrex. Matrex is using immutable binaries
and NIF code is calling enif_make_binary
to return a result (matrix). enif_make_binary
allocates
memory space for the new binary. Numy on other hand is using mutable NIF resources and can reuse
already allocated memory to store the result inside the context of NIF module.
Ubuntu 18.04, sudo apt install build-essential liblapacke-dev gfortran
.
The package can be installed
by adding numy
to your list of dependencies in mix.exs
:
def deps do
[
{:numy, "~> 0.1.5"}
]
end
For performance reasons, Numy NIF objects are mutable. That is, some API functions change internal state of an object. Two sets of APIs are provided, one has functions that change object's internal state and other that does not change it. In order to maintain that immutability, original input/output object is copied and it is its copy that gets mutated.
iex(1)> v = Numy.Lapack.Vector.new([1,2,3])
iex(2)> Numy.Vc.add(v,v) # Vc API functions do not mutate internal state
iex(3)> Numy.Vc.data(v)
[1.0, 2.0, 3.0]
iex(1)> v = Numy.Lapack.Vector.new([1,2,3])
iex(4)> Numy.Vcm.add!(v,v) # Vcm is API that mutates internal state, functions have suffix '!'
iex(5)> Numy.Vc.data(v)
[2.0, 4.0, 6.0]
Vector Jupyter tutorials:
- Vector creation and initialization.
- Vector access.
- Vector unary operations.
- Vector binary operations.
Function | Vc |
Vcm |
Description |
---|---|---|---|
new(nelm) |
Create new vector of size nelm | ||
new(list) |
Create new vector from Elixir list | ||
new(v) |
Create new vector as copy of another vector | ||
clone(v) |
x | Make a clone of original vector | |
new(v1,v2) |
Create new vector as concatenation of 2 other vectors | ||
save_to_file(v) |
Save vectors to a file | ||
load_from_file(fn) |
Load vecotr from a file | ||
assign_zeros(v) |
x | Assign 0.0 to all elements | |
assign_ones(v) |
x | Assign 1.0 to all elements | |
assign_random(v) |
x | Assign random values to the elements | |
assign_all(v,val) |
x | Assign certain values to all elements | |
empty?(v) |
x | Return true if vector is empty | |
data(v) |
x | Get data as a list | |
at(v,pos) |
x | Get value of N-th element | |
set_at!(v,pos,val) |
x | Set value of N-th element | |
contains?(v,val) |
x | Check if value exists in the vector | |
find(v,val) |
x | Find value in vector and return its position, -1 if can't find | |
equal?(v1,v2) |
x | Compare 2 vectors | |
add(v1,v2) |
x | Add 2 vectors, cᵢ ← aᵢ + bᵢ | |
add!(v1,v2) |
x | aᵢ ← aᵢ + bᵢ | |
sub(v1,v2) |
x | Subtract one vector from other, cᵢ ← aᵢ - bᵢ | |
sub!(v1,v2) |
x | aᵢ ← aᵢ - bᵢ | |
mul(v1,v2) |
x | Multiply 2 vectors, cᵢ ← aᵢ×bᵢ | |
mul!(v1,v2) |
x | aᵢ ← aᵢ×bᵢ | |
div(v1,v2) |
x | Divide 2 vectors, cᵢ ← aᵢ÷bᵢ | |
div!(v1,v2) |
x | aᵢ ← aᵢ÷bᵢ | |
scale(v,factor) |
x | Multiply each element by a constant, aᵢ ← aᵢ×scale_factor | |
scale!(v,factor) |
x | aᵢ ← aᵢ×scale_factor | |
offset(v,off) |
x | Add a constant to each element, aᵢ ← aᵢ + offset | |
offset!(c,off) |
x | aᵢ ← aᵢ + offset | |
negate(v) |
x | Change sign of each element, aᵢ ← -aᵢ | |
negate!(v) |
x | aᵢ ← -aᵢ | |
dot(v1,v2) |
x | Dot product of 2 vectors, ∑aᵢ×bᵢ | |
sum(v) |
x | Sum of all elements, ∑aᵢ | |
average(v) |
x | Average (∑aᵢ)/length | |
max(v) |
x | Get max value | |
min(v) |
x | Get min value | |
max_index(v) |
x | Get index of max value | |
min_index(v) |
x | Get index of min value | |
apply_heaviside(v) |
x | Step function, aᵢ ← 0 if aᵢ < 0 else 1 | |
apply_heaviside!(v) |
x | ||
apply_sigmoid(v) |
x | f(x) = 1/(1 + e⁻ˣ) | |
apply_sigmoid!(v) |
x | ||
sort(v) |
x | Sort elements of array | |
sort!(v) |
x | Sort elements of array in-place | |
reverse(v) |
x | Reverse order of elements | |
reverse!(v) |
x | Reverse in-place | |
axpby(v) |
x | cᵢ ← aᵢ×factor_a + bᵢ×factor_b | |
axpby!(v) |
x | aᵢ ← aᵢ×factor_a + bᵢ×factor_b | |
swap_ranges(a,b,n) |
swap values between 2 vectors | ||
abs(v) |
x | ||
abs!(v) |
x | ||
pow(v) |
x | ||
pow!(v) |
x | ||
pow2(v) |
x | cᵢ ← aᵢ² | |
pow2!(v) |
x | aᵢ ← aᵢ² | |
norm2(v) |
x | √x₀² + x₁² + ... + xₙ² |
Numy.Lapack.Vector
implements Numy.Set
protocol with base Set operations:
union
, intersection
, diff
, symm_diff
, jaccard_index
.
Note: order of elements of input vector can change (they get sorted) when
Numy.Set
functions are invoked. In other words, Set functions mutate inputs.
iex(6)> a = Numy.Lapack.Vector.new(1..5)
#Vector<size=5, [1.0, 2.0, 3.0, 4.0, 5.0]>
iex(7)> b = Numy.Lapack.Vector.new(5..10)
#Vector<size=6, [5.0, 6.0, 7.0, 8.0, 9.0, 10.0]>
iex(8)> Numy.Set.union(a,b)
#Vector<size=10, [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]>
iex(9)> Numy.Set.intersection(a,b)
#Vector<size=1, [5.0]>
iex(10)> Numy.Set.diff(a,b)
#Vector<size=4, [1.0, 2.0, 3.0, 4.0]>
iex(11)> Numy.Set.symm_diff(a,b)
#Vector<size=9, [1.0, 2.0, 3.0, 4.0, 6.0, 7.0, 8.0, 9.0, 10.0]>
Basic vector operation like mean
, offset
, pow2
and sum
allow to implement simple Linear Regression in Elixir.
Module Numy.Fit.SimpleLinear
is Elixir code that finds the line that
fits input data by calculating variance and covariance.
iex(34)> x = Numy.Lapack.Vector.new(0..9)
#Vector<size=10, [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]>
iex(35)> y = Vc.scale(x,2) |> Vcm.offset!(-3.0) # make slope=2 and intercept=-3
#Vector<size=10, [-3.0, -1.0, 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0]>
iex(36)> err = Numy.Lapack.Vector.new(10) |> Vc.assign_random |> Vcm.offset!(-0.5) |> Vcm.scale!(0.1)
iex(37)> Vcm.add!(y,err) # add errors to the ideal line
iex(38)> line = Numy.Fit.SimpleLinear.fit(x,y)
{-2.9939933270609496, 1.9966330251198818} # got intercept=-3 and slope=2 as expected
See tutorial.