├── .formatter.exs ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bench ├── snapshots │ ├── 2016-03-30_11-52-51.snapshot │ └── 2019-02-27_18-33-20.snapshot └── vector_bench.exs ├── config └── config.exs ├── doc ├── .build ├── 404.html ├── Vector.html ├── api-reference.html ├── index.html └── search.html ├── lib └── vector.ex ├── mix.exs ├── mix.lock ├── test ├── test_helper.exs ├── vector_identities_test.exs └── vector_test.exs └── validate.sh /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | format: 7 | name: Validation of source code 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2.3.1 11 | 12 | - name: Install OTP and Elixir 13 | uses: erlef/setup-beam@v1 14 | with: 15 | otp-version: 25.2 16 | elixir-version: 1.14.2 17 | 18 | - name: Install dependencies 19 | run: mix deps.get 20 | 21 | - name: Run validations 22 | run: mix validate 23 | 24 | test: 25 | name: Test (Elixir ${{matrix.elixir}} | Erlang/OTP ${{matrix.otp}}) 26 | runs-on: ubuntu-latest 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | include: 31 | - elixir: 1.14.x 32 | otp: 25 33 | - elixir: 1.14.x 34 | otp: 24 35 | - elixir: 1.13.x 36 | otp: 25 37 | - elixir: 1.13.x 38 | otp: 24 39 | - elixir: 1.12.x 40 | otp: 24 41 | env: 42 | MIX_ENV: test 43 | 44 | 45 | steps: 46 | - uses: actions/checkout@v1 47 | 48 | - name: Install OTP and Elixir 49 | uses: erlef/setup-beam@v1 50 | with: 51 | otp-version: ${{matrix.otp}} 52 | elixir-version: ${{matrix.elixir}} 53 | 54 | - name: Install dependencies 55 | run: mix deps.get --only test 56 | 57 | - name: Run tests 58 | run: mix test --trace 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc 12 | 13 | # If the VM crashes, it generates a dump, let's ignore it too. 14 | erl_crash.dump 15 | 16 | # Also ignore archive artifacts (built via "mix archive.build"). 17 | *.ez 18 | 19 | .elixir_ls 20 | 21 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.0] - 2023-01-09 4 | 5 | ### Changed 6 | 7 | - Updated CI to use GitHub Actions instead of travis-ci 8 | - Updated dependencies 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Powell Kinney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vector Math Library for Elixir 2 | 3 | ![Build Status](https://github.com/pkinney/vector_ex/actions/workflows/ci.yaml/badge.svg) 4 | [![Hex.pm](https://img.shields.io/hexpm/v/vector.svg)](https://hex.pm/packages/vector) 5 | [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/vector) 6 | 7 | Library of common vector functions for use in geometric or graphical calculations. 8 | 9 | ## Installation 10 | 11 | ```elixir 12 | defp deps do 13 | [{:vector, "~> 1.1"}] 14 | end 15 | ``` 16 | 17 | ## Usage 18 | 19 | **[Full Documentation](https://hexdocs.pm/vector/Vector.html)** 20 | 21 | The `Vector` module contains several functions that take one or more vectors. 22 | Each vector can be a 2- or 3-element tuple. It is possible to mix two- and 23 | three-dimensional vectors, in which case the `z` of the two-dimensional vector 24 | will be assumed 0. 25 | 26 | ```elixir 27 | Vector.dot({2, 3}, {1, 4}) #=> 14 28 | Vector.cross({2, 0, -1}, {0, 3, 3}) #=> {3, -6, 6} 29 | Vector.norm({3, -6, 6}) #=> 9 30 | Vector.unit({2, -1}) #=> {0.894, -0.447} 31 | Vector.add({2, 0, -1}, {0, 3, 3}) #=> {2, 3, 1} 32 | Vector.subtract({2, 0, -1}, {1, 3}) #=> {1, -3, 1} 33 | Vector.component({2, 3, -2}, :y) #=> 3 34 | ``` 35 | 36 | ## Tests 37 | 38 | ```bash 39 | > mix test 40 | ``` 41 | 42 | Included in the project are a suite of 43 | [algebraic identities](https://en.wikipedia.org/wiki/Vector_algebra_relations#Addition_and_multiplication_of_vectors) 44 | that can be run against a large generated set of vectors. 45 | -------------------------------------------------------------------------------- /bench/snapshots/2016-03-30_11-52-51.snapshot: -------------------------------------------------------------------------------- 1 | duration:1.0;mem stats:false;sys mem stats:false 2 | module;test;tags;iterations;elapsed 3 | VectorBench zero-vector cross 3D 10000000 1066433 4 | VectorBench zero-vector cross 2D 100000000 6624801 5 | VectorBench unit vector 500 2564368 6 | VectorBench B (A . C) - C (A . B) 5 1639784 7 | VectorBench A x B 1000 1347786 8 | VectorBench A x (B x C) 10 1612823 9 | VectorBench A . B 2000 1774450 10 | VectorBench A . (B x C) 5 1500177 11 | -------------------------------------------------------------------------------- /bench/snapshots/2019-02-27_18-33-20.snapshot: -------------------------------------------------------------------------------- 1 | duration:1.0;mem stats:false;sys mem stats:false 2 | module;test;tags;iterations;elapsed 3 | VectorBench A . (B x C) 10 1113209 4 | VectorBench A . B 5000 2131973 5 | VectorBench A x (B x C) 20 1609173 6 | VectorBench A x B 5000 3273046 7 | VectorBench B (A . C) - C (A . B) 10 1508749 8 | VectorBench unit vector 1000 2031061 9 | VectorBench zero-vector cross 2D 100000000 2992802 10 | VectorBench zero-vector cross 3D 100000000 6862010 11 | -------------------------------------------------------------------------------- /bench/vector_bench.exs: -------------------------------------------------------------------------------- 1 | defmodule VectorBench do 2 | use Benchfella 3 | import Vector 4 | 5 | @values [-1, 0, 1, 2] 6 | @vectors (for x <- @values, y <- @values, do: {x, y}) ++ (for x <- @values, y <- @values, z <- @values, do: {x, y, z}) 7 | @zero_2 {0, 0} 8 | @zero_3 {0, 0, 0} 9 | 10 | def run_vector_set_2(func) do 11 | for a <- @vectors, b <- @vectors, do: func.(a, b) 12 | end 13 | 14 | def run_vector_set_3(func) do 15 | for a <- @vectors, b <- @vectors, c <- @vectors, do: func.(a, b, c) 16 | end 17 | 18 | bench "zero-vector cross 2D" do 19 | Vector.cross(@zero_2, @zero_2) 20 | end 21 | 22 | bench "zero-vector cross 3D" do 23 | Vector.cross(@zero_3, @zero_3) 24 | end 25 | 26 | bench "unit vector" do 27 | run_vector_set_2 fn a, b-> cross(unit_safe(a), unit_safe(b)) end 28 | end 29 | 30 | bench "A x B" do 31 | run_vector_set_2 fn a, b -> cross(a, b) end 32 | end 33 | 34 | bench "A . B" do 35 | run_vector_set_2 fn a, b -> dot(a, b) end 36 | end 37 | 38 | bench "A . (B x C)" do 39 | run_vector_set_3 fn a, b, c -> cross(a, cross(b, c)) end 40 | end 41 | 42 | bench "A x (B x C)" do 43 | run_vector_set_3 fn a, b, c -> dot(a, cross(b, c)) end 44 | end 45 | 46 | bench "B (A . C) - C (A . B)" do 47 | run_vector_set_3 fn a, b, c -> subtract(multiply(b, dot(a, c)), multiply(c, dot(a, b))) end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | import Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :vector, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:vector, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /doc/.build: -------------------------------------------------------------------------------- 1 | dist/html-399e30b9b028e3059575.css 2 | dist/html-399e30b9b028e3059575.js 3 | dist/html/fonts/icomoon.eot 4 | dist/html/fonts/icomoon.svg 5 | dist/html/fonts/icomoon.ttf 6 | dist/html/fonts/icomoon.woff 7 | dist/sidebar_items-71d22232a3.js 8 | api-reference.html 9 | search.html 10 | 404.html 11 | Vector.html 12 | index.html 13 | -------------------------------------------------------------------------------- /doc/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 404 — vector v1.0.1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 22 | 26 | 55 | 56 |
57 |
58 |
59 | 60 | 61 |

