├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .travis.yml ├── DESCRIPTION.rst ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── ext │ ├── docscrape.py │ ├── docscrape_sphinx.py │ └── numpydoc.py ├── index.rst └── make.bat ├── setup.cfg ├── setup.py └── tinynumpy ├── __init__.py ├── benchmark.py ├── test_tinyndarray.py ├── test_tinynumpy.py ├── tinylinalg.py └── tinynumpy.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.5, 3.6, 3.7, 3.8] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install flake8 pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Lint with flake8 32 | run: | 33 | # stop the build if there are Python syntax errors or undefined names 34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 37 | - name: Test with pytest 38 | run: | 39 | pytest 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Added local files 2 | *.sublime-* 3 | .* 4 | setup-ex.py 5 | *.egg-info/* 6 | *.tar 7 | *.gz 8 | *.tar.gz 9 | 10 | ======================== 11 | # Folders in root folder 12 | _website 13 | htmlcov 14 | build 15 | dist 16 | 17 | # Files in root folder 18 | MANIFEST 19 | .coverage 20 | 21 | # Specific folders 22 | docs/_build 23 | 24 | # Files / folders that should always be ignored 25 | __pychache__ 26 | *.pyc 27 | *.pyo 28 | *.dll 29 | *.dylib 30 | 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.6" 5 | - "nightly" 6 | # command to install dependencies 7 | #install: "pip install -r requirements.txt" 8 | # command to run tests 9 | script: python -m unittest discover 10 | -------------------------------------------------------------------------------- /DESCRIPTION.rst: -------------------------------------------------------------------------------- 1 | tinynumpy: 2 | 3 | A lightweight, pure Python, numpy compliant ndarray class. 4 | 5 | This module is intended to allow libraries that depend on numpy, but do not make much use of array processing, to make numpy an optional dependency. This might make such libaries better available, also on platforms like Pypy and Jython -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | All of tinyndarray is licensed under the MIT license. 2 | 3 | Copyright (c) 2014 by Wade Brainerd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 6 | and associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 15 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README.txt 3 | setup.cfg 4 | setup.py 5 | tinynumpy/__init__.py 6 | tinynumpy/benchmark.py 7 | tinynumpy/test_tinyndarray.py 8 | tinynumpy/test_tinynumpy.py 9 | tinynumpy/tinylinalg.py 10 | tinynumpy/tinynumpy.py 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tinynumpy 2 | ========= 3 | 4 | A lightweight, pure Python, numpy compliant ndarray class. 5 | 6 | This module is intended to allow libraries that depend on numpy, but 7 | do not make much use of array processing, to make numpy an optional 8 | dependency. This might make such libaries better available, also on 9 | platforms like Pypy and Jython. 10 | 11 | Links 12 | ----- 13 | 14 | * Github: https://github.com/wadetb/tinynumpy 15 | * Docs: http://tinynumpy.readthedocs.org/en/latest/ 16 | 17 | 18 | Features 19 | -------- 20 | 21 | * The ndarray class has all the same properties as the numpy ndarray 22 | class. 23 | * Pretty good compliance with numpy in terms of behavior (such as views). 24 | * Can be converted to a numpy array (with shared memory). 25 | * Can get views of real numpy arrays (with shared memory). 26 | * Support for wrapping ctypes arrays, or provide ctypes pointer to data. 27 | * Pretty fast for being pure Python. 28 | * Works on Python 2.5+, Python 3.x, Pypy and Jython. 29 | 30 | Caveats 31 | ------- 32 | 33 | * ndarray.flat iterator cannot be indexed (it is a generator). 34 | * No support for Fortran order. 35 | * Support for data types limited to bool, uin8, uint16, uint32, uint64, 36 | int8, int16, int32, int64, float32, float64. 37 | * Functions that calculate statistics on the data are much slower, since 38 | the iteration takes place in Python. 39 | * Assigning via slicing is usually pretty fast, but can be slow if the 40 | striding is unfortunate. 41 | 42 | 43 | Examples 44 | -------- 45 | 46 | ```python 47 | 48 | >>> from tinynumpy import tinynumpy as tnp 49 | 50 | >>> a = tnp.array([[1, 2, 3, 4],[5, 6, 7, 8]]) 51 | 52 | >>> a 53 | array([[ 1., 2., 3., 4.], 54 | [ 5., 6., 7., 8.]], dtype='float64') 55 | 56 | >>> a[:, 2:] 57 | array([[ 3., 4.], 58 | [ 7., 8.]], dtype='float64') 59 | 60 | >>> a[:, ::2] 61 | array([[ 1., 3.], 62 | [ 5., 7.]], dtype='float64') 63 | 64 | >>> a.shape 65 | (2, 4) 66 | 67 | >>> a.shape = 4, 2 68 | 69 | >>> a 70 | array([[ 1., 2.], 71 | [ 3., 4.], 72 | [ 5., 6.], 73 | [ 7., 8.]], dtype='float64') 74 | 75 | >>> b = a.ravel() 76 | 77 | >>> a[0, 0] = 100 78 | 79 | >>> b 80 | array([ 100., 2., 3., 4., 5., 6., 7., 8.], dtype='float64') 81 | ``` 82 | -------------------------------------------------------------------------------- /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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/tinynumpy.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/tinynumpy.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/tinynumpy" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/tinynumpy" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # tinynumpy documentation build configuration file, created by 5 | # sphinx-quickstart on Thu Nov 20 10:11:12 2014. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('ext')) 23 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc', 'numpydoc', ] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = 'tinynumpy' 49 | copyright = '2014, Almar Klein, Wade Brainerd' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | import tinynumpy 57 | # The short X.Y version. 58 | version = tinynumpy.__version__[:3] 59 | # The full version, including alpha/beta/rc tags. 60 | release = tinynumpy.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all 77 | # documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | # If true, keep warnings as "system message" paragraphs in the built documents. 98 | #keep_warnings = False 99 | 100 | 101 | # -- Options for HTML output ---------------------------------------------- 102 | 103 | # The theme to use for HTML and HTML Help pages. See the documentation for 104 | # a list of builtin themes. 105 | html_theme = 'default' 106 | 107 | # Theme options are theme-specific and customize the look and feel of a theme 108 | # further. For a list of options available for each theme, see the 109 | # documentation. 110 | #html_theme_options = {} 111 | 112 | # Add any paths that contain custom themes here, relative to this directory. 113 | #html_theme_path = [] 114 | 115 | # The name for this set of Sphinx documents. If None, it defaults to 116 | # " v documentation". 117 | #html_title = None 118 | 119 | # A shorter title for the navigation bar. Default is the same as html_title. 120 | #html_short_title = None 121 | 122 | # The name of an image file (relative to this directory) to place at the top 123 | # of the sidebar. 124 | #html_logo = None 125 | 126 | # The name of an image file (within the static path) to use as favicon of the 127 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 128 | # pixels large. 129 | #html_favicon = None 130 | 131 | # Add any paths that contain custom static files (such as style sheets) here, 132 | # relative to this directory. They are copied after the builtin static files, 133 | # so a file named "default.css" will overwrite the builtin "default.css". 134 | html_static_path = ['_static'] 135 | 136 | # Add any extra paths that contain custom files (such as robots.txt or 137 | # .htaccess) here, relative to this directory. These files are copied 138 | # directly to the root of the documentation. 139 | #html_extra_path = [] 140 | 141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 142 | # using the given strftime format. 143 | #html_last_updated_fmt = '%b %d, %Y' 144 | 145 | # If true, SmartyPants will be used to convert quotes and dashes to 146 | # typographically correct entities. 147 | #html_use_smartypants = True 148 | 149 | # Custom sidebar templates, maps document names to template names. 150 | #html_sidebars = {} 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | #html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | #html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | #html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | #html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | #html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | #html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | #html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | #html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | #html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = 'tinynumpydoc' 184 | 185 | 186 | # -- Options for LaTeX output --------------------------------------------- 187 | 188 | latex_elements = { 189 | # The paper size ('letterpaper' or 'a4paper'). 190 | #'papersize': 'letterpaper', 191 | 192 | # The font size ('10pt', '11pt' or '12pt'). 193 | #'pointsize': '10pt', 194 | 195 | # Additional stuff for the LaTeX preamble. 196 | #'preamble': '', 197 | } 198 | 199 | # Grouping the document tree into LaTeX files. List of tuples 200 | # (source start file, target name, title, 201 | # author, documentclass [howto, manual, or own class]). 202 | latex_documents = [ 203 | ('index', 'tinynumpy.tex', 'tinynumpy Documentation', 204 | 'Almar Klein, Wade Brainerd', 'manual'), 205 | ] 206 | 207 | # The name of an image file (relative to this directory) to place at the top of 208 | # the title page. 209 | #latex_logo = None 210 | 211 | # For "manual" documents, if this is true, then toplevel headings are parts, 212 | # not chapters. 213 | #latex_use_parts = False 214 | 215 | # If true, show page references after internal links. 216 | #latex_show_pagerefs = False 217 | 218 | # If true, show URL addresses after external links. 219 | #latex_show_urls = False 220 | 221 | # Documents to append as an appendix to all manuals. 222 | #latex_appendices = [] 223 | 224 | # If false, no module index is generated. 225 | #latex_domain_indices = True 226 | 227 | 228 | # -- Options for manual page output --------------------------------------- 229 | 230 | # One entry per manual page. List of tuples 231 | # (source start file, name, description, authors, manual section). 232 | man_pages = [ 233 | ('index', 'tinynumpy', 'tinynumpy Documentation', 234 | ['Almar Klein, Wade Brainerd'], 1) 235 | ] 236 | 237 | # If true, show URL addresses after external links. 238 | #man_show_urls = False 239 | 240 | 241 | # -- Options for Texinfo output ------------------------------------------- 242 | 243 | # Grouping the document tree into Texinfo files. List of tuples 244 | # (source start file, target name, title, author, 245 | # dir menu entry, description, category) 246 | texinfo_documents = [ 247 | ('index', 'tinynumpy', 'tinynumpy Documentation', 248 | 'Almar Klein, Wade Brainerd', 'tinynumpy', 'One line description of project.', 249 | 'Miscellaneous'), 250 | ] 251 | 252 | # Documents to append as an appendix to all manuals. 253 | #texinfo_appendices = [] 254 | 255 | # If false, no module index is generated. 256 | #texinfo_domain_indices = True 257 | 258 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 259 | #texinfo_show_urls = 'footnote' 260 | 261 | # If true, do not generate a @detailmenu in the "Top" node's menu. 262 | #texinfo_no_detailmenu = False 263 | -------------------------------------------------------------------------------- /docs/ext/docscrape.py: -------------------------------------------------------------------------------- 1 | """Extract reference documentation from the NumPy source tree. 2 | 3 | """ 4 | 5 | import inspect 6 | import textwrap 7 | import re 8 | import pydoc 9 | try: 10 | from StringIO import StringIO 11 | except ImportError: 12 | from io import StringIO 13 | from warnings import warn 14 | 15 | 16 | class Reader(object): 17 | 18 | """A line-based string reader. 19 | 20 | """ 21 | 22 | def __init__(self, data): 23 | """ 24 | Parameters 25 | ---------- 26 | data : str 27 | String with lines separated by '\n'. 28 | 29 | """ 30 | if isinstance(data, list): 31 | self._str = data 32 | else: 33 | self._str = data.split('\n') # store string as list of lines 34 | 35 | self.reset() 36 | 37 | def __getitem__(self, n): 38 | return self._str[n] 39 | 40 | def reset(self): 41 | self._l = 0 # current line nr 42 | 43 | def read(self): 44 | if not self.eof(): 45 | out = self[self._l] 46 | self._l += 1 47 | return out 48 | else: 49 | return '' 50 | 51 | def seek_next_non_empty_line(self): 52 | for l in self[self._l:]: 53 | if l.strip(): 54 | break 55 | else: 56 | self._l += 1 57 | 58 | def eof(self): 59 | return self._l >= len(self._str) 60 | 61 | def read_to_condition(self, condition_func): 62 | start = self._l 63 | for line in self[start:]: 64 | if condition_func(line): 65 | return self[start:self._l] 66 | self._l += 1 67 | if self.eof(): 68 | return self[start:self._l + 1] 69 | return [] 70 | 71 | def read_to_next_empty_line(self): 72 | self.seek_next_non_empty_line() 73 | 74 | def is_empty(line): 75 | return not line.strip() 76 | return self.read_to_condition(is_empty) 77 | 78 | def read_to_next_unindented_line(self): 79 | def is_unindented(line): 80 | return (line.strip() and (len(line.lstrip()) == len(line))) 81 | return self.read_to_condition(is_unindented) 82 | 83 | def peek(self, n=0): 84 | if self._l + n < len(self._str): 85 | return self[self._l + n] 86 | else: 87 | return '' 88 | 89 | def is_empty(self): 90 | return not ''.join(self._str).strip() 91 | 92 | 93 | class NumpyDocString(object): 94 | 95 | def __init__(self, docstring, config={}): 96 | docstring = textwrap.dedent(docstring).split('\n') 97 | 98 | self._doc = Reader(docstring) 99 | self._parsed_data = { 100 | 'Signature': '', 101 | 'Summary': [''], 102 | 'Extended Summary': [], 103 | 'Parameters': [], 104 | 'Returns': [], 105 | 'Raises': [], 106 | 'Warns': [], 107 | 'Other Parameters': [], 108 | 'Attributes': [], 109 | 'Methods': [], 110 | 'See Also': [], 111 | 'Notes': [], 112 | 'Warnings': [], 113 | 'References': '', 114 | 'Examples': '', 115 | 'index': {} 116 | } 117 | 118 | self._parse() 119 | 120 | def __getitem__(self, key): 121 | return self._parsed_data[key] 122 | 123 | def __setitem__(self, key, val): 124 | if not key in self._parsed_data: 125 | warn("Unknown section %s" % key) 126 | else: 127 | self._parsed_data[key] = val 128 | 129 | def _is_at_section(self): 130 | self._doc.seek_next_non_empty_line() 131 | 132 | if self._doc.eof(): 133 | return False 134 | 135 | l1 = self._doc.peek().strip() # e.g. Parameters 136 | 137 | if l1.startswith('.. index::'): 138 | return True 139 | 140 | l2 = self._doc.peek(1).strip() # ---------- or ========== 141 | return l2.startswith('-' * len(l1)) or l2.startswith('=' * len(l1)) 142 | 143 | def _strip(self, doc): 144 | i = 0 145 | j = 0 146 | for i, line in enumerate(doc): 147 | if line.strip(): 148 | break 149 | 150 | for j, line in enumerate(doc[::-1]): 151 | if line.strip(): 152 | break 153 | 154 | return doc[i:len(doc) - j] 155 | 156 | def _read_to_next_section(self): 157 | section = self._doc.read_to_next_empty_line() 158 | 159 | while not self._is_at_section() and not self._doc.eof(): 160 | if not self._doc.peek(-1).strip(): # previous line was empty 161 | section += [''] 162 | 163 | section += self._doc.read_to_next_empty_line() 164 | 165 | return section 166 | 167 | def _read_sections(self): 168 | while not self._doc.eof(): 169 | data = self._read_to_next_section() 170 | name = data[0].strip() 171 | 172 | if name.startswith('..'): # index section 173 | yield name, data[1:] 174 | elif len(data) < 2: 175 | yield StopIteration 176 | else: 177 | yield name, self._strip(data[2:]) 178 | 179 | def _parse_param_list(self, content): 180 | r = Reader(content) 181 | params = [] 182 | while not r.eof(): 183 | header = r.read().strip() 184 | if ' : ' in header: 185 | arg_name, arg_type = header.split(' : ')[:2] 186 | else: 187 | arg_name, arg_type = header, '' 188 | 189 | desc = r.read_to_next_unindented_line() 190 | desc = dedent_lines(desc) 191 | 192 | params.append((arg_name, arg_type, desc)) 193 | 194 | return params 195 | 196 | _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" 197 | r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) 198 | 199 | def _parse_see_also(self, content): 200 | """ 201 | func_name : Descriptive text 202 | continued text 203 | another_func_name : Descriptive text 204 | func_name1, func_name2, :meth:`func_name`, func_name3 205 | 206 | """ 207 | items = [] 208 | 209 | def parse_item_name(text): 210 | """Match ':role:`name`' or 'name'""" 211 | m = self._name_rgx.match(text) 212 | if m: 213 | g = m.groups() 214 | if g[1] is None: 215 | return g[3], None 216 | else: 217 | return g[2], g[1] 218 | raise ValueError("%s is not a item name" % text) 219 | 220 | def push_item(name, rest): 221 | if not name: 222 | return 223 | name, role = parse_item_name(name) 224 | items.append((name, list(rest), role)) 225 | del rest[:] 226 | 227 | current_func = None 228 | rest = [] 229 | 230 | for line in content: 231 | if not line.strip(): 232 | continue 233 | 234 | m = self._name_rgx.match(line) 235 | if m and line[m.end():].strip().startswith(':'): 236 | push_item(current_func, rest) 237 | current_func, line = line[:m.end()], line[m.end():] 238 | rest = [line.split(':', 1)[1].strip()] 239 | if not rest[0]: 240 | rest = [] 241 | elif not line.startswith(' '): 242 | push_item(current_func, rest) 243 | current_func = None 244 | if ',' in line: 245 | for func in line.split(','): 246 | push_item(func, []) 247 | elif line.strip(): 248 | current_func = line 249 | elif current_func is not None: 250 | rest.append(line.strip()) 251 | push_item(current_func, rest) 252 | return items 253 | 254 | def _parse_index(self, section, content): 255 | """ 256 | .. index: default 257 | :refguide: something, else, and more 258 | 259 | """ 260 | def strip_each_in(lst): 261 | return [s.strip() for s in lst] 262 | 263 | out = {} 264 | section = section.split('::') 265 | if len(section) > 1: 266 | out['default'] = strip_each_in(section[1].split(','))[0] 267 | for line in content: 268 | line = line.split(':') 269 | if len(line) > 2: 270 | out[line[1]] = strip_each_in(line[2].split(',')) 271 | return out 272 | 273 | def _parse_summary(self): 274 | """Grab signature (if given) and summary""" 275 | if self._is_at_section(): 276 | return 277 | 278 | summary = self._doc.read_to_next_empty_line() 279 | summary_str = " ".join([s.strip() for s in summary]).strip() 280 | if re.compile('^([\w., ]+=)?\s*[\w\.]+\(.*\)$').match(summary_str): 281 | self['Signature'] = summary_str 282 | if not self._is_at_section(): 283 | self['Summary'] = self._doc.read_to_next_empty_line() 284 | else: 285 | self['Summary'] = summary 286 | 287 | if not self._is_at_section(): 288 | self['Extended Summary'] = self._read_to_next_section() 289 | 290 | def _parse(self): 291 | self._doc.reset() 292 | self._parse_summary() 293 | 294 | for (section, content) in self._read_sections(): 295 | if not section.startswith('..'): 296 | section = ' '.join([s.capitalize() 297 | for s in section.split(' ')]) 298 | if section in ('Parameters', 'Attributes', 'Methods', 299 | 'Returns', 'Raises', 'Warns'): 300 | self[section] = self._parse_param_list(content) 301 | elif section.startswith('.. index::'): 302 | self['index'] = self._parse_index(section, content) 303 | elif section == 'See Also': 304 | self['See Also'] = self._parse_see_also(content) 305 | else: 306 | self[section] = content 307 | 308 | # string conversion routines 309 | 310 | def _str_header(self, name, symbol='-'): 311 | return [name, len(name) * symbol] 312 | 313 | def _str_indent(self, doc, indent=4): 314 | out = [] 315 | for line in doc: 316 | out += [' ' * indent + line] 317 | return out 318 | 319 | def _str_signature(self): 320 | if self['Signature']: 321 | return [self['Signature'].replace('*', '\*')] + [''] 322 | else: 323 | return [''] 324 | 325 | def _str_summary(self): 326 | if self['Summary']: 327 | return self['Summary'] + [''] 328 | else: 329 | return [] 330 | 331 | def _str_extended_summary(self): 332 | if self['Extended Summary']: 333 | return self['Extended Summary'] + [''] 334 | else: 335 | return [] 336 | 337 | def _str_param_list(self, name): 338 | out = [] 339 | if self[name]: 340 | out += self._str_header(name) 341 | for param, param_type, desc in self[name]: 342 | out += ['%s : %s' % (param, param_type)] 343 | out += self._str_indent(desc) 344 | out += [''] 345 | return out 346 | 347 | def _str_section(self, name): 348 | out = [] 349 | if self[name]: 350 | out += self._str_header(name) 351 | out += self[name] 352 | out += [''] 353 | return out 354 | 355 | def _str_see_also(self, func_role): 356 | if not self['See Also']: 357 | return [] 358 | out = [] 359 | out += self._str_header("See Also") 360 | last_had_desc = True 361 | for func, desc, role in self['See Also']: 362 | if role: 363 | link = ':%s:`%s`' % (role, func) 364 | elif func_role: 365 | link = ':%s:`%s`' % (func_role, func) 366 | else: 367 | link = "`%s`_" % func 368 | if desc or last_had_desc: 369 | out += [''] 370 | out += [link] 371 | else: 372 | out[-1] += ", %s" % link 373 | if desc: 374 | out += self._str_indent([' '.join(desc)]) 375 | last_had_desc = True 376 | else: 377 | last_had_desc = False 378 | out += [''] 379 | return out 380 | 381 | def _str_index(self): 382 | idx = self['index'] 383 | out = [] 384 | out += ['.. index:: %s' % idx.get('default', '')] 385 | for section, references in idx.items(): 386 | if section == 'default': 387 | continue 388 | out += [' :%s: %s' % (section, ', '.join(references))] 389 | return out 390 | 391 | def __str__(self, func_role=''): 392 | out = [] 393 | out += self._str_signature() 394 | out += self._str_summary() 395 | out += self._str_extended_summary() 396 | for param_list in ('Parameters', 'Returns', 'Raises'): 397 | out += self._str_param_list(param_list) 398 | out += self._str_section('Warnings') 399 | out += self._str_see_also(func_role) 400 | for s in ('Notes', 'References', 'Examples'): 401 | out += self._str_section(s) 402 | for param_list in ('Attributes', 'Methods'): 403 | out += self._str_param_list(param_list) 404 | out += self._str_index() 405 | return '\n'.join(out) 406 | 407 | 408 | def indent(str, indent=4): 409 | indent_str = ' ' * indent 410 | if str is None: 411 | return indent_str 412 | lines = str.split('\n') 413 | return '\n'.join(indent_str + l for l in lines) 414 | 415 | 416 | def dedent_lines(lines): 417 | """Deindent a list of lines maximally""" 418 | return textwrap.dedent("\n".join(lines)).split("\n") 419 | 420 | 421 | def header(text, style='-'): 422 | return text + '\n' + style * len(text) + '\n' 423 | 424 | 425 | class FunctionDoc(NumpyDocString): 426 | 427 | def __init__(self, func, role='func', doc=None, config={}): 428 | self._f = func 429 | self._role = role # e.g. "func" or "meth" 430 | 431 | if doc is None: 432 | if func is None: 433 | raise ValueError("No function or docstring given") 434 | doc = inspect.getdoc(func) or '' 435 | NumpyDocString.__init__(self, doc) 436 | 437 | if not self['Signature'] and func is not None: 438 | func, func_name = self.get_func() 439 | try: 440 | # try to read signature 441 | argspec = inspect.getargspec(func) 442 | argspec = inspect.formatargspec(*argspec) 443 | argspec = argspec.replace('*', '\*') 444 | signature = '%s%s' % (func_name, argspec) 445 | except TypeError as e: 446 | signature = '%s()' % func_name 447 | self['Signature'] = signature 448 | 449 | def get_func(self): 450 | func_name = getattr(self._f, '__name__', self.__class__.__name__) 451 | if inspect.isclass(self._f): 452 | func = getattr(self._f, '__call__', self._f.__init__) 453 | else: 454 | func = self._f 455 | return func, func_name 456 | 457 | def __str__(self): 458 | out = '' 459 | 460 | func, func_name = self.get_func() 461 | signature = self['Signature'].replace('*', '\*') 462 | 463 | roles = {'func': 'function', 464 | 'meth': 'method'} 465 | 466 | if self._role: 467 | if not self._role in roles: 468 | print("Warning: invalid role %s" % self._role) 469 | out += '.. %s:: %s\n \n\n' % (roles.get(self._role, ''), 470 | func_name) 471 | 472 | out += super(FunctionDoc, self).__str__(func_role=self._role) 473 | return out 474 | 475 | 476 | class ClassDoc(NumpyDocString): 477 | 478 | def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, 479 | config={}): 480 | if not inspect.isclass(cls) and cls is not None: 481 | raise ValueError("Expected a class or None, but got %r" % cls) 482 | self._cls = cls 483 | 484 | if modulename and not modulename.endswith('.'): 485 | modulename += '.' 486 | self._mod = modulename 487 | 488 | if doc is None: 489 | if cls is None: 490 | raise ValueError("No class or documentation string given") 491 | doc = pydoc.getdoc(cls) 492 | 493 | NumpyDocString.__init__(self, doc) 494 | 495 | if config.get('show_class_members', True): 496 | if not self['Methods']: 497 | self['Methods'] = [(name, '', '') 498 | for name in sorted(self.methods)] 499 | if not self['Attributes']: 500 | self['Attributes'] = [(name, '', '') 501 | for name in sorted(self.properties)] 502 | 503 | @property 504 | def methods(self): 505 | if self._cls is None: 506 | return [] 507 | return [name for name, func in inspect.getmembers(self._cls) 508 | if not name.startswith('_') and callable(func)] 509 | 510 | @property 511 | def properties(self): 512 | if self._cls is None: 513 | return [] 514 | return [name for name, func in inspect.getmembers(self._cls) 515 | if not name.startswith('_') and func is None] 516 | -------------------------------------------------------------------------------- /docs/ext/docscrape_sphinx.py: -------------------------------------------------------------------------------- 1 | import re 2 | import inspect 3 | import textwrap 4 | import pydoc 5 | import sphinx 6 | from docscrape import NumpyDocString, FunctionDoc, ClassDoc 7 | 8 | 9 | class SphinxDocString(NumpyDocString): 10 | 11 | def __init__(self, docstring, config={}): 12 | self.use_plots = config.get('use_plots', False) 13 | NumpyDocString.__init__(self, docstring, config=config) 14 | 15 | # string conversion routines 16 | def _str_header(self, name, symbol='`'): 17 | return ['.. rubric:: ' + name, ''] 18 | 19 | def _str_field_list(self, name): 20 | return [':' + name + ':'] 21 | 22 | def _str_indent(self, doc, indent=4): 23 | out = [] 24 | for line in doc: 25 | out += [' ' * indent + line] 26 | return out 27 | 28 | def _str_signature(self): 29 | return [''] 30 | if self['Signature']: 31 | return ['``%s``' % self['Signature']] + [''] 32 | else: 33 | return [''] 34 | 35 | def _str_summary(self): 36 | return self['Summary'] + [''] 37 | 38 | def _str_extended_summary(self): 39 | return self['Extended Summary'] + [''] 40 | 41 | def _str_param_list(self, name): 42 | out = [] 43 | if self[name]: 44 | out += self._str_field_list(name) 45 | out += [''] 46 | for param, param_type, desc in self[name]: 47 | out += self._str_indent(['**%s** : %s' % (param.strip(), 48 | param_type)]) 49 | out += [''] 50 | out += self._str_indent(desc, 8) 51 | out += [''] 52 | return out 53 | 54 | @property 55 | def _obj(self): 56 | if hasattr(self, '_cls'): 57 | return self._cls 58 | elif hasattr(self, '_f'): 59 | return self._f 60 | return None 61 | 62 | def _str_member_list(self, name): 63 | """ 64 | Generate a member listing, autosummary:: table where possible, 65 | and a table where not. 66 | 67 | """ 68 | out = [] 69 | if self[name]: 70 | out += ['.. rubric:: %s' % name, ''] 71 | prefix = getattr(self, '_name', '') 72 | 73 | if prefix: 74 | prefix = '~%s.' % prefix 75 | 76 | autosum = [] 77 | others = [] 78 | for param, param_type, desc in self[name]: 79 | param = param.strip() 80 | if not self._obj or hasattr(self._obj, param): 81 | autosum += [" %s%s" % (prefix, param)] 82 | else: 83 | others.append((param, param_type, desc)) 84 | 85 | if False and autosum: 86 | out += ['.. autosummary::', ' :toctree:', ''] 87 | out += autosum 88 | 89 | if others: 90 | maxlen_0 = max([len(x[0]) for x in others]) 91 | maxlen_1 = max([len(x[1]) for x in others]) 92 | hdr = "=" * maxlen_0 + " " + "=" * maxlen_1 + " " + "=" * 10 93 | fmt = '%%%ds %%%ds ' % (maxlen_0, maxlen_1) 94 | n_indent = maxlen_0 + maxlen_1 + 4 95 | out += [hdr] 96 | for param, param_type, desc in others: 97 | out += [fmt % (param.strip(), param_type)] 98 | out += self._str_indent(desc, n_indent) 99 | out += [hdr] 100 | out += [''] 101 | return out 102 | 103 | def _str_section(self, name): 104 | out = [] 105 | if self[name]: 106 | out += self._str_header(name) 107 | out += [''] 108 | content = textwrap.dedent("\n".join(self[name])).split("\n") 109 | out += content 110 | out += [''] 111 | return out 112 | 113 | def _str_see_also(self, func_role): 114 | out = [] 115 | if self['See Also']: 116 | see_also = super(SphinxDocString, self)._str_see_also(func_role) 117 | out = ['.. seealso::', ''] 118 | out += self._str_indent(see_also[2:]) 119 | return out 120 | 121 | def _str_warnings(self): 122 | out = [] 123 | if self['Warnings']: 124 | out = ['.. warning::', ''] 125 | out += self._str_indent(self['Warnings']) 126 | return out 127 | 128 | def _str_index(self): 129 | idx = self['index'] 130 | out = [] 131 | if len(idx) == 0: 132 | return out 133 | 134 | out += ['.. index:: %s' % idx.get('default', '')] 135 | for section, references in idx.items(): 136 | if section == 'default': 137 | continue 138 | elif section == 'refguide': 139 | out += [' single: %s' % (', '.join(references))] 140 | else: 141 | out += [' %s: %s' % (section, ','.join(references))] 142 | return out 143 | 144 | def _str_references(self): 145 | out = [] 146 | if self['References']: 147 | out += self._str_header('References') 148 | if isinstance(self['References'], str): 149 | self['References'] = [self['References']] 150 | out.extend(self['References']) 151 | out += [''] 152 | # Latex collects all references to a separate bibliography, 153 | # so we need to insert links to it 154 | if sphinx.__version__ >= "0.6": 155 | out += ['.. only:: latex', ''] 156 | else: 157 | out += ['.. latexonly::', ''] 158 | items = [] 159 | for line in self['References']: 160 | m = re.match(r'.. \[([a-z0-9._-]+)\]', line, re.I) 161 | if m: 162 | items.append(m.group(1)) 163 | out += [' ' + ", ".join(["[%s]_" % item for item in items]), ''] 164 | return out 165 | 166 | def _str_examples(self): 167 | examples_str = "\n".join(self['Examples']) 168 | 169 | if (self.use_plots and 'import matplotlib' in examples_str 170 | and 'plot::' not in examples_str): 171 | out = [] 172 | out += self._str_header('Examples') 173 | out += ['.. plot::', ''] 174 | out += self._str_indent(self['Examples']) 175 | out += [''] 176 | return out 177 | else: 178 | return self._str_section('Examples') 179 | 180 | def __str__(self, indent=0, func_role="obj"): 181 | out = [] 182 | out += self._str_signature() 183 | out += self._str_index() + [''] 184 | out += self._str_summary() 185 | out += self._str_extended_summary() 186 | for param_list in ('Parameters', 'Returns', 'Raises'): 187 | out += self._str_param_list(param_list) 188 | out += self._str_warnings() 189 | out += self._str_see_also(func_role) 190 | out += self._str_section('Notes') 191 | out += self._str_references() 192 | out += self._str_examples() 193 | for param_list in ('Attributes', 'Methods'): 194 | out += self._str_member_list(param_list) 195 | out = self._str_indent(out, indent) 196 | return '\n'.join(out) 197 | 198 | 199 | class SphinxFunctionDoc(SphinxDocString, FunctionDoc): 200 | 201 | def __init__(self, obj, doc=None, config={}): 202 | self.use_plots = config.get('use_plots', False) 203 | FunctionDoc.__init__(self, obj, doc=doc, config=config) 204 | 205 | 206 | class SphinxClassDoc(SphinxDocString, ClassDoc): 207 | 208 | def __init__(self, obj, doc=None, func_doc=None, config={}): 209 | self.use_plots = config.get('use_plots', False) 210 | ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) 211 | 212 | 213 | class SphinxObjDoc(SphinxDocString): 214 | 215 | def __init__(self, obj, doc=None, config={}): 216 | self._f = obj 217 | SphinxDocString.__init__(self, doc, config=config) 218 | 219 | 220 | def get_doc_object(obj, what=None, doc=None, config={}): 221 | if what is None: 222 | if inspect.isclass(obj): 223 | what = 'class' 224 | elif inspect.ismodule(obj): 225 | what = 'module' 226 | elif callable(obj): 227 | what = 'function' 228 | else: 229 | what = 'object' 230 | if what == 'class': 231 | return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, 232 | config=config) 233 | elif what in ('function', 'method'): 234 | return SphinxFunctionDoc(obj, doc=doc, config=config) 235 | else: 236 | if doc is None: 237 | doc = pydoc.getdoc(obj) 238 | return SphinxObjDoc(obj, doc, config=config) 239 | -------------------------------------------------------------------------------- /docs/ext/numpydoc.py: -------------------------------------------------------------------------------- 1 | """ 2 | ======== 3 | numpydoc 4 | ======== 5 | 6 | Sphinx extension that handles docstrings in the Numpy standard format. [1] 7 | 8 | It will: 9 | 10 | - Convert Parameters etc. sections to field lists. 11 | - Convert See Also section to a See also entry. 12 | - Renumber references. 13 | - Extract the signature from the docstring, if it can't be determined otherwise. 14 | 15 | .. [1] http://projects.scipy.org/numpy/wiki/CodingStyleGuidelines#docstring-standard 16 | 17 | """ 18 | 19 | import os 20 | import re 21 | import pydoc 22 | import sys 23 | from docscrape_sphinx import get_doc_object, SphinxDocString 24 | from sphinx.util.compat import Directive 25 | import inspect 26 | if sys.version_info > (3,): 27 | unicode = str 28 | 29 | 30 | def mangle_docstrings(app, what, name, obj, options, lines, 31 | reference_offset=[0]): 32 | 33 | cfg = dict(use_plots=app.config.numpydoc_use_plots, 34 | show_class_members=app.config.numpydoc_show_class_members) 35 | 36 | if what == 'module': 37 | # Strip top title 38 | title_re = re.compile(r'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', 39 | re.I | re.S) 40 | lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n") 41 | else: 42 | doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg) 43 | lines[:] = unicode(doc).split(u"\n") 44 | 45 | if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ 46 | obj.__name__: 47 | if hasattr(obj, '__module__'): 48 | v = dict(full_name=u"%s.%s" % (obj.__module__, obj.__name__)) 49 | else: 50 | v = dict(full_name=obj.__name__) 51 | lines += [u'', u'.. htmlonly::', ''] 52 | lines += [u' %s' % x for x in 53 | (app.config.numpydoc_edit_link % v).split("\n")] 54 | 55 | # replace reference numbers so that there are no duplicates 56 | references = [] 57 | for line in lines: 58 | line = line.strip() 59 | m = re.match(r'^.. \[([a-z0-9_.-])\]', line, re.I) 60 | if m: 61 | references.append(m.group(1)) 62 | 63 | # start renaming from the longest string, to avoid overwriting parts 64 | references.sort(key=lambda x: -len(x)) 65 | if references: 66 | for i, line in enumerate(lines): 67 | for r in references: 68 | if re.match(r'^\d+$', r): 69 | new_r = u"R%d" % (reference_offset[0] + int(r)) 70 | else: 71 | new_r = u"%s%d" % (r, reference_offset[0]) 72 | lines[i] = lines[i].replace(u'[%s]_' % r, 73 | u'[%s]_' % new_r) 74 | lines[i] = lines[i].replace(u'.. [%s]' % r, 75 | u'.. [%s]' % new_r) 76 | 77 | reference_offset[0] += len(references) 78 | 79 | 80 | def mangle_signature(app, what, name, obj, options, sig, retann): 81 | # Do not try to inspect classes that don't define `__init__` 82 | if (inspect.isclass(obj) and 83 | (not hasattr(obj, '__init__') or 84 | 'initializes x; see ' in pydoc.getdoc(obj.__init__))): 85 | return '', '' 86 | 87 | if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): 88 | return 89 | if not hasattr(obj, '__doc__'): 90 | return 91 | 92 | doc = SphinxDocString(pydoc.getdoc(obj)) 93 | if doc['Signature']: 94 | sig = re.sub(u"^[^(]*", u"", doc['Signature']) 95 | return sig, u'' 96 | 97 | 98 | def setup(app, get_doc_object_=get_doc_object): 99 | global get_doc_object 100 | get_doc_object = get_doc_object_ 101 | 102 | app.connect('autodoc-process-docstring', mangle_docstrings) 103 | app.connect('autodoc-process-signature', mangle_signature) 104 | app.add_config_value('numpydoc_edit_link', None, False) 105 | app.add_config_value('numpydoc_use_plots', None, False) 106 | app.add_config_value('numpydoc_show_class_members', True, True) 107 | 108 | # Extra mangling domains 109 | app.add_domain(NumpyPythonDomain) 110 | app.add_domain(NumpyCDomain) 111 | 112 | #------------------------------------------------------------------------------ 113 | # Docstring-mangling domains 114 | #------------------------------------------------------------------------------ 115 | 116 | from docutils.statemachine import ViewList 117 | from sphinx.domains.c import CDomain 118 | from sphinx.domains.python import PythonDomain 119 | 120 | 121 | class ManglingDomainBase(object): 122 | directive_mangling_map = {} 123 | 124 | def __init__(self, *a, **kw): 125 | super(ManglingDomainBase, self).__init__(*a, **kw) 126 | self.wrap_mangling_directives() 127 | 128 | def wrap_mangling_directives(self): 129 | for name, objtype in self.directive_mangling_map.items(): 130 | self.directives[name] = wrap_mangling_directive( 131 | self.directives[name], objtype) 132 | 133 | 134 | class NumpyPythonDomain(ManglingDomainBase, PythonDomain): 135 | name = 'np' 136 | directive_mangling_map = { 137 | 'function': 'function', 138 | 'class': 'class', 139 | 'exception': 'class', 140 | 'method': 'function', 141 | 'classmethod': 'function', 142 | 'staticmethod': 'function', 143 | 'attribute': 'attribute', 144 | } 145 | 146 | 147 | class NumpyCDomain(ManglingDomainBase, CDomain): 148 | name = 'np-c' 149 | directive_mangling_map = { 150 | 'function': 'function', 151 | 'member': 'attribute', 152 | 'macro': 'function', 153 | 'type': 'class', 154 | 'var': 'object', 155 | } 156 | 157 | 158 | def wrap_mangling_directive(base_directive, objtype): 159 | class directive(base_directive): 160 | 161 | def run(self): 162 | env = self.state.document.settings.env 163 | 164 | name = None 165 | if self.arguments: 166 | m = re.match(r'^(.*\s+)?(.*?)(\(.*)?', self.arguments[0]) 167 | name = m.group(2).strip() 168 | 169 | if not name: 170 | name = self.arguments[0] 171 | 172 | lines = list(self.content) 173 | mangle_docstrings(env.app, objtype, name, None, None, lines) 174 | self.content = ViewList(lines, self.content.parent) 175 | 176 | return base_directive.run(self) 177 | 178 | return directive 179 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. tinynumpy documentation master file, created by 2 | sphinx-quickstart on Thu Nov 20 10:11:12 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to tinynumpy's documentation! 7 | ===================================== 8 | 9 | .. automodule:: tinynumpy 10 | :members: 11 | :undoc-members: 12 | :exclude-members: ndarray 13 | 14 | 15 | ---- 16 | 17 | .. autoclass:: tinynumpy.ndarray 18 | :members: 19 | :undoc-members: 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. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%tinynumpy.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\tinynumpy.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name='tinynumpy', 4 | packages=['tinynumpy'], # this must be the same as the name above 5 | version='1.2.0', 6 | description='A lightweight, pure Python, numpy compliant ndarray class', 7 | author='Wade Brainerd, Almar Klein', 8 | author_email='wadetb@gmail.com', 9 | url='https://github.com/wadetb/tinynumpy', # use the URL to the github repo 10 | # download_url = 'https://github.com/peterldowns/mypackage/tarball/0.1', 11 | keywords=['Science', 'Research', 'Engineering', 'Software Development'], 12 | classifiers=[], 13 | ) 14 | -------------------------------------------------------------------------------- /tinynumpy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wadetb/tinynumpy/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/__init__.py -------------------------------------------------------------------------------- /tinynumpy/benchmark.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2014, Almar Klein and Wade Brainerd 3 | # tinynumpy is distributed under the terms of the MIT License. 4 | 5 | """ Benchmarks for tinynumpy 6 | 7 | Findings: 8 | 9 | * A list of floats costs about 33 bytes per float 10 | * A list if ints costs anout 41 bytes per int 11 | * A huge list of ints 0-255, costs about 1 byter per int 12 | * Python list takes about 5-6 times as much memory than array for 64bit 13 | data types. Up to 40 times as much for uint8, unless Python can reuse 14 | values. 15 | * __slots__ help reduce the size of custom classes 16 | * tinynumpy is about 100 times slower than numpy 17 | * using _toflatlist instead of flat taks 50-60% of time (but more memory) 18 | 19 | """ 20 | 21 | from __future__ import division 22 | 23 | import os 24 | import sys 25 | import time 26 | import subprocess 27 | 28 | import numpy as np 29 | import tinynumpy as tnp 30 | 31 | 32 | def _prettymem(n): 33 | if n > 2**20: 34 | return '%1.2f MiB' % (n / 2**20) 35 | elif n > 2**10: 36 | return '%1.2f KiB' % (n / 2**10) 37 | else: 38 | return '%1.0f B' % n 39 | 40 | def _prettysec(n): 41 | if n < 0.0001: 42 | return '%1.2f us' % (n * 1000000) 43 | elif n < 0.1: 44 | return '%1.2f ms' % (n * 1000) 45 | else: 46 | return '%1.2f s' % n 47 | 48 | 49 | code_template = """ 50 | import psutil 51 | import os 52 | import random 53 | import numpy as np 54 | import tinynumpy as tnp 55 | 56 | N = 100 * 1000 57 | M = 1000 * 1000 58 | 59 | class A(object): 60 | def __init__(self): 61 | self.foo = 8 62 | self.bar = 3.3 63 | 64 | class B(object): 65 | __slots__ = ['foo', 'bar'] 66 | def __init__(self): 67 | self.foo = 8 68 | self.bar = 3.3 69 | 70 | def getmem(): 71 | process = psutil.Process(os.getpid()) 72 | return process.get_memory_info()[0] 73 | 74 | M0 = getmem() 75 | %s 76 | M1 = getmem() 77 | print(M1-M0) 78 | """ 79 | 80 | def measure_mem(what, code, divide=1): 81 | 82 | cmd = [sys.executable, '-c', code_template % code] 83 | res = subprocess.check_output(cmd, cwd=os.getcwd()).decode('utf-8') 84 | m = int(res) / divide 85 | 86 | print('Memory for %s:%s%s' % 87 | (what, ' '*(22-len(what)), _prettymem(m))) 88 | 89 | 90 | def measure_speed(what, func, *args, **kwargs): 91 | 92 | 93 | N = 1 94 | t0 = time.perf_counter() 95 | func(*args, **kwargs) 96 | t1 = time.perf_counter() 97 | while (t1 - t0) < 0.2: 98 | N *= 10 99 | t0 = time.perf_counter() 100 | for i in range(N): 101 | func(*args, **kwargs) 102 | t1 = time.perf_counter() 103 | 104 | te = t1 - t0 105 | print('Time for %s:%s%s (%i iters)' % 106 | (what, ' '*(22-len(what)), _prettysec(te/N), N)) 107 | 108 | 109 | if __name__ == '__main__': 110 | N = 100 * 1000 111 | M = 1000 * 1000 112 | 113 | print('=== MEMORY ====') 114 | measure_mem('floats 0-M', 'L = [i*1.0 for i in range(N)]', N) 115 | measure_mem('ints 0-M', 'L = [i for i in range(N)]', N) 116 | measure_mem('ints 0-255', 'L = [int(random.uniform(0, 255)) for i in range(N)]', N) 117 | 118 | measure_mem('regular object', 'L = [A() for i in range(N)]', N) 119 | measure_mem('object with slots', 'L = [B() for i in range(N)]', N) 120 | 121 | measure_mem(' Numpy arr size 1', 'L = [np.ones((1,)) for i in range(N)]', N) 122 | measure_mem('Tinynumpy arr size 1', 'L = [tnp.ones((1,)) for i in range(N)]', N) 123 | 124 | measure_mem(' Numpy arr size M', 'a = np.ones((M,))') 125 | measure_mem('Tinynumpy arr size M', 'a = tnp.ones((M,))') 126 | 127 | print('=== SPEED ====') 128 | 129 | a1 = np.ones((100, 100)) 130 | a2 = tnp.ones((100, 100)) 131 | measure_speed(' numpy sum 10k', a1.sum) 132 | measure_speed('tinynumpy sum 10k', a2.sum) 133 | measure_speed(' numpy max 10k', a1.max) 134 | measure_speed('tinynumpy max 10k', a2.max) 135 | 136 | a1 = np.ones((1000, 1000)) 137 | a2 = tnp.ones((1000, 1000)) 138 | measure_speed(' numpy sum 1M', a1.sum) 139 | measure_speed('tinynumpy sum 1M', a2.sum) 140 | measure_speed(' numpy max 1M', a1.max) 141 | measure_speed('tinynumpy max 1M', a2.max) 142 | -------------------------------------------------------------------------------- /tinynumpy/test_tinyndarray.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | import pytest 4 | 5 | try: 6 | import tinynumpy.tinynumpy as tinynumpy 7 | except ImportError: 8 | import tinynumpy as tinynumpy 9 | 10 | # Numpy is optional. If not available, will compare against ourselves. 11 | try: 12 | import numpy 13 | except ImportError: 14 | numpy = tinynumpy 15 | 16 | #numpy.set_printoptions(formatter={'float': repr}) 17 | 18 | def _clean_repr(a): 19 | return "".join(repr(a).split()) 20 | 21 | class TestNDArray(unittest.TestCase): 22 | def setUp(self): 23 | a = [[ 1.0, 2.0, 3.0, 4.0], 24 | [ 5.0, 6.0, 7.0, 8.0], 25 | [ 9.0,10.0,11.0,12.0], 26 | [13.0,14.0,15.0,16.0]] 27 | self.t0 = tinynumpy.array(a) 28 | self.n0 = numpy.array(a) 29 | 30 | def test_repr(self): 31 | self.assertEqual(_clean_repr(self.t0), _clean_repr(self.n0)) 32 | 33 | def test_index(self): 34 | self.assertEqual(float(self.t0[1,1]), float(self.n0[1,1])) 35 | 36 | def test_slice(self): 37 | self.assertEqual(_clean_repr(self.t0[1:]), _clean_repr(self.n0[1:])) 38 | self.assertEqual(_clean_repr(self.t0[1:,1:]), _clean_repr(self.n0[1:,1:])) 39 | self.assertEqual(_clean_repr(self.t0[-1:,]), _clean_repr(self.n0[-1:,])) 40 | self.assertEqual(_clean_repr(self.t0[-1:,1]), _clean_repr(self.n0[-1:,1])) 41 | 42 | def test_double_slice(self): 43 | self.assertEqual(_clean_repr(self.t0[1:][2::2]), _clean_repr(self.n0[1:][2::2])) 44 | 45 | def test_len(self): 46 | self.assertEqual(len(self.t0), len(self.n0)) 47 | 48 | def test_newaxis(self): 49 | self.assertEqual(self.t0[tinynumpy.newaxis,2:].shape, (1,2,4)) 50 | self.assertEqual(self.n0[numpy.newaxis,2:].shape, (1,2,4)) 51 | self.assertEqual(_clean_repr(self.t0[tinynumpy.newaxis,2:]), _clean_repr(self.n0[numpy.newaxis,2:])) 52 | 53 | class TestNDIter(unittest.TestCase): 54 | def setUp(self): 55 | self.t0 = tinynumpy.array([[1.0,2.0],[3.0,4.0]]) 56 | self.n0 = numpy.array([[1.0,2.0],[3.0,4.0]]) 57 | 58 | def test_basic(self): 59 | self.assertEqual( 60 | [float(i) for i in tinynumpy.nditer(self.t0)], 61 | [float(i) for i in numpy.nditer(self.n0)]) 62 | 63 | def test_reversed(self): 64 | # NumPy reversed just returns array(1.0). 65 | self.assertEqual( 66 | [float(i) for i in reversed(tinynumpy.nditer(self.t0))], 67 | [4.0, 3.0, 2.0, 1.0]) 68 | 69 | class TestFunctions(unittest.TestCase): 70 | def setUp(self): 71 | a0 = [ 72 | [ 73 | [ 74 | 1, 75 | [1, 1, 1, 1], 76 | [2, 2], 77 | [2, 2] 78 | ], 79 | 1 80 | ], 81 | [2, 2, 1, 1], 82 | [3, 3], 83 | [3, 3] 84 | ] 85 | self.t0 = tinynumpy.array(a0) 86 | 87 | a1 = [[1,2],[3,4]] 88 | self.t1 = tinynumpy.array(a1) 89 | self.n1 = numpy.array(a1) 90 | 91 | def test_array(self): 92 | # NumPy requires that the input array be full in all dimensions, so don't check compatibility. 93 | self.assertEqual(self.t0.shape, (4, 4, 4, 4)) 94 | 95 | def test_zeros(self): 96 | self.assertEqual(tinynumpy.zeros((5,5,5)).sum(), 0) 97 | 98 | def test_eye(self): 99 | self.assertEqual(_clean_repr(tinynumpy.eye(3)), _clean_repr(numpy.eye(3))) 100 | self.assertEqual(tinynumpy.eye(25).sum(), 25) 101 | 102 | def test_sum(self): 103 | self.assertEqual(self.t1.sum(), 10) 104 | 105 | def test_mean(self): 106 | self.assertEqual(tinynumpy.array([-5, 5]).mean(), 0) 107 | 108 | def test_argmax(self): 109 | self.assertEqual(tinynumpy.array([1, 2, 3]).argmax(), 2) 110 | 111 | def test_argmin(self): 112 | self.assertEqual(tinynumpy.array([1, 2, -3]).argmin(), 2) 113 | 114 | def test_cumsum(self): 115 | self.assertEqual(_clean_repr(tinynumpy.array([1, 2, 3]).cumsum()), _clean_repr(tinynumpy.array([1, 3, 6]))) 116 | 117 | def test_cumprod(self): 118 | self.assertEqual(_clean_repr(tinynumpy.array([1, 2, 3]).cumprod()), _clean_repr(tinynumpy.array([1, 2, 6]))) 119 | 120 | def test_all(self): 121 | self.assertEqual(tinynumpy.array([1, 0, 1]).all(), False) 122 | 123 | def test_any(self): 124 | self.assertEqual(tinynumpy.array([1, 0, 1]).any(), True) 125 | 126 | def test_argmin(self): 127 | self.assertEqual(tinynumpy.array([1, 2, -3]).argmin(), 2) 128 | 129 | def test_sum(self): 130 | self.assertEqual(tinynumpy.array([1, 2, -3]).sum(), 0) 131 | 132 | def test_max(self): 133 | self.assertEqual(self.t1.max(), 4) 134 | 135 | def test_min(self): 136 | self.assertEqual(self.t1.min(), 1) 137 | 138 | def test_prod(self): 139 | self.assertEqual(tinynumpy.array([1, 2, 3, 10]).prod(), 60) 140 | 141 | def test_ptp(self): 142 | self.assertEqual(tinynumpy.array([-1, 2, 3]).ptp(), 4) 143 | 144 | def test_fill(self): 145 | t = tinynumpy.zeros((4, 4, 4)) 146 | t.fill(1) 147 | self.assertEqual(t.sum(), 4*4*4) 148 | 149 | def test_copy(self): 150 | t = self.t1.copy() 151 | t[0,0] = 0 152 | self.assertEqual(self.t1.sum(), 10) 153 | self.assertEqual(t.sum(), 9) 154 | 155 | def test_flatten(self): 156 | self.assertEqual(_clean_repr(self.t1.flatten()), _clean_repr(tinynumpy.array([1, 2, 3, 4]))) 157 | 158 | def test_var(self): 159 | self.assertEqual(tinynumpy.array([1,1,1,1]).var(), 0) 160 | self.assertEqual(_clean_repr(self.t1.var()), _clean_repr(self.n1.var())) 161 | 162 | def test_std(self): 163 | self.assertEqual(tinynumpy.array([1,1,1,1]).std(), 0) 164 | self.assertEqual(int(self.t1.std()*1000000), int(self.n1.std()*1000000)) 165 | 166 | if __name__ == '__main__': 167 | unittest.main() 168 | #unittest.main(defaultTest='TestNDArray.test_newaxis') -------------------------------------------------------------------------------- /tinynumpy/test_tinynumpy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2014, Almar Klein and Wade Brainerd 3 | # tinynumpy is distributed under the terms of the MIT License. 4 | 5 | """ Test suite for tinynumpy 6 | """ 7 | 8 | import os 9 | import sys 10 | import ctypes 11 | 12 | import pytest 13 | from _pytest import runner 14 | from pytest import raises, skip 15 | 16 | try: 17 | import tinynumpy.tinynumpy as tnp 18 | except ImportError: 19 | import tinynumpy as tnp 20 | 21 | # Numpy is optional. If not available, will compare against ourselves. 22 | try: 23 | import numpy as np 24 | except ImportError: 25 | np = tnp 26 | 27 | 28 | def test_TESTING_WITH_NUMPY(): 29 | # So we can see in the result whether numpy was used 30 | if np is None or np is tnp: 31 | skip('Numpy is not available') 32 | 33 | 34 | def test_shapes_and_strides(): 35 | 36 | for shape in [(9, ), (109, ), 37 | (9, 4), (109, 104), 38 | (9, 4, 5), (109, 104, 105), 39 | (9, 4, 5, 6), # not (109, 104, 105, 106) -> too big 40 | ]: 41 | 42 | # Test shape and strides 43 | a = np.empty(shape) 44 | b = tnp.empty(shape) 45 | assert a.ndim == len(shape) 46 | assert a.ndim == b.ndim 47 | assert a.shape == b.shape 48 | assert a.strides == b.strides 49 | assert a.size == b.size 50 | 51 | # Also test repr length 52 | if b.size > 100: 53 | assert len(repr(b)) < 80 54 | else: 55 | assert len(repr(b)) > (b.size * 3) # "x.0" for each element 56 | 57 | 58 | def test_repr(): 59 | 60 | for dtype in ['float32', 'float64', 'int32', 'int64']: 61 | for data in [[1, 2, 3, 4, 5, 6, 7, 8], 62 | [[1, 2], [3, 4], [5, 6], [7, 8]], 63 | [[[1, 2], [3, 4]],[[5, 6], [7, 8]]], 64 | ]: 65 | a = np.array(data, dtype) 66 | b = tnp.array(data, dtype) 67 | # Compare line by line (forget leading whitespace) 68 | charscompared = 0 69 | for l1, l2 in zip(repr(a).splitlines(), repr(b).splitlines()): 70 | l1, l2 = l1.rstrip(), l2.rstrip() 71 | l1, l2 = l1.split('dtype=')[0], l2.split('dtype=')[0] 72 | assert l1 == l2 73 | charscompared += len(l1) 74 | assert charscompared > (3 * b.size) 75 | 76 | 77 | def test_dtype(): 78 | 79 | for shape in [(9, ), (9, 4), (9, 4, 5)]: 80 | for dtype in ['bool', 'int8', 'uint8', 'int16', 'uint16', 81 | 'int32', 'uint32', 'float32', 'float64']: 82 | a = np.empty(shape, dtype=dtype) 83 | b = tnp.empty(shape, dtype=dtype) 84 | assert a.shape == b.shape 85 | assert a.dtype == b.dtype 86 | assert a.itemsize == b.itemsize 87 | 88 | raises(TypeError, tnp.zeros, (9, ), 'blaa') 89 | 90 | assert tnp.array([1.0, 2.0]).dtype == 'float64' 91 | assert tnp.array([1, 2]).dtype == 'int64' 92 | 93 | 94 | def test_reshape(): 95 | 96 | a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype='int32') 97 | b = tnp.array([1, 2, 3, 4, 5, 6, 7, 8], dtype='int32') 98 | 99 | for shape in [(2, 4), (4, 2), (2, 2, 2), (8,)]: 100 | a.shape = shape 101 | b.shape = shape 102 | assert a.shape == b.shape 103 | print(repr(a), repr(b), a.strides, b.strides) 104 | assert a.strides == b.strides 105 | 106 | a.shape = 2, 4 107 | b.shape = 2, 4 108 | 109 | # Test transpose 110 | assert b.T.shape == (4, 2) 111 | assert (a.T == b.T).all() 112 | assert (b.T.T == b).all() 113 | 114 | # Make non-contiguous versions 115 | a2 = a[:, 2:] 116 | b2 = b[:, 2:] 117 | 118 | # Test contiguous flag 119 | assert a.flags['C_CONTIGUOUS'] 120 | assert not a2.flags['C_CONTIGUOUS'] 121 | 122 | # Test base 123 | assert a2.base is a 124 | assert b2.base is b 125 | assert a2[:].base is a 126 | assert b2[:].base is b 127 | 128 | # Fail 129 | with raises(ValueError): # Invalid shape 130 | a.shape = (3, 3) 131 | with raises(ValueError): 132 | b.shape = (3, 3) 133 | with raises(AttributeError): # Cannot reshape non-contiguous arrays 134 | a2.shape = 4, 135 | with raises(AttributeError): 136 | b2.shape = 4, 137 | 138 | 139 | def test_from_and_to_numpy(): 140 | # This also tests __array_interface__ 141 | 142 | for dtype in ['float32', 'float64', 'int32', 'uint32', 'uint8', 'int8']: 143 | for data in [[1, 2, 3, 4, 5, 6, 7, 8], 144 | [[1, 2], [3, 4], [5, 6], [7, 8]], 145 | [[[1, 2], [3, 4]],[[5, 6], [7, 8]]], 146 | ]: 147 | 148 | # Convert from numpy, from tinynumpy, to numpy 149 | a1 = np.array(data, dtype) 150 | b1 = tnp.array(a1) 151 | b2 = tnp.array(b1) 152 | a2 = np.array(b2) 153 | 154 | # Check if its the same 155 | for c in [b1, b2, a2]: 156 | assert a1.shape == c.shape 157 | assert a1.dtype == c.dtype 158 | assert a1.strides == c.strides 159 | assert (a1 == c).all() 160 | 161 | # Also test using a numpy array as a buffer 162 | a = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], 'float32') 163 | b = tnp.ndarray(a.shape, a.dtype, strides=a.strides, buffer=a.ravel()) 164 | assert (a==b).all() 165 | 166 | # Test that is indeed same data 167 | a[0, 0] = 99 168 | assert (a==b).all() 169 | 170 | 171 | def test_from_ctypes(): 172 | 173 | for type, dtype in [(ctypes.c_int16, 'int16'), 174 | (ctypes.c_uint8, 'uint8'), 175 | (ctypes.c_float, 'float32'), 176 | (ctypes.c_double, 'float64')]: 177 | # Create ctypes array, possibly something that we get from a c lib 178 | buffer = (type*100)() 179 | 180 | # Create array! 181 | b = tnp.ndarray((4, 25), dtype, buffer=buffer) 182 | 183 | # Check that we can turn it into a numpy array 184 | a = np.array(b, copy=False) 185 | assert (a == b).all() 186 | assert a.dtype == dtype 187 | 188 | # Verify that both point to the same data 189 | assert a.__array_interface__['data'][0] == ctypes.addressof(buffer) 190 | assert b.__array_interface__['data'][0] == ctypes.addressof(buffer) 191 | 192 | # also verify offset in __array_interface__ here 193 | for a0, b0 in zip([a[2:], a[:, 10::2], a[1::2, 10:20:2]], 194 | [b[2:], b[:, 10::2], b[1::2, 10:20:2]]): 195 | pa = a0.__array_interface__['data'][0] 196 | pb = b0.__array_interface__['data'][0] 197 | assert pa > ctypes.addressof(buffer) 198 | assert pa == pb 199 | 200 | 201 | def test_from_bytes(): 202 | skip('Need ndarray.frombytes or something') 203 | # Create bytes 204 | buffer = b'x' * 100 205 | 206 | # Create array! 207 | b = tnp.ndarray((4, 25), 'uint8', buffer=buffer) 208 | ptr = ctypes.cast(buffer, ctypes.c_void_p).value 209 | 210 | # Check that we can turn it into a numpy array 211 | a = np.array(b, copy=False) 212 | assert (a == b).all() 213 | 214 | # Verify readonly 215 | with raises(Exception): 216 | a[0, 0] = 1 217 | with raises(Exception): 218 | b[0, 0] = 1 219 | 220 | # Verify that both point to the same data 221 | assert a.__array_interface__['data'][0] == ptr 222 | assert b.__array_interface__['data'][0] == ptr 223 | 224 | # also verify offset in __array_interface__ here 225 | for a0, b0 in zip([a[2:], a[:, 10::2], a[1::2, 10:20:2]], 226 | [b[2:], b[:, 10::2], b[1::2, 10:20:2]]): 227 | pa = a0.__array_interface__['data'][0] 228 | pb = b0.__array_interface__['data'][0] 229 | assert pa > ptr 230 | assert pa == pb 231 | 232 | 233 | def test_creating_functions(): 234 | 235 | # Test array 236 | b1 = tnp.array([[1, 2, 3], [4, 5, 6]]) 237 | assert b1.shape == (2, 3) 238 | 239 | 240 | def test_getitem(): 241 | 242 | a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) 243 | b = tnp.array([[1, 2, 3, 4], [5, 6, 7, 8]]) 244 | 245 | 246 | # Start vector cross product tests 247 | def test_cross(): 248 | """test the cross product of two 3 dimensional vectors""" 249 | 250 | # Vector cross-product. 251 | x = [1, 2, 3] 252 | y = [4, 5, 6] 253 | z = tnp.cross(x, y) 254 | 255 | assert z == [-3, 6, -3] 256 | 257 | 258 | def test_2dim_cross(): 259 | """test the cross product of two 2 dimensional vectors""" 260 | 261 | # 2 dim cross-product. 262 | x = [1, 2] 263 | y = [4, 5] 264 | z = tnp.cross(x, y) 265 | 266 | assert z == [-3] 267 | 268 | 269 | def test_4dim_cross(): 270 | """test the cross product of two 2 dimensional vectors""" 271 | 272 | # 4 dim cross-product. 273 | x = [1, 2, 3, 4] 274 | y = [5, 6, 7, 8] 275 | 276 | with pytest.raises(IndexError) as execinfo: 277 | z = tnp.cross(x, y) 278 | 279 | assert 'Vector has invalid dimensions' in str(execinfo.value) 280 | 281 | 282 | def test_mdim_cross(): 283 | """test the cross product of two 4 dimensional vectors""" 284 | 285 | # Mixed dim cross-product. 286 | x = [1, 2, 3] 287 | y = [4, 5, 6, 7] 288 | 289 | with pytest.raises(IndexError) as execinfo: 290 | z = tnp.cross(x, y) 291 | 292 | assert 'Vector has invalid dimensions' in str(execinfo.value) 293 | 294 | 295 | # Start vector dot product tests 296 | def test_dot(): 297 | """test the dot product of two mixed dimensional vectors""" 298 | 299 | # Vector dot-product. 300 | x = [1, 2, 3] 301 | y = [4, 5, 6] 302 | z = tnp.dot(x, y) 303 | 304 | assert z == 32 305 | 306 | 307 | def test_2dim_dot(): 308 | """test the dot product of two 2 dimensional vectors""" 309 | 310 | # 2 dim dot-product. 311 | x = [1, 2] 312 | y = [4, 5] 313 | z = tnp.dot(x, y) 314 | 315 | assert z == 14 316 | 317 | 318 | def test_4dim_dot(): 319 | """test the dot product of two 4 dimensional vectors""" 320 | 321 | # 4 dim dot-product. 322 | x = [1, 2, 3, 4] 323 | y = [5, 6, 7, 8] 324 | z = tnp.dot(x, y) 325 | 326 | assert z == 70 327 | 328 | 329 | def test_mdim_dot(): 330 | """test the dot product of two mixed dimensional vectors""" 331 | 332 | # Mixed dim dot-product. 333 | x = [1, 2, 3] 334 | y = [4, 5, 6, 7] 335 | 336 | with pytest.raises(IndexError) as execinfo: 337 | z = tnp.dot(x, y) 338 | 339 | assert 'Vector has invalid dimensions' in str(execinfo.value) 340 | 341 | 342 | # Start vector determinant tests 343 | def test_det(): 344 | """test calculation of the determinant of a three dimensional""" 345 | 346 | # Three dim determinant 347 | x = [5, -2, 1] 348 | y = [0, 3, -1] 349 | z = [2, 0, 7] 350 | mat = [x, y, z] 351 | 352 | a = tnp.linalg.det(mat) 353 | 354 | assert a == 103 355 | 356 | # Start simple math function tests 357 | def test_add(): 358 | """test the addition function for tinynumpy""" 359 | 360 | x = [5, -2, 1] 361 | y = [0, 3, -1] 362 | 363 | a = tnp.add(x,y) 364 | 365 | assert a == tnp.array([5, 1, 0], dtype='int64') 366 | 367 | 368 | def test_subtract(): 369 | """test the addition function for tinynumpy""" 370 | 371 | x = [5, -2, 1] 372 | y = [0, 3, -1] 373 | 374 | a = tnp.add(x,y) 375 | 376 | assert a == tnp.array([5, -5, -2], dtype='int64') 377 | 378 | 379 | def test_divide(): 380 | """test the addition function for tinynumpy""" 381 | 382 | x = [15, -12, 3] 383 | y = 3 384 | 385 | a = tnp.divide(x,y) 386 | 387 | assert a == tnp.array([5, -4, 1], dtype='int64') 388 | 389 | 390 | def test_multiply(): 391 | """test the addition function for tinynumpy""" 392 | 393 | x = [5, -2, 1] 394 | y = [0, 3, -1] 395 | 396 | a = tnp.multiply(x,y) 397 | 398 | assert a == tnp.array([0, -6, -1], dtype='int64') 399 | 400 | 401 | if __name__ == '__main__': 402 | 403 | # Run tests with or without pytest. Running with pytest creates 404 | # coverage report, running without allows PM debugging to fix bugs. 405 | if False: 406 | del sys.modules['tinynumpy'] # or coverage wont count globals 407 | pytest.main('-v -x --color=yes --cov tinynumpy --cov-config .coveragerc ' 408 | '--cov-report html %s' % repr(__file__)) 409 | # Run these lines to open coverage report 410 | #import webbrowser 411 | #webbrowser.open_new_tab(os.path.join('htmlcov', 'index.html')) 412 | 413 | else: 414 | # Collect function names 415 | test_functions = [] 416 | for line in open(__file__, 'rt').readlines(): 417 | if line.startswith('def'): 418 | name = line[3:].split('(')[0].strip() 419 | if name.startswith('test_'): 420 | test_functions.append(name) 421 | # Report 422 | print('Collected %i test functions.' % len(test_functions)) 423 | # Run 424 | print('\nRunning tests ...\n') 425 | for name in test_functions: 426 | print('Running %s ... ' % name) 427 | func = globals()[name] 428 | try: 429 | func() 430 | except runner.Skipped as err: 431 | print('SKIP:', err) 432 | except Exception: 433 | print('FAIL') 434 | raise 435 | else: 436 | print('OK') 437 | -------------------------------------------------------------------------------- /tinynumpy/tinylinalg.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Eric Allen Youngson 2 | 3 | """This module is used to implement native Python functions to replace those 4 | called from numpy, when not available""" 5 | # Written by Eric Youngson eric@scneco.com / eayoungs@gmail.com 6 | # Succession Ecological Services: Portland, Oregon 7 | 8 | class LinAlgError(Exception): 9 | pass 10 | 11 | def det(A): 12 | if len(A) == 3 and [len(vec)==3 for vec in A]: 13 | try: 14 | # http://mathworld.wolfram.com/Determinant.html 15 | det_A = (A[0][0] * A[1][1] * A[2][2] + A[0][1] * A[1][2] * 16 | A[2][0] + A[0][2] * A[1][0] * A[2][1] - (A[0][2] * 17 | A[1][1] * A[2][0] + A[0][1] * A[1][0] * A[2][2] + 18 | A[0][0] * A[1][2] * A[2][1])) 19 | except LinAlgError as e: 20 | det_A = e 21 | else: 22 | raise IndexError('Vector has invalid dimensions') 23 | return det_A 24 | -------------------------------------------------------------------------------- /tinynumpy/tinynumpy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2014, Almar Klein and Wade Brainerd 3 | # tinynumpy is distributed under the terms of the MIT License. 4 | # 5 | # Original code by Wade Brainerd (https://github.com/wadetb/tinyndarray) 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | # THE SOFTWARE. 24 | 25 | """ 26 | A lightweight, pure Python, numpy compliant ndarray class. 27 | 28 | The documenation in this module is rather compact. For details on each 29 | function, see the corresponding documentation at: 30 | http://docs.scipy.org/doc/numpy/reference/index.html Be aware that the 31 | behavior of tinynumpy may deviate in some ways from numpy, or that 32 | certain features may not be supported. 33 | """ 34 | 35 | # todo: keep track of readonly better 36 | # todo: mathematical operators 37 | # todo: more methods? 38 | # todo: logspace, meshgrid 39 | # todo: Fortran order? 40 | 41 | from __future__ import division 42 | from __future__ import absolute_import 43 | 44 | import sys 45 | import ctypes 46 | 47 | from math import sqrt 48 | from copy import copy, deepcopy 49 | from collections import Iterable 50 | import operator 51 | 52 | import tinynumpy.tinylinalg as linalg 53 | from tinynumpy.tinylinalg import LinAlgError as LinAlgError 54 | 55 | # Python 2/3 compat 56 | if sys.version_info >= (3, ): 57 | xrange = range 58 | 59 | # Define version numer 60 | __version__ = '0.0.1dev' 61 | 62 | # Define dtypes: struct name, short name, numpy name, ctypes type 63 | _dtypes = [('B', 'b1', 'bool', ctypes.c_bool), 64 | ('b', 'i1', 'int8', ctypes.c_int8), 65 | ('B', 'u1', 'uint8', ctypes.c_uint8), 66 | ('h', 'i2', 'int16', ctypes.c_int16), 67 | ('H', 'u2', 'uint16', ctypes.c_uint16), 68 | ('i', 'i4', 'int32', ctypes.c_int32), 69 | ('I', 'u4', 'uint32', ctypes.c_uint32), 70 | ('q', 'i8', 'int64', ctypes.c_int64), 71 | ('Q', 'u8', 'uint64', ctypes.c_uint64), 72 | ('f', 'f4', 'float32', ctypes.c_float), 73 | ('d', 'f8', 'float64', ctypes.c_double), 74 | ] 75 | 76 | # Inject common dtype names 77 | _known_dtypes = [d[2] for d in _dtypes] 78 | for d in _known_dtypes: 79 | globals()[d] = d 80 | 81 | newaxis = None 82 | 83 | nan = float('nan') 84 | 85 | def _convert_dtype(dtype, to='numpy'): 86 | """ Convert dtype, if could not find, pass as it was. 87 | """ 88 | if dtype is None: 89 | return dtype 90 | dtype = str(dtype) 91 | index = {'array':0, 'short':1, 'numpy':2, 'ctypes':3}[to] 92 | for dd in _dtypes: 93 | if dtype in dd: 94 | return dd[index] 95 | return dtype # Otherwise return original 96 | 97 | 98 | def _ceildiv(a, b): 99 | return -(-a // b) 100 | 101 | 102 | def _get_step(view): 103 | """ Return step to walk over array. If 1, the array is fully 104 | C-contiguous. If 0, the striding is such that one cannot 105 | step through the array. 106 | """ 107 | cont_strides = _strides_for_shape(view.shape, view.itemsize) 108 | 109 | step = view.strides[-1] // cont_strides[-1] 110 | corrected_strides = tuple([i * step for i in cont_strides]) 111 | 112 | almost_cont = view.strides == corrected_strides 113 | if almost_cont: 114 | return step 115 | else: 116 | return 0 # not contiguous 117 | 118 | 119 | def _strides_for_shape(shape, itemsize): 120 | strides = [] 121 | stride_product = 1 122 | for s in reversed(shape): 123 | strides.append(stride_product) 124 | stride_product *= s 125 | return tuple([i * itemsize for i in reversed(strides)]) 126 | 127 | 128 | def _size_for_shape(shape): 129 | stride_product = 1 130 | for s in shape: 131 | stride_product *= s 132 | return stride_product 133 | 134 | 135 | def squeeze_strides(s): 136 | """ Pop strides for singular dimensions. """ 137 | return tuple([s[0]] + [s[i] for i in range(1, len(s)) if s[i] != s[i-1]]) 138 | 139 | 140 | def _shape_from_object(obj): 141 | 142 | shape = [] 143 | # todo: make more efficient, use len() etc 144 | def _shape_from_object_r(index, element, axis): 145 | try: 146 | for i, e in enumerate(element): 147 | _shape_from_object_r(i, e, axis+1) 148 | while len(shape) <= axis: 149 | shape.append(0) 150 | l = i + 1 151 | s = shape[axis] 152 | if l > s: 153 | shape[axis] = l 154 | except TypeError: 155 | pass 156 | 157 | _shape_from_object_r(0, obj, 0) 158 | return tuple(shape) 159 | 160 | 161 | def _assign_from_object(array, obj): 162 | key = [] 163 | # todo: make more efficient, especially the try-except 164 | def _assign_from_object_r(element): 165 | try: 166 | for i, e in enumerate(element): 167 | key.append(i) 168 | _assign_from_object_r(e) 169 | key.pop() 170 | except TypeError: 171 | array[tuple(key)] = element 172 | 173 | _assign_from_object_r(obj) 174 | 175 | 176 | def _increment_mutable_key(key, shape): 177 | for axis in reversed(xrange(len(shape))): 178 | key[axis] += 1 179 | if key[axis] < shape[axis]: 180 | return True 181 | if axis == 0: 182 | return False 183 | key[axis] = 0 184 | 185 | 186 | def _key_for_index(index, shape): 187 | key = [] 188 | cumshape = [1] 189 | for i in reversed(shape): 190 | cumshape.insert(0, cumshape[0] * i) 191 | for s in cumshape[1:-1]: 192 | n = index // s 193 | key.append(n) 194 | index -= n * s 195 | key.append(index) 196 | return tuple(key) 197 | 198 | 199 | def _zerositer(n): 200 | for i in xrange(n): 201 | yield 0 202 | 203 | 204 | 205 | ## Public functions 206 | 207 | 208 | def array(obj, dtype=None, copy=True, order=None): 209 | """ array(obj, dtype=None, copy=True, order=None) 210 | 211 | Create a new array. If obj is an ndarray, and copy=False, a view 212 | of that array is returned. For details see: 213 | http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html 214 | """ 215 | dtype = _convert_dtype(dtype) 216 | 217 | if isinstance(obj, ndarray): 218 | # From existing array 219 | a = obj.view() 220 | if dtype is not None and dtype != a.dtype: 221 | a = a.astype(dtype) 222 | elif copy: 223 | a = a.copy() 224 | return a 225 | if hasattr(obj, '__array_interface__'): 226 | # From something that looks like an array, we can create 227 | # the ctypes array for this and use that as a buffer 228 | D = obj.__array_interface__ 229 | # Get dtype 230 | dtype_orig = _convert_dtype(D['typestr'][1:]) 231 | # Create array 232 | if D['strides']: 233 | itemsize = int(D['typestr'][-1]) 234 | bufsize = D['strides'][0] * D['shape'][0] // itemsize 235 | else: 236 | bufsize = _size_for_shape(D['shape']) 237 | 238 | BufType = (_convert_dtype(dtype_orig, 'ctypes') * bufsize) 239 | buffer = BufType.from_address(D['data'][0]) 240 | a = ndarray(D['shape'], dtype_orig, 241 | buffer=buffer, strides=D['strides'], order=order) 242 | # Convert or copy? 243 | if dtype is not None and dtype != dtype_orig: 244 | a = a.astype(dtype) 245 | elif copy: 246 | a = a.copy() 247 | return a 248 | else: 249 | # From some kind of iterable 250 | shape = _shape_from_object(obj) 251 | # Try to derive dtype 252 | if dtype is None: 253 | el = obj 254 | while isinstance(el, (tuple, list)) and el: 255 | el = el[0] 256 | if isinstance(el, int): 257 | dtype = 'int64' 258 | # Create array 259 | a = ndarray(shape, dtype, order=None) 260 | _assign_from_object(a, obj) 261 | return a 262 | 263 | 264 | def zeros_like(a, dtype=None, order=None): 265 | """ Return an array of zeros with the same shape and type as a given array. 266 | """ 267 | dtype = a.dtype if dtype is None else dtype 268 | return zeros(a.shape, dtype, order) 269 | 270 | 271 | def ones_like(a, dtype=None, order=None): 272 | """ Return an array of ones with the same shape and type as a given array. 273 | """ 274 | dtype = a.dtype if dtype is None else dtype 275 | return ones(a.shape, dtype, order) 276 | 277 | 278 | def empty_like(a, dtype=None, order=None): 279 | """ Return a new array with the same shape and type as a given array. 280 | """ 281 | dtype = a.dtype if dtype is None else dtype 282 | return empty(a.shape, dtype, order) 283 | 284 | 285 | def zeros(shape, dtype=None, order=None): 286 | """Return a new array of given shape and type, filled with zeros 287 | """ 288 | return empty(shape, dtype, order) 289 | 290 | 291 | def ones(shape, dtype=None, order=None): 292 | """Return a new array of given shape and type, filled with ones 293 | """ 294 | a = empty(shape, dtype, order) 295 | a.fill(1) 296 | return a 297 | 298 | 299 | def eye(size): 300 | """Return a new 2d array with given dimensions, filled with ones on the 301 | diagonal and zeros elsewhere. 302 | """ 303 | a = zeros((size,size)) 304 | for i in xrange(size): 305 | a[i,i] = 1 306 | return a 307 | 308 | 309 | def empty(shape, dtype=None, order=None): 310 | """Return a new array of given shape and type, without initializing entries 311 | """ 312 | return ndarray(shape, dtype, order=order) 313 | 314 | 315 | def arange(*args, **kwargs): 316 | """ arange([start,] stop[, step,], dtype=None) 317 | 318 | Return evenly spaced values within a given interval. 319 | 320 | Values are generated within the half-open interval ``[start, stop)`` 321 | (in other words, the interval including `start` but excluding `stop`). 322 | For integer arguments the function is equivalent to the Python built-in 323 | `range `_ function, 324 | but returns an ndarray rather than a list. 325 | 326 | When using a non-integer step, such as 0.1, the results will often not 327 | be consistent. It is better to use ``linspace`` for these cases. 328 | """ 329 | # Get dtype 330 | dtype = kwargs.pop('dtype', None) 331 | if kwargs: 332 | x = list(kwargs.keys())[0] 333 | raise TypeError('arange() got an unexpected keyword argument %r' % x) 334 | # Parse start, stop, step 335 | if len(args) == 0: 336 | raise TypeError('Required argument "start" not found') 337 | elif len(args) == 1: 338 | start, stop, step = 0, int(args[0]), 1 339 | elif len(args) == 2: 340 | start, stop, step = int(args[0]), int(args[1]), 1 341 | elif len(args) == 3: 342 | start, stop, step = int(args[0]), int(args[1]), int(args[2]) 343 | else: 344 | raise TypeError('Too many input arguments') 345 | # Init 346 | iter = xrange(start, stop, step) 347 | a = empty((len(iter),), dtype=dtype) 348 | a[:] = list(iter) 349 | return a 350 | 351 | 352 | def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None): 353 | """ linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None) 354 | 355 | Return evenly spaced numbers over a specified interval. Returns num 356 | evenly spaced samples, calculated over the interval [start, stop]. 357 | The endpoint of the interval can optionally be excluded. 358 | """ 359 | # Prepare 360 | start, stop = float(start), float(stop) 361 | ra = stop - start 362 | if endpoint: 363 | step = ra / (num-1) 364 | else: 365 | step = ra / num 366 | # Create 367 | a = empty((num,), dtype) 368 | a[:] = [start + i * step for i in xrange(num)] 369 | # Return 370 | if retstep: 371 | return a, step 372 | else: 373 | return a 374 | 375 | def add(ndarray_vec1, ndarray_vec2): 376 | c = [] 377 | for a, b in zip(ndarray_vec1, ndarray_vec2): 378 | c.append(a+b) 379 | cRay = array(c) 380 | return cRay 381 | 382 | def subtract(ndarray_vec1, ndarray_vec2): 383 | c = [] 384 | for a, b in zip(ndarray_vec1, ndarray_vec2): 385 | c.append(a-b) 386 | cRay = array(c) 387 | return cRay 388 | 389 | def multiply(ndarray_vec1, ndarray_vec2): 390 | c = [] 391 | for a, b in zip(ndarray_vec1, ndarray_vec2): 392 | c.append(a*b) 393 | cRay = array(c) 394 | return cRay 395 | 396 | def divide(ndarray_vec1, integer): 397 | c = [] 398 | for a in ndarray_vec1: 399 | c.append(a / integer) 400 | cRay = array(c) 401 | return cRay 402 | 403 | def cross(u, v): 404 | """ 405 | Return the cross product of two 2 or 3 dimensional vectors. 406 | """ 407 | 408 | uDim = len(u) 409 | vDim = len(v) 410 | 411 | uxv = [] 412 | 413 | # http://mathworld.wolfram.com/CrossProduct.html 414 | if uDim == vDim == 2: 415 | try: 416 | uxv = [u[0]*v[1]-u[1]*v[0]] 417 | except LinAlgError as e: 418 | uxv = e 419 | elif uDim == vDim == 3: 420 | try: 421 | for i in range(uDim): 422 | uxv = [u[1]*v[2]-u[2]*v[1], -(u[0]*v[2]-u[2]*v[0]), 423 | u[0]*v[1]-u[1]*v[0]] 424 | except LinAlgError as e: 425 | uxv = e 426 | else: 427 | raise IndexError('Vector has invalid dimensions') 428 | return uxv 429 | 430 | def dot(u, v): 431 | """ 432 | Return the dot product of two equal-dimensional vectors. 433 | """ 434 | 435 | uDim = len(u) 436 | vDim = len(v) 437 | 438 | # http://reference.wolfram.com/language/ref/Dot.html 439 | if uDim == vDim: 440 | try: 441 | u_dot_v = sum(map(operator.mul, u, v)) 442 | except LinAlgError as e: 443 | u_dot_v = e 444 | else: 445 | raise IndexError('Vector has invalid dimensions') 446 | return u_dot_v 447 | 448 | def reshape(X,shape): 449 | """ 450 | Returns the reshaped image of an ndarray 451 | """ 452 | assert isinstance(X, ndarray) 453 | assert isinstance(shape, tuple) or isinstance(shape, list) 454 | return X.reshape(shape) 455 | 456 | ## The class 457 | 458 | class ndarray(object): 459 | """ ndarray(shape, dtype='float64', buffer=None, offset=0, 460 | strides=None, order=None) 461 | 462 | Array class similar to numpy's ndarray, implemented in pure Python. 463 | This class can be distinguished from a real numpy array in that 464 | the repr always shows the dtype as a string, and for larger arrays 465 | (more than 100 elements) it shows a short one-line repr. 466 | 467 | An array object represents a multidimensional, homogeneous array 468 | of fixed-size items. An associated data-type property describes the 469 | format of each element in the array. 470 | 471 | Arrays should be constructed using `array`, `zeros` or `empty` (refer 472 | to the See Also section below). The parameters given here refer to 473 | a low-level method (`ndarray(...)`) for instantiating an array. 474 | 475 | Parameters 476 | ---------- 477 | shape : tuple of ints 478 | Shape of created array. 479 | dtype : data-type, optional 480 | Any object that can be interpreted as a numpy data type. 481 | buffer : object contaning data, optional 482 | Used to fill the array with data. If another ndarray is given, 483 | the underlying data is used. Can also be a ctypes.Array or any 484 | object that exposes the buffer interface. 485 | offset : int, optional 486 | Offset of array data in buffer. 487 | strides : tuple of ints, optional 488 | Strides of data in memory. 489 | order : {'C', 'F'}, optional NOT SUPPORTED 490 | Row-major or column-major order. 491 | 492 | Attributes 493 | ---------- 494 | T : ndarray 495 | Transpose of the array. In tinynumpy only supported for ndim <= 3. 496 | data : buffer 497 | The array's elements, in memory. In tinynumpy this is a ctypes array. 498 | dtype : str 499 | Describes the format of the elements in the array. In tinynumpy 500 | this is a string. 501 | flags : dict 502 | Dictionary containing information related to memory use, e.g., 503 | 'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc. 504 | flat : iterator object 505 | Flattened version of the array as an iterator. In tinynumpy 506 | the iterator cannot be indexed. 507 | size : int 508 | Number of elements in the array. 509 | itemsize : int 510 | The memory use of each array element in bytes. 511 | nbytes : int 512 | The total number of bytes required to store the array data, 513 | i.e., ``itemsize * size``. 514 | ndim : int 515 | The array's number of dimensions. 516 | shape : tuple of ints 517 | Shape of the array. 518 | strides : tuple of ints 519 | The step-size required to move from one element to the next in 520 | memory. For example, a contiguous ``(3, 4)`` array of type 521 | ``int16`` in C-order has strides ``(8, 2)``. This implies that 522 | to move from element to element in memory requires jumps of 2 bytes. 523 | To move from row-to-row, one needs to jump 8 bytes at a time 524 | (``2 * 4``). 525 | base : ndarray 526 | If the array is a view into another array, that array is its `base` 527 | (unless that array is also a view). The `base` array is where the 528 | array data is actually stored. 529 | __array_interface__ : dict 530 | Dictionary with low level array information. Used by numpy to 531 | turn into a real numpy array. Can also be used to give C libraries 532 | access to the data via ctypes. 533 | 534 | See Also 535 | -------- 536 | array : Construct an array. 537 | zeros : Create an array, each element of which is zero. 538 | empty : Create an array, but leave its allocated memory unchanged (i.e., 539 | it contains "garbage"). 540 | 541 | Notes 542 | ----- 543 | There are two modes of creating an array: 544 | 545 | 1. If `buffer` is None, then only `shape`, `dtype`, and `order` 546 | are used. 547 | 2. If `buffer` is an object exposing the buffer interface, then 548 | all keywords are interpreted. 549 | 550 | """ 551 | 552 | __slots__ = ['_dtype', '_shape', '_strides', '_itemsize', 553 | '_offset', '_base', '_data'] 554 | 555 | def __init__(self, shape, dtype='float64', buffer=None, offset=0, 556 | strides=None, order=None): 557 | # Check order 558 | if order is not None: 559 | raise RuntimeError('ndarray order parameter is not supported') 560 | # Check and set shape 561 | try : 562 | assert isinstance(shape, Iterable) 563 | shape = tuple(shape) 564 | except Exception as e: 565 | raise AssertionError('The shape must be tuple or list') 566 | assert all([isinstance(x, int) for x in shape]) 567 | self._shape = shape 568 | # Check and set dtype 569 | dtype = _convert_dtype(dtype) if (dtype is not None) else 'float64' 570 | if dtype not in _known_dtypes: 571 | raise TypeError('data type %r not understood' % dtype) 572 | self._dtype = dtype 573 | # Itemsize is directly derived from dtype 574 | self._itemsize = int(_convert_dtype(dtype, 'short')[-1]) 575 | 576 | if buffer is None: 577 | # New array 578 | self._base = None 579 | # Check and set offset and strides 580 | assert offset == 0 581 | self._offset = 0 582 | assert strides is None 583 | self._strides = _strides_for_shape(self._shape, self.itemsize) 584 | 585 | else: 586 | # Existing array 587 | if isinstance(buffer, ndarray) and buffer.base is not None: 588 | buffer = buffer.base 589 | # Keep a reference to avoid memory cleanup 590 | self._base = buffer 591 | # for ndarray we use the data property 592 | if isinstance(buffer, ndarray): 593 | buffer = buffer.data 594 | # Check and set offset 595 | assert isinstance(offset, int) and offset >= 0 596 | self._offset = offset 597 | # Check and set strides 598 | if strides is None: 599 | strides = _strides_for_shape(shape, self.itemsize) 600 | assert isinstance(strides, tuple) 601 | assert all([isinstance(x, int) for x in strides]) 602 | assert len(strides) == len(shape) 603 | self._strides = strides 604 | 605 | # Define our buffer class 606 | buffersize = self._strides[0] * self._shape[0] // self._itemsize 607 | buffersize += self._offset 608 | BufferClass = _convert_dtype(dtype, 'ctypes') * buffersize 609 | # Create buffer 610 | if buffer is None: 611 | self._data = BufferClass() 612 | elif isinstance(buffer, ctypes.Array): 613 | self._data = BufferClass.from_address(ctypes.addressof(buffer)) 614 | else: 615 | self._data = BufferClass.from_buffer(buffer) 616 | 617 | @property 618 | def __array_interface__(self): 619 | """ Allow converting to real numpy array, or pass pointer to C library 620 | http://docs.scipy.org/doc/numpy/reference/arrays.interface.html 621 | """ 622 | readonly = False 623 | # typestr 624 | typestr = '<' + _convert_dtype(self.dtype, 'short') 625 | # Pointer 626 | if isinstance(self._data, ctypes.Array): 627 | ptr = ctypes.addressof(self._data) 628 | elif hasattr(self._data, '__array_interface__'): 629 | ptr, readonly = self._data.__array_interface__['data'] 630 | elif hasattr(self._data, 'buffer_info'): # Python's array.array 631 | ptr = self._data.buffer_info()[0] 632 | elif isinstance(self._data, bytes): 633 | ptr = ctypes.cast(self._data, ctypes.c_void_p).value 634 | readonly = True 635 | else: 636 | raise TypeError('Cannot get address to underlying array data') 637 | ptr += self._offset * self.itemsize 638 | # 639 | return dict(version=3, 640 | shape=self.shape, 641 | typestr=typestr, 642 | descr=[('', typestr)], 643 | data=(ptr, readonly), 644 | strides=self.strides, 645 | #offset=self._offset, 646 | #mask=None, 647 | ) 648 | 649 | def __len__(self): 650 | return self.shape[0] 651 | 652 | def __getitem__(self, key): 653 | offset, shape, strides = self._index_helper(key) 654 | if not shape: 655 | # Return scalar 656 | return self._data[offset] 657 | else: 658 | # Return view 659 | return ndarray(shape, self.dtype, 660 | offset=offset, strides=strides, buffer=self) 661 | 662 | def __setitem__(self, key, value): 663 | 664 | # Get info for view 665 | offset, shape, strides = self._index_helper(key) 666 | 667 | # Is this easy? 668 | if not shape: 669 | self._data[offset] = value 670 | return 671 | 672 | # Create view to set data to 673 | view = ndarray(shape, self.dtype, 674 | offset=offset, strides=strides, buffer=self) 675 | 676 | # Get data to set as a list (because getting slices from ctype 677 | # arrays yield lists anyway). The list is our "contiguous array" 678 | if isinstance(value, (float, int)): 679 | value_list = [value] * view.size 680 | elif isinstance(value, (tuple, list)): 681 | value_list = value 682 | else: 683 | if not isinstance(value, ndarray): 684 | value = array(value, copy=False) 685 | value_list = value._toflatlist() 686 | 687 | # Check if size match 688 | if view.size != len(value_list): 689 | raise ValueError('Number of elements in source does not match ' 690 | 'number of elements in target.') 691 | 692 | # Assign data in most efficient way that we can. This code 693 | # looks for the largest semi-contiguous block: the block that 694 | # we can access as a 1D array with a stepsize. 695 | subviews = [view] 696 | value_index = 0 697 | count = 0 698 | while subviews: 699 | subview = subviews.pop(0) 700 | step = _get_step(subview) 701 | if step: 702 | block = value_list[value_index:value_index+subview.size] 703 | s = slice(subview._offset, 704 | subview._offset + subview.size * step, 705 | step) 706 | view._data[s] = block 707 | value_index += subview.size 708 | count += 1 709 | else: 710 | for i in range(subview.shape[0]): 711 | subviews.append(subview[i]) 712 | assert value_index == len(value_list) 713 | 714 | def __float__(self): 715 | if self.size == 1: 716 | return float(self.data[self._offset]) 717 | else: 718 | raise TypeError('Only length-1 arrays can be converted to scalar') 719 | 720 | def __int__(self): 721 | if self.size == 1: 722 | return int(self.data[self._offset]) 723 | else: 724 | raise TypeError('Only length-1 arrays can be converted to scalar') 725 | 726 | def __repr__(self): 727 | # If more than 100 elements, show short repr 728 | if self.size > 100: 729 | shapestr = 'x'.join([str(i) for i in self.shape]) 730 | return '' % (shapestr, self.dtype, id(self)) 731 | # Otherwise, try to show in nice way 732 | def _repr_r(s, axis, offset): 733 | axisindent = min(2, max(0, (self.ndim - axis - 1))) 734 | if axis < len(self.shape): 735 | s += '[' 736 | for k_index, k in enumerate(xrange(self.shape[axis])): 737 | if k_index > 0: 738 | s += ('\n ' + ' ' * axis) * axisindent 739 | offset_ = offset + k * self._strides[axis] // self.itemsize 740 | s = _repr_r(s, axis+1, offset_) 741 | if k_index < self.shape[axis] - 1: 742 | s += ', ' 743 | s += ']' 744 | else: 745 | r = repr(self.data[offset]) 746 | if '.' in r: 747 | r = ' ' + r 748 | if r.endswith('.0'): 749 | r = r[:-1] 750 | s += r 751 | return s 752 | 753 | s = _repr_r('', 0, self._offset) 754 | if self.dtype != 'float64' and self.dtype != 'int32': 755 | return "array(" + s + ", dtype='%s')" % self.dtype 756 | else: 757 | return "array(" + s + ")" 758 | 759 | def __eq__(self, other): 760 | if other.__module__.split('.')[0] == 'numpy': 761 | return other == self 762 | else: 763 | out = empty(self.shape, 'bool') 764 | out[:] = [i1==i2 for (i1, i2) in zip(self.flat, other.flat)] 765 | return out 766 | 767 | def __add__(self, other): 768 | '''classic addition 769 | ''' 770 | if (isinstance(other, int) or isinstance(other, float)) : 771 | out = empty(self.shape, self.dtype) 772 | out[:] = [dat+other for dat in self._data] 773 | return out 774 | if (isinstance(other, ndarray)): 775 | if self.shape == other.shape : 776 | out = empty(self.shape, self.dtype) 777 | out[:] = [i+j for (i,j) in zip(self.flat, other.flat)] 778 | return out 779 | 780 | def __radd__(self, other): 781 | return self.__add__(other) 782 | 783 | def __sub__(self, other): 784 | if (isinstance(other, int) or isinstance(other, float)) : 785 | out = empty(self.shape, self.dtype) 786 | out[:] = [dat-other for dat in self._data] 787 | return out 788 | if (isinstance(other, ndarray)): 789 | if self.shape == other.shape : 790 | out = empty(self.shape, self.dtype) 791 | out[:] = [i-j for (i,j) in zip(self.flat, other.flat)] 792 | return out 793 | 794 | def __rsub__(self, other): 795 | return self.__sub__(other) 796 | 797 | def __mul__(self, other): 798 | '''multiply element-wise with array or float/scalar''' 799 | if (isinstance(other, int) or isinstance(other, float)) : 800 | out = empty(self.shape, self.dtype) 801 | out[:] = [dat*other for dat in self._data] 802 | return out 803 | if (isinstance(other, ndarray)): 804 | if self.shape == other.shape : 805 | out = empty(self.shape, self.dtype) 806 | out[:] = [i*j for (i,j) in zip(self.flat, other.flat)] 807 | return out 808 | 809 | def __rmul__(self, other): 810 | return self.__mul__(other) 811 | 812 | def __div__(self, other): 813 | '''divide element-wise with array or float/scalar''' 814 | if (isinstance(other, int) or isinstance(other, float)) : 815 | if other == 0 : raise ZeroDivisionError 816 | out = empty(self.shape, self.dtype) 817 | out[:] = [dat/other for dat in self._data] 818 | return out 819 | if (isinstance(other, ndarray)): 820 | if self.shape == other.shape : 821 | out = empty(self.shape, self.dtype) 822 | out[:] = [i/j for (i,j) in zip(self.flat, other.flat)] 823 | return out 824 | else : 825 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 826 | +' versus '+str(other.shape)) 827 | 828 | def __truediv__(self, other): 829 | '''divide element-wise with array or float/scalar''' 830 | if (isinstance(other, int) or isinstance(other, float)) : 831 | if other == 0 : raise ZeroDivisionError 832 | out = empty(self.shape, self.dtype) 833 | out[:] = [dat/other for dat in self._data] 834 | return out 835 | if (isinstance(other, ndarray)): 836 | if self.shape == other.shape : 837 | out = empty(self.shape, self.dtype) 838 | out[:] = [i/j for (i,j) in zip(self.flat, other.flat)] 839 | return out 840 | else : 841 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 842 | +' versus '+str(other.shape)) 843 | 844 | def __floordiv__(self, other): 845 | '''divide element-wise with array or float/scalar''' 846 | if (isinstance(other, int) or isinstance(other, float)) : 847 | if other == 0 : raise ZeroDivisionError 848 | out = empty(self.shape, self.dtype) 849 | out[:] = [dat//other for dat in self._data] 850 | return out 851 | if (isinstance(other, ndarray)): 852 | if self.shape == other.shape : 853 | out = empty(self.shape, self.dtype) 854 | out[:] = [i//j for (i,j) in zip(self.flat, other.flat)] 855 | return out 856 | else : 857 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 858 | +' versus '+str(other.shape)) 859 | 860 | def __mod__(self, other): 861 | '''divide element-wise with array or float/scalar''' 862 | if (isinstance(other, int) or isinstance(other, float)) : 863 | out = empty(self.shape, self.dtype) 864 | out[:] = [dat%other for dat in self._data] 865 | return out 866 | if (isinstance(other, ndarray)): 867 | if self.shape == other.shape : 868 | out = empty(self.shape, self.dtype) 869 | out[:] = [i%j for (i,j) in zip(self.flat, other.flat)] 870 | return out 871 | else : 872 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 873 | +' versus '+str(other.shape)) 874 | 875 | def __pow__(self, other): 876 | '''power of two arrays element-wise (of just float power)''' 877 | if (isinstance(other, int) or isinstance(other, float)) : 878 | out = empty(self.shape, self.dtype) 879 | out[:] = [dat**other for dat in self._data] 880 | return out 881 | if (isinstance(other, ndarray)): 882 | if self.shape == other.shape : 883 | out = empty(self.shape, self.dtype) 884 | out[:] = [i**j for (i,j) in zip(self.flat, other.flat)] 885 | return out 886 | else : 887 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 888 | +' versus '+str(other.shape)) 889 | 890 | def __iadd__(self, other): 891 | '''Addition of other array or float in place with += operator 892 | ''' 893 | if (isinstance(other, int) or isinstance(other, float)) : 894 | for i in range(len(self._data)): 895 | self._data[i]+=other 896 | return self 897 | if (isinstance(other, ndarray)): 898 | if self.shape == other.shape : 899 | for i in range(len(self._data)): 900 | self._data[i]+=other._data[i] 901 | return self 902 | else : 903 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 904 | +' versus '+str(other.shape)) 905 | 906 | def __isub__(self, other): 907 | '''Addition of other array or float in place with += operator 908 | ''' 909 | if (isinstance(other, int) or isinstance(other, float)) : 910 | for i in range(len(self._data)): 911 | self._data[i]-=other 912 | return self 913 | if (isinstance(other, ndarray)): 914 | if self.shape == other.shape : 915 | for i in range(len(self._data)): 916 | self._data[i]-=other._data[i] 917 | return self 918 | else : 919 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 920 | +' versus '+str(other.shape)) 921 | 922 | def __imul__(self, other): 923 | '''multiplication woth other array or float in place with *= operator 924 | ''' 925 | if (isinstance(other, int) or isinstance(other, float)) : 926 | for i in range(len(self._data)): 927 | self._data[i]*=other 928 | return self 929 | if (isinstance(other, ndarray)): 930 | if self.shape == other.shape : 931 | for i in range(len(self._data)): 932 | self._data[i]*=other._data[i] 933 | return self 934 | else : 935 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 936 | +' versus '+str(other.shape)) 937 | 938 | def __idiv__(self, other): 939 | '''Division of other array or float in place with /= operator 940 | ''' 941 | if (isinstance(other, int) or isinstance(other, float)) : 942 | if other == 0 : raise ZeroDivisionError 943 | for i in range(len(self._data)): 944 | self._data[i]/=other 945 | return self 946 | if (isinstance(other, ndarray)): 947 | if self.shape == other.shape : 948 | for i in range(len(self._data)): 949 | self._data[i]/=other._data[i] 950 | return self 951 | else : 952 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 953 | +' versus '+str(other.shape)) 954 | 955 | def __itruediv__(self, other): 956 | '''Division of other array or float in place with /= operator 957 | ''' 958 | if (isinstance(other, int) or isinstance(other, float)) : 959 | if other == 0 : raise ZeroDivisionError 960 | for i in range(len(self._data)): 961 | self._data[i]/=other 962 | return self 963 | if (isinstance(other, ndarray)): 964 | if self.shape == other.shape : 965 | for i in range(len(self._data)): 966 | self._data[i]/=other._data[i] 967 | return self 968 | else : 969 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 970 | +' versus '+str(other.shape)) 971 | 972 | def __ifloordiv__(self, other): 973 | '''Division of other array or float in place with /= operator 974 | ''' 975 | if (isinstance(other, int) or isinstance(other, float)) : 976 | if other == 0 : raise ZeroDivisionError 977 | for i in range(len(self._data)): 978 | self._data[i]//=other 979 | return self 980 | if (isinstance(other, ndarray)): 981 | if self.shape == other.shape : 982 | for i in range(len(self._data)): 983 | self._data[i]//=other._data[i] 984 | return self 985 | else : 986 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 987 | +' versus '+str(other.shape)) 988 | 989 | def __imod__(self, other): 990 | '''mod of other array or float in place with /= operator 991 | ''' 992 | if (isinstance(other, int) or isinstance(other, float)) : 993 | if other == 0 : raise ZeroDivisionError 994 | for i in range(len(self._data)): 995 | self._data[i]%=other 996 | return self 997 | if (isinstance(other, ndarray)): 998 | if self.shape == other.shape : 999 | for i in range(len(self._data)): 1000 | self._data[i]%=other._data[i] 1001 | return self 1002 | else : 1003 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 1004 | +' versus '+str(other.shape)) 1005 | 1006 | def __imod__(self, other): 1007 | '''mod of other array or float in place with /= operator 1008 | ''' 1009 | if (isinstance(other, int) or isinstance(other, float)) : 1010 | if other == 0 : raise ZeroDivisionError 1011 | for i in range(len(self._data)): 1012 | self._data[i]%=other 1013 | return self 1014 | if (isinstance(other, ndarray)): 1015 | if self.shape == other.shape : 1016 | for i in range(len(self._data)): 1017 | self._data[i]%=other._data[i] 1018 | return self 1019 | else : 1020 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 1021 | +' versus '+str(other.shape)) 1022 | 1023 | def __ipow__(self, other): 1024 | '''mod of other array or float in place with /= operator 1025 | ''' 1026 | if (isinstance(other, int) or isinstance(other, float)) : 1027 | for i in range(len(self._data)): 1028 | self._data[i]**=other 1029 | return self 1030 | if (isinstance(other, ndarray)): 1031 | if self.shape == other.shape : 1032 | for i in range(len(self._data)): 1033 | self._data[i]**=other._data[i] 1034 | return self 1035 | else : 1036 | raise ValueError('Array sizes do not match. '+str(self.shape)\ 1037 | +' versus '+str(other.shape)) 1038 | 1039 | 1040 | ## Private helper functions 1041 | 1042 | def _index_helper(self, key): 1043 | 1044 | # Indexing spec is located at: 1045 | # http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html 1046 | 1047 | # Promote to tuple. 1048 | if not isinstance(key, tuple): 1049 | key = (key,) 1050 | 1051 | axis = 0 1052 | shape = [] 1053 | strides = [] 1054 | offset = self._offset 1055 | 1056 | for k in key: 1057 | axissize = self._shape[axis] 1058 | if isinstance(k, int): 1059 | if k >= axissize: 1060 | raise IndexError('index %i is out of bounds for axis %i ' 1061 | 'with size %s' % (k, axis, axissize)) 1062 | offset += k * self._strides[axis] // self.itemsize 1063 | axis += 1 1064 | elif isinstance(k, slice): 1065 | start, stop, step = k.indices(self.shape[axis]) 1066 | shape.append(_ceildiv(stop - start, step)) 1067 | strides.append(step * self._strides[axis]) 1068 | offset += start * self._strides[axis] // self.itemsize 1069 | axis += 1 1070 | elif k is Ellipsis: 1071 | raise TypeError("ellipsis are not supported.") 1072 | elif k is None: 1073 | shape.append(1) 1074 | stride = 1 1075 | for s in self._strides[axis:]: 1076 | stride *= s 1077 | strides.append(stride) 1078 | else: 1079 | raise TypeError("key elements must be instaces of int or slice.") 1080 | 1081 | shape.extend(self.shape[axis:]) 1082 | strides.extend(self._strides[axis:]) 1083 | 1084 | return offset, tuple(shape), tuple(strides) 1085 | 1086 | def _toflatlist(self): 1087 | value_list = [] 1088 | subviews = [self] 1089 | count = 0 1090 | while subviews: 1091 | subview = subviews.pop(0) 1092 | step = _get_step(subview) 1093 | if step: 1094 | s = slice(subview._offset, 1095 | subview._offset + subview.size * step, 1096 | step) 1097 | value_list += self._data[s] 1098 | count += 1 1099 | else: 1100 | for i in range(subview.shape[0]): 1101 | subviews.append(subview[i]) 1102 | return value_list 1103 | 1104 | ## Properties 1105 | 1106 | @property 1107 | def ndim(self): 1108 | return len(self._shape) 1109 | 1110 | @property 1111 | def size(self): 1112 | return _size_for_shape(self._shape) 1113 | 1114 | @property 1115 | def nbytes(self): 1116 | return _size_for_shape(self._shape) * self.itemsize 1117 | 1118 | def _get_shape(self): 1119 | return self._shape 1120 | 1121 | def _set_shape(self, newshape): 1122 | if newshape == self.shape: 1123 | return 1124 | if self.size != _size_for_shape(newshape): 1125 | raise ValueError('Total size of new array must be unchanged') 1126 | if _get_step(self) == 1: 1127 | # Contiguous, hooray! 1128 | self._shape = tuple(newshape) 1129 | self._strides = _strides_for_shape(self._shape, self.itemsize) 1130 | return 1131 | 1132 | # Else, try harder ... This code supports adding /removing 1133 | # singleton dimensions. Although it may sometimes be possible 1134 | # to split a dimension in two if the contiguous blocks allow 1135 | # this, we don't bother with such complex cases for now. 1136 | # Squeeze shape / strides 1137 | N = self.ndim 1138 | shape = [self.shape[i] for i in range(N) if self.shape[i] > 1] 1139 | strides = [self.strides[i] for i in range(N) if self.shape[i] > 1] 1140 | # Check if squeezed shapes match 1141 | newshape_ = [newshape[i] for i in range(len(newshape)) 1142 | if newshape[i] > 1] 1143 | if newshape_ != shape: 1144 | raise AttributeError('incompatible shape for non-contiguous array') 1145 | # Modify to make this data work in loop 1146 | strides.append(strides[-1]) 1147 | shape.append(1) 1148 | # Form new strides 1149 | i = -1 1150 | newstrides = [] 1151 | try: 1152 | for s in reversed(newshape): 1153 | if s == 1: 1154 | newstrides.append(strides[i] * shape[i]) 1155 | else: 1156 | i -= 1 1157 | newstrides.append(strides[i]) 1158 | except IndexError: 1159 | # Fail 1160 | raise AttributeError('incompatible shape for non-contiguous array') 1161 | else: 1162 | # Success 1163 | newstrides.reverse() 1164 | self._shape = tuple(newshape) 1165 | self._strides = tuple(newstrides) 1166 | 1167 | shape = property(_get_shape, _set_shape) # Python 2.5 compat (e.g. Jython) 1168 | 1169 | @property 1170 | def strides(self): 1171 | return self._strides 1172 | 1173 | @property 1174 | def dtype(self): 1175 | return self._dtype 1176 | 1177 | @property 1178 | def itemsize(self): 1179 | return self._itemsize 1180 | 1181 | @property 1182 | def base(self): 1183 | return self._base 1184 | 1185 | @property 1186 | def data(self): 1187 | return self._data 1188 | 1189 | @property 1190 | def flat(self): 1191 | subviews = [self] 1192 | count = 0 1193 | while subviews: 1194 | subview = subviews.pop(0) 1195 | step = _get_step(subview) 1196 | if step: 1197 | s = slice(subview._offset, 1198 | subview._offset + subview.size * step, 1199 | step) 1200 | for i in self._data[s]: 1201 | yield i 1202 | else: 1203 | for i in range(subview.shape[0]): 1204 | subviews.append(subview[i]) 1205 | 1206 | @property 1207 | def T(self): 1208 | if self.ndim < 2: 1209 | return self 1210 | else: 1211 | return self.transpose() 1212 | 1213 | @property 1214 | def flags(self): 1215 | 1216 | c_cont = _get_step(self) == 1 1217 | return dict(C_CONTIGUOUS=c_cont, 1218 | F_CONTIGUOUS=(c_cont and self.ndim < 2), 1219 | OWNDATA=(self._base is None), 1220 | WRITEABLE=True, # todo: fix this 1221 | ALIGNED=c_cont, # todo: different from contiguous? 1222 | UPDATEIFCOPY=False, # We don't support this feature 1223 | ) 1224 | 1225 | ## Methods - managemenet 1226 | 1227 | def fill(self, value): 1228 | assert isinstance(value, (int, float)) 1229 | self[:] = value 1230 | 1231 | def clip(self, a_min, a_max, out=None): 1232 | if out is None: 1233 | out = empty(self.shape, self.dtype) 1234 | L = self._toflatlist() 1235 | L = [min(a_max, max(a_min, x)) for x in L] 1236 | out[:] = L 1237 | return out 1238 | 1239 | def copy(self): 1240 | out = empty(self.shape, self.dtype) 1241 | out[:] = self 1242 | return out 1243 | 1244 | def flatten(self): 1245 | out = empty((self.size,), self.dtype) 1246 | out[:] = self 1247 | return out 1248 | 1249 | def ravel(self): 1250 | return self.reshape((self.size, )) 1251 | 1252 | def repeat(self, repeats, axis=None): 1253 | if axis: 1254 | raise (TypeError, "axis argument is not supported") 1255 | out = empty((self.size * repeats,), self.dtype) 1256 | for i in range(repeats): 1257 | out[i*self.size:(i+1)*self.size] = self 1258 | return out 1259 | 1260 | def reshape(self, newshape): 1261 | out = self.view() 1262 | try: 1263 | out.shape = newshape 1264 | except AttributeError: 1265 | out = self.copy() 1266 | out.shape = newshape 1267 | return out 1268 | 1269 | def transpose(self): 1270 | # Numpy returns a view, but we cannot do that since we do not 1271 | # support Fortran ordering 1272 | ndim = self.ndim 1273 | if ndim < 2: 1274 | return self.view() 1275 | shape = self.shape[::-1] 1276 | out = empty(shape, self.dtype) 1277 | # 1278 | if ndim == 2: 1279 | for i in xrange(self.shape[0]): 1280 | out[:, i] = self[i, :] 1281 | elif ndim == 3: 1282 | for i in xrange(self.shape[0]): 1283 | for j in xrange(self.shape[1]): 1284 | out[:, j, i] = self[i, j, :] 1285 | else: 1286 | raise ValueError('Tinynumpy supports transpose up to ndim=3') 1287 | return out 1288 | 1289 | def astype(self, dtype): 1290 | out = empty(self.shape, dtype) 1291 | out[:] = self 1292 | 1293 | def view(self, dtype=None, type=None): 1294 | if dtype is None: 1295 | dtype = self.dtype 1296 | if dtype == self.dtype: 1297 | return ndarray(self.shape, dtype, buffer=self, 1298 | offset=self._offset, strides=self.strides) 1299 | elif self.ndim == 1: 1300 | itemsize = int(_convert_dtype(dtype, 'short')[-1]) 1301 | size = self.nbytes // itemsize 1302 | offsetinbytes = self._offset * self.itemsize 1303 | offset = offsetinbytes // itemsize 1304 | return ndarray((size, ), dtype, buffer=self, offset=offset) 1305 | else: 1306 | raise ValueError('new type not compatible with array.') 1307 | 1308 | ## Methods - statistics 1309 | 1310 | # We use the self.flat generator here. self._toflatlist() would be 1311 | # faster, but it might take up significantly more memory. 1312 | 1313 | def all(self, axis=None): 1314 | if axis: 1315 | raise (TypeError, "axis argument is not supported") 1316 | return all(self.flat) 1317 | 1318 | def any(self, axis=None): 1319 | if axis: 1320 | raise (TypeError, "axis argument is not supported") 1321 | return any(self.flat) 1322 | 1323 | def min(self, axis=None): 1324 | if axis: 1325 | raise (TypeError, "axis argument is not supported") 1326 | return min(self.flat) 1327 | 1328 | def max(self, axis=None): 1329 | if axis: 1330 | raise (TypeError, "axis argument is not supported") 1331 | return max(self.flat) 1332 | #return max(self._toflatlist()) # almost twice as fast 1333 | 1334 | def sum(self, axis=None): 1335 | if axis: 1336 | raise (TypeError, "axis argument is not supported") 1337 | return sum(self.flat) 1338 | 1339 | def prod(self, axis=None): 1340 | if axis: 1341 | raise (TypeError, "axis argument is not supported") 1342 | p = 1.0 1343 | for i in self.flat: 1344 | p *= float(i) 1345 | return p 1346 | 1347 | def ptp(self, axis=None): 1348 | if axis: 1349 | raise (TypeError, "axis argument is not supported") 1350 | mn = self.data[self._offset] 1351 | mx = mn 1352 | for i in self.flat: 1353 | if i > mx: 1354 | mx = i 1355 | if i < mn: 1356 | mn = i 1357 | return mx - mn 1358 | 1359 | def mean(self, axis=None): 1360 | if axis: 1361 | raise (TypeError, "axis argument is not supported") 1362 | return self.sum() / self.size 1363 | 1364 | def argmax(self, axis=None): 1365 | if axis: 1366 | raise (TypeError, "axis argument is not supported") 1367 | r = self.data[self._offset] 1368 | r_index = 0 1369 | for i_index, i in enumerate(self.flat): 1370 | if i > r: 1371 | r = i 1372 | r_index = i_index 1373 | return r_index 1374 | 1375 | def argmin(self, axis=None): 1376 | if axis: 1377 | raise (TypeError, "axis argument is not supported") 1378 | r = self.data[self._offset] 1379 | r_index = 0 1380 | for i_index, i in enumerate(self.flat): 1381 | if i < r: 1382 | r = i 1383 | r_index = i_index 1384 | return r_index 1385 | 1386 | def cumprod(self, axis=None, out=None): 1387 | if axis: 1388 | raise (TypeError, "axis argument is not supported") 1389 | if out is None: 1390 | out = empty((self.size,), self.dtype) 1391 | p = 1 1392 | L = [] 1393 | for x in self.flat: 1394 | p *= x 1395 | L.append(p) 1396 | out[:] = L 1397 | return out 1398 | 1399 | def cumsum(self, axis=None, out=None): 1400 | if axis: 1401 | raise (TypeError, "axis argument is not supported") 1402 | if out is None: 1403 | out = empty((self.size,), self.dtype) 1404 | p = 0 1405 | L = [] 1406 | for x in self.flat: 1407 | p += x 1408 | L.append(p) 1409 | out[:] = L 1410 | return out 1411 | 1412 | def var(self, axis=None): 1413 | if axis: 1414 | raise (TypeError, "axis argument is not supported") 1415 | m = self.mean() 1416 | acc = 0 1417 | for x in self.flat: 1418 | acc += abs(x - m) ** 2 1419 | return acc / self.size 1420 | 1421 | def std(self, axis=None): 1422 | return sqrt(self.var(axis)) 1423 | 1424 | def argwhere(self, val): 1425 | #assumes that list has only values of same dtype 1426 | 1427 | idx = [i for i, e in enumerate(self.flat) if e == val] 1428 | keys = [list(_key_for_index(i, self.shape)) for i in idx] 1429 | return keys 1430 | 1431 | def tolist(self): 1432 | ''' 1433 | Returns the ndarray as a comprehensive list 1434 | ''' 1435 | shp = list(self.shape).copy() 1436 | jump = self.size//shp[-1] 1437 | n_comp = 0 #comprehension depth 1438 | comp = list(self._data).copy() 1439 | while n_comp < len(self.shape)-1 : 1440 | comp = [comp[i*shp[-1]:i*shp[-1]+shp[-1]] for i in range(jump)] 1441 | shp.pop() 1442 | jump = len(comp)//shp[-1] 1443 | n_comp +=1 1444 | return comp 1445 | 1446 | 1447 | 1448 | 1449 | class nditer: 1450 | def __init__(self, array): 1451 | self.array = array 1452 | self.key = [0] * len(self.array.shape) 1453 | 1454 | def __iter__(self): 1455 | return self 1456 | 1457 | def __len__(self): 1458 | return _size_for_shape(self.array.shape) 1459 | 1460 | def __getitem__(self, index): 1461 | key = _key_for_index(index, self.array.shape) 1462 | return self.array[key] 1463 | 1464 | def __next__(self): 1465 | if self.key is None: 1466 | raise StopIteration 1467 | value = self.array[tuple(self.key)] 1468 | if not _increment_mutable_key(self.key, self.array.shape): 1469 | self.key = None 1470 | return value 1471 | 1472 | def next(self): 1473 | return self.__next__() --------------------------------------------------------------------------------