Adde some stuff to Vector

This commit is contained in:
Chris Watson 2019-06-17 15:00:21 -07:00
parent d5482eae70
commit ef28e84ecc
No known key found for this signature in database
GPG Key ID: 37DAEF5F446370A4
2 changed files with 101 additions and 40 deletions

View File

@ -9,8 +9,12 @@ require "./apatite/linear_algebra"
# of Crystal.
module Apatite
extend self
include Apatite::LinearAlgebra
class_property precision = 1e-6
class_property approx_precision = 1e-5
## ## ## ## ## ## ## ## ## ## ## ## ##
# # Vector Creation
## ## ## ## ## ## ## ## ## ## ## ## ##
@ -110,4 +114,74 @@ module Apatite
def scalar(n, value)
Matrix.scalar(n, value)
end
## ## ## ## ## ## ## ## ## ## ## ##
# # Vector Manipulation
## ## ## ## ## ## ## ## ## ## ## ##
# Get the scalar (dot) product of two vectors.
#
# [https://en.wikipedia.org/wiki/Scalar_product](https://en.wikipedia.org/wiki/Scalar_product
def dot(x, y)
unless x.size == y.size
raise "Cannot compute the dot product of vectors with different dimensionality"
end
(0...x.size).reduce(0) do |acc, i|
acc + x[i] * y[i]
end
end
# Compute the cosine similarity between two vectors.
def similarity(x, y)
dot(x, y) / (
Math.sqrt(dot(x, x)) *
Math.sqrt(dot(y, y))
)
end
# Returns the angle between this vector and another in radians.
# If the vectors are mirrored across their axes this will return `nil`.
def angle_from(a, b)
unless a.size == b.size
raise "Cannot compute the angle between vectors with different dimensionality"
end
dot = 0_f64
mod1 = 0_f64
mod2 = 0_f64
a.zip(b).each do |x, v|
dot += x * v
mod1 += x * x
mod2 += v * v
end
mod1 = Math.sqrt(mod1)
mod2 = Math.sqrt(mod2)
if mod2 * mod2 == 0
return 0.0
end
theta = (dot / (mod1 * mod2)).clamp(-1, 1)
Math.acos(theta)
end
# Returns whether the vectors are parallel to each other.
def parallel_to?(a, b)
angle = angle_from(a, b)
angle <= precision
end
# Returns whether the vectors are antiparallel to each other.
def antiparallel_to?(a, b)
angle = angle_from(a, b)
(angle - Math::PI).abs <= precision
end
# Returns whether the vectors are perpendicular to each other.
def perpendicular_to?(a, b)
(dot(a, b)).abs <= precision
end
end

View File

@ -371,6 +371,10 @@ module Apatite::LinearAlgebra
multiply(other)
end
def /(other)
divide(other)
end
def clone
Vector.create(@elements.clone)
end
@ -435,51 +439,35 @@ module Apatite::LinearAlgebra
r == 0 ? dup : map { |x| x.to_f64 / r }
end
# ditto
def normalize
to_unit_vector
end
# Returns the angle between this vector and another in radians.
# If the vectors are mirrored across their axes this will return `nil`.
def angle_from(vector)
v = vector.is_a?(Vector) ? vector : Vector.create(vector)
Apatite.angle_from(self, vector)
end
unless size == v.size
raise "Cannot compute the angle between vectors with different dimensionality"
end
dot = 0_f64
mod1 = 0_f64
mod2 = 0_f64
zip(vector).each do |x, v|
dot += x * v
mod1 += x * x
mod2 += v * v
end
mod1 = Math.sqrt(mod1)
mod2 = Math.sqrt(mod2)
if mod2 * mod2 == 0
return 0.0
end
theta = (dot / (mod1 * mod2)).clamp(-1, 1)
Math.acos(theta)
# Compute the cosine similarity between this vector and another.
def similarity(other)
Apatite.similarity(self, other)
end
# Returns whether the vectors are parallel to each other.
def parallel_to?(vector)
angle = angle_from(vector)
angle <= Apatite.precision
Apatite.parallel_to?(self, vector)
end
# Returns whether the vectors are antiparallel to each other.
def antiparallel_to?(vector)
angle = angle_from(vector)
(angle - Math::PI).abs <= Apatite.precision
Apatite.antiparallel_to?(self, vector)
end
# Returns whether the vectors are perpendicular to each other.
def perpendicular_to?(vector)
(dot(vector)).abs <= Apatite.precision
Apatite.perpendicular_to?(self, vector)
end
# When the input is a number, this returns the result of adding
@ -503,6 +491,13 @@ module Apatite::LinearAlgebra
run_binary_op(value) { |a, b| a * b }
end
# When the input is a number, this returns the result of dividing
# it to all vector elements. When it's a vector, the vectors
# will be element-wise divided.
def divide(value)
run_binary_op(value) { |a, b| a / b }
end
# Returns the sum of all elements in the vector.
def sum
reduce(0) { |acc, i| acc + i }
@ -573,16 +568,7 @@ module Apatite::LinearAlgebra
# [https://en.wikipedia.org/wiki/Scalar_product](https://en.wikipedia.org/wiki/Scalar_product)
def dot(other)
other = other.is_a?(Vector) ? other : Vector.create(other)
unless size == other.size
raise "Cannot compute the dot product of vectors with different dimensionality"
end
product = 0
(0...size).each do |i|
product += self[i] * other[i]
end
product
Apatite.dot(self, other)
end
# Returns the (absolute) largest element in this vector.
@ -811,8 +797,9 @@ module Apatite::LinearAlgebra
end
# Call a block on the value
private def run_binary_op(value, &block : (T, T) -> T)
private def run_binary_op(value, &block : (Float64, Float64) -> Float64)
if value.is_a?(Number)
value = value.to_f64
return map { |v| yield(v, value) }
end