Page not found

62 | 63 |

Sorry, but the page you were trying to get to, does not exist. You 64 | may want to try searching this site using the sidebar 65 | or using our API Reference page 66 | to find what you were looking for.

67 | 68 | 80 |
81 |
82 |
83 |
84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /doc/Vector.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vector — vector v1.0.1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 22 | 26 | 55 | 56 |
57 |
58 |
59 | 60 | 61 |

62 | vector v1.0.1 63 | Vector

64 | 65 | 66 |
67 |

A library of two- and three-dimensional vector operations. All vectors 68 | are represented as tuples with either two or three elements.

69 |

70 | 71 | Examples 72 |

73 | 74 |
iex> # Vector Tripple Product Identity
 75 | ...> a = {2, 3, 1}
 76 | ...> b = {1, 4, -2}
 77 | ...> c = {-1, 2, 1}
 78 | ...> Vector.equal?(
 79 | ...>   Vector.cross(Vector.cross(a, b), c),
 80 | ...>   Vector.subtract(Vector.multiply(b, Vector.dot(a, c)), Vector.multiply(a, Vector.dot(b, c))))
 81 | true
82 |
83 | 84 |
85 |

86 | 87 | 88 | Link to this section 89 | 90 | Summary 91 |

92 |
93 |

94 | Types 95 |

96 |
97 |
98 | location() 99 |
100 |
101 |
102 |
103 | vector() 104 |
105 |
106 |
107 |
108 |

109 | Functions 110 |

111 |
112 |
113 | add(arg1, arg2) 114 |
115 |

Adds two vectors

116 |
117 |
118 |
119 |
120 | basis(atom) 121 |
122 |

Returns the basis vector for the given axis

123 |
124 |
125 |
126 |
127 | component(arg, atom) 128 |
129 |

Returns the scalar component for the axis given

130 |
131 |
132 |
133 |
134 | cross(arg1, arg2) 135 |
136 |

Returns the cross product of two vectors AB

137 |
138 |
139 |
140 | 143 |

Returns the norm (magnitude) of the cross product of two vectors AB

144 |
145 |
146 |
147 |
148 | divide(arg, s) 149 |
150 |

Divide a vector by scalar value s

151 |
152 |
153 |
154 |
155 | dot(arg1, arg2) 156 |
157 |

Returns the dot product of two vectors AB

158 |
159 |
160 |
161 | 164 |

Compares two vectors for euqality, with an optional tolerance

165 |
166 |
167 |
168 |
169 | multiply(arg, s) 170 |
171 |

Multiply a vector by scalar value s

172 |
173 |
174 |
175 |
176 | norm(arg) 177 |
178 |

Returns the norm (magnitude) of a vector

179 |
180 |
181 |
182 |
183 | norm_squared(arg) 184 |
185 |

Returns the square of the norm norm (magnitude) of a vector

186 |
187 |
188 |
189 | 192 |

Returns a new coordinate by projecting a given length distance from 193 | coordinate start along vector

194 |
195 |
196 |
197 |
198 | reverse(arg) 199 |
200 |

Reverses a vector

201 |
202 |
203 |
204 |
205 | subtract(a, b) 206 |
207 |

Subtract vector B from vector A. Equivalent to Vector.add(A, Vector.revers(B))

208 |
209 |
210 |
211 |
212 | unit(v) 213 |
214 |

