├── .bumpversion.cfg
├── .gitignore
├── .pylintrc
├── .pypirc.enc
├── .travis.yml
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs
├── Makefile
├── conf.py
├── content
│ └── basic.rst
├── index.rst
└── make.bat
├── requirements.txt
├── requirements_dev.txt
├── setup.py
├── tests
├── test_docs.py
├── test_vector2.py
└── test_vector3.py
└── vectormath
├── __init__.py
└── vector.py
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.2.2
3 | files = setup.py vectormath/__init__.py docs/conf.py README.rst
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 |
85 | # Spyder project settings
86 | .spyderproject
87 |
88 | # Rope project settings
89 | .ropeproject
90 | .pypirc
91 | pylint.html
92 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # Specify a configuration file.
4 | #rcfile=
5 |
6 | # Python code to execute, usually for sys.path manipulation such as
7 | # pygtk.require().
8 | #init-hook=
9 |
10 | # Add files or directories to the blacklist. They should be base names, not
11 | # paths.
12 | ignore=CVS
13 |
14 | # Pickle collected data for later comparisons.
15 | persistent=yes
16 |
17 | # List of plugins (as comma separated values of python modules names) to load,
18 | # usually to register additional checkers.
19 | load-plugins=
20 |
21 | # Use multiple processes to speed up Pylint.
22 | jobs=1
23 |
24 | # Allow loading of arbitrary C extensions. Extensions are imported into the
25 | # active Python interpreter and may run arbitrary code.
26 | unsafe-load-any-extension=no
27 |
28 | # A comma-separated list of package or module names from where C extensions may
29 | # be loaded. Extensions are loading into the active Python interpreter and may
30 | # run arbitrary code
31 | extension-pkg-whitelist=
32 |
33 | # Allow optimization of some AST trees. This will activate a peephole AST
34 | # optimizer, which will apply various small optimizations. For instance, it can
35 | # be used to obtain the result of joining multiple strings with the addition
36 | # operator. Joining a lot of strings can lead to a maximum recursion error in
37 | # Pylint and this flag can prevent that. It has one side effect, the resulting
38 | # AST will be different than the one from reality.
39 | optimize-ast=no
40 |
41 |
42 | [MESSAGES CONTROL]
43 |
44 | # Only show warnings with the listed confidence levels. Leave empty to show
45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
46 | confidence=
47 |
48 | # Enable the message, report, category or checker with the given id(s). You can
49 | # either give multiple identifier separated by comma (,) or put this option
50 | # multiple time (only on the command line, not in the configuration file where
51 | # it should appear only once). See also the "--disable" option for examples.
52 | #enable=
53 |
54 | # Disable the message, report, category or checker with the given id(s). You
55 | # can either give multiple identifiers separated by comma (,) or put this
56 | # option multiple times (only on the command line, not in the configuration
57 | # file where it should appear only once).You can also use "--disable=all" to
58 | # disable everything first and then reenable specific checks. For example, if
59 | # you want to run only the similarities checker, you can use "--disable=all
60 | # --enable=similarities". If you want to run only the classes checker, but have
61 | # no Warning level messages displayed, use"--disable=all --enable=classes
62 | # --disable=W"
63 | disable=E1111,E1121,W0223
64 |
65 |
66 | [REPORTS]
67 |
68 | # Set the output format. Available formats are text, parseable, colorized, msvs
69 | # (visual studio) and html. You can also give a reporter class, eg
70 | # mypackage.mymodule.MyReporterClass.
71 | output-format=text
72 |
73 | # Put messages in a separate file for each module / package specified on the
74 | # command line instead of printing them on stdout. Reports (if any) will be
75 | # written in a file name "pylint_global.[txt|html]".
76 | files-output=no
77 |
78 | # Tells whether to display a full report or only the messages
79 | reports=yes
80 |
81 | # Python expression which should return a note less than 10 (10 is the highest
82 | # note). You have access to the variables errors warning, statement which
83 | # respectively contain the number of errors / warnings messages and the total
84 | # number of statements analyzed. This is used by the global evaluation report
85 | # (RP0004).
86 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
87 |
88 | # Template used to display messages. This is a python new-style format string
89 | # used to format the message information. See doc for all details
90 | #msg-template=
91 |
92 |
93 | [BASIC]
94 |
95 | # List of builtins function names that should not be used, separated by a comma
96 | bad-functions=map,filter,input
97 |
98 | # Good variable names which should always be accepted, separated by a comma
99 | good-names=i,j,k,ex,Run,_,x,y,z,X,Y,Z,nV
100 |
101 | # Bad variable names which should always be refused, separated by a comma
102 | bad-names=foo,bar,baz,toto,tutu,tata
103 |
104 | # Colon-delimited sets of names that determine each other's naming style when
105 | # the name regexes allow several styles.
106 | name-group=
107 |
108 | # Include a hint for the correct naming format with invalid-name
109 | include-naming-hint=no
110 |
111 | # Regular expression matching correct function names
112 | function-rgx=[a-z_][a-z0-9_]{2,30}$
113 |
114 | # Naming hint for function names
115 | function-name-hint=[a-z_][a-z0-9_]{2,30}$
116 |
117 | # Regular expression matching correct variable names
118 | variable-rgx=[a-z_][a-z0-9_]{2,30}$
119 |
120 | # Naming hint for variable names
121 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$
122 |
123 | # Regular expression matching correct constant names
124 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
125 |
126 | # Naming hint for constant names
127 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
128 |
129 | # Regular expression matching correct attribute names
130 | attr-rgx=[a-z_][a-z0-9_]{2,30}$
131 |
132 | # Naming hint for attribute names
133 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$
134 |
135 | # Regular expression matching correct argument names
136 | argument-rgx=[a-z_][a-z0-9_]{2,30}$
137 |
138 | # Naming hint for argument names
139 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$
140 |
141 | # Regular expression matching correct class attribute names
142 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
143 |
144 | # Naming hint for class attribute names
145 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
146 |
147 | # Regular expression matching correct inline iteration names
148 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
149 |
150 | # Naming hint for inline iteration names
151 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
152 |
153 | # Regular expression matching correct class names
154 | class-rgx=[A-Z_][a-zA-Z0-9]+$
155 |
156 | # Naming hint for class names
157 | class-name-hint=[A-Z_][a-zA-Z0-9]+$
158 |
159 | # Regular expression matching correct module names
160 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
161 |
162 | # Naming hint for module names
163 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
164 |
165 | # Regular expression matching correct method names
166 | method-rgx=[a-z_][a-z0-9_]{2,30}$
167 |
168 | # Naming hint for method names
169 | method-name-hint=[a-z_][a-z0-9_]{2,30}$
170 |
171 | # Regular expression which should only match function or class names that do
172 | # not require a docstring.
173 | no-docstring-rgx=^_
174 |
175 | # Minimum line length for functions/classes that require docstrings, shorter
176 | # ones are exempt.
177 | docstring-min-length=-1
178 |
179 |
180 | [ELIF]
181 |
182 | # Maximum number of nested blocks for function / method body
183 | max-nested-blocks=5
184 |
185 |
186 | [FORMAT]
187 |
188 | # Maximum number of characters on a single line.
189 | max-line-length=100
190 |
191 | # Regexp for a line that is allowed to be longer than the limit.
192 | ignore-long-lines=^\s*(# )??$
193 |
194 | # Allow the body of an if to be on the same line as the test if there is no
195 | # else.
196 | single-line-if-stmt=no
197 |
198 | # List of optional constructs for which whitespace checking is disabled. `dict-
199 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
200 | # `trailing-comma` allows a space between comma and closing bracket: (a, ).
201 | # `empty-line` allows space-only lines.
202 | no-space-check=trailing-comma,dict-separator
203 |
204 | # Maximum number of lines in a module
205 | max-module-lines=1000
206 |
207 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
208 | # tab).
209 | indent-string=' '
210 |
211 | # Number of spaces of indent required inside a hanging or continued line.
212 | indent-after-paren=4
213 |
214 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
215 | expected-line-ending-format=
216 |
217 |
218 | [LOGGING]
219 |
220 | # Logging modules to check that the string format arguments are in logging
221 | # function parameter format
222 | logging-modules=logging
223 |
224 |
225 | [MISCELLANEOUS]
226 |
227 | # List of note tags to take in consideration, separated by a comma.
228 | notes=FIXME,XXX,TODO
229 |
230 |
231 | [SIMILARITIES]
232 |
233 | # Minimum lines number of a similarity.
234 | min-similarity-lines=20
235 |
236 | # Ignore comments when computing similarities.
237 | ignore-comments=yes
238 |
239 | # Ignore docstrings when computing similarities.
240 | ignore-docstrings=yes
241 |
242 | # Ignore imports when computing similarities.
243 | ignore-imports=yes
244 |
245 |
246 | [SPELLING]
247 |
248 | # Spelling dictionary name. Available dictionaries: none. To make it working
249 | # install python-enchant package.
250 | spelling-dict=
251 |
252 | # List of comma separated words that should not be checked.
253 | spelling-ignore-words=
254 |
255 | # A path to a file that contains private dictionary; one word per line.
256 | spelling-private-dict-file=
257 |
258 | # Tells whether to store unknown words to indicated private dictionary in
259 | # --spelling-private-dict-file option instead of raising a message.
260 | spelling-store-unknown-words=no
261 |
262 |
263 | [TYPECHECK]
264 |
265 | # Tells whether missing members accessed in mixin class should be ignored. A
266 | # mixin class is detected if its name ends with "mixin" (case insensitive).
267 | ignore-mixin-members=yes
268 |
269 | # List of module names for which member attributes should not be checked
270 | # (useful for modules/projects where namespaces are manipulated during runtime
271 | # and thus existing member attributes cannot be deduced by static analysis. It
272 | # supports qualified module names, as well as Unix pattern matching.
273 | ignored-modules=
274 |
275 | # List of classes names for which member attributes should not be checked
276 | # (useful for classes with attributes dynamically set). This supports can work
277 | # with qualified names.
278 | ignored-classes=
279 |
280 | # List of members which are set dynamically and missed by pylint inference
281 | # system, and so shouldn't trigger E1101 when accessed. Python regular
282 | # expressions are accepted.
283 | generated-members=
284 |
285 |
286 | [VARIABLES]
287 |
288 | # Tells whether we should check for unused import in __init__ files.
289 | init-import=no
290 |
291 | # A regular expression matching the name of dummy variables (i.e. expectedly
292 | # not used).
293 | dummy-variables-rgx=_$|dummy
294 |
295 | # List of additional names supposed to be defined in builtins. Remember that
296 | # you should avoid to define new builtins when possible.
297 | additional-builtins=
298 |
299 | # List of strings which can identify a callback function by name. A callback
300 | # name must start or end with one of those strings.
301 | callbacks=cb_,_cb
302 |
303 |
304 | [CLASSES]
305 |
306 | # List of method names used to declare (i.e. assign) instance attributes.
307 | defining-attr-methods=__init__,__new__,setUp
308 |
309 | # List of valid names for the first argument in a class method.
310 | valid-classmethod-first-arg=cls
311 |
312 | # List of valid names for the first argument in a metaclass class method.
313 | valid-metaclass-classmethod-first-arg=mcs
314 |
315 | # List of member names, which should be excluded from the protected access
316 | # warning.
317 | exclude-protected=_asdict,_fields,_replace,_source,_make,_props
318 |
319 |
320 | [DESIGN]
321 |
322 | # Maximum number of arguments for function / method
323 | max-args=5
324 |
325 | # Argument names that match this expression will be ignored. Default to name
326 | # with leading underscore
327 | ignored-argument-names=_.*
328 |
329 | # Maximum number of locals for function / method body
330 | max-locals=15
331 |
332 | # Maximum number of return / yield for function / method body
333 | max-returns=6
334 |
335 | # Maximum number of branch for function / method body
336 | max-branches=12
337 |
338 | # Maximum number of statements in function / method body
339 | max-statements=50
340 |
341 | # Maximum number of parents for a class (see R0901).
342 | max-parents=7
343 |
344 | # Maximum number of attributes for a class (see R0902).
345 | max-attributes=7
346 |
347 | # Minimum number of public methods for a class (see R0903).
348 | min-public-methods=2
349 |
350 | # Maximum number of public methods for a class (see R0904).
351 | max-public-methods=20
352 |
353 | # Maximum number of boolean expressions in a if statement
354 | max-bool-expr=5
355 |
356 |
357 | [IMPORTS]
358 |
359 | # Deprecated modules which should not be used, separated by a comma
360 | deprecated-modules=regsub,TERMIOS,Bastion,rexec
361 |
362 | # Create a graph of every (i.e. internal and external) dependencies in the
363 | # given file (report RP0402 must not be disabled)
364 | import-graph=
365 |
366 | # Create a graph of external dependencies in the given file (report RP0402 must
367 | # not be disabled)
368 | ext-import-graph=
369 |
370 | # Create a graph of internal dependencies in the given file (report RP0402 must
371 | # not be disabled)
372 | int-import-graph=
373 |
374 |
375 | [EXCEPTIONS]
376 |
377 | # Exceptions that will emit a warning when being caught. Defaults to
378 | # "Exception"
379 | overgeneral-exceptions=Exception
380 |
--------------------------------------------------------------------------------
/.pypirc.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/seequent/vectormath/a2259fb82cf5a665170f50d216b11a738400d878/.pypirc.enc
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 2.7.13
4 | - 3.4
5 | - 3.5
6 | - 3.6
7 |
8 | sudo: false
9 |
10 | addons:
11 | apt:
12 | packages:
13 | - python-numpy
14 |
15 | install:
16 | - pip install -r requirements_dev.txt
17 |
18 | script:
19 | - make tests
20 |
21 | after_success:
22 | - bash <(curl -s https://codecov.io/bash)
23 | - openssl aes-256-cbc -K $encrypted_0f57b1eef12d_key -iv $encrypted_0f57b1eef12d_iv -in .pypirc.enc -out ~/.pypirc -d
24 | - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" -a $TRAVIS_PYTHON_VERSION == "2.7.13" -a -n "$(grep version setup.py | cut -d \' -f 2 | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$')" ]; then
25 | make publish;
26 | fi
27 | - if [ "$TRAVIS_BRANCH" = "beta" -a "$TRAVIS_PULL_REQUEST" = "false" -a $TRAVIS_PYTHON_VERSION == "2.7.13" -a -n "$(grep version setup.py | cut -d \' -f 2 | grep -E '^[0-9]+\.[0-9]+\.[0-9]+b[0-9]+$')" ]; then
28 | make publish;
29 | fi
30 |
31 | notifications:
32 | slack:
33 | secure: "C8P6i9K+OjBLPngi1R20OVL4Xe6xe5DCfUMR33ebHtMA/sfUnMuoRgZf5f+wjWsJNBBVC6kRFBETk4/SlWTwu0VrjFPsFjRqjX9fy/jOt2s5PD+hVGclx1fDd8t8FDsWJ9KMG15hKgoM2BFnOC9MOI7mvRt1V4PvskJ9rhqzLw3gdvwsAM0fFs12CE0lEQowieLKnIoBtWIV/ihfnorLid3led+b+vCKZcSLig3FR1fN56yS9qJXPw7lgUZX5qyyL1gHrV+VKX+fQpI/tDX611w6BLHlx0Y45pDH5jVGtMoQzhxQ6QH2ibrdw/3hHE9Ki7CS36h8oyzQ7hxXP6iwI16uPUqJRIqwdQcaHvNP7CnpnCyRtbhbOS0fCHjduXKWo+ttP++1x11vlj2F4qOg9tEWQH/6EU4rmo/naUwrTtybTUY0UmWWx8ASF4/kjZSYr9b0xZHwM3nVAwlfxNokvk/BPZs04/lm42UucZEC+IbVV4LTh1TURbXScDKj5Ve01nUfhrQkAqcl4TsQ1n2PAG+T//Ia3vO9r2w5kBSNaXA0Q8WDc51z/mdCmkFnQLrxZOMvRDQ8vbI8J7fMtPG/LmjmiXYK8bOf71pDwn3qYPFsf96Jg2wgAcJ2fPQt8apllCULzl0gZ0xOlC6zgEkCXCb/LLpBS+ud/C4t0Ureq/k="
34 | email: false
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Seequent
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 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PACKAGE_NAME=vectormath
2 |
3 | .PHONY: install publish docs coverage lint lint-html graphs tests
4 |
5 | install:
6 | python setup.py install
7 |
8 | publish:
9 | python setup.py sdist upload
10 |
11 | docs:
12 | cd docs && make html
13 |
14 | coverage:
15 | nosetests --logging-level=INFO --with-coverage --cover-package=$(PACKAGE_NAME) --cover-html
16 | open cover/index.html
17 |
18 | lint:
19 | pylint $(PACKAGE_NAME)
20 |
21 | lint-html:
22 | pylint --output-format=html $(PACKAGE_NAME) > pylint.html
23 |
24 | graphs:
25 | pyreverse -my -A -o pdf -p vectormathpy $(PACKAGE_NAME)/**.py $(PACKAGE_NAME)/**/**.py
26 |
27 | tests:
28 | nosetests --logging-level=INFO --with-coverage --cover-package=$(PACKAGE_NAME)
29 | make lint
30 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | vectormath
2 | ==========
3 |
4 |
5 | .. image:: https://img.shields.io/pypi/v/vectormath.svg
6 | :target: https://pypi.org/project/vectormath
7 | :alt: Latest PyPI version
8 |
9 | .. image:: https://img.shields.io/badge/license-MIT-blue.svg
10 | :target: https://github.com/seequent/vectormath/blob/master/LICENSE
11 | :alt: MIT license
12 |
13 | .. image:: https://api.travis-ci.org/seequent/vectormath.svg?branch=master
14 | :target: https://travis-ci.org/seequent/vectormath
15 | :alt: Travis CI build status
16 |
17 | .. image:: https://codecov.io/gh/seequent/vectormath/branch/master/graph/badge.svg
18 | :target: https://codecov.io/gh/seequent/vectormath
19 | :alt: Code test coverage
20 |
21 |
22 | Vector math utilities for Python built on `NumPy `_
23 |
24 |
25 | Why
26 | ---
27 |
28 | The :code:`vectormath` package provides a fast, simple library of vector math
29 | utilities by leveraging NumPy. This allows explicit
30 | geometric constructs to be created (for example, :code:`Vector3` and :code:`Plane`)
31 | without redefining the underlying array math.
32 |
33 | Scope
34 | -----
35 |
36 | The :code:`vectormath` package includes :code:`Vector3`/:code:`Vector2` and
37 | :code:`Vector3Array`/:code:`Vector2Array`.
38 |
39 |
40 | Goals
41 | -----
42 |
43 | * Speed: All low-level operations rely on NumPy arrays. These are densely packed,
44 | typed, and partially implemented in C. The :code:`VectorArray` classes in particular
45 | take advantage of this speed by performing vector operations on all Vectors at
46 | once, rather than in a loop.
47 | * Simplicty: High-level operations are explicit and straight-forward.
48 | This library should be usable by Programmers, Mathematicians, and Geologists.
49 |
50 |
51 | Alternatives
52 | ------------
53 |
54 | * `NumPy `_ can be used for any array operations
55 | * Many small libraries on PyPI (e.g. `vectors `_)
56 | implement vector math operations but are are only built with single vectors
57 | in mind.
58 |
59 | Connections
60 | -----------
61 |
62 | * `properties `_ uses :code:`vectormath`
63 | as the underlying framework for Vector properties.
64 |
65 | Installation
66 | ------------
67 |
68 | To install the repository, ensure that you have
69 | `pip installed `_ and run:
70 |
71 | .. code::
72 |
73 | pip install vectormath
74 |
75 | For the development version:
76 |
77 | .. code::
78 |
79 | git clone https://github.com/seequent/vectormath.git
80 | cd vectormath
81 | pip install -e .
82 |
83 |
84 | Examples
85 | ========
86 |
87 | This example gives a brief demonstration of some of the notable features of
88 | :code:`Vector3` and :code:`Vector3Array`
89 |
90 | .. code:: python
91 |
92 | import numpy as np
93 | import vectormath as vmath
94 |
95 | # Single Vectors
96 | v = vmath.Vector3(5, 0, 0)
97 | v.normalize()
98 | print(v) # >> [1, 0, 0]
99 | print(v.x) # >> 1.0
100 |
101 | # VectorArrays are much faster than a for loop over Vectors
102 | v_array = vmath.Vector3Array([[4, 0, 0], [0, 2, 0], [0, 0, 3]])
103 | print(v_array.x) # >> [4, 0, 0]
104 | print(v_array.length) # >> [4, 2, 3]
105 | print(v_array.normalize()) # >> [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
106 |
107 | # Vectors can be accessed individually or in slices
108 | print(type(v_array[1:])) # >> vectormath.Vector3Array
109 | print(type(v_array[2])) # >> vectormath.Vector3
110 |
111 | # All these classes are just numpy arrays
112 | print(isinstance(v, np.ndarray)) # >> True
113 | print(type(v_array[1:, 1:])) # >> numpy.ndarray
114 |
115 |
116 | Current version: v0.2.2
117 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help
23 | help:
24 | @echo "Please use \`make ' where is one of"
25 | @echo " html to make standalone HTML files"
26 | @echo " dirhtml to make HTML files named index.html in directories"
27 | @echo " singlehtml to make a single large HTML file"
28 | @echo " pickle to make pickle files"
29 | @echo " json to make JSON files"
30 | @echo " htmlhelp to make HTML files and a HTML help project"
31 | @echo " qthelp to make HTML files and a qthelp project"
32 | @echo " applehelp to make an Apple Help Book"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " epub3 to make an epub3"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 | @echo " dummy to check syntax errors of document sources"
51 |
52 | .PHONY: clean
53 | clean:
54 | rm -rf $(BUILDDIR)/*
55 |
56 | .PHONY: html
57 | html:
58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
61 |
62 | .PHONY: dirhtml
63 | dirhtml:
64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
65 | @echo
66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
67 |
68 | .PHONY: singlehtml
69 | singlehtml:
70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
71 | @echo
72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
73 |
74 | .PHONY: pickle
75 | pickle:
76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
77 | @echo
78 | @echo "Build finished; now you can process the pickle files."
79 |
80 | .PHONY: json
81 | json:
82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
83 | @echo
84 | @echo "Build finished; now you can process the JSON files."
85 |
86 | .PHONY: htmlhelp
87 | htmlhelp:
88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
89 | @echo
90 | @echo "Build finished; now you can run HTML Help Workshop with the" \
91 | ".hhp project file in $(BUILDDIR)/htmlhelp."
92 |
93 | .PHONY: qthelp
94 | qthelp:
95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
96 | @echo
97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/vectormath.qhcp"
100 | @echo "To view the help file:"
101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/vectormath.qhc"
102 |
103 | .PHONY: applehelp
104 | applehelp:
105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
106 | @echo
107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
108 | @echo "N.B. You won't be able to view it unless you put it in" \
109 | "~/Library/Documentation/Help or install it in your application" \
110 | "bundle."
111 |
112 | .PHONY: devhelp
113 | devhelp:
114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
115 | @echo
116 | @echo "Build finished."
117 | @echo "To view the help file:"
118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/vectormath"
119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/vectormath"
120 | @echo "# devhelp"
121 |
122 | .PHONY: epub
123 | epub:
124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
125 | @echo
126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
127 |
128 | .PHONY: epub3
129 | epub3:
130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
131 | @echo
132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
133 |
134 | .PHONY: latex
135 | latex:
136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
137 | @echo
138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
140 | "(use \`make latexpdf' here to do that automatically)."
141 |
142 | .PHONY: latexpdf
143 | latexpdf:
144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
145 | @echo "Running LaTeX files through pdflatex..."
146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
148 |
149 | .PHONY: latexpdfja
150 | latexpdfja:
151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
152 | @echo "Running LaTeX files through platex and dvipdfmx..."
153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
155 |
156 | .PHONY: text
157 | text:
158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
159 | @echo
160 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
161 |
162 | .PHONY: man
163 | man:
164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
165 | @echo
166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
167 |
168 | .PHONY: texinfo
169 | texinfo:
170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
171 | @echo
172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
173 | @echo "Run \`make' in that directory to run these through makeinfo" \
174 | "(use \`make info' here to do that automatically)."
175 |
176 | .PHONY: info
177 | info:
178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
179 | @echo "Running Texinfo files through makeinfo..."
180 | make -C $(BUILDDIR)/texinfo info
181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
182 |
183 | .PHONY: gettext
184 | gettext:
185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
186 | @echo
187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
188 |
189 | .PHONY: changes
190 | changes:
191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
192 | @echo
193 | @echo "The overview file is in $(BUILDDIR)/changes."
194 |
195 | .PHONY: linkcheck
196 | linkcheck:
197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
198 | @echo
199 | @echo "Link check complete; look for any errors in the above output " \
200 | "or in $(BUILDDIR)/linkcheck/output.txt."
201 |
202 | .PHONY: doctest
203 | doctest:
204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
205 | @echo "Testing of doctests in the sources finished, look at the " \
206 | "results in $(BUILDDIR)/doctest/output.txt."
207 |
208 | .PHONY: coverage
209 | coverage:
210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
211 | @echo "Testing of coverage in the sources finished, look at the " \
212 | "results in $(BUILDDIR)/coverage/python.txt."
213 |
214 | .PHONY: xml
215 | xml:
216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
217 | @echo
218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
219 |
220 | .PHONY: pseudoxml
221 | pseudoxml:
222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
223 | @echo
224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
225 |
226 | .PHONY: dummy
227 | dummy:
228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
229 | @echo
230 | @echo "Build finished. Dummy builder generates no files."
231 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # vectormath documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Sep 26 14:50:08 2016.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | #sys.path.insert(0, os.path.abspath('.'))
22 |
23 | # -- General configuration ------------------------------------------------
24 |
25 | # If your documentation needs a minimal Sphinx version, state it here.
26 | #needs_sphinx = '1.0'
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = [
32 | 'sphinx.ext.autodoc',
33 | 'sphinx.ext.doctest',
34 | 'sphinx.ext.intersphinx',
35 | 'sphinx.ext.mathjax',
36 | 'sphinx.ext.viewcode',
37 | ]
38 |
39 | # Add any paths that contain templates here, relative to this directory.
40 | templates_path = ['_templates']
41 |
42 | # The suffix(es) of source filenames.
43 | # You can specify multiple suffix as a list of string:
44 | # source_suffix = ['.rst', '.md']
45 | source_suffix = '.rst'
46 |
47 | # The encoding of source files.
48 | #source_encoding = 'utf-8-sig'
49 |
50 | # The master toctree document.
51 | master_doc = 'index'
52 |
53 | # General information about the project.
54 | project = u'vectormath'
55 | copyright = u'2018, Seequent'
56 | author = u'Seequent'
57 |
58 | # The version info for the project you're documenting, acts as replacement for
59 | # |version| and |release|, also used in various other places throughout the
60 | # built documents.
61 | #
62 | # The short X.Y version.
63 | version = u'0.2.2'
64 | # The full version, including alpha/beta/rc tags.
65 | release = u'0.2.2'
66 |
67 | # The language for content autogenerated by Sphinx. Refer to documentation
68 | # for a list of supported languages.
69 | #
70 | # This is also used if you do content translation via gettext catalogs.
71 | # Usually you set "language" from the command line for these cases.
72 | language = None
73 |
74 | # There are two options for replacing |today|: either, you set today to some
75 | # non-false value, then it is used:
76 | #today = ''
77 | # Else, today_fmt is used as the format for a strftime call.
78 | #today_fmt = '%B %d, %Y'
79 |
80 | # List of patterns, relative to source directory, that match files and
81 | # directories to ignore when looking for source files.
82 | # This patterns also effect to html_static_path and html_extra_path
83 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
84 |
85 | # The reST default role (used for this markup: `text`) to use for all
86 | # documents.
87 | #default_role = None
88 |
89 | # If true, '()' will be appended to :func: etc. cross-reference text.
90 | #add_function_parentheses = True
91 |
92 | # If true, the current module name will be prepended to all description
93 | # unit titles (such as .. function::).
94 | #add_module_names = True
95 |
96 | # If true, sectionauthor and moduleauthor directives will be shown in the
97 | # output. They are ignored by default.
98 | #show_authors = False
99 |
100 | # The name of the Pygments (syntax highlighting) style to use.
101 | pygments_style = 'sphinx'
102 |
103 | # A list of ignored prefixes for module index sorting.
104 | #modindex_common_prefix = []
105 |
106 | # If true, keep warnings as "system message" paragraphs in the built documents.
107 | #keep_warnings = False
108 |
109 | # If true, `todo` and `todoList` produce output, else they produce nothing.
110 | todo_include_todos = False
111 |
112 |
113 | # -- Options for HTML output ----------------------------------------------
114 |
115 | # The theme to use for HTML and HTML Help pages. See the documentation for
116 | # a list of builtin themes.
117 | try:
118 | import sphinx_rtd_theme
119 | html_theme = 'sphinx_rtd_theme'
120 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
121 | pass
122 | except:
123 | html_theme = 'default'
124 |
125 | # Theme options are theme-specific and customize the look and feel of a theme
126 | # further. For a list of options available for each theme, see the
127 | # documentation.
128 | #html_theme_options = {}
129 |
130 | # Add any paths that contain custom themes here, relative to this directory.
131 | #html_theme_path = []
132 |
133 | # The name for this set of Sphinx documents.
134 | # " v documentation" by default.
135 | #html_title = u'vectormath v0.2.2'
136 |
137 | # A shorter title for the navigation bar. Default is the same as html_title.
138 | #html_short_title = None
139 |
140 | # The name of an image file (relative to this directory) to place at the top
141 | # of the sidebar.
142 | #html_logo = None
143 |
144 | # The name of an image file (relative to this directory) to use as a favicon of
145 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
146 | # pixels large.
147 | #html_favicon = None
148 |
149 | # Add any paths that contain custom static files (such as style sheets) here,
150 | # relative to this directory. They are copied after the builtin static files,
151 | # so a file named "default.css" will overwrite the builtin "default.css".
152 | # html_static_path = ['_static']
153 |
154 | # Add any extra paths that contain custom files (such as robots.txt or
155 | # .htaccess) here, relative to this directory. These files are copied
156 | # directly to the root of the documentation.
157 | #html_extra_path = []
158 |
159 | # If not None, a 'Last updated on:' timestamp is inserted at every page
160 | # bottom, using the given strftime format.
161 | # The empty string is equivalent to '%b %d, %Y'.
162 | #html_last_updated_fmt = None
163 |
164 | # If true, SmartyPants will be used to convert quotes and dashes to
165 | # typographically correct entities.
166 | #html_use_smartypants = True
167 |
168 | # Custom sidebar templates, maps document names to template names.
169 | #html_sidebars = {}
170 |
171 | # Additional templates that should be rendered to pages, maps page names to
172 | # template names.
173 | #html_additional_pages = {}
174 |
175 | # If false, no module index is generated.
176 | #html_domain_indices = True
177 |
178 | # If false, no index is generated.
179 | #html_use_index = True
180 |
181 | # If true, the index is split into individual pages for each letter.
182 | #html_split_index = False
183 |
184 | # If true, links to the reST sources are added to the pages.
185 | #html_show_sourcelink = True
186 |
187 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
188 | #html_show_sphinx = True
189 |
190 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
191 | #html_show_copyright = True
192 |
193 | # If true, an OpenSearch description file will be output, and all pages will
194 | # contain a tag referring to it. The value of this option must be the
195 | # base URL from which the finished HTML is served.
196 | #html_use_opensearch = ''
197 |
198 | # This is the file name suffix for HTML files (e.g. ".xhtml").
199 | #html_file_suffix = None
200 |
201 | # Language to be used for generating the HTML full-text search index.
202 | # Sphinx supports the following languages:
203 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
204 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
205 | #html_search_language = 'en'
206 |
207 | # A dictionary with options for the search language support, empty by default.
208 | # 'ja' uses this config value.
209 | # 'zh' user can custom change `jieba` dictionary path.
210 | #html_search_options = {'type': 'default'}
211 |
212 | # The name of a javascript file (relative to the configuration directory) that
213 | # implements a search results scorer. If empty, the default will be used.
214 | #html_search_scorer = 'scorer.js'
215 |
216 | # Output file base name for HTML help builder.
217 | htmlhelp_basename = 'vectormathdoc'
218 |
219 | # -- Options for LaTeX output ---------------------------------------------
220 |
221 | latex_elements = {
222 | # The paper size ('letterpaper' or 'a4paper').
223 | #'papersize': 'letterpaper',
224 |
225 | # The font size ('10pt', '11pt' or '12pt').
226 | #'pointsize': '10pt',
227 |
228 | # Additional stuff for the LaTeX preamble.
229 | #'preamble': '',
230 |
231 | # Latex figure (float) alignment
232 | #'figure_align': 'htbp',
233 | }
234 |
235 | # Grouping the document tree into LaTeX files. List of tuples
236 | # (source start file, target name, title,
237 | # author, documentclass [howto, manual, or own class]).
238 | latex_documents = [
239 | (master_doc, 'vectormath.tex', u'vectormath Documentation',
240 | u'Seequent', 'manual'),
241 | ]
242 |
243 | # The name of an image file (relative to this directory) to place at the top of
244 | # the title page.
245 | #latex_logo = None
246 |
247 | # For "manual" documents, if this is true, then toplevel headings are parts,
248 | # not chapters.
249 | #latex_use_parts = False
250 |
251 | # If true, show page references after internal links.
252 | #latex_show_pagerefs = False
253 |
254 | # If true, show URL addresses after external links.
255 | #latex_show_urls = False
256 |
257 | # Documents to append as an appendix to all manuals.
258 | #latex_appendices = []
259 |
260 | # If false, no module index is generated.
261 | #latex_domain_indices = True
262 |
263 |
264 | # -- Options for manual page output ---------------------------------------
265 |
266 | # One entry per manual page. List of tuples
267 | # (source start file, name, description, authors, manual section).
268 | man_pages = [
269 | (master_doc, 'vectormath', u'vectormath Documentation',
270 | [author], 1)
271 | ]
272 |
273 | # If true, show URL addresses after external links.
274 | #man_show_urls = False
275 |
276 |
277 | # -- Options for Texinfo output -------------------------------------------
278 |
279 | # Grouping the document tree into Texinfo files. List of tuples
280 | # (source start file, target name, title, author,
281 | # dir menu entry, description, category)
282 | texinfo_documents = [
283 | (master_doc, 'vectormath', u'vectormath Documentation',
284 | author, 'vectormath', 'One line description of project.',
285 | 'Miscellaneous'),
286 | ]
287 |
288 | # Documents to append as an appendix to all manuals.
289 | #texinfo_appendices = []
290 |
291 | # If false, no module index is generated.
292 | #texinfo_domain_indices = True
293 |
294 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
295 | #texinfo_show_urls = 'footnote'
296 |
297 | # If true, do not generate a @detailmenu in the "Top" node's menu.
298 | #texinfo_no_detailmenu = False
299 |
300 | # Example configuration for intersphinx: refer to the Python standard library.
301 | intersphinx_mapping = {'https://docs.python.org/3/': None,
302 | # 'https://docs.scipy.org/doc/numpy/': None,
303 | # 'https://docs.scipy.org/doc/scipy/reference/': None,
304 | }
305 | linkcheck_retries = 10
306 |
307 | import sphinx.environment
308 | from docutils.utils import get_source_line
309 |
310 |
311 | def _supress_nonlocal_image_warn(self, msg, node, **kwargs):
312 | if not msg.startswith('nonlocal image URI found:'):
313 | self._warnfunc(msg, '{:s}:{:s}'.format(get_source_line(node), **kwargs))
314 |
315 | sphinx.environment.BuildEnvironment.warn_node = _supress_nonlocal_image_warn
316 |
--------------------------------------------------------------------------------
/docs/content/basic.rst:
--------------------------------------------------------------------------------
1 | .. _basic:
2 |
3 | Basic Vector Math
4 | =================
5 |
6 | BaseVector
7 | ----------
8 |
9 | .. autoclass:: vectormath.vector.BaseVector
10 | :members:
11 | :undoc-members:
12 |
13 | BaseVectorArray
14 | ---------------
15 |
16 | .. autoclass:: vectormath.vector.BaseVectorArray
17 | :members:
18 | :undoc-members:
19 |
20 | Vector3
21 | -------
22 |
23 | .. autoclass:: vectormath.vector.Vector3
24 | :members:
25 | :undoc-members:
26 |
27 | Vector2
28 | -------
29 |
30 | .. autoclass:: vectormath.vector.Vector2
31 | :members:
32 | :undoc-members:
33 |
34 | Vector3Array
35 | ------------
36 |
37 | .. autoclass:: vectormath.vector.Vector3Array
38 | :members:
39 | :undoc-members:
40 |
41 | Vector2Array
42 | ------------
43 |
44 | .. autoclass:: vectormath.vector.Vector2Array
45 | :members:
46 | :undoc-members:
47 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 |
2 |
3 | .. include:: ../README.rst
4 |
5 | Contents:
6 |
7 | .. toctree::
8 | :maxdepth: 2
9 |
10 | content/basic.rst
11 |
12 |
13 | Indices and tables
14 | ==================
15 |
16 | * :ref:`genindex`
17 | * :ref:`modindex`
18 | * :ref:`search`
19 |
20 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. epub3 to make an epub3
31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
32 | echo. text to make text files
33 | echo. man to make manual pages
34 | echo. texinfo to make Texinfo files
35 | echo. gettext to make PO message catalogs
36 | echo. changes to make an overview over all changed/added/deprecated items
37 | echo. xml to make Docutils-native XML files
38 | echo. pseudoxml to make pseudoxml-XML files for display purposes
39 | echo. linkcheck to check all external links for integrity
40 | echo. doctest to run all doctests embedded in the documentation if enabled
41 | echo. coverage to run coverage check of the documentation if enabled
42 | echo. dummy to check syntax errors of document sources
43 | goto end
44 | )
45 |
46 | if "%1" == "clean" (
47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
48 | del /q /s %BUILDDIR%\*
49 | goto end
50 | )
51 |
52 |
53 | REM Check if sphinx-build is available and fallback to Python version if any
54 | %SPHINXBUILD% 1>NUL 2>NUL
55 | if errorlevel 9009 goto sphinx_python
56 | goto sphinx_ok
57 |
58 | :sphinx_python
59 |
60 | set SPHINXBUILD=python -m sphinx.__init__
61 | %SPHINXBUILD% 2> nul
62 | if errorlevel 9009 (
63 | echo.
64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
65 | echo.installed, then set the SPHINXBUILD environment variable to point
66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
67 | echo.may add the Sphinx directory to PATH.
68 | echo.
69 | echo.If you don't have Sphinx installed, grab it from
70 | echo.http://sphinx-doc.org/
71 | exit /b 1
72 | )
73 |
74 | :sphinx_ok
75 |
76 |
77 | if "%1" == "html" (
78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
79 | if errorlevel 1 exit /b 1
80 | echo.
81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
82 | goto end
83 | )
84 |
85 | if "%1" == "dirhtml" (
86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
87 | if errorlevel 1 exit /b 1
88 | echo.
89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
90 | goto end
91 | )
92 |
93 | if "%1" == "singlehtml" (
94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
95 | if errorlevel 1 exit /b 1
96 | echo.
97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
98 | goto end
99 | )
100 |
101 | if "%1" == "pickle" (
102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
103 | if errorlevel 1 exit /b 1
104 | echo.
105 | echo.Build finished; now you can process the pickle files.
106 | goto end
107 | )
108 |
109 | if "%1" == "json" (
110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
111 | if errorlevel 1 exit /b 1
112 | echo.
113 | echo.Build finished; now you can process the JSON files.
114 | goto end
115 | )
116 |
117 | if "%1" == "htmlhelp" (
118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
119 | if errorlevel 1 exit /b 1
120 | echo.
121 | echo.Build finished; now you can run HTML Help Workshop with the ^
122 | .hhp project file in %BUILDDIR%/htmlhelp.
123 | goto end
124 | )
125 |
126 | if "%1" == "qthelp" (
127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
128 | if errorlevel 1 exit /b 1
129 | echo.
130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
131 | .qhcp project file in %BUILDDIR%/qthelp, like this:
132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\vectormath.qhcp
133 | echo.To view the help file:
134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\vectormath.ghc
135 | goto end
136 | )
137 |
138 | if "%1" == "devhelp" (
139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
140 | if errorlevel 1 exit /b 1
141 | echo.
142 | echo.Build finished.
143 | goto end
144 | )
145 |
146 | if "%1" == "epub" (
147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
148 | if errorlevel 1 exit /b 1
149 | echo.
150 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
151 | goto end
152 | )
153 |
154 | if "%1" == "epub3" (
155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
156 | if errorlevel 1 exit /b 1
157 | echo.
158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
159 | goto end
160 | )
161 |
162 | if "%1" == "latex" (
163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
164 | if errorlevel 1 exit /b 1
165 | echo.
166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
167 | goto end
168 | )
169 |
170 | if "%1" == "latexpdf" (
171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
172 | cd %BUILDDIR%/latex
173 | make all-pdf
174 | cd %~dp0
175 | echo.
176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
177 | goto end
178 | )
179 |
180 | if "%1" == "latexpdfja" (
181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
182 | cd %BUILDDIR%/latex
183 | make all-pdf-ja
184 | cd %~dp0
185 | echo.
186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
187 | goto end
188 | )
189 |
190 | if "%1" == "text" (
191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
192 | if errorlevel 1 exit /b 1
193 | echo.
194 | echo.Build finished. The text files are in %BUILDDIR%/text.
195 | goto end
196 | )
197 |
198 | if "%1" == "man" (
199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
200 | if errorlevel 1 exit /b 1
201 | echo.
202 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
203 | goto end
204 | )
205 |
206 | if "%1" == "texinfo" (
207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
208 | if errorlevel 1 exit /b 1
209 | echo.
210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
211 | goto end
212 | )
213 |
214 | if "%1" == "gettext" (
215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
216 | if errorlevel 1 exit /b 1
217 | echo.
218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
219 | goto end
220 | )
221 |
222 | if "%1" == "changes" (
223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
224 | if errorlevel 1 exit /b 1
225 | echo.
226 | echo.The overview file is in %BUILDDIR%/changes.
227 | goto end
228 | )
229 |
230 | if "%1" == "linkcheck" (
231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
232 | if errorlevel 1 exit /b 1
233 | echo.
234 | echo.Link check complete; look for any errors in the above output ^
235 | or in %BUILDDIR%/linkcheck/output.txt.
236 | goto end
237 | )
238 |
239 | if "%1" == "doctest" (
240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
241 | if errorlevel 1 exit /b 1
242 | echo.
243 | echo.Testing of doctests in the sources finished, look at the ^
244 | results in %BUILDDIR%/doctest/output.txt.
245 | goto end
246 | )
247 |
248 | if "%1" == "coverage" (
249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
250 | if errorlevel 1 exit /b 1
251 | echo.
252 | echo.Testing of coverage in the sources finished, look at the ^
253 | results in %BUILDDIR%/coverage/python.txt.
254 | goto end
255 | )
256 |
257 | if "%1" == "xml" (
258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
259 | if errorlevel 1 exit /b 1
260 | echo.
261 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
262 | goto end
263 | )
264 |
265 | if "%1" == "pseudoxml" (
266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
267 | if errorlevel 1 exit /b 1
268 | echo.
269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
270 | goto end
271 | )
272 |
273 | if "%1" == "dummy" (
274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
275 | if errorlevel 1 exit /b 1
276 | echo.
277 | echo.Build finished. Dummy builder generates no files.
278 | goto end
279 | )
280 |
281 | :end
282 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | -e .
2 |
--------------------------------------------------------------------------------
/requirements_dev.txt:
--------------------------------------------------------------------------------
1 | nose-cov
2 | python-coveralls
3 | sphinx
4 | pylint
5 | -e .
6 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Vector math utilities for Python
4 | """
5 |
6 | from distutils.core import setup
7 | from setuptools import find_packages
8 |
9 | CLASSIFIERS = [
10 | 'Development Status :: 4 - Beta',
11 | 'Programming Language :: Python',
12 | 'Topic :: Scientific/Engineering',
13 | 'Topic :: Scientific/Engineering :: Mathematics',
14 | 'Topic :: Scientific/Engineering :: Physics',
15 | 'Operating System :: Microsoft :: Windows',
16 | 'Operating System :: POSIX',
17 | 'Operating System :: Unix',
18 | 'Operating System :: MacOS',
19 | 'Natural Language :: English',
20 | ]
21 |
22 | with open('README.rst') as f:
23 | LONG_DESCRIPTION = ''.join(f.readlines())
24 |
25 | setup(
26 | name='vectormath',
27 | version='0.2.2',
28 | packages=find_packages(),
29 | install_requires=[
30 | 'numpy>=1.7',
31 | ],
32 | author='Seequent',
33 | author_email='it@seequent.com',
34 | description='vectormath: vector math utilities for Python',
35 | long_description=LONG_DESCRIPTION,
36 | keywords='linear algebra, vector, plane, math',
37 | url='https://github.com/seequent/vectormath',
38 | download_url='https://github.com/seequent/vectormath',
39 | classifiers=CLASSIFIERS,
40 | platforms=['Windows', 'Linux', 'Solaris', 'Mac OS-X', 'Unix'],
41 | license='MIT License',
42 | include_package_data=True,
43 | use_2to3=False,
44 | )
45 |
--------------------------------------------------------------------------------
/tests/test_docs.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from __future__ import division
3 | from __future__ import print_function
4 | from __future__ import unicode_literals
5 |
6 | import subprocess
7 | import unittest
8 | import os
9 |
10 |
11 | def docs_dir():
12 | dirname, filename = os.path.split(os.path.abspath(__file__))
13 | return os.path.sep.join(dirname.split(os.path.sep)[:-1] + ['docs'])
14 |
15 |
16 | class TestDocs(unittest.TestCase):
17 |
18 | def setUp(self):
19 | self.build_dir = os.path.sep.join(
20 | docs_dir().split(os.path.sep) + ['_build']
21 | )
22 | if not os.path.isdir(self.build_dir):
23 | subprocess.call(["mkdir", "{0}".format(self.build_dir)])
24 |
25 | self.doctrees_dir = os.path.sep.join(
26 | self.build_dir.split(os.path.sep) + ['doctrees']
27 | )
28 | if not os.path.isdir(self.doctrees_dir):
29 | subprocess.call(["mkdir", "{0}".format(self.doctrees_dir)])
30 |
31 | self.html_dir = os.path.sep.join(
32 | self.build_dir.split(os.path.sep) + ['html']
33 | )
34 | if not os.path.isdir(self.html_dir):
35 | subprocess.call(["mkdir", "{0}".format(self.html_dir)])
36 |
37 | def test_html(self):
38 | check = subprocess.call([
39 | "sphinx-build", "-nW", "-b", "html", "-d",
40 | "{}".format(self.doctrees_dir),
41 | "{}".format(docs_dir()),
42 | "{}".format(self.html_dir)
43 | ])
44 | assert check == 0
45 |
46 | def test_linkcheck(self):
47 | check = subprocess.call([
48 | "sphinx-build", "-nW", "-b", "linkcheck", "-d",
49 | "{}".format(self.doctrees_dir),
50 | "{}".format(docs_dir()),
51 | "{}".format(self.build_dir)
52 | ])
53 | assert check == 0
54 |
55 | if __name__ == '__main__':
56 | unittest.main()
57 |
--------------------------------------------------------------------------------
/tests/test_vector2.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from __future__ import division
3 | from __future__ import print_function
4 | from __future__ import unicode_literals
5 |
6 | import unittest
7 | import numpy as np
8 |
9 | from vectormath import Vector2, Vector2Array, Vector3, Vector3Array
10 |
11 |
12 | class TestVMathVector2(unittest.TestCase):
13 |
14 | def test_init_exceptions(self):
15 | self.assertRaises(TypeError, Vector2Array, np.r_[1], 1.0)
16 | self.assertRaises(ValueError, Vector2Array, np.r_[1, 2], np.r_[1])
17 | self.assertRaises(ValueError, Vector2Array, np.array([0, 0, 0]))
18 | self.assertRaises(ValueError, Vector2Array, 'Make', ' me a ')
19 | self.assertRaises(ValueError, Vector2Array, ([0, 0, 0], [0, 0, 0]))
20 |
21 | def test_init(self):
22 | v1 = Vector2Array()
23 | v2 = Vector2Array(0, 0)
24 | self.assertTrue(np.array_equal(v1, v2))
25 | v3 = Vector2Array(v1)
26 | self.assertTrue(np.array_equal(v1, v3))
27 | self.assertTrue(v1 is not v3)
28 | v4 = Vector2Array(np.r_[0, 0])
29 | self.assertTrue(np.array_equal(v1, v4))
30 | v5 = Vector2Array(np.c_[np.r_[1, 0], np.r_[0, 1]])
31 | self.assertTrue(np.array_equal(v5.length, np.r_[1, 1]))
32 | v6 = Vector2Array(np.r_[1, 0], np.r_[0, 1])
33 | self.assertTrue(np.array_equal(v6.length, np.r_[1, 1]))
34 | v7 = Vector2Array([0, 0])
35 | self.assertTrue(np.array_equal(v1, v7))
36 | v8 = Vector2Array(x=0, y=0)
37 | self.assertTrue(np.array_equal(v1, v8))
38 | v9 = Vector2Array(
39 | [0, 0, 0, 0, 0],
40 | [0, 0, 0, 0, 0]
41 | )
42 | v10 = Vector2Array([
43 | [0, 0],
44 | [0, 0],
45 | [0, 0],
46 | [0, 0],
47 | [0, 0]
48 | ])
49 | self.assertTrue(np.array_equal(v9, v10))
50 | v11 = Vector2Array([[[[[0]], [[0]], ]]])
51 | self.assertTrue(np.array_equal(v1, v11))
52 | v12 = Vector2Array([0]*5, [0]*5)
53 | self.assertTrue(np.array_equal(v10, v12))
54 | v13 = Vector2Array((0, 0))
55 | self.assertTrue(np.array_equal(v1, v13))
56 | v14 = Vector2Array(([0, 0], [0, 0]))
57 | self.assertTrue(np.array_equal(v14, Vector2Array([0]*2, [0]*2)))
58 |
59 | def test_indexing(self):
60 | v2 = Vector2Array(1, 2)
61 | self.assertTrue(v2[0, 0] == 1)
62 | self.assertTrue(v2[0, 1] == 2)
63 | self.assertTrue(len(v2[0]) == 2)
64 |
65 | def f(): v2[3]
66 | self.assertRaises(IndexError, f)
67 |
68 | def f(): v2[0, 3]
69 | self.assertRaises(IndexError, f)
70 | l = []
71 | for x in v2[0]:
72 | l.append(x)
73 | self.assertTrue(np.array_equal(np.array(l), np.r_[1, 2]))
74 | self.assertTrue(np.array_equal(v2, Vector2Array(l)))
75 | l = []
76 | v3 = Vector2Array([[1, 2],
77 | [2, 3]])
78 | for v in v3:
79 | l.append(v)
80 | self.assertTrue(np.array_equal(
81 | np.array(l),
82 | np.array([[1, 2], [2, 3]]))
83 | )
84 | self.assertTrue(np.array_equal(Vector2Array(l), v3))
85 | v4 = Vector2Array()
86 | v4[0, 0] = 1
87 | v4[0, 1] = 2
88 | self.assertTrue(np.array_equal(v2, v4))
89 |
90 | def test_copy(self):
91 | vOrig = Vector2Array()
92 | vCopy = vOrig.copy()
93 | self.assertTrue(np.array_equal(vOrig, vCopy))
94 | self.assertTrue(vOrig is not vCopy)
95 |
96 | def test_size(self):
97 | v1 = Vector2Array()
98 | self.assertTrue(v1.nV == 1)
99 | v2 = Vector2Array(np.c_[np.r_[1, 0, 0], np.r_[0, 1, 0]])
100 | self.assertTrue(v2.nV == 3)
101 | v3 = Vector2Array(
102 | [0, 0, 0, 0, 0],
103 | [0, 0, 0, 0, 0]
104 | )
105 | self.assertTrue(v3.nV == 5)
106 | v4 = Vector2Array(0, 0)
107 | self.assertTrue(v4.nV == 1)
108 |
109 | def test_setget(self):
110 | v1 = Vector2Array(1, 1)
111 | self.assertTrue(v1.x == 1)
112 | v1.x = 2
113 | self.assertTrue(v1.x == 2)
114 | self.assertTrue(v1.y == 1)
115 | v1.y = 2
116 | self.assertTrue(v1.y == 2)
117 |
118 | v2 = Vector2Array([[0, 1],
119 | [1, 2]])
120 | self.assertTrue(np.array_equal(v2.x, [0, 1]))
121 | v2.x = [0, -1]
122 | self.assertTrue(np.array_equal(v2.x, [0, -1]))
123 | self.assertTrue(np.array_equal(v2.y, [1, 2]))
124 | v2.y = [-1, -2]
125 | self.assertTrue(np.array_equal(v2.y, [-1, -2]))
126 |
127 | def test_length(self):
128 | v1 = Vector2Array(1, 1)
129 | self.assertTrue(v1.length == np.sqrt(2))
130 | v2 = Vector2Array(np.r_[1, 2], np.r_[1, 2])
131 | self.assertTrue(np.array_equal(v2.length, np.sqrt(np.r_[2, 8])))
132 | v3 = Vector2Array(1, 0)
133 | v3.length = 5
134 | assert v3.x == 5
135 | self.assertTrue(v3.length == 5)
136 | v4 = Vector2Array(np.r_[1, 1], np.r_[0, 0])
137 |
138 | def f(): v4.length = 5
139 | self.assertRaises(ValueError, f)
140 | v5 = Vector2Array(np.r_[1, 0], np.r_[0, 1])
141 | self.assertTrue(np.array_equal(v5.length, [1, 1]))
142 | v5.length = [-1, 3]
143 | self.assertTrue(np.array_equal(v5, [[-1., -0.], [0., 3.]]))
144 | self.assertTrue(np.array_equal(v5.length, [1, 3]))
145 | v6 = Vector2Array()
146 | self.assertTrue(v6.length == 0)
147 |
148 | def f(): v6.length = 5
149 | self.assertRaises(ZeroDivisionError, f)
150 | v6.length = 0
151 | self.assertTrue(v6.length == 0)
152 | v7 = Vector2Array(
153 | [0, 0, 1, 0, 0],
154 | [1, 1, 0, 0, 0]
155 | )
156 | length = [5, 5, 5, 5, 5]
157 |
158 | def f(): v7.length = length
159 | self.assertRaises(ZeroDivisionError, f)
160 | length = [5, 5, 5, 0, 0]
161 | v7.length = length
162 | self.assertTrue(np.array_equal(length, v7.length))
163 |
164 | def test_ops(self):
165 | v1 = Vector2Array(1, 1)
166 | v2 = Vector2Array(2, 2)
167 | self.assertTrue(np.array_equal(v2-v1, v1))
168 | self.assertTrue(np.array_equal(v1-v2, -v1))
169 | self.assertTrue(np.array_equal(v1+v1, v2))
170 | self.assertTrue(np.array_equal(v1*v2, v2))
171 | self.assertTrue(np.array_equal(v2/v1, v2))
172 | self.assertTrue(np.array_equal(2*v1, v2))
173 | self.assertTrue(np.array_equal(v2/2, v1))
174 | self.assertTrue(np.array_equal(v1+1, v2))
175 | self.assertTrue(np.array_equal(v2-1, v1))
176 | v1 = Vector2Array(np.r_[1, 1.], np.r_[1, 1.])
177 | v2 = Vector2Array(np.r_[2, 2.], np.r_[2, 2.])
178 | self.assertTrue(np.array_equal(v2-v1, v1))
179 | self.assertTrue(np.array_equal(v1-v2, -v1))
180 | self.assertTrue(np.array_equal(v1+v1, v2))
181 | self.assertTrue(np.array_equal(v1*v2, v2))
182 | self.assertTrue(np.array_equal(v2/v1, v2))
183 | self.assertTrue(np.array_equal(2*v1, v2))
184 | self.assertTrue(np.array_equal(v2/2, v1))
185 | self.assertTrue(np.array_equal(v1+1, v2))
186 | self.assertTrue(np.array_equal(v2-1, v1))
187 |
188 | def test_dot(self):
189 | v1 = Vector2Array(1, 1)
190 | v2 = Vector2Array(2, 2)
191 | self.assertTrue(v1.dot(v2) == 4)
192 | v1l = Vector2Array(np.r_[1, 1.], np.r_[1, 1.])
193 | v2l = Vector2Array(np.r_[2, 2.], np.r_[2, 2.])
194 | self.assertTrue(np.array_equal(v1l.dot(v2l), np.r_[4, 4]))
195 | self.assertTrue(np.array_equal(v1.dot(v2l), np.r_[4, 4]))
196 | self.assertTrue(np.array_equal(v1l.dot(v2), np.r_[4, 4]))
197 | v3 = Vector2Array([3]*4, [3]*4)
198 |
199 | def f(): v3.dot(v2l)
200 | self.assertRaises(ValueError, f)
201 |
202 | def f(): v3.dot(5)
203 | self.assertRaises(TypeError, f)
204 |
205 | def test_as_percent(self):
206 | v1 = Vector2Array(10, 0)
207 | v2 = Vector2Array(20, 0)
208 | self.assertTrue(np.array_equal(v1.as_percent(2), v2))
209 | self.assertTrue(np.array_equal(v1, Vector2Array(10, 0))) # not copied
210 | v3 = Vector2Array(
211 | [0, 0, 2, 0, 0],
212 | [0, 2, 0, 0, 0]
213 | )
214 | v4 = v3 * .5
215 | self.assertTrue(np.array_equal(v3.as_percent(.5), v4))
216 | v5 = Vector2Array()
217 | self.assertTrue(np.array_equal(v5.as_percent(100), v5))
218 | v6 = Vector2Array(5, 5)
219 | self.assertTrue(np.array_equal(v6.as_percent(0), v5))
220 |
221 | def f(): v6.as_percent('One Hundred Percent')
222 | self.assertRaises(TypeError, f)
223 |
224 | def test_normalize(self):
225 | v1 = Vector2Array(5, 0)
226 | self.assertTrue(v1.length == 5)
227 | self.assertTrue(v1.normalize() is v1)
228 | self.assertTrue(v1.length == 1)
229 | v2 = Vector2Array()
230 |
231 | def f(): v2.normalize()
232 | self.assertRaises(ZeroDivisionError, f)
233 | v3 = Vector2Array(
234 | [0, 2],
235 | [2, 0]
236 | )
237 | self.assertTrue(np.array_equal(v3.length, [2, 2]))
238 | self.assertTrue(v3.normalize() is v3)
239 | self.assertTrue(np.array_equal(v3.length, [1, 1]))
240 |
241 | def test_as_length(self):
242 | v1 = Vector2Array(1, 1)
243 | v2 = v1.as_length(1)
244 | self.assertTrue(v1 is not v2)
245 | self.assertTrue(v1.length == np.sqrt(2))
246 | self.assertAlmostEqual(v2.length[0], 1)
247 |
248 | v3 = Vector2Array(np.r_[1, 2], np.r_[1, 2])
249 | v4 = v3.as_length([1, 2])
250 | self.assertTrue(np.allclose(v4.length, [1, 2]))
251 |
252 | def f(): v = v3.as_length(5)
253 | self.assertRaises(ValueError, f)
254 | v5 = Vector2Array(np.r_[1, 0], np.r_[0, 1])
255 | self.assertTrue(np.allclose(v5.length, [1, 1]))
256 | v6 = v5.as_length([-1, 3])
257 | self.assertTrue(np.allclose(v6, [[-1., -0.], [0., 3.]]))
258 | self.assertTrue(np.allclose(v6.length, [1, 3]))
259 | v7 = Vector2Array()
260 |
261 | def f(): v = v7.as_length(5)
262 | self.assertRaises(ZeroDivisionError, f)
263 | v8 = v7.as_length(0)
264 | self.assertTrue(v8.length == 0)
265 | v9 = Vector2Array(
266 | [0, 0, 1, 0, 0],
267 | [1, 1, 0, 0, 0]
268 | )
269 | length = [5, 5, 5, 5, 5]
270 |
271 | def f(): v = v9.as_length(length)
272 | self.assertRaises(ZeroDivisionError, f)
273 | length = [5, 5, 5, 0, 0]
274 | v10 = v9.as_length(length)
275 | self.assertTrue(np.array_equal(length, v10.length))
276 |
277 | def test_as_unit(self):
278 | v1 = Vector2Array(1, 0)
279 | v2 = v1.as_unit()
280 | self.assertTrue(v1 is not v2)
281 | self.assertTrue(np.array_equal(v1, v2))
282 | self.assertTrue(v2.length == 1)
283 | v3 = Vector2Array(np.r_[1, 2], np.r_[1, 2])
284 | v4 = v3.as_unit()
285 | self.assertTrue(np.allclose(v4.length, [1, 1]))
286 | v5 = Vector2Array(1, 1)
287 | v6 = v5.as_unit()
288 | self.assertAlmostEqual(v6.length[0], 1)
289 | self.assertTrue(v6.x == v6.y)
290 | v7 = Vector2Array()
291 |
292 | def f(): v = v7.as_unit()
293 | self.assertRaises(ZeroDivisionError, f)
294 | v9 = Vector2Array(
295 | [0, 0, 1, 0, 0],
296 | [0, 1, 0, 0, 0]
297 | )
298 |
299 | def f(): v = v9.as_unit()
300 | self.assertRaises(ZeroDivisionError, f)
301 |
302 | def test_view_types(self):
303 | v1 = Vector2Array(np.random.rand(100, 2))
304 | self.assertTrue(isinstance(v1, Vector2Array))
305 | self.assertTrue(isinstance(v1[1:2], Vector2Array))
306 | self.assertTrue(isinstance(v1[1:50:2], Vector2Array))
307 | self.assertTrue(isinstance(v1[4], Vector2))
308 | self.assertTrue(isinstance(v1[4, :], np.ndarray))
309 | self.assertTrue(isinstance(v1.x, np.ndarray))
310 | self.assertTrue(isinstance(v1[1:30, :], np.ndarray))
311 |
312 | a1 = np.array([1., 2., 3])
313 | with self.assertRaises(ValueError):
314 | a1.view(Vector2)
315 | with self.assertRaises(ValueError):
316 | a1.view(Vector2Array)
317 | a1 = np.array([1., 2.])
318 | self.assertTrue(isinstance(a1.view(Vector2), Vector2))
319 | with self.assertRaises(ValueError):
320 | a1.view(Vector2Array)
321 | a1 = np.array([[1., 2.]])
322 | with self.assertRaises(ValueError):
323 | a1.view(Vector2)
324 | self.assertTrue(isinstance(a1.view(Vector2Array), Vector2Array))
325 |
326 | with self.assertRaises(ValueError):
327 | v1.view(Vector3Array)
328 | self.assertTrue(isinstance(v1.view(Vector2Array), Vector2Array))
329 | with self.assertRaises(ValueError):
330 | v1.view(Vector3)
331 | with self.assertRaises(ValueError):
332 | v1.view(Vector2)
333 | v1 = Vector2([1., 2.])
334 | with self.assertRaises(ValueError):
335 | v1.view(Vector3Array)
336 | with self.assertRaises(ValueError):
337 | v1.view(Vector2Array)
338 | with self.assertRaises(ValueError):
339 | v1.view(Vector3)
340 | self.assertTrue(isinstance(v1.view(Vector2), Vector2))
341 |
342 | v1 = np.kron(Vector2([1., 0.]), np.atleast_2d(np.ones(10)).T)
343 | self.assertFalse(isinstance(v1, Vector2))
344 |
345 | def test_cross(self):
346 | vector2 = Vector2(5, 0)
347 | vector2_2 = Vector2(1, 0)
348 | crossResult = vector2.cross(vector2_2)
349 | self.assertEqual(crossResult[0], 0)
350 | self.assertEqual(crossResult[1], 0)
351 | self.assertEqual(crossResult[2], 0)
352 | with self.assertRaises(TypeError):
353 | dotResult2 = vector2.cross("Banana")
354 |
355 | def test_angle(self):
356 |
357 | # test a unit vector along each coordinate
358 | v1 = Vector2(1, 0) # x-coordinate, use this as datum
359 | v = [Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1)]
360 | angles_deg = [0, 90, 180, 90]
361 | angles_rad = [0, np.pi / 2, np.pi, np.pi / 2]
362 | for k in range(4):
363 | a_deg = v1.angle(v[k], unit='deg')
364 | a_rad0 = v1.angle(v[k], unit='rad')
365 | a_rad1 = v1.angle(v[k])
366 | self.assertEqual(a_deg, angles_deg[k])
367 | self.assertEqual(a_rad0, angles_rad[k])
368 | self.assertEqual(a_rad1, angles_rad[k])
369 |
370 | # verify the associative property
371 | self.assertEqual(v1.angle(v[k]), v[k].angle(v1))
372 |
373 | with self.assertRaises(TypeError):
374 | angleResult = v1.angle('anything but Vector2')
375 | with self.assertRaises(ValueError):
376 | angleResult = v1.angle(v[0], unit='invalid entry')
377 | with self.assertRaises(ZeroDivisionError):
378 | angleResult = v1.angle(Vector2(0, 0))
379 |
380 | def test_polar(self):
381 | # polar <-> cartesian conversions
382 | cases = [
383 | # ((rho, theta), (x, y))
384 | ((1, 0), (1, 0)),
385 | ((1, np.pi), (-1, 0)),
386 | ((2, -np.pi / 2), (0, -2)),
387 | ((1, np.pi * 3 / 4), (-1 / np.sqrt(2), 1 / np.sqrt(2))),
388 | ((3, np.pi / 4), (3 / np.sqrt(2), 3 / np.sqrt(2))),
389 | ]
390 | for polar, cartesian in cases:
391 | rho, theta = polar
392 | x, y = cartesian
393 | v = Vector2(rho, theta, polar=True)
394 | self.assertAlmostEqual(v.x, x)
395 | self.assertAlmostEqual(v.y, y)
396 | v = Vector2(x, y)
397 | self.assertAlmostEqual(v.rho, rho)
398 | self.assertAlmostEqual(v.theta, theta)
399 |
400 | # degrees -> radians
401 | cases = [
402 | # (degrees, radians)
403 | (0, 0),
404 | (90, np.pi/2),
405 | (-90, -np.pi/2),
406 | (45, np.pi/4),
407 | (180, np.pi),
408 | ]
409 | for deg, rad in cases:
410 | v = Vector2(1, deg, polar=True, unit='deg')
411 | self.assertAlmostEqual(v.theta, rad)
412 | self.assertAlmostEqual(v.theta_deg, deg)
413 |
414 | # faulty input
415 | with self.assertRaises(ValueError):
416 | Vector2(1, np.pi, polar=True, unit='invalid_unit')
417 | with self.assertRaises(ValueError):
418 | v = Vector2(1, np.pi, polar=True)
419 | # copying doesn't support polar=True
420 | Vector2(v, polar=True)
421 |
422 | def test_spherical(self):
423 | # cartesian -> sperical conversions
424 | cases = [
425 | # ((x, y, z), (rho, theta, phi))
426 | ((1, 0, 0), (1, 0, np.pi/2)),
427 | ((1, 0, 1), (np.sqrt(2), 0, np.pi/4)),
428 | ((1, 0, -1), (np.sqrt(2), 0, np.pi/4*3)),
429 | ]
430 | for cartesian, sperical in cases:
431 | rho, theta, phi = sperical
432 | x, y, z = cartesian
433 | v = Vector3(x, y, z)
434 | self.assertAlmostEqual(v.rho, rho)
435 | self.assertAlmostEqual(v.theta, theta)
436 | self.assertAlmostEqual(v.phi, phi)
437 |
438 |
439 | if __name__ == '__main__':
440 | unittest.main()
441 |
--------------------------------------------------------------------------------
/tests/test_vector3.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 | from __future__ import division
3 | from __future__ import print_function
4 | from __future__ import unicode_literals
5 |
6 | import unittest
7 | import numpy as np
8 |
9 | from vectormath import Vector2, Vector2Array, Vector3, Vector3Array
10 |
11 |
12 | class TestVMathVector3(unittest.TestCase):
13 |
14 | def test_init_exceptions(self):
15 | self.assertRaises(TypeError, Vector3Array, np.r_[1], np.r_[1], 3)
16 | self.assertRaises(ValueError,
17 | Vector3Array, np.r_[1, 2], np.r_[1], np.r_[1])
18 | self.assertRaises(ValueError, Vector3Array, np.array([0, 0]))
19 | self.assertRaises(ValueError,
20 | Vector3Array, 'Make', ' me a ', 'vector!')
21 | self.assertRaises(ValueError, Vector3Array, ([0, 0], [0, 0], [0, 0]))
22 |
23 | def test_init(self):
24 | v1 = Vector3Array()
25 | v2 = Vector3Array(0, 0, 0)
26 | self.assertTrue(np.array_equal(v1, v2))
27 | v3 = Vector3Array(v1)
28 | self.assertTrue(np.array_equal(v1, v3))
29 | self.assertTrue(v1 is not v3)
30 | v4 = Vector3Array(np.r_[0, 0, 0])
31 | self.assertTrue(np.array_equal(v1, v4))
32 | v5 = Vector3Array(np.c_[np.r_[1, 0, 0],
33 | np.r_[0, 1, 0],
34 | np.r_[0, 0, 1]])
35 | self.assertTrue(np.array_equal(v5.length, np.r_[1, 1, 1]))
36 | v6 = Vector3Array(np.r_[1, 0, 0], np.r_[0, 1, 0], np.r_[0, 0, 1])
37 | self.assertTrue(np.array_equal(v6.length, np.r_[1, 1, 1]))
38 | v7 = Vector3Array([0, 0, 0])
39 | self.assertTrue(np.array_equal(v1, v7))
40 | v8 = Vector3Array(x=0, y=0, z=0)
41 | self.assertTrue(np.array_equal(v1, v8))
42 | v9 = Vector3Array(
43 | [0, 0, 0, 0, 0],
44 | [0, 0, 0, 0, 0],
45 | [0, 0, 0, 0, 0]
46 | )
47 | v10 = Vector3Array([
48 | [0, 0, 0],
49 | [0, 0, 0],
50 | [0, 0, 0],
51 | [0, 0, 0],
52 | [0, 0, 0]
53 | ])
54 | self.assertTrue(np.array_equal(v9, v10))
55 | v11 = Vector3Array([[[[[0]], [[0]], [[0]]]]])
56 | self.assertTrue(np.array_equal(v1, v11))
57 | v12 = Vector3Array([0]*5, [0]*5, [0]*5)
58 | self.assertTrue(np.array_equal(v10, v12))
59 | v13 = Vector3Array((0, 0, 0))
60 | self.assertTrue(np.array_equal(v1, v13))
61 | v14 = Vector3Array(([0, 0, 0], [0, 0, 0]))
62 | self.assertTrue(np.array_equal(v14, Vector3Array([0]*2, [0]*2, [0]*2)))
63 |
64 | def test_indexing(self):
65 | v2 = Vector3Array(1, 2, 3)
66 | self.assertTrue(v2[0, 0] == 1)
67 | self.assertTrue(v2[0, 1] == 2)
68 | self.assertTrue(v2[0, 2] == 3)
69 | self.assertTrue(len(v2[0]) == 3)
70 |
71 | self.assertRaises(IndexError, lambda: v2[3])
72 | self.assertRaises(IndexError, lambda: v2[0, 3])
73 | l = []
74 | for x in v2[0]:
75 | l.append(x)
76 | self.assertTrue(np.array_equal(np.array(l), np.r_[1, 2, 3]))
77 | self.assertTrue(np.array_equal(v2, Vector3Array(l)))
78 | l = []
79 | v3 = Vector3Array([[1, 2, 3],
80 | [2, 3, 4]])
81 | for v in v3:
82 | l.append(v)
83 | self.assertTrue(np.array_equal(
84 | np.array(l),
85 | np.array([[1, 2, 3], [2, 3, 4]]))
86 | )
87 | self.assertTrue(np.array_equal(Vector3Array(l), v3))
88 | v4 = Vector3Array()
89 | v4[0, 0] = 1
90 | v4[0, 1] = 2
91 | v4[0, 2] = 3
92 | self.assertTrue(np.array_equal(v2, v4))
93 |
94 | def test_copy(self):
95 | vOrig = Vector3Array()
96 | vCopy = vOrig.copy()
97 | self.assertTrue(np.array_equal(vOrig, vCopy))
98 | self.assertTrue(vOrig is not vCopy)
99 |
100 | def test_size(self):
101 | v1 = Vector3Array()
102 | self.assertTrue(v1.nV == 1)
103 | v2 = Vector3Array(np.c_[np.r_[1, 0, 0],
104 | np.r_[0, 1, 0],
105 | np.r_[0, 0, 1]])
106 | self.assertTrue(v2.nV == 3)
107 | v3 = Vector3Array(
108 | [0, 0, 0, 0, 0],
109 | [0, 0, 0, 0, 0],
110 | [0, 0, 0, 0, 0]
111 | )
112 | self.assertTrue(v3.nV == 5)
113 | v4 = Vector3Array(0, 0, 0)
114 | self.assertTrue(v4.nV == 1)
115 |
116 | def test_setget(self):
117 | v1 = Vector3Array(1, 1, 1)
118 | self.assertTrue(v1.x == 1)
119 | v1.x = 2
120 | self.assertTrue(v1.x == 2)
121 | self.assertTrue(v1.y == 1)
122 | v1.y = 2
123 | self.assertTrue(v1.y == 2)
124 | self.assertTrue(v1.z == 1)
125 | v1.z = 2
126 | self.assertTrue(v1.z == 2)
127 | v2 = Vector3Array([[0, 1, 2],
128 | [1, 2, 3]])
129 | self.assertTrue(np.array_equal(v2.x, [0, 1]))
130 | v2.x = [0, -1]
131 | self.assertTrue(np.array_equal(v2.x, [0, -1]))
132 | self.assertTrue(np.array_equal(v2.y, [1, 2]))
133 | v2.y = [-1, -2]
134 | self.assertTrue(np.array_equal(v2.y, [-1, -2]))
135 | self.assertTrue(np.array_equal(v2.z, [2, 3]))
136 | v2.z = [0, 0]
137 | self.assertTrue(np.array_equal(v2.z, [0, 0]))
138 |
139 | def test_length(self):
140 | v1 = Vector3Array(1, 1, 1)
141 | self.assertTrue(v1.length == np.sqrt(3))
142 | v2 = Vector3Array(np.r_[1, 2], np.r_[1, 2], np.r_[1, 2])
143 | self.assertTrue(np.array_equal(v2.length, np.sqrt(np.r_[3, 12])))
144 | v3 = Vector3Array(1, 0, 0)
145 | v3.length = 5
146 | self.assertTrue(v3.length == 5)
147 | v4 = Vector3Array(np.r_[1, 1], np.r_[0, 0], np.r_[1, 2])
148 |
149 | self.assertRaises(ValueError, lambda: setattr(v4, 'length', 5))
150 | v5 = Vector3Array(np.r_[1, 0], np.r_[0, 0], np.r_[0, 1])
151 | self.assertTrue(np.array_equal(v5.length, [1, 1]))
152 | v5.length = [-1, 3]
153 | self.assertTrue(np.array_equal(v5, [[-1., -0., -0.], [0., 0., 3.]]))
154 | self.assertTrue(np.array_equal(v5.length, [1, 3]))
155 | v6 = Vector3Array()
156 | self.assertTrue(v6.length == 0)
157 |
158 | self.assertRaises(ZeroDivisionError, lambda: setattr(v6, 'length', 5))
159 | v6.length = 0
160 | self.assertTrue(v6.length == 0)
161 | v7 = Vector3Array(
162 | [0, 0, 1, 0, 0],
163 | [0, 1, 0, 0, 0],
164 | [1, 0, 0, 0, 0]
165 | )
166 | length = [5, 5, 5, 5, 5]
167 |
168 | self.assertRaises(ZeroDivisionError,
169 | lambda: setattr(v7, 'length', length))
170 | length = [5, 5, 5, 0, 0]
171 | v7.length = length
172 | self.assertTrue(np.array_equal(length, v7.length))
173 |
174 | def test_ops(self):
175 | v1 = Vector3Array(1, 1, 1)
176 | v2 = Vector3Array(2, 2, 2)
177 | self.assertTrue(np.array_equal(v2-v1, v1))
178 | self.assertTrue(np.array_equal(v1-v2, -v1))
179 | self.assertTrue(np.array_equal(v1+v1, v2))
180 | self.assertTrue(np.array_equal(v1*v2, v2))
181 | self.assertTrue(np.array_equal(v2/v1, v2))
182 | self.assertTrue(np.array_equal(2*v1, v2))
183 | self.assertTrue(np.array_equal(v2/2, v1))
184 | self.assertTrue(np.array_equal(v1+1, v2))
185 | self.assertTrue(np.array_equal(v2-1, v1))
186 | v1 = Vector3Array(np.r_[1, 1.], np.r_[1, 1.], np.r_[1, 1.])
187 | v2 = Vector3Array(np.r_[2, 2.], np.r_[2, 2.], np.r_[2, 2.])
188 | self.assertTrue(np.array_equal(v2-v1, v1))
189 | self.assertTrue(np.array_equal(v1-v2, -v1))
190 | self.assertTrue(np.array_equal(v1+v1, v2))
191 | self.assertTrue(np.array_equal(v1*v2, v2))
192 | self.assertTrue(np.array_equal(v2/v1, v2))
193 | self.assertTrue(np.array_equal(2*v1, v2))
194 | self.assertTrue(np.array_equal(v2/2, v1))
195 | self.assertTrue(np.array_equal(v1+1, v2))
196 | self.assertTrue(np.array_equal(v2-1, v1))
197 |
198 | def test_dot(self):
199 | v1 = Vector3Array(1, 1, 1)
200 | v2 = Vector3Array(2, 2, 2)
201 | self.assertTrue(v1.dot(v2) == 6)
202 | v1l = Vector3Array(np.r_[1, 1.], np.r_[1, 1.], np.r_[1, 1.])
203 | v2l = Vector3Array(np.r_[2, 2.], np.r_[2, 2.], np.r_[2, 2.])
204 | self.assertTrue(np.array_equal(v1l.dot(v2l), np.r_[6, 6]))
205 | self.assertTrue(np.array_equal(v1.dot(v2l), np.r_[6, 6]))
206 | self.assertTrue(np.array_equal(v1l.dot(v2), np.r_[6, 6]))
207 | v3 = Vector3Array([3]*4, [3]*4, [3]*4)
208 |
209 | self.assertRaises(ValueError, lambda: v3.dot(v2l))
210 | self.assertRaises(TypeError, lambda: v3.dot(5))
211 |
212 | def test_cross(self):
213 | v1 = Vector3Array(1, 0, 0)
214 | v2 = Vector3Array(0, 1, 0)
215 | vC = Vector3Array(0, 0, 1)
216 | self.assertTrue(np.array_equal(v1.cross(v2), vC))
217 | v1 = Vector3Array(np.r_[1, 1], np.r_[0, 0], np.r_[0, 0])
218 | v2 = Vector3Array(np.r_[0, 0], np.r_[1, 1], np.r_[0, 0])
219 | vC = Vector3Array(np.r_[0, 0], np.r_[0, 0], np.r_[1, 1])
220 | self.assertTrue(np.array_equal(v1.cross(v2), vC))
221 | v3 = Vector3Array([3]*4, [3]*4, [3]*4)
222 |
223 | def f(): v3.cross(v2)
224 | self.assertRaises(ValueError, f)
225 |
226 | def f(): v3.cross(5)
227 | self.assertRaises(TypeError, f)
228 |
229 | def test_as_percent(self):
230 | v1 = Vector3Array(10, 0, 0)
231 | v2 = Vector3Array(20, 0, 0)
232 | self.assertTrue(np.array_equal(v1.as_percent(2), v2))
233 | self.assertTrue(np.array_equal(v1, Vector3Array(10, 0, 0)))# not copied
234 | v3 = Vector3Array(
235 | [0, 0, 2, 0, 0],
236 | [0, 2, 0, 0, 0],
237 | [2, 0, 0, 0, 0]
238 | )
239 | v4 = v3 * .5
240 | self.assertTrue(np.array_equal(v3.as_percent(.5), v4))
241 | v5 = Vector3Array()
242 | self.assertTrue(np.array_equal(v5.as_percent(100), v5))
243 | v6 = Vector3Array(5, 5, 5)
244 | self.assertTrue(np.array_equal(v6.as_percent(0), v5))
245 |
246 | self.assertRaises(TypeError,
247 | lambda: v6.as_percent('One Hundred Percent'))
248 |
249 | def test_normalize(self):
250 | v1 = Vector3Array(5, 0, 0)
251 | self.assertTrue(v1.length == 5)
252 | self.assertTrue(v1.normalize() is v1)
253 | self.assertTrue(v1.length == 1)
254 | v2 = Vector3Array()
255 |
256 | self.assertRaises(ZeroDivisionError, lambda: v2.normalize())
257 | v3 = Vector3Array(
258 | [0, 0, 2],
259 | [0, 2, 0],
260 | [2, 0, 0]
261 | )
262 | self.assertTrue(np.array_equal(v3.length, [2, 2, 2]))
263 | self.assertTrue(v3.normalize() is v3)
264 | self.assertTrue(np.array_equal(v3.length, [1, 1, 1]))
265 |
266 | def test_as_length(self):
267 | v1 = Vector3Array(1, 1, 1)
268 | v2 = v1.as_length(1)
269 | self.assertTrue(v1 is not v2)
270 | self.assertTrue(v1.length == np.sqrt(3))
271 | self.assertTrue(v2.length == 1)
272 |
273 | v3 = Vector3Array(np.r_[1, 2], np.r_[1, 2], np.r_[1, 2])
274 | v4 = v3.as_length([1, 2])
275 | self.assertTrue(np.array_equal(v4.length, [1, 2]))
276 |
277 | self.assertRaises(ValueError, lambda: v3.as_length(5))
278 | v5 = Vector3Array(np.r_[1, 0], np.r_[0, 0], np.r_[0, 1])
279 | self.assertTrue(np.array_equal(v5.length, [1, 1]))
280 | v6 = v5.as_length([-1, 3])
281 | self.assertTrue(np.array_equal(v6, [[-1., -0., -0.], [0., 0., 3.]]))
282 | self.assertTrue(np.array_equal(v6.length, [1, 3]))
283 | v7 = Vector3Array()
284 |
285 | self.assertRaises(ZeroDivisionError, lambda: v7.as_length(5))
286 | v8 = v7.as_length(0)
287 | self.assertTrue(v8.length == 0)
288 | v9 = Vector3Array(
289 | [0, 0, 1, 0, 0],
290 | [0, 1, 0, 0, 0],
291 | [1, 0, 0, 0, 0]
292 | )
293 | length = [5, 5, 5, 5, 5]
294 |
295 | self.assertRaises(ZeroDivisionError, lambda: v9.as_length(length))
296 | length = [5, 5, 5, 0, 0]
297 | v10 = v9.as_length(length)
298 | self.assertTrue(np.array_equal(length, v10.length))
299 |
300 | def test_as_unit(self):
301 | v1 = Vector3Array(1, 0, 0)
302 | v2 = v1.as_unit()
303 | self.assertTrue(v1 is not v2)
304 | self.assertTrue(np.array_equal(v1, v2))
305 | self.assertTrue(v2.length == 1)
306 | v3 = Vector3Array(np.r_[1, 2], np.r_[1, 2], np.r_[1, 2])
307 | v4 = v3.as_unit()
308 | self.assertTrue(np.array_equal(v4.length, [1, 1]))
309 | v5 = Vector3Array(1, 1, 1)
310 | v6 = v5.as_unit()
311 | self.assertTrue(v6.length == 1)
312 | self.assertTrue(v6.x == v6.y)
313 | self.assertTrue(v6.z == v6.y)
314 | v7 = Vector3Array()
315 |
316 | self.assertRaises(ZeroDivisionError, v7.as_unit)
317 | v9 = Vector3Array(
318 | [0, 0, 1, 0, 0],
319 | [0, 1, 0, 0, 0],
320 | [1, 0, 0, 0, 0]
321 | )
322 |
323 | self.assertRaises(ZeroDivisionError, v9.as_unit)
324 |
325 | def test_view_types(self):
326 | v1 = Vector3Array(np.random.rand(100, 3))
327 | self.assertTrue(isinstance(v1, Vector3Array))
328 | self.assertTrue(isinstance(v1[1:2], Vector3Array))
329 | self.assertTrue(isinstance(v1[1:50:2], Vector3Array))
330 | self.assertTrue(isinstance(v1[4], Vector3))
331 | self.assertTrue(isinstance(v1[4, :], np.ndarray))
332 | self.assertTrue(isinstance(v1.x, np.ndarray))
333 | self.assertTrue(isinstance(v1[1:30, :], np.ndarray))
334 |
335 | a1 = np.array([1., 2.])
336 | with self.assertRaises(ValueError):
337 | a1.view(Vector3)
338 | with self.assertRaises(ValueError):
339 | a1.view(Vector3Array)
340 | a1 = np.array([1., 2., 3.])
341 | self.assertTrue(isinstance(a1.view(Vector3), Vector3))
342 | with self.assertRaises(ValueError):
343 | a1.view(Vector3Array)
344 | a1 = np.array([[1., 2., 3.]])
345 | with self.assertRaises(ValueError):
346 | a1.view(Vector3)
347 | self.assertTrue(isinstance(a1.view(Vector3Array), Vector3Array))
348 |
349 | self.assertTrue(isinstance(v1.view(Vector3Array), Vector3Array))
350 | with self.assertRaises(ValueError):
351 | v1.view(Vector2Array)
352 | with self.assertRaises(ValueError):
353 | v1.view(Vector3)
354 | with self.assertRaises(ValueError):
355 | v1.view(Vector2)
356 | v1 = Vector3([1., 2., 3.])
357 | with self.assertRaises(ValueError):
358 | v1.view(Vector3Array)
359 | with self.assertRaises(ValueError):
360 | v1.view(Vector2Array)
361 | with self.assertRaises(ValueError):
362 | v1.view(Vector2)
363 | self.assertTrue(isinstance(v1.view(Vector3), Vector3))
364 |
365 | v1 = np.kron(Vector3([1., 0., 0.]), np.atleast_2d(np.ones(10)).T)
366 | self.assertFalse(isinstance(v1, Vector3))
367 |
368 | def test_angle(self):
369 |
370 | # test a unit vector along each coordinate
371 | v1 = Vector3(1, 0, 0) # x-axis, use this as datum
372 | v = [Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1),
373 | Vector3(-1, 0, 0), Vector3(0, -1, 0), Vector3(0, 0, -1)]
374 | angles_deg = [0, 90, 90, 180, 90, 90]
375 | angles_rad = [0, np.pi / 2, np.pi / 2, np.pi, np.pi / 2, np.pi / 2]
376 | for k in range(6):
377 | a_deg = v1.angle(v[k], unit='deg')
378 | a_rad0 = v1.angle(v[k], unit='rad')
379 | a_rad1 = v1.angle(v[k])
380 | self.assertEqual(a_deg, angles_deg[k])
381 | self.assertEqual(a_rad0, angles_rad[k])
382 | self.assertEqual(a_rad1, angles_rad[k])
383 |
384 | # verify the associative property
385 | self.assertEqual(v1.angle(v[k]), v[k].angle(v1))
386 |
387 | with self.assertRaises(TypeError):
388 | angleResult = v1.angle('anything but Vector3')
389 | with self.assertRaises(ValueError):
390 | angleResult = v1.angle(v[0], unit='invalid entry')
391 | with self.assertRaises(ZeroDivisionError):
392 | angleResult = v1.angle(Vector3(0, 0, 0))
393 |
394 | # def test_mult_warning(self):
395 | # with warnings.catch_warnings(record=True) as w:
396 | # v1 = Vector3Array()
397 | # v2 = v1 * 3
398 | # self.assertTrue(len(w) == 0)
399 | # M = Matrix3()
400 | # v3 = v2 * M
401 | # self.assertTrue(len(w) == 1)
402 |
403 |
404 | if __name__ == '__main__':
405 | unittest.main()
406 |
--------------------------------------------------------------------------------
/vectormath/__init__.py:
--------------------------------------------------------------------------------
1 | """vectormath: Vector math utilities for Python built on NumPy"""
2 | from __future__ import absolute_import
3 | from __future__ import division
4 | from __future__ import print_function
5 | from __future__ import unicode_literals
6 |
7 | from .vector import Vector3, Vector2, Vector3Array, Vector2Array
8 |
9 | __version__ = '0.2.2'
10 | __author__ = 'Seequent'
11 | __license__ = 'MIT'
12 | __copyright__ = 'Copyright 2018 Seequent'
13 |
--------------------------------------------------------------------------------
/vectormath/vector.py:
--------------------------------------------------------------------------------
1 | """vector.py contains definitions for Vector and VectorArray classes"""
2 | from __future__ import absolute_import
3 | from __future__ import division
4 | from __future__ import print_function
5 | from __future__ import unicode_literals
6 |
7 | import numpy as np
8 |
9 |
10 | class BaseVector(np.ndarray):
11 | """Class to contain basic operations used by all Vector classes"""
12 |
13 | def __new__(cls, *args, **kwargs):
14 | """BaseVector cannot be created"""
15 | raise NotImplementedError('Please specify Vector2 or Vector3')
16 |
17 | @property
18 | def x(self):
19 | """x-component of vector"""
20 | return self[0]
21 |
22 | @x.setter
23 | def x(self, value):
24 | self[0] = value
25 |
26 | @property
27 | def y(self):
28 | """y-component of vector"""
29 | return self[1]
30 |
31 | @y.setter
32 | def y(self, value):
33 | self[1] = value
34 |
35 | @property
36 | def length(self):
37 | """Length of vector"""
38 | return float(np.sqrt(np.sum(self**2)))
39 |
40 | @length.setter
41 | def length(self, value):
42 | if not np.isscalar(value):
43 | raise ValueError('Length must be a scalar')
44 | value = float(value)
45 | if self.length != 0:
46 | new_length = value/self.length
47 | self *= new_length
48 | return
49 | if value != 0:
50 | raise ZeroDivisionError('Cannot resize vector of length 0 to '
51 | 'nonzero length')
52 |
53 | @property
54 | def rho(self):
55 | """Radial coordinate of this vector (equal to the length of the vector)"""
56 | return self.length
57 |
58 | @rho.setter
59 | def rho(self, value):
60 | self.length = value
61 |
62 | @property
63 | def theta(self):
64 | """Angular coordinate / azimuthal angle of this vector in radians
65 |
66 | Based on polar coordinate space (or sperical coordinate space for `Vector3`)
67 | returns angle between this vector and the positive x-axis
68 | range: (-pi <= theta <= pi)
69 | """
70 | return float(np.arctan2(self.y, self.x))
71 |
72 | # pylint: disable=fixme
73 | # TODO: Add `theta` and `theta_deg` setters
74 | # @theta.setter
75 | # def theta(self, value):
76 | # ...
77 |
78 | @property
79 | def theta_deg(self):
80 | """Angular coordinate / azimuthal angle of this vector in degrees
81 |
82 | Based on polar coordinate space (or sperical coordinate space for `Vector3`)
83 | returns angle between this vector and the positive x-axis
84 | range: (-180 <= theta_deg <= 180)
85 | """
86 | return self.theta * 180 / np.pi
87 |
88 | def as_length(self, value):
89 | """Return a new vector scaled to given length"""
90 | new_vec = self.copy()
91 | new_vec.length = value
92 | return new_vec
93 |
94 | def as_percent(self, value):
95 | """Return a new vector scaled by given decimal percent"""
96 | new_vec = self.copy()
97 | new_vec.length = value * self.length
98 | return new_vec
99 |
100 | def as_unit(self):
101 | """Return a new vector scaled to length 1"""
102 | new_vec = self.copy()
103 | new_vec.normalize()
104 | return new_vec
105 |
106 | def normalize(self):
107 | """Scale the length of a vector to 1 in place"""
108 | self.length = 1
109 | return self
110 |
111 | def dot(self, vec):
112 | """Dot product with another vector"""
113 | if not isinstance(vec, self.__class__):
114 | raise TypeError('Dot product operand must be a vector')
115 | return np.dot(self, vec)
116 |
117 | def cross(self, vec):
118 | """Cross product with another vector"""
119 | if not isinstance(vec, self.__class__):
120 | raise TypeError('Cross product operand must be a vector')
121 | return self.__class__(np.cross(self, vec))
122 |
123 | def angle(self, vec, unit='rad'):
124 | """Calculate the angle between two Vectors
125 |
126 | unit: unit for returned angle, either 'rad' or 'deg'. Defaults to 'rad'
127 | """
128 | if not isinstance(vec, self.__class__):
129 | raise TypeError('Angle operand must be of class {}'
130 | .format(self.__class__.__name__))
131 | if unit not in ['deg', 'rad']:
132 | raise ValueError('Only units of rad or deg are supported')
133 |
134 | denom = self.length * vec.length
135 | if denom == 0:
136 | raise ZeroDivisionError('Cannot calculate angle between '
137 | 'zero-length vector(s)')
138 |
139 | ang = np.arccos(self.dot(vec) / denom)
140 | if unit == 'deg':
141 | ang = ang * 180 / np.pi
142 | return ang
143 |
144 | def __mul__(self, multiplier):
145 | return self.__class__(self.view(np.ndarray) * multiplier)
146 |
147 |
148 | class Vector3(BaseVector):
149 | """Primitive 3D vector defined from the origin
150 |
151 | New Vector3 can be created with:
152 | - another Vector3
153 | - length-3 array
154 | - x, y, and y values
155 | - no input (returns [0., 0., 0.])
156 | """
157 |
158 | # pylint: disable=fixme
159 | # TODO: add support for instantiating Vector3 with `polar`=True
160 |
161 | def __new__(cls, x=None, y=None, z=None): #pylint: disable=arguments-differ
162 |
163 | def read_array(X, Y, Z):
164 | """Build Vector3 from another Vector3, [x, y, z], or x/y/z"""
165 | if isinstance(X, cls) and Y is None and Z is None:
166 | return cls(X.x, X.y, X.z)
167 | if (isinstance(X, (list, tuple, np.ndarray)) and len(X) == 3 and
168 | Y is None and Z is None):
169 | return cls(X[0], X[1], X[2])
170 | if X is None and Y is None and Z is None:
171 | return cls(0, 0, 0)
172 | if np.isscalar(X) and np.isscalar(Y) and np.isscalar(Z):
173 | xyz = np.r_[X, Y, Z]
174 | xyz = xyz.astype(float)
175 | return xyz.view(cls)
176 | raise ValueError('Invalid input for Vector3 - must be an instance '
177 | 'of a Vector3, a length-3 array, 3 scalars, or '
178 | 'nothing for [0., 0., 0.]')
179 |
180 | return read_array(x, y, z)
181 |
182 | def __array_wrap__(self, out_arr, context=None): #pylint: disable=no-self-use, unused-argument
183 | """This is called at the end of ufuncs
184 |
185 | If the output is the wrong shape, return the ndarray view
186 | instead of vector view
187 | """
188 | if out_arr.shape != (3,):
189 | out_arr = out_arr.view(np.ndarray)
190 | return out_arr
191 |
192 | def __array_finalize__(self, obj):
193 | """This is called when initializing the vector
194 |
195 | If the constructor is used, obj is None. If slicing is
196 | used, obj has the same class as self. In both these cases,
197 | we let things pass.
198 |
199 | If we are viewing another array class as a vector, then obj has
200 | a different class than self. In this case, if the array has
201 | an invalid shape a ValueError is raised
202 | """
203 | if obj is None or obj.__class__ is Vector3:
204 | return
205 | if self.shape != (3,):
206 | raise ValueError(
207 | 'Invalid array to view as Vector3 - must be length-3 array.'
208 | )
209 |
210 | @property
211 | def z(self):
212 | """z-component of vector"""
213 | return self[2]
214 |
215 | @z.setter
216 | def z(self, value):
217 | self[2] = value
218 |
219 | @property
220 | def phi(self):
221 | """Polar angle / inclination of this vector in radians
222 |
223 | Based on sperical coordinate space
224 | returns angle between this vector and the positive z-azis
225 | range: (0 <= phi <= pi)
226 | """
227 | return np.arctan2(np.sqrt(self.x**2 + self.y**2), self.z)
228 |
229 | # pylint: disable=fixme
230 | # TODO: Add `phi` and `phi_deg` setters
231 | # @phi.setter
232 | # def phi(self, value):
233 | # ...
234 |
235 | @property
236 | def phi_deg(self):
237 | """Polar angle / inclination of this vector in degrees
238 |
239 | Based on sperical coordinate space
240 | returns angle between this vector and the positive z-azis
241 | range: (0 <= phi <= pi)
242 | """
243 | return self.phi * 180 / np.pi
244 |
245 |
246 | class Vector2(BaseVector):
247 | """Primitive 2D vector defined from the origin
248 |
249 | New Vector2 can be created with:
250 | - another Vector2
251 | - length-2 array
252 | - x and y values
253 | - rho and theta, if polar=True; specify unit as 'rad' (default) or 'deg'
254 | - no input (returns [0., 0.])
255 | """
256 |
257 | def __new__(cls, x=None, y=None, polar=False, unit='rad'): #pylint: disable=arguments-differ
258 |
259 | def read_array(X, Y):
260 | """Build Vector2 from another Vector2, [x, y], or x/y"""
261 | if isinstance(X, cls) and Y is None:
262 | if polar:
263 | raise ValueError(
264 | 'When copying a Vector2, polar=True is not supported'
265 | )
266 | return cls(X.x, X.y)
267 | if (isinstance(X, (list, tuple, np.ndarray)) and len(X) == 2 and
268 | Y is None):
269 | return cls(X[0], X[1], polar, unit)
270 | if X is None and Y is None:
271 | return cls(0, 0, polar, unit)
272 | if np.isscalar(X) and np.isscalar(Y):
273 | if polar:
274 | if unit not in ['deg', 'rad']:
275 | raise ValueError(
276 | 'Only units of rad or deg are supported'
277 | )
278 | if unit == 'deg':
279 | Y = Y / 180 * np.pi
280 | X, Y = X * np.cos(Y), X * np.sin(Y)
281 | xyz = np.r_[X, Y]
282 | xyz = xyz.astype(float)
283 | return xyz.view(cls)
284 | raise ValueError('Invalid input for Vector2 - must be an instance '
285 | 'of a Vector2, a length-2 array, 2 scalars, or '
286 | 'nothing for [0., 0.]')
287 |
288 | return read_array(x, y)
289 |
290 | def __array_wrap__(self, out_arr, context=None): #pylint: disable=no-self-use, unused-argument
291 | if out_arr.shape != (2,):
292 | out_arr = out_arr.view(np.ndarray)
293 | return out_arr
294 |
295 | def __array_finalize__(self, obj):
296 | if obj is None or obj.__class__ is Vector2:
297 | return
298 | if self.shape != (2,):
299 | raise ValueError(
300 | 'Invalid array to view as Vector2 - must be length-2 array.'
301 | )
302 |
303 | def cross(self, vec):
304 | """Cross product with another vector"""
305 | if not isinstance(vec, self.__class__):
306 | raise TypeError('Cross product operand must be a vector')
307 | return Vector3(0, 0, np.asscalar(np.cross(self, vec)))
308 |
309 |
310 | class BaseVectorArray(BaseVector):
311 | """Class to contain basic operations used by all VectorArray classes"""
312 |
313 | @property
314 | def x(self):
315 | """Array of x-component of vectors"""
316 | return self[:, 0]
317 |
318 | @x.setter
319 | def x(self, value):
320 | self[:, 0] = value
321 |
322 | @property
323 | def y(self):
324 | """Array of y-component of vectors"""
325 | return self[:, 1]
326 |
327 | @y.setter
328 | def y(self, value):
329 | self[:, 1] = value
330 |
331 | @property
332 | def nV(self):
333 | """Number of vectors"""
334 | return self.shape[0]
335 |
336 | def normalize(self):
337 | """Scale the length of all vectors to 1 in place"""
338 | self.length = np.ones(self.nV)
339 | return self
340 |
341 | @property
342 | def dims(self):
343 | """Tuple of different dimension names for Vector type"""
344 | raise NotImplementedError('Please use Vector2Array or Vector3Array')
345 |
346 | @property
347 | def length(self):
348 | """Array of vector lengths"""
349 | return np.sqrt(np.sum(self**2, axis=1)).view(np.ndarray)
350 |
351 | @length.setter
352 | def length(self, l):
353 | l = np.array(l)
354 | if self.nV != l.size:
355 | raise ValueError('Length vector must be the same number of '
356 | 'elements as vector.')
357 | # This case resizes all vectors with nonzero length
358 | if np.all(self.length != 0):
359 | new_length = l/self.length
360 | for dim in self.dims:
361 | setattr(self, dim, new_length*getattr(self, dim))
362 | return
363 | # This case only applies to single vectors
364 | if self.nV == 1 and l == 0:
365 | assert self.length == 0, \
366 | 'Nonzero length should be resized in the first case'
367 | for dim in self.dims:
368 | setattr(self, dim, 0.)
369 | return
370 | # This case only applies if vectors with length == 0
371 | # in an array are getting resized to 0
372 | if self.nV > 1 and np.array_equal(self.length.nonzero(), l.nonzero()): #pylint: disable=no-member
373 | new_length = l/[x if x != 0 else 1 for x in self.length]
374 | for dim in self.dims:
375 | setattr(self, dim, new_length*getattr(self, dim))
376 | return
377 | # Error if length zero array is resized to nonzero value
378 | raise ZeroDivisionError('Cannot resize vector of length 0 to '
379 | 'nonzero length')
380 |
381 | def dot(self, vec):
382 | """Dot product with another vector"""
383 | if not isinstance(vec, self.__class__):
384 | raise TypeError('Dot product operand must be a VectorArray')
385 | if self.nV != 1 and vec.nV != 1 and self.nV != vec.nV:
386 | raise ValueError('Dot product operands must have the same '
387 | 'number of elements.')
388 | return np.sum((getattr(self, d)*getattr(vec, d) for d in self.dims), 1)
389 |
390 | def angle(self, vec, unit='rad'):
391 | """Angle method is only for Vectors, not VectorArrays"""
392 | raise NotImplementedError('angle not implemented for VectorArrays')
393 |
394 |
395 | class Vector3Array(BaseVectorArray):
396 | """List of Vector3
397 |
398 | A new Vector3Array can be created with:
399 | - another Vector3Array
400 | - x/y/z lists of equal length
401 | - n x 3 array
402 | - nothing (returns [[0., 0., 0.]])
403 | """
404 |
405 | def __new__(cls, x=None, y=None, z=None): #pylint: disable=arguments-differ
406 |
407 | def read_array(X, Y, Z):
408 | """Build Vector3Array from various inputs"""
409 | if isinstance(X, cls) and Y is None and Z is None:
410 | X = np.atleast_2d(X)
411 | return cls(X.x.copy(), X.y.copy(), X.z.copy())
412 | if isinstance(X, (list, tuple)):
413 | X = np.array(X)
414 | if isinstance(Y, (list, tuple)):
415 | Y = np.array(Y)
416 | if isinstance(Z, (list, tuple)):
417 | Z = np.array(Z)
418 | if isinstance(X, np.ndarray) and Y is None and Z is None:
419 | X = np.squeeze(X)
420 | if X.size == 3:
421 | X = X.flatten()
422 | return cls(X[0], X[1], X[2])
423 | if len(X.shape) == 2 and X.shape[1] == 3:
424 | return cls(
425 | X[:, 0].copy(), X[:, 1].copy(), X[:, 2].copy()
426 | )
427 | raise ValueError(
428 | 'Unexpected shape for vector init: {shp}'.format(
429 | shp=X.shape
430 | )
431 | )
432 | if np.isscalar(X) and np.isscalar(Y) and np.isscalar(Z):
433 | X, Y, Z = float(X), float(Y), float(Z)
434 | elif not (isinstance(X, type(Y)) and isinstance(X, type(Z))):
435 | raise TypeError('Must be the same types for x, y, and '
436 | 'z for vector init')
437 | if isinstance(X, np.ndarray):
438 | if not (X.shape == Y.shape and X.shape == Z.shape):
439 | raise ValueError('Must be the same shapes for x, y, '
440 | 'and z in vector init')
441 | vec_ndarray = np.c_[X, Y, Z]
442 | vec_ndarray = vec_ndarray.astype(float)
443 | return vec_ndarray.view(cls)
444 | if X is None:
445 | X, Y, Z = 0.0, 0.0, 0.0
446 | vec_ndarray = np.r_[X, Y, Z].reshape((1, 3))
447 | return np.asarray(vec_ndarray).view(cls)
448 |
449 | return read_array(x, y, z)
450 |
451 | def __array_wrap__(self, out_arr, context=None): #pylint: disable=no-self-use, unused-argument
452 | if len(out_arr.shape) != 2 or out_arr.shape[1] != 3:
453 | out_arr = out_arr.view(np.ndarray)
454 | return out_arr
455 |
456 | def __array_finalize__(self, obj):
457 | if obj is None or obj.__class__ is Vector3Array:
458 | return
459 | if len(self.shape) != 2 or self.shape[1] != 3: #pylint: disable=unsubscriptable-object
460 | raise ValueError(
461 | 'Invalid array to view as Vector3Array - must be '
462 | 'array of shape (*, 3).'
463 | )
464 |
465 | def __getitem__(self, i):
466 | """Overriding _getitem__ allows coersion to Vector3 or ndarray"""
467 | item_out = super(Vector3Array, self).__getitem__(i)
468 | if np.isscalar(i):
469 | return item_out.view(Vector3)
470 | if isinstance(i, slice):
471 | return item_out
472 | return item_out.view(np.ndarray)
473 |
474 | @property
475 | def z(self):
476 | """Array of z-component of vectors"""
477 | return self[:, 2]
478 |
479 | @z.setter
480 | def z(self, value):
481 | self[:, 2] = value
482 |
483 | @property
484 | def dims(self):
485 | return ('x', 'y', 'z')
486 |
487 | def cross(self, vec):
488 | """Cross product with another Vector3Array"""
489 | if not isinstance(vec, Vector3Array):
490 | raise TypeError('Cross product operand must be a Vector3Array')
491 | if self.nV != 1 and vec.nV != 1 and self.nV != vec.nV:
492 | raise ValueError('Cross product operands must have the same '
493 | 'number of elements.')
494 | return Vector3Array(np.cross(self, vec))
495 |
496 |
497 | class Vector2Array(BaseVectorArray):
498 | """List of Vector2
499 |
500 | A new Vector2Array can be created with:
501 | - another Vector2Array
502 | - x/y lists of equal length
503 | - n x 2 array
504 | - nothing (returns [[0., 0.]])
505 | """
506 |
507 | def __new__(cls, x=None, y=None): #pylint: disable=arguments-differ
508 |
509 | def read_array(X, Y):
510 | """Build Vector2Array from various inputs"""
511 | if isinstance(X, cls) and Y is None:
512 | X = np.atleast_2d(X)
513 | return cls(X.x.copy(), X.y.copy())
514 | if isinstance(X, (list, tuple)):
515 | X = np.array(X)
516 | if isinstance(Y, (list, tuple)):
517 | Y = np.array(Y)
518 | if isinstance(X, np.ndarray) and Y is None:
519 | X = np.squeeze(X)
520 | if X.size == 2:
521 | X = X.flatten()
522 | return cls(X[0], X[1])
523 | if len(X.shape) == 2 and X.shape[1] == 2:
524 | return cls(
525 | X[:, 0].copy(), X[:, 1].copy()
526 | )
527 | raise ValueError(
528 | 'Unexpected shape for vector init: {shp}'.format(
529 | shp=X.shape
530 | )
531 | )
532 | if np.isscalar(X) and np.isscalar(Y):
533 | X, Y = float(X), float(Y)
534 | elif not isinstance(X, type(Y)):
535 | raise TypeError('Must be the same types for x and y '
536 | 'for vector init')
537 | if isinstance(X, np.ndarray):
538 | if X.shape != Y.shape:
539 | raise ValueError('Must be the same shapes for x and y '
540 | 'in vector init')
541 | vec_ndarray = np.c_[X, Y]
542 | vec_ndarray = vec_ndarray.astype(float)
543 | return vec_ndarray.view(cls)
544 | if X is None:
545 | X, Y = 0.0, 0.0
546 | vec_ndarray = np.r_[X, Y].reshape((1, 2))
547 | return np.asarray(vec_ndarray).view(cls)
548 |
549 | return read_array(x, y)
550 |
551 | def __array_wrap__(self, out_arr, context=None): #pylint: disable=no-self-use, unused-argument
552 | if len(out_arr.shape) != 2 or out_arr.shape[1] != 2:
553 | out_arr = out_arr.view(np.ndarray)
554 | return out_arr
555 |
556 | def __array_finalize__(self, obj):
557 | if obj is None or obj.__class__ is Vector2Array:
558 | return
559 | if len(self.shape) != 2 or self.shape[1] != 2: #pylint: disable=unsubscriptable-object
560 | raise ValueError(
561 | 'Invalid array to view as Vector2Array - must be '
562 | 'array of shape (*, 2).'
563 | )
564 |
565 | def __getitem__(self, i):
566 | """Overriding _getitem__ allows coercion to Vector2 or ndarray"""
567 | item_out = super(Vector2Array, self).__getitem__(i)
568 | if np.isscalar(i):
569 | return item_out.view(Vector2)
570 | if isinstance(i, slice):
571 | return item_out
572 | return item_out.view(np.ndarray)
573 |
574 | @property
575 | def dims(self):
576 | return ('x', 'y')
577 |
--------------------------------------------------------------------------------