defmodule Numy.Lapack.Vector do @moduledoc """ LAPACK Vector. Implements protocols: `Numy.Vc`, `Numy.Vcm` ## Example of mutating `add!` iex(7)> v = Numy.Lapack.Vector.new([1,2,3]) %Numy.Lapack.Vector{lapack: #Numy.Lapack, nelm: 3} iex(8)> Numy.Vcm.add!(v,v) :ok iex(9)> Numy.Lapack.data(v.lapack) [2.0, 4.0, 6.0] ## Example of non-mutating `add` iex(3)> v = Numy.Lapack.Vector.new([1,2,3]) %Numy.Lapack.Vector{lapack: #Numy.Lapack, nelm: 3} iex(4)> Numy.Vc.add(v,v) [1.0, 2.0, 3.0] """ @enforce_keys [:nelm] defstruct [ :nelm, # length of the vector :lapack # %Numy.Lapack structure ] alias Numy.Lapack.Vector, as: LVec def new(nelm) when is_integer(nelm) do %Numy.Lapack.Vector{nelm: nelm, lapack: Numy.Lapack.new_tensor([nelm])} end def new(list) when is_list(list) do nelm = length(list) v = %Numy.Lapack.Vector{nelm: nelm, lapack: Numy.Lapack.new_tensor([nelm])} cond do v.lapack == nil -> nil true -> Numy.Lapack.assign(v.lapack, Numy.Enumy.all_to_float(list)) v end end @doc "Create new Vector as a copy of other Vector" def new(%Numy.Lapack.Vector{nelm: sz, lapack: lpk} = _other_vec) do new_vec = Numy.Lapack.Vector.new(sz) Numy.Lapack.copy(new_vec.lapack, lpk) new_vec end @doc "Create new Vector from Elixir Range" def new(%Range{} = range) do new(Enum.to_list(range)) end @doc """ Create new Vector as a concatination of 2 other vectors ## Examples iex(1)> v1 = Numy.Lapack.Vector.new([1,2,3]) iex(2)> v2 = Numy.Lapack.Vector.new([4,5,6]) iex(3)> v3 = Numy.Lapack.Vector.new(v1,v2) iex(4)> Numy.Vc.data(v3) [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] """ def new(%Numy.Lapack.Vector{nelm: sz1, lapack: lpk1} = _v1, %Numy.Lapack.Vector{nelm: sz2, lapack: lpk2} = _v2) do new_vec = Numy.Lapack.Vector.new(sz1 + sz2) Numy.Lapack.copy(new_vec.lapack, lpk1) Numy.Lapack.vector_copy_range(new_vec.lapack.nif_resource, lpk2.nif_resource, sz2, sz1, 0, 1, 1) new_vec end def make_from_nif_res(res) do nrelm = Numy.Lapack.tensor_nrelm(res); %Numy.Lapack.Vector{nelm: nrelm, lapack: %Numy.Lapack{nif_resource: res, shape: [nrelm]}} end def save_to_file(v, filename) when is_map(v) do Numy.Lapack.tensor_save_to_file(v.lapack.nif_resource, filename) end @doc """ ## Examples iex(4)> v = Numy.Lapack.Vector.new(1..100) #Vector iex(5)> Numy.Lapack.Vector.save_to_file(v, 'vec.numy.bin') :ok iex(6)> Numy.Lapack.Vector.load_from_file('vec.numy.bin') #Vector """ def load_from_file(filename) do res = Numy.Lapack.tensor_load_from_file(filename) make_from_nif_res(res) end defimpl Numy.Vc do @doc "Make a clone" def clone(%Numy.Lapack.Vector{} = v) do Numy.Lapack.Vector.new(v) end def assign_all(v, val) when is_map(v) and is_number(val) do Numy.Lapack.vector_assign_all(v.lapack.nif_resource, val) v end def assign_zeros(v) when is_map(v) do Numy.Lapack.vector_assign_all(v.lapack.nif_resource, 0.0) v end def assign_ones(v) when is_map(v) do Numy.Lapack.vector_assign_all(v.lapack.nif_resource, 1.0) v end def assign_random(v) when is_map(v) do Numy.Lapack.assign(v.lapack, Numy.Float.make_list_randoms(v.nelm)) v end def data(v, nelm) when is_map(v) and is_integer(nelm) do Numy.Lapack.data(v.lapack, nelm) end def at(%Numy.Lapack.Vector{nelm: nelm}, index, default) when index < 0 or index >= nelm, do: default def at(v, index, _default) when is_map(v) and is_integer(index) do Numy.Lapack.vector_get_at(v.lapack.nif_resource, index) end def empty?(v) when is_map(v) do v.nelm == 0 end @doc "Return size/length of the vector." def size(v) when is_map(v) do v.nelm end def equal?(v1,v2) when is_map(v1) and is_map(v2) do Numy.Lapack.vector_equal(v1.lapack.nif_resource, v2.lapack.nif_resource) end @doc """ Add two vectors. ## Examples iex(5)> v = Numy.Lapack.Vector.new([1,2,3]) %Numy.Vector{data: [1.0, 2.0, 3.0], nelm: 3} iex(6)> Numy.Vc.add(v, v) %Numy.Vector{data: [2.0, 4.0, 6.0], nelm: 3} """ def add(v1, v2) when is_map(v1) and is_map(v2) do Numy.Vcm.add!(LVec.new(v1), v2) end def sub(v1, v2) when is_map(v1) and is_map(v2) do Numy.Vcm.sub!(LVec.new(v1), v2) end def mul(v1, v2) when is_map(v1) and is_map(v2) do Numy.Vcm.mul!(LVec.new(v1), v2) end def div(v1, v2) when is_map(v1) and is_map(v2) do Numy.Vcm.div!(LVec.new(v1), v2) end def scale(v, factor) when is_map(v) and is_number(factor) do Numy.Vcm.scale!(LVec.new(v), factor) end def offset(v, off) when is_map(v) and is_number(off) do Numy.Vcm.offset!(LVec.new(v), off) end def negate(v) when is_map(v) do Numy.Vcm.negate!(LVec.new(v)) end def dot(v1, v2) when is_map(v1) and is_map(v2) do Numy.Lapack.vector_dot(v1.lapack.nif_resource, v2.lapack.nif_resource) end @doc "Sum of all elements, ∑aᵢ" def sum(v) do Numy.Lapack.vector_sum(v.lapack.nif_resource) end @doc "Average (∑aᵢ)/length" def mean(%Numy.Lapack.Vector{nelm: nelm, lapack: _}) when nelm == 0 do raise "empty vector"; end def mean(%Numy.Lapack.Vector{nelm: nelm, lapack: _} = v) do Numy.Vc.sum(v) / nelm end @doc "Return max value" def max(v) when is_map(v) do Numy.Lapack.vector_max(v.lapack.nif_resource) end @doc "Return min value" def min(v) when is_map(v) do Numy.Lapack.vector_min(v.lapack.nif_resource) end @doc "Return index of max value" def max_index(v) when is_map(v) do Numy.Lapack.vector_max_index(v.lapack.nif_resource) end @doc "Return index of min value" def min_index(v) when is_map(v) do Numy.Lapack.vector_min_index(v.lapack.nif_resource) end @doc "Step function, aᵢ ← 0 if aᵢ < 0 else 1" def apply_heaviside(v, cutoff \\ 0.0) when is_map(v) and is_number(cutoff) do Numy.Vcm.apply_heaviside!(LVec.new(v), cutoff) end @doc "f(x) = 1/(1 + e⁻ˣ)" def apply_sigmoid(v) when is_map(v) do Numy.Vcm.apply_sigmoid!(LVec.new(v)) end def sort(v) when is_map(v) do Numy.Vcm.sort!(LVec.new(v)) end def reverse(v) when is_map(v) do Numy.Vcm.reverse!(LVec.new(v)) end @doc "Concatenate 2 vectors" def concat(v1,v2) when is_map(v1) and is_map(v2) do Numy.Lapack.Vector.new(v1,v2) end def find(v,val) when is_map(v) and is_number(val) do Numy.Lapack.vector_find(v.lapack.nif_resource,val) end def contains?(v,val) when is_map(v) and is_number(val) do Numy.Vc.find(v,val) != -1 end def abs(v) when is_map(v) do Numy.Vcm.abs!(LVec.new(v)) end def pow2(v) when is_map(v) do Numy.Vcm.pow2!(LVec.new(v)) end def pow(v,p) when is_map(v) do Numy.Vcm.pow!(LVec.new(v),p) end def norm2(v) when is_map(v) do Numy.Lapack.vector_norm2(v.lapack.nif_resource) end end # defimpl Numy.Vc defimpl Numy.Vcm do def add!(v1,v2) when is_map(v1) and is_map(v2) do try do Numy.Lapack.vector_add(v1.lapack.nif_resource, v2.lapack.nif_resource) v1 rescue _ -> :error end end def sub!(v1,v2) when is_map(v1) and is_map(v2) do try do Numy.Lapack.vector_sub(v1.lapack.nif_resource, v2.lapack.nif_resource) v1 rescue _ -> :error end end def mul!(v1,v2) when is_map(v1) and is_map(v2) do try do Numy.Lapack.vector_mul(v1.lapack.nif_resource, v2.lapack.nif_resource) v1 rescue _ -> :error end end def div!(v1,v2) when is_map(v1) and is_map(v2) do try do Numy.Lapack.vector_div(v1.lapack.nif_resource, v2.lapack.nif_resource) v1 rescue _ -> :error end end def scale!(v, factor) when is_map(v) and is_number(factor) do try do Numy.Lapack.vector_scale(v.lapack.nif_resource, factor) v rescue _ -> :error end end @spec offset!(map, number) :: :error def offset!(v, factor) when is_map(v) and is_number(factor) do try do Numy.Lapack.vector_offset(v.lapack.nif_resource, factor) v rescue _ -> :error end end def negate!(v) when is_map(v) do try do Numy.Lapack.vector_negate(v.lapack.nif_resource) v rescue _ -> :error end end def apply_heaviside!(v, cutoff \\ 0.0) when is_map(v) and is_number(cutoff) do try do Numy.Lapack.vector_heaviside(v.lapack.nif_resource, cutoff) v rescue _ -> :error end end def apply_sigmoid!(v) when is_map(v) do try do Numy.Lapack.vector_sigmoid(v.lapack.nif_resource) v rescue _ -> :error end end def sort!(v) when is_map(v) do try do Numy.Lapack.vector_sort(v.lapack.nif_resource) v rescue _ -> :error end end def reverse!(v) when is_map(v) do try do Numy.Lapack.vector_reverse(v.lapack.nif_resource) v rescue _ -> :error end end def set_at!(v, pos, val) when is_map(v) and is_integer(pos) and is_number(val) do try do Numy.Lapack.vector_set_at(v.lapack.nif_resource, pos, val) v rescue _ -> :error end end def axpby!(v1, v2, f1, f2) when is_map(v1) and is_map(v2) and is_number(f1) and is_number(f2) do try do Numy.Lapack.vector_axpby(v1.lapack.nif_resource, v1.lapack.nif_resource, f1, f2) v1 rescue _ -> :error end end def abs!(v) when is_map(v) do try do Numy.Lapack.vector_abs(v.lapack.nif_resource) v rescue _ -> :error end end def pow2!(v) when is_map(v) do try do Numy.Lapack.vector_pow2(v.lapack.nif_resource) v rescue _ -> :error end end def pow!(v,p) when is_map(v) and is_number(p) do try do Numy.Lapack.vector_pow(v.lapack.nif_resource, p) v rescue _ -> :error end end end # defimpl Numy.Vcm @doc """ ## Examples iex(1)> alias Numy.Lapack.Vector Numy.Lapack.Vector iex(2)> a = Vector.new(1..10) #Vector iex(3)> b = Vector.new(1..10) #Vector iex(4)> Vector.swap_ranges(a,b,3,2,7) :ok iex(5)> a #Vector iex(6)> b #Vector """ def swap_ranges(v1, v2, nelm, off_a, off_b) do Numy.Lapack.vector_swap_ranges(v1.lapack.nif_resource, v2.lapack.nif_resource, nelm, off_a, off_b) end end defimpl Inspect, for: Numy.Lapack.Vector do import Inspect.Algebra def inspect(v, opts) do opts = %{opts | limit: 10} concat(["#Vector"]) end end defimpl Numy.Set, for: Numy.Lapack.Vector do @doc """ The union of two sets is formed by the elements that are present in either one of the sets, or in both. C = A ∪ B = {x : x ∈ A or x ∈ B} """ def union(a, b) when is_map(a) and is_map(b) do res = Numy.Lapack.set_op(a.lapack.nif_resource, b.lapack.nif_resource, :union) Numy.Lapack.Vector.make_from_nif_res(res) end @doc """ The intersection of two sets is formed only by the elements that are present in both sets. C = A ∩ B = {x : x ∈ A and x ∈ B} """ def intersection(a, b) when is_map(a) and is_map(b) do res = Numy.Lapack.set_op(a.lapack.nif_resource, b.lapack.nif_resource, :intersection) Numy.Lapack.Vector.make_from_nif_res(res) end @doc """ The difference of two sets is formed by the elements that are present in the first set, but not in the second one. """ def diff(a, b) when is_map(a) and is_map(b) do res = Numy.Lapack.set_op(a.lapack.nif_resource, b.lapack.nif_resource, :diff) Numy.Lapack.Vector.make_from_nif_res(res) end @doc """ The symmetric difference of two sets is formed by the elements that are present in one of the sets, but not in the other. """ def symm_diff(a, b) when is_map(a) and is_map(b) do res = Numy.Lapack.set_op(a.lapack.nif_resource, b.lapack.nif_resource, :symm_diff) Numy.Lapack.Vector.make_from_nif_res(res) end @doc """ The [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index) (also known as similarity coefficient) measures similarity between finite sample sets, and is defined as the size of the intersection divided by the size of the union of the sample sets. """ def jaccard_index(a, b) when is_map(a) and is_map(b) do union_size = Numy.Set.union(a, b) |> Numy.Vc.size intersection_size = Numy.Set.intersection(a, b) |> Numy.Vc.size cond do union_size == 0 -> raise ArgumentError, message: "divide by 0" true -> intersection_size / union_size end end end