Returns the unit vector parallel ot the given vector. 215 | This will raise an ArithmeticError if a zero-magnitude vector is given. 216 | Use unit_safe if there is a chance that a zero-magnitude vector 217 | will be sent

218 |
219 |
220 |
221 |
222 | unit_safe(v) 223 |
224 |

Returns the unit vector parallel ot the given vector, but will handle 225 | the vectors {0, 0} and {0, 0, 0} by returning the same vector

226 |
227 |
228 |
229 |
230 | 231 |
232 |

233 | 234 | 235 | Link to this section 236 | 237 | Types

238 |
239 |
240 |
241 | 242 | 243 | Link to this type 244 | 245 |

location() 246 | 247 |
248 |
location() :: {number(), number()} | {number(), number(), number()}
249 |
250 |

251 |
252 |
253 |
254 |
255 |
256 | 257 | 258 | Link to this type 259 | 260 |

vector() 261 | 262 |
263 |
vector() :: {number(), number()} | {number(), number(), number()}
264 |
265 |

266 |
267 |
268 |
269 |
270 |
271 |
272 |

273 | 274 | 275 | Link to this section 276 | 277 | Functions

278 |
279 |
280 |
281 | 282 | 283 | Link to this function 284 | 285 |

add(arg1, arg2) 286 | 287 |
288 |
add(vector(), vector()) :: vector()
289 |
290 |

291 |
292 |

Adds two vectors

293 |

294 | 295 | Examples 296 |

297 | 298 |
iex> Vector.add({3, -4}, {2, 1})
299 | {5,-3}
300 | iex> Vector.add({-2, 0, 5}, {0, 0, 0})
301 | {-2, 0, 5}
302 | iex> Vector.add({2, 1, -2}, Vector.reverse({2, 1, -2}))
303 | {0, 0, 0}
304 |
305 |
306 |
307 |
308 | 309 | 310 | Link to this function 311 | 312 |

basis(atom) 313 | 314 |
315 |
basis(atom()) :: vector()
316 |
317 |

318 |
319 |

Returns the basis vector for the given axis

320 |

321 | 322 | Examples 323 |

324 | 325 |
iex> Vector.basis(:x)
326 | {1, 0, 0}
327 | iex> Vector.basis(:y)
328 | {0, 1, 0}
329 | iex> Vector.component(Vector.basis(:y), :y)
330 | 1
331 |
332 |
333 |
334 |
335 | 336 | 337 | Link to this function 338 | 339 |

component(arg, atom) 340 | 341 |
342 |
component(vector(), atom()) :: number()
343 |
344 |

345 |
346 |

Returns the scalar component for the axis given

347 |

348 | 349 | Examples 350 |

351 | 352 |
iex> Vector.component({3, -4}, :y)
353 | -4
354 | iex> Vector.component({-6, 0, 8}, :z)
355 | 8
356 | iex> Vector.component({1, -2}, :z)
357 | 0
358 | iex> Vector.component(Vector.basis(:x), :z)
359 | 0
360 |
361 |
362 |
363 |
364 | 365 | 366 | Link to this function 367 | 368 |

cross(arg1, arg2) 369 | 370 |
371 |
cross(vector(), vector()) :: vector()
372 |
373 |

374 |
375 |

Returns the cross product of two vectors AB

376 |

377 | 378 | Examples 379 |

380 | 381 |
iex> Vector.cross({2, 3}, {1, 4})
382 | {0, 0, 5}
383 | iex> Vector.cross({2, 2, -1}, {1, 4, 2})
384 | {8, -5, 6}
385 | iex> Vector.cross({3, -3, 1}, {4, 9, 2})
386 | {-15, -2, 39}
387 |
388 |
389 |
390 |
391 | 392 | 393 | Link to this function 394 | 395 |

cross_norm(arg1, arg2) 396 | 397 |
398 |
cross_norm(vector(), vector()) :: number()
399 |
400 |

401 |
402 |

Returns the norm (magnitude) of the cross product of two vectors AB

403 |

404 | 405 | Examples 406 |

407 | 408 |
iex> Vector.cross_norm({2, 3}, {1, 4})
409 | 5
410 | iex> Vector.cross_norm({1, 4}, {2, 2})
411 | 6
412 | iex> Vector.cross_norm({2, 0, -1}, {0, 3, 3})
413 | 9.0
414 | iex> Float.floor(:math.pow(Vector.cross_norm({2, 2, -1}, {1, 4, 2}), 2))
415 | 125.0
416 |
417 |
418 |
419 |
420 | 421 | 422 | Link to this function 423 | 424 |

divide(arg, s) 425 | 426 |
427 |
divide(vector(), number()) :: vector()
428 |
429 |

430 |
431 |

Divide a vector by scalar value s

432 |

433 | 434 | Examples 435 |

436 | 437 |
iex> Vector.divide({3, -4}, 2.5)
438 | {1.2, -1.6}
439 | iex> Vector.divide({-2, 0, 5}, -2)
440 | {1.0, 0.0, -2.5}
441 |
442 |
443 |
444 |
445 | 446 | 447 | Link to this function 448 | 449 |

dot(arg1, arg2) 450 | 451 |
452 |
dot(vector(), vector()) :: number()
453 |
454 |

455 |
456 |

Returns the dot product of two vectors AB

457 |

458 | 459 | Examples 460 |

461 | 462 |
iex> Vector.dot({2, 3}, {1, 4})
463 | 14
464 | iex> Vector.dot({1, 4}, {2, 2})
465 | 10
466 | iex> Vector.dot({2, 0, -1}, {0, 3, 3})
467 | -3
468 |
469 |
470 |
471 | 472 | 473 |
474 | 475 | 476 | Link to this function 477 | 478 |

equal?(a, b, tolerance \\ 0.0) 479 | 480 |
481 |
equal?(vector(), vector(), number()) :: boolean()
482 |
483 |

484 |
485 |

Compares two vectors for euqality, with an optional tolerance

486 |

487 | 488 | Examples 489 |

490 | 491 |
iex> Vector.equal?({3, -4}, {3, -4})
492 | true
493 | iex> Vector.equal?({3, -4}, {3.0001, -3.9999})
494 | false
495 | iex> Vector.equal?({3, -4}, {3.0001, -3.9999}, 0.001)
496 | true
497 | iex> Vector.equal?({3, -4, 1}, {3.0001, -3.9999, 1.0}, 0.001)
498 | true
499 |
500 |
501 |
502 |
503 | 504 | 505 | Link to this function 506 | 507 |

multiply(arg, s) 508 | 509 |
510 |
multiply(vector(), number()) :: vector()
511 |
512 |

513 |
514 |

Multiply a vector by scalar value s

515 |

516 | 517 | Examples 518 |

519 | 520 |
iex> Vector.multiply({3, -4}, 2.5)
521 | {7.5, -10.0}
522 | iex> Vector.multiply({-2, 0, 5}, -2)
523 | {4, 0, -10}
524 |
525 |
526 |
527 |
528 | 529 | 530 | Link to this function 531 | 532 |

norm(arg) 533 | 534 |
535 |
norm(vector()) :: number()
536 |
537 |

538 |
539 |

Returns the norm (magnitude) of a vector

540 |

541 | 542 | Examples 543 |

544 | 545 |
iex> Vector.norm({3, 4})
546 | 5.0
547 | iex> Vector.norm({-1, 0})
548 | 1
549 | iex> Vector.norm({0, -2, 0})
550 | 2
551 |
552 |
553 |
554 |
555 | 556 | 557 | Link to this function 558 | 559 |

norm_squared(arg) 560 | 561 |
562 |
norm_squared(vector()) :: number()
563 |
564 |

565 |
566 |

Returns the square of the norm norm (magnitude) of a vector

567 |

568 | 569 | Examples 570 |

571 | 572 |
iex> Vector.norm_squared({3, 4})
573 | 25
574 | iex> Vector.norm_squared({1, 0})
575 | 1
576 | iex> Vector.norm_squared({2, 0, -1})
577 | 5
578 | iex> Vector.norm_squared({-2, 3, 1})
579 | 14
580 |
581 |
582 |
583 |
584 | 585 | 586 | Link to this function 587 | 588 |

project(vector, start, distance) 589 | 590 |
591 |
project(vector(), location(), number()) :: location()
592 |
593 |

594 |
595 |

Returns a new coordinate by projecting a given length distance from 596 | coordinate start along vector

597 |

598 | 599 | Examples 600 |

601 | 602 |
iex> Vector.project({3, -4}, {-1, 1}, 4)
603 | {1.4, -2.2}
604 | iex> Vector.project({-6, 0, 8}, {1, -2, 0.4}, 2.5)
605 | {-0.5, -2.0, 2.4}
606 | iex> Vector.project({-2, 1, 3}, {0, 0, 0}, 2.5) |> Vector.norm()
607 | 2.5
608 |
609 |
610 |
611 |
612 | 613 | 614 | Link to this function 615 | 616 |

reverse(arg) 617 | 618 |
619 |
reverse(vector()) :: vector()
620 |
621 |

622 |
623 |

Reverses a vector

624 |

625 | 626 | Examples 627 |

628 | 629 |
iex> Vector.reverse({3, -4})
630 | {-3, 4}
631 | iex> Vector.reverse({-2, 0, 5})
632 | {2, 0, -5}
633 | iex> Vector.cross_norm({-2, 3, 5}, Vector.reverse({-2, 3, 5}))
634 | 0
635 |
636 |
637 |
638 |
639 | 640 | 641 | Link to this function 642 | 643 |

subtract(a, b) 644 | 645 |
646 |
subtract(vector(), vector()) :: vector()
647 |
648 |

649 |
650 |

Subtract vector B from vector A. Equivalent to Vector.add(A, Vector.revers(B))

651 |

652 | 653 | Examples 654 |

655 | 656 |
iex> Vector.subtract({3, -4}, {2, 1})
657 | {1,-5}
658 | iex> Vector.subtract({-2, 0, 5}, {-3, 1, 2})
659 | {1, -1, 3}
660 |
661 |
662 |
663 |
664 | 665 | 666 | Link to this function 667 | 668 |

unit(v) 669 | 670 |
671 |
unit(vector()) :: vector()
672 |
673 |

674 |
675 |

Returns the unit vector parallel ot the given vector. 676 | This will raise an ArithmeticError if a zero-magnitude vector is given. 677 | Use unit_safe if there is a chance that a zero-magnitude vector 678 | will be sent.

679 |

680 | 681 | Examples 682 |

683 | 684 |
iex> Vector.unit({3, 4})
685 | {0.6, 0.8}
686 | iex> Vector.unit({8, 0, 6})
687 | {0.8, 0.0, 0.6}
688 | iex> Vector.unit({-2, 0, 0})
689 | {-1.0, 0.0, 0.0}
690 | iex> Vector.unit({0, 0, 0})
691 | ** (ArithmeticError) bad argument in arithmetic expression
692 |
693 |
694 |
695 |
696 | 697 | 698 | Link to this function 699 | 700 |

unit_safe(v) 701 | 702 |
703 |
unit_safe(vector()) :: vector()
704 |
705 |

706 |
707 |

Returns the unit vector parallel ot the given vector, but will handle 708 | the vectors {0, 0} and {0, 0, 0} by returning the same vector

709 |

710 | 711 | Examples 712 |

713 | 714 |
iex> Vector.unit_safe({3, 4})
715 | {0.6, 0.8}
716 | iex> Vector.unit_safe({0, 0})
717 | {0, 0}
718 | iex> Vector.unit_safe({0, 0, 0})
719 | {0, 0, 0}
720 |
721 |
722 |
723 |
724 | 736 |
737 |
738 |
739 |
740 | 741 | 742 | 743 | 744 | -------------------------------------------------------------------------------- /doc/api-reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | API Reference — vector v1.0.1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 22 | 26 | 55 | 56 |
57 |
58 |
59 | 60 | 61 |

62 | vector v1.0.1 63 | API Reference 64 |

65 | 66 |
67 |

68 | 69 | Modules 70 |

71 | 72 |
73 |
74 |
75 | Vector 76 |
77 |

A library of two- and three-dimensional vector operations. All vectors 78 | are represented as tuples with either two or three elements

79 |
80 |
81 |
82 |
83 | 84 | 85 | 97 |
98 |
99 |
100 |
101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vector v1.0.1 — Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /doc/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search — vector v1.0.1 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 22 | 26 | 55 | 56 |
57 |
58 |
59 | 60 | 63 | 75 |
76 |
77 |
78 |
79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /lib/vector.ex: -------------------------------------------------------------------------------- 1 | defmodule Vector do 2 | @moduledoc ~S""" 3 | A library of two- and three-dimensional vector operations. All vectors 4 | are represented as tuples with either two or three elements. 5 | 6 | ## Examples 7 | iex> # Vector Tripple Product Identity 8 | ...> a = {2, 3, 1} 9 | ...> b = {1, 4, -2} 10 | ...> c = {-1, 2, 1} 11 | ...> Vector.equal?( 12 | ...> Vector.cross(Vector.cross(a, b), c), 13 | ...> Vector.subtract(Vector.multiply(b, Vector.dot(a, c)), Vector.multiply(a, Vector.dot(b, c)))) 14 | true 15 | """ 16 | 17 | @type vector :: {number, number} | {number, number, number} 18 | @type location :: {number, number} | {number, number, number} 19 | 20 | @doc ~S""" 21 | Returns the cross product of two vectors *A*⨯*B* 22 | 23 | ## Examples 24 | 25 | iex> Vector.cross({2, 3}, {1, 4}) 26 | {0, 0, 5} 27 | iex> Vector.cross({2, 2, -1}, {1, 4, 2}) 28 | {8, -5, 6} 29 | iex> Vector.cross({3, -3, 1}, {4, 9, 2}) 30 | {-15, -2, 39} 31 | """ 32 | @spec cross(vector, vector) :: vector 33 | def cross({x1, y1}, {x2, y2}), do: {0, 0, x1 * y2 - y1 * x2} 34 | def cross({x1, y1, z1}, {x2, y2}), do: cross({x1, y1, z1}, {x2, y2, 0}) 35 | def cross({x1, y1}, {x2, y2, z2}), do: cross({x1, y1, 0}, {x2, y2, z2}) 36 | 37 | def cross({x1, y1, z1}, {x2, y2, z2}) do 38 | {y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2} 39 | end 40 | 41 | @doc ~S""" 42 | Returns the norm (magnitude) of the cross product of two vectors *A*⨯*B* 43 | 44 | ## Examples 45 | 46 | iex> Vector.cross_norm({2, 3}, {1, 4}) 47 | 5 48 | iex> Vector.cross_norm({1, 4}, {2, 2}) 49 | 6 50 | iex> Vector.cross_norm({2, 0, -1}, {0, 3, 3}) 51 | 9.0 52 | iex> Float.floor(:math.pow(Vector.cross_norm({2, 2, -1}, {1, 4, 2}), 2)) 53 | 125.0 54 | """ 55 | @spec cross_norm(vector, vector) :: number 56 | def cross_norm({x1, y1}, {x2, y2}), do: cross_norm({x1, y1, 0}, {x2, y2, 0}) 57 | 58 | def cross_norm({x1, y1, z1}, {x2, y2, z2}) do 59 | norm(cross({x1, y1, z1}, {x2, y2, z2})) 60 | end 61 | 62 | @doc ~S""" 63 | Returns the dot product of two vectors *A*⨯*B* 64 | 65 | ## Examples 66 | 67 | iex> Vector.dot({2, 3}, {1, 4}) 68 | 14 69 | iex> Vector.dot({1, 4}, {2, 2}) 70 | 10 71 | iex> Vector.dot({2, 0, -1}, {0, 3, 3}) 72 | -3 73 | """ 74 | @spec dot(vector, vector) :: number 75 | def dot({x1, y1}, {x2, y2}), do: dot({x1, y1, 0}, {x2, y2, 0}) 76 | def dot({x1, y1, z1}, {x2, y2}), do: dot({x1, y1, z1}, {x2, y2, 0}) 77 | def dot({x1, y1}, {x2, y2, z2}), do: dot({x1, y1, 0}, {x2, y2, z2}) 78 | 79 | def dot({x1, y1, z1}, {x2, y2, z2}) do 80 | x1 * x2 + y1 * y2 + z1 * z2 81 | end 82 | 83 | @doc ~S""" 84 | Returns the norm (magnitude) of a vector 85 | 86 | ## Examples 87 | 88 | iex> Vector.norm({3, 4}) 89 | 5.0 90 | iex> Vector.norm({-1, 0}) 91 | 1 92 | iex> Vector.norm({0, -2, 0}) 93 | 2 94 | """ 95 | @spec norm(vector) :: number 96 | def norm({x, y}), do: norm({x, y, 0}) 97 | def norm({0, 0, z}), do: abs(z) 98 | def norm({x, 0, 0}), do: abs(x) 99 | def norm({0, y, 0}), do: abs(y) 100 | def norm({x, y, z}), do: :math.sqrt(norm_squared({x, y, z})) 101 | 102 | @doc ~S""" 103 | Returns the square of the norm norm (magnitude) of a vector 104 | 105 | ## Examples 106 | 107 | iex> Vector.norm_squared({3, 4}) 108 | 25 109 | iex> Vector.norm_squared({1, 0}) 110 | 1 111 | iex> Vector.norm_squared({2, 0, -1}) 112 | 5 113 | iex> Vector.norm_squared({-2, 3, 1}) 114 | 14 115 | """ 116 | @spec norm_squared(vector) :: number 117 | def norm_squared({x, y}), do: norm_squared({x, y, 0}) 118 | 119 | def norm_squared({x, y, z}) do 120 | x * x + y * y + z * z 121 | end 122 | 123 | @doc ~S""" 124 | Returns the unit vector parallel ot the given vector. 125 | This will raise an `ArithmeticError` if a zero-magnitude vector is given. 126 | Use `unit_safe` if there is a chance that a zero-magnitude vector 127 | will be sent. 128 | 129 | ## Examples 130 | 131 | iex> Vector.unit({3, 4}) 132 | {0.6, 0.8} 133 | iex> Vector.unit({8, 0, 6}) 134 | {0.8, 0.0, 0.6} 135 | iex> Vector.unit({-2, 0, 0}) 136 | {-1.0, 0.0, 0.0} 137 | iex> Vector.unit({0, 0, 0}) 138 | ** (ArithmeticError) bad argument in arithmetic expression 139 | """ 140 | @spec unit(vector) :: vector 141 | def unit({x, 0, 0}), do: {x / abs(x), 0.0, 0.0} 142 | def unit({0, y, 0}), do: {0.0, y / abs(y), 0.0} 143 | def unit({0, 0, z}), do: {0.0, 0.0, z / abs(z)} 144 | def unit(v), do: divide(v, norm(v)) 145 | 146 | @doc ~S""" 147 | Returns the unit vector parallel ot the given vector, but will handle 148 | the vectors `{0, 0}` and `{0, 0, 0}` by returning the same vector 149 | 150 | ## Examples 151 | 152 | iex> Vector.unit_safe({3, 4}) 153 | {0.6, 0.8} 154 | iex> Vector.unit_safe({0, 0}) 155 | {0, 0} 156 | iex> Vector.unit_safe({0, 0, 0}) 157 | {0, 0, 0} 158 | """ 159 | @spec unit_safe(vector) :: vector 160 | def unit_safe({0, 0}), do: {0, 0} 161 | def unit_safe({0, 0, 0}), do: {0, 0, 0} 162 | def unit_safe(v), do: unit(v) 163 | 164 | @doc ~S""" 165 | Reverses a vector 166 | 167 | ## Examples 168 | 169 | iex> Vector.reverse({3, -4}) 170 | {-3, 4} 171 | iex> Vector.reverse({-2, 0, 5}) 172 | {2, 0, -5} 173 | iex> Vector.cross_norm({-2, 3, 5}, Vector.reverse({-2, 3, 5})) 174 | 0 175 | """ 176 | @spec reverse(vector) :: vector 177 | def reverse({x, y}), do: {-x, -y} 178 | def reverse({x, y, z}), do: {-x, -y, -z} 179 | 180 | @doc ~S""" 181 | Adds two vectors 182 | 183 | ## Examples 184 | 185 | iex> Vector.add({3, -4}, {2, 1}) 186 | {5,-3} 187 | iex> Vector.add({-2, 0, 5}, {0, 0, 0}) 188 | {-2, 0, 5} 189 | iex> Vector.add({2, 1, -2}, Vector.reverse({2, 1, -2})) 190 | {0, 0, 0} 191 | """ 192 | @spec add(vector, vector) :: vector 193 | def add({x1, y1}, {x2, y2}), do: {x1 + x2, y1 + y2} 194 | def add({x1, y1, z1}, {x2, y2}), do: add({x1, y1, z1}, {x2, y2, 0}) 195 | def add({x1, y1}, {x2, y2, z2}), do: add({x1, y1, 0}, {x2, y2, z2}) 196 | def add({x1, y1, z1}, {x2, y2, z2}), do: {x1 + x2, y1 + y2, z1 + z2} 197 | 198 | @doc ~S""" 199 | Subtract vector *B* from vector *A*. Equivalent to `Vector.add(A, Vector.revers(B))` 200 | 201 | ## Examples 202 | 203 | iex> Vector.subtract({3, -4}, {2, 1}) 204 | {1,-5} 205 | iex> Vector.subtract({-2, 0, 5}, {-3, 1, 2}) 206 | {1, -1, 3} 207 | """ 208 | @spec subtract(vector, vector) :: vector 209 | def subtract(a, b), do: add(a, reverse(b)) 210 | 211 | @doc ~S""" 212 | Multiply a vector by scalar value `s` 213 | 214 | ## Examples 215 | 216 | iex> Vector.multiply({3, -4}, 2.5) 217 | {7.5, -10.0} 218 | iex> Vector.multiply({-2, 0, 5}, -2) 219 | {4, 0, -10} 220 | """ 221 | @spec multiply(vector, number) :: vector 222 | def multiply({x, y}, s), do: {x * s, y * s} 223 | def multiply({x, y, z}, s), do: {x * s, y * s, z * s} 224 | 225 | @doc ~S""" 226 | Divide a vector by scalar value `s` 227 | 228 | ## Examples 229 | 230 | iex> Vector.divide({3, -4}, 2.5) 231 | {1.2, -1.6} 232 | iex> Vector.divide({-2, 0, 5}, -2) 233 | {1.0, 0.0, -2.5} 234 | """ 235 | @spec divide(vector, number) :: vector 236 | def divide({x, y}, s), do: {x / s, y / s} 237 | def divide({x, y, z}, s), do: {x / s, y / s, z / s} 238 | 239 | @doc ~S""" 240 | Returns a new coordinate by projecting a given length `distance` from 241 | coordinate `start` along `vector` 242 | 243 | ## Examples 244 | 245 | iex> Vector.project({3, -4}, {-1, 1}, 4) 246 | {1.4, -2.2} 247 | iex> Vector.project({-6, 0, 8}, {1, -2, 0.4}, 2.5) 248 | {-0.5, -2.0, 2.4} 249 | iex> Vector.project({-2, 1, 3}, {0, 0, 0}, 2.5) |> Vector.norm() 250 | 2.5 251 | """ 252 | @spec project(vector, location, number) :: location 253 | def project(vector, start, distance) do 254 | vector 255 | |> unit() 256 | |> multiply(distance) 257 | |> add(start) 258 | end 259 | 260 | @doc ~S""" 261 | Compares two vectors for equality, with an optional tolerance 262 | 263 | ## Examples 264 | 265 | iex> Vector.equal?({3, -4}, {3, -4}) 266 | true 267 | iex> Vector.equal?({3, -4}, {3.0001, -3.9999}) 268 | false 269 | iex> Vector.equal?({3, -4}, {3.0001, -3.9999}, 0.001) 270 | true 271 | iex> Vector.equal?({3, -4, 1}, {3.0001, -3.9999, 1.0}, 0.001) 272 | true 273 | """ 274 | @spec equal?(vector, vector, number) :: boolean 275 | def equal?(a, b, tolerance \\ 0.0) do 276 | norm_squared(subtract(a, b)) <= tolerance * tolerance 277 | end 278 | 279 | @doc ~S""" 280 | Returns the scalar component for the axis given 281 | 282 | ## Examples 283 | 284 | iex> Vector.component({3, -4}, :y) 285 | -4 286 | iex> Vector.component({-6, 0, 8}, :z) 287 | 8 288 | iex> Vector.component({1, -2}, :z) 289 | 0 290 | iex> Vector.component(Vector.basis(:x), :z) 291 | 0 292 | """ 293 | @spec component(vector, atom) :: number 294 | def component({x, _}, :x), do: x 295 | def component({_, y}, :y), do: y 296 | def component({_, _}, :z), do: 0 297 | def component({x, _, _}, :x), do: x 298 | def component({_, y, _}, :y), do: y 299 | def component({_, _, z}, :z), do: z 300 | 301 | @doc ~S""" 302 | Returns the basis vector for the given axis 303 | 304 | ## Examples 305 | 306 | iex> Vector.basis(:x) 307 | {1, 0, 0} 308 | iex> Vector.basis(:y) 309 | {0, 1, 0} 310 | iex> Vector.component(Vector.basis(:y), :y) 311 | 1 312 | """ 313 | @spec basis(atom) :: vector 314 | def basis(:x), do: {1, 0, 0} 315 | def basis(:y), do: {0, 1, 0} 316 | def basis(:z), do: {0, 0, 1} 317 | end 318 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Vector.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :vector, 7 | version: "1.1.0", 8 | elixir: "~> 1.2", 9 | description: description(), 10 | package: package(), 11 | build_embedded: Mix.env() == :prod, 12 | start_permanent: Mix.env() == :prod, 13 | test_coverage: [tool: ExCoveralls], 14 | preferred_cli_env: [coveralls: :test], 15 | dialyzer: [plt_add_apps: [:poison, :mix]], 16 | deps: deps(), 17 | aliases: aliases() 18 | ] 19 | end 20 | 21 | # Configuration for the OTP application 22 | # 23 | # Type "mix help compile.app" for more information 24 | def application do 25 | [applications: [:logger]] 26 | end 27 | 28 | defp deps do 29 | [ 30 | {:earmark, "~> 1.0", only: :dev}, 31 | {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, 32 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, 33 | {:credo, "~> 1.5", only: :dev, runtime: false}, 34 | {:benchfella, "~> 0.3", only: :dev}, 35 | {:excoveralls, "~> 0.4", only: :test} 36 | ] 37 | end 38 | 39 | defp description do 40 | """ 41 | Library of common vector functions for use in geometric or graphical calculations. 42 | """ 43 | end 44 | 45 | defp package do 46 | [ 47 | files: ["lib", "mix.exs", "README*"], 48 | maintainers: ["Powell Kinney"], 49 | licenses: ["MIT"], 50 | links: %{ 51 | "GitHub" => "https://github.com/pkinney/vector_ex", 52 | "Docs" => "https://hexdocs.pm/vector/Vector.html" 53 | } 54 | ] 55 | end 56 | 57 | defp aliases do 58 | [ 59 | validate: [ 60 | "clean", 61 | "compile --warnings-as-error", 62 | "format --check-formatted", 63 | "credo", 64 | "dialyzer" 65 | ] 66 | ] 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchfella": {:hex, :benchfella, "0.3.5", "b2122c234117b3f91ed7b43b6e915e19e1ab216971154acd0a80ce0e9b8c05f5", [:mix], [], "hexpm", "23f27cbc482cbac03fc8926441eb60a5e111759c17642bac005c3225f5eb809d"}, 3 | "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, 4 | "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, 5 | "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, 6 | "dialyxir": {:hex, :dialyxir, "1.2.0", "58344b3e87c2e7095304c81a9ae65cb68b613e28340690dfe1a5597fd08dec37", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "61072136427a851674cab81762be4dbeae7679f85b1272b6d25c3a839aff8463"}, 7 | "earmark": {:hex, :earmark, "1.4.34", "d7f89d3bbd7567a0bffc465e0a949f8f8dcbe43909c3acf96f4761a302cea10c", [:mix], [{:earmark_parser, "~> 1.4.29", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "90b106f3dad85b133b10d7d628167c88246123fd1cecb4557d83d21ec9e65504"}, 8 | "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, 9 | "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, 10 | "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, 11 | "excoveralls": {:hex, :excoveralls, "0.15.2", "809c1016660d80b28bbcd8cb7fd761791300def53345c1af5bd97db1330619ad", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f359dda36f15ae885d3259a90919b09ae9318f37c583c403493fe23808b2b882"}, 12 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, 13 | "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, 14 | "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, 15 | "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, 16 | "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, 17 | "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm", "b4c5d3230b397c8d95579e4a3d72826bb6463160130ccf4182f5be8579b5f44c"}, 18 | "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, 19 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, 20 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 21 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, 22 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, 23 | "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, 24 | "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, 25 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, 26 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, 27 | } 28 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/vector_identities_test.exs: -------------------------------------------------------------------------------- 1 | defmodule VectorIdentitiesTest do 2 | use ExUnit.Case, async: true 3 | 4 | import Vector 5 | 6 | @values [-2, 0, 1, 42] 7 | @vectors for(x <- @values, y <- @values, do: {x, y}) ++ 8 | for(x <- @values, y <- @values, z <- @values, do: {x, y, z}) 9 | @tolerance 0.00001 10 | 11 | defp are_equal?(r1, r2) when is_tuple(r1) and is_tuple(r2), do: equal?(r1, r2, @tolerance) 12 | defp are_equal?(r1, r2), do: abs(r1 - r2) <= @tolerance 13 | 14 | def test_identity_2(left, right) do 15 | for a <- @vectors, b <- @vectors, do: assert(are_equal?(left.(a, b), right.(a, b))) 16 | end 17 | 18 | def test_identity_3(left, right) do 19 | for a <- @vectors, 20 | b <- @vectors, 21 | c <- @vectors, 22 | do: assert(are_equal?(left.(a, b, c), right.(a, b, c))) 23 | end 24 | 25 | test "A + B = B + A" do 26 | test_identity_2( 27 | fn a, b -> add(a, b) end, 28 | fn a, b -> add(b, a) end 29 | ) 30 | end 31 | 32 | test "A . B = B . A" do 33 | test_identity_2( 34 | fn a, b -> dot(a, b) end, 35 | fn a, b -> dot(b, a) end 36 | ) 37 | end 38 | 39 | test "A x B = -B x A" do 40 | test_identity_2( 41 | fn a, b -> cross(a, b) end, 42 | fn a, b -> reverse(cross(b, a)) end 43 | ) 44 | end 45 | 46 | test "(A x B) . (A x B)) = |A x B|^2 = (A . A)(B . B) - (A . B)^2" do 47 | test_identity_2( 48 | fn a, b -> dot(cross(a, b), cross(a, b)) end, 49 | fn a, b -> :math.pow(norm(cross(a, b)), 2) end 50 | ) 51 | 52 | test_identity_2( 53 | fn a, b -> dot(cross(a, b), cross(a, b)) end, 54 | fn a, b -> dot(a, a) * dot(b, b) - :math.pow(dot(a, b), 2) end 55 | ) 56 | end 57 | 58 | test "|^A| = |^B|" do 59 | f = fn v -> 60 | case norm(v) do 61 | 0 -> 1 62 | _ -> norm(unit(v)) 63 | end 64 | end 65 | 66 | test_identity_2( 67 | fn a, _ -> f.(a) end, 68 | fn _, b -> f.(b) end 69 | ) 70 | end 71 | 72 | test "||A x B||^2 + (A . B)^2 = ||A||^2 ||B||^2" do 73 | test_identity_2( 74 | fn a, b -> :math.pow(norm(cross(a, b)), 2) + :math.pow(dot(a, b), 2) end, 75 | fn a, b -> :math.pow(norm(a), 2) * :math.pow(norm(b), 2) end 76 | ) 77 | end 78 | 79 | test "(A + B) . C = A . C + B . C" do 80 | test_identity_3( 81 | fn a, b, c -> a |> add(b) |> dot(c) end, 82 | fn a, b, c -> dot(a, c) + dot(b, c) end 83 | ) 84 | end 85 | 86 | test "(A + B) x C = A x C + B x C" do 87 | test_identity_3( 88 | fn a, b, c -> cross(add(a, b), c) end, 89 | fn a, b, c -> add(cross(a, c), cross(b, c)) end 90 | ) 91 | end 92 | 93 | test "A . (B x C) = B . (C x A) = C . (A x B)" do 94 | test_identity_3( 95 | fn a, b, c -> dot(a, cross(b, c)) end, 96 | fn a, b, c -> dot(b, cross(c, a)) end 97 | ) 98 | 99 | test_identity_3( 100 | fn a, b, c -> dot(b, cross(c, a)) end, 101 | fn a, b, c -> dot(c, cross(a, b)) end 102 | ) 103 | end 104 | 105 | test "A x (B x C) = B (A . C) - C (A . B)" do 106 | test_identity_3( 107 | fn a, b, c -> cross(a, cross(b, c)) end, 108 | fn a, b, c -> subtract(multiply(b, dot(a, c)), multiply(c, dot(a, b))) end 109 | ) 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /test/vector_test.exs: -------------------------------------------------------------------------------- 1 | defmodule VectorTest do 2 | use ExUnit.Case, async: true 3 | 4 | import Vector 5 | doctest Vector 6 | 7 | test "basis vectors" do 8 | assert unit(basis(:x)) == basis(:x) 9 | assert unit(basis(:y)) == basis(:y) 10 | assert unit(basis(:z)) == basis(:z) 11 | 12 | assert dot(basis(:x), basis(:y)) == 0 13 | assert dot(basis(:y), basis(:z)) == 0 14 | assert dot(basis(:x), basis(:z)) == 0 15 | 16 | assert cross_norm(basis(:x), basis(:y)) == 1 17 | assert cross_norm(basis(:y), basis(:z)) == 1 18 | assert cross_norm(basis(:x), basis(:z)) == 1 19 | end 20 | 21 | test "component of a vector" do 22 | assert component({-1, 2}, :x) == -1 23 | assert component({-1, 2}, :y) == 2 24 | assert component({-1, 2}, :z) == 0 25 | 26 | assert component({-1, 2, 1}, :x) == -1 27 | assert component({-1, 2, 1}, :y) == 2 28 | assert component({-1, 2, 1}, :z) == 1 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /validate.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | mix test 6 | mix credo --strict 7 | mix coveralls 8 | mix dialyzer --------------------------------------------------------------------------------