├── .gitignore ├── README.md ├── doc ├── Makefile ├── conf.py ├── extendingufunc.rst ├── index.rst ├── make.bat ├── newgufunc.rst ├── newufunc.rst └── ufuncs-draft.txt ├── npytypes ├── __init__.py ├── quaternion │ ├── .gitignore │ ├── README │ ├── __init__.py │ ├── info.py │ ├── numpy_quaternion.c │ ├── quaternion.c │ └── quaternion.h └── rational │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── __init__.py │ ├── info.py │ ├── rational.c │ └── test_rational.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor temporary/working/backup files # 2 | ######################################### 3 | .#* 4 | [#]*# 5 | *~ 6 | *$ 7 | *.bak 8 | *.diff 9 | *.org 10 | .project 11 | *.rej 12 | .settings/ 13 | .*.sw[nop] 14 | .sw[nop] 15 | *.tmp 16 | 17 | # Compiled source # 18 | ################### 19 | *.a 20 | *.com 21 | *.class 22 | *.dll 23 | *.exe 24 | *.o 25 | *.py[ocd] 26 | *.so 27 | 28 | # Packages # 29 | ############ 30 | # it's better to unpack these files and commit the raw source 31 | # git has its own built in compression methods 32 | *.7z 33 | *.bz2 34 | *.bzip2 35 | *.dmg 36 | *.gz 37 | *.iso 38 | *.jar 39 | *.rar 40 | *.tar 41 | *.tbz2 42 | *.tgz 43 | *.zip 44 | 45 | # Python files # 46 | ################ 47 | # setup.py working directory 48 | build 49 | # sphinx build directory 50 | _build 51 | # setup.py dist directory 52 | dist 53 | doc/build 54 | doc/cdoc/build 55 | # Egg metadata 56 | *.egg-info 57 | # The shelf plugin uses this dir 58 | ./.shelf 59 | 60 | # Logs and databases # 61 | ###################### 62 | *.log 63 | *.sql 64 | *.sqlite 65 | 66 | # Patches # 67 | ########### 68 | *.patch 69 | *.diff 70 | 71 | # OS generated files # 72 | ###################### 73 | .gdb_history 74 | .DS_Store* 75 | ehthumbs.db 76 | Icon? 77 | Thumbs.db 78 | 79 | # Things specific to this project # 80 | ################################### 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: These examples are for legacy DTypes :warning: 2 | ======================================================== 3 | 4 | NumPy has new-style DTypes with additional features and improved consistency. As of NumPy 1.24, these still require use of unstable/experimental API and are not quite production ready. However, projects planning in the mid- or long-term are recommended to use the new API, and we are actively working on finalizing them. 5 | 6 | Because of this, no more improvements are expectd here and this repository is now archived. 7 | 8 | Examples for new style DTypes can be found in https://github.com/numpy/numpy-user-dtypes 9 | 10 | numpy-dtypes 11 | ============ 12 | 13 | Repository for the development of user dtypes for numpy. 14 | -------------------------------------------------------------------------------- /doc/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/UFuncsandCustomDTypes.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/UFuncsandCustomDTypes.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/UFuncsandCustomDTypes" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/UFuncsandCustomDTypes" 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 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # UFuncs and Custom DTypes documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jun 27 10:58:14 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Custom NumPy Data Types' 44 | copyright = u'2012, Jay Bourque' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'UFuncsandCustomDTypesdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'UFuncsandCustomDTypes.tex', u'UFuncs and Custom DTypes Documentation', 187 | u'Jay Bourque', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'ufuncsandcustomdtypes', u'UFuncs and Custom DTypes Documentation', 217 | [u'Jay Bourque'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'UFuncsandCustomDTypes', u'UFuncs and Custom DTypes Documentation', 231 | u'Jay Bourque', 'UFuncsandCustomDTypes', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /doc/extendingufunc.rst: -------------------------------------------------------------------------------- 1 | Extending Existing UFunc for Custom DType 2 | ----------------------------------------- 3 | 4 | .. highlight:: c 5 | 6 | The first example shows how to extend the existing 'add' ufunc for the Rational 7 | dtype. The add ufunc is extended for Rational dtypes using Rational's 8 | 'rational_add' function which takes two rational numbers and returns a rational 9 | object representing the sum of those two rational numbers:: 10 | 11 | static NPY_INLINE rational 12 | rational_add(rational x, rational y) { 13 | // d(y) retrieves denominator for y 14 | return make_rational_fast((int64_t)x.n*d(y) + (int64_t)d(x)*y.n, (int64_t)d(x)*d(y)); 15 | } 16 | 17 | 1. A 1-d loop function is created which loops over each pair of elements from two 18 | 1-d arrays of rationals and calls rational_add for each pair:: 19 | 20 | void rational_ufunc_add(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { 21 | npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; 22 | char *i0 = args[0], *i1 = args[1], *o = args[2]; 23 | int k; 24 | for (k = 0; k < n; k++) { 25 | rational x = *(rational*)i0; 26 | rational y = *(rational*)i1; 27 | *(rational*)o = rational_add(x,y); 28 | i0 += is0; i1 += is1; o += os; 29 | } 30 | } 31 | 32 | The loop function must have the exact signature as above. The function parameters are: 33 | 34 | - char \**args - array of pointers to the actual data for the input and output arrays. 35 | In this example there are three pointers: two pointers pointing to blocks of 36 | memory for the two input arrays, and one pointer pointing to the block of 37 | memory for the output array. The result of rational_add should be stored in 38 | the output array. 39 | 40 | - dimensions - a pointer to the size of the dimension over which this function is looping 41 | 42 | - steps - a pointer to the number of bytes to jump to get to the next element in this 43 | dimension for each of the input and output arguments 44 | 45 | - data - arbitrary data (extra arguments, function names, etc.) that can be stored with 46 | the ufunc and will be passed in when it is called 47 | 48 | The Rational dtype has an example of a C macro which can be used to generate create the 49 | above function for different rational ufuncs:: 50 | 51 | #define BINARY_UFUNC(name,intype0,intype1,outtype,exp) \ 52 | void name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 53 | npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; \ 54 | char *i0 = args[0], *i1 = args[1], *o = args[2]; \ 55 | int k; \ 56 | for (k = 0; k < n; k++) { \ 57 | intype0 x = *(intype0*)i0; \ 58 | intype1 y = *(intype1*)i1; \ 59 | *(outtype*)o = exp; \ 60 | i0 += is0; i1 += is1; o += os; \ 61 | } \ 62 | } 63 | 64 | #define RATIONAL_BINARY_UFUNC(name, type, exp) BINARY_UFUNC(rational_ufunc_##name, rational, rational, type, exp) 65 | 66 | which can be used like so:: 67 | 68 | RATIONAL_BINARY_UFUNC(add, rational, rational_add(x,y)) 69 | 70 | with the following arguments: 71 | 72 | - name suffix of 1-d loop function (the generated loop function will have the name 'rational_ufunc_' 73 | - output type 74 | - expression to calculate the output value for each pair of input elements. 75 | In this example the expression is a call to the function rational_add. 76 | 77 | | 78 | 79 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, a 80 | PyUFuncObject is obtained for the existing 'add' ufunc in the numpy module:: 81 | 82 | PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,"add"); 83 | 84 | | 85 | 86 | 3. The 1-d loop function is registered using the PyUFuncObject obtained in step 2:: 87 | 88 | int types[] = {npy_rational,npy_rational,npy_rational}; 89 | 90 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_add,types,0) < 0) { 91 | return; 92 | } 93 | 94 | The function parameters are: 95 | 96 | - pointer to PyUFuncObject obtained in step 2 97 | - custom rational dtype id (obtained when dtype is registered with call to PyArray_RegisterDataType) 98 | - 1-d loop function 99 | - array of input and output type ids (in this case two input rational types and one 100 | output rational type) 101 | - pointer to arbitrary data that will be passed to 1-d loop function 102 | 103 | | 104 | 105 | 4. Steps 2-3 can also be accomplished by defining a c macro similar to the one 106 | provided with Rational:: 107 | 108 | #define REGISTER_UFUNC(name,...) { \ 109 | PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,#name); \ 110 | if (!ufunc) { \ 111 | return; \ 112 | } \ 113 | int _types[] = __VA_ARGS__; \ 114 | if (sizeof(_types)/sizeof(int)!=ufunc->nargs) { \ 115 | PyErr_Format(PyExc_AssertionError,"ufunc %s takes %d arguments, our loop takes %ld",#name,ufunc->nargs,sizeof(_types)/sizeof(int)); \ 116 | return; \ 117 | } \ 118 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,_types,0)<0) { \ 119 | return; \ 120 | } \ 121 | } 122 | #define REGISTER_UFUNC_BINARY_RATIONAL(name) REGISTER_UFUNC(name,{npy_rational,npy_rational,npy_rational}) 123 | 124 | and calling it like so:: 125 | 126 | REGISTER_UFUNC_BINARY_RATIONAL(add) 127 | 128 | | 129 | 130 | An example of using the add ufunc with the Rational dtype:: 131 | 132 | In [1]: import numpy as np 133 | 134 | In [2]: from rational import rational 135 | 136 | In [3]: r1=rational(1,2) 137 | 138 | In [4]: r2=rational(3,4) 139 | 140 | In [5]: r3=rational(5,6) 141 | 142 | In [6]: r4=rational(7,8) 143 | 144 | In [7]: a=np.array([r1,r2], dtype=rational) 145 | 146 | In [8]: b=np.array([r3,r4], dtype=rational) 147 | 148 | In [9]: np.add(a,b) 149 | Out[9]: array([4/3, 13/8], dtype=rational) 150 | 151 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. UFuncs and Custom DTypes documentation master file, created by 2 | sphinx-quickstart on Wed Jun 27 10:58:14 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ======================= 7 | Custom NumPy Data Types 8 | ======================= 9 | 10 | Writing Your Own UFunc and Generalized UFunc For a Custom Data Type 11 | =================================================================== 12 | 13 | The following are several examples for creating your own ufuncs and generalized 14 | ufuncs for custom data types. All the examples use the custom dtype 'Rational' 15 | located in the numpy-dtypes repository on github.com 16 | 17 | .. toctree:: 18 | :maxdepth: 2 19 | 20 | extendingufunc 21 | newufunc 22 | newgufunc 23 | 24 | 25 | -------------------------------------------------------------------------------- /doc/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%\qthelp\UFuncsandCustomDTypes.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\UFuncsandCustomDTypes.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 | -------------------------------------------------------------------------------- /doc/newgufunc.rst: -------------------------------------------------------------------------------- 1 | Creating New Generalized UFunc for Custom DType 2 | ----------------------------------------------- 3 | 4 | .. highlight:: c 5 | 6 | The next example shows how to create a new generalized ufunc for the Rational dtype. 7 | The gufunc example creates a gufunc 'matrix_multiply' which loops over a pair of 8 | vectors or matrices and performs a matrix multiply on each pair of matrix elements. 9 | 10 | 11 | 1. A loop function is created to loop through the outer or loop dimensions, performing a 12 | matrix multiply operation on the core dimensions for each loop:: 13 | 14 | static void 15 | rational_gufunc_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) 16 | { 17 | // outer dimensions counter 18 | npy_intp N_; 19 | 20 | // length of flattened outer dimensions 21 | npy_intp dN = dimensions[0]; 22 | 23 | // striding over flattened outer dimensions for input and output arrays 24 | npy_intp s0 = steps[0]; 25 | npy_intp s1 = steps[1]; 26 | npy_intp s2 = steps[2]; 27 | 28 | // loop through outer dimensions, performing matrix multiply on core dimensions for each loop 29 | for (N_ = 0; N_ < dN; N_++, args[0] += s0, args[1] += s1, args[2] += s2) { 30 | rational_matrix_multiply(args, dimensions+1, steps+3); 31 | } 32 | } 33 | 34 | If the input matrices have more than one outer dimension, the outer dimensions are flattened from the 35 | perspective of the loop function. 36 | 37 | The matrix multiply function:: 38 | 39 | static NPY_INLINE void 40 | rational_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps) 41 | { 42 | // pointers to data for input and output arrays 43 | char *ip1 = args[0]; 44 | char *ip2 = args[1]; 45 | char *op = args[2]; 46 | 47 | // lengths of core dimensions 48 | npy_intp dm = dimensions[0]; 49 | npy_intp dn = dimensions[1]; 50 | npy_intp dp = dimensions[2]; 51 | 52 | // striding over core dimensions 53 | npy_intp is1_m = steps[0]; 54 | npy_intp is1_n = steps[1]; 55 | npy_intp is2_n = steps[2]; 56 | npy_intp is2_p = steps[3]; 57 | npy_intp os_m = steps[4]; 58 | npy_intp os_p = steps[5]; 59 | 60 | // core dimensions counters 61 | npy_intp m, n, p; 62 | 63 | // calculate dot product for each row/column vector pair 64 | for (m = 0; m < dm; m++) { 65 | for (p = 0; p < dp; p++) { 66 | npyrational_dot(ip1, is1_n, ip2, is2_n, op, dn, NULL); 67 | 68 | ip2 += is2_p; 69 | op += os_p; 70 | } 71 | 72 | ip2 -= is2_p * p; 73 | op -= os_p * p; 74 | 75 | ip1 += is1_m; 76 | op += os_m; 77 | } 78 | } 79 | 80 | | 81 | 82 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, 83 | a new PyUFuncObject is created for the new 'matrix_multiply' generalized ufunc using the 84 | PyUFunc_FromFuncAndDataAndSignature function:: 85 | 86 | PyObject* gufunc = PyUFunc_FromFuncAndDataAndSignature(0,0,0,0,2,1,PyUFunc_None,(char*)"matrix_multiply",(char*)"return result of multiplying two matrices of rationals",0,"(m,n),(n,p)->(m,p)"); 87 | 88 | This is identical to the PyUFunc_FromFuncAndData function used to create a ufunc object in the examples above, 89 | with the addition of a ufunc signature describing the core dimensions of the input and output arrays. In this 90 | example, the generalized ufunc operates on pairs of matrices with dimensions (m,n) and (n,p), producing an 91 | output matrix of dimensions (m,p). 92 | 93 | 3. The loop function is registered using the loop function and the PyUFuncObject created in step 2:: 94 | 95 | int types2[3] = {npy_rational,npy_rational,npy_rational}; 96 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)gufunc,npy_rational,rational_gufunc_matrix_multiply,types2,0) < 0) { 97 | return; 98 | } 99 | 100 | 4. Finally, a function called 'matrix_multiply' is added to the rational module which 101 | will call the numerator ufunc:: 102 | 103 | PyModule_AddObject(m,"matrix_multiply",(PyObject*)gufunc); 104 | 105 | | 106 | 107 | An example of using the add ufunc with the Rational dtype:: 108 | 109 | In [1]: import numpy as np 110 | 111 | In [2]: from rational import rational, matrix_multiply 112 | 113 | In [3]: r1=rational(1,2) 114 | 115 | In [4]: r2=rational(3,4) 116 | 117 | In [5]: r3=rational(5,6) 118 | 119 | In [6]: r4=rational(7,8) 120 | 121 | In [7]: a=np.array([[[[r1,r2],[r3,r4]],[[r1,r2],[r3,r4]]], [[[r1,r2],[r3,r4]],[[r1,r2],[r3,r4]]]], dtype=rational) 122 | 123 | In [8]: b=np.array([[[[r3,r4],[r1,r2]],[[r3,r4],[r1,r2]]], [[[r3,r4],[r1,r2]],[[r3,r4],[r1,r2]]]], dtype=rational) 124 | 125 | In [9]: matrix_multiply(a,b) 126 | Out[9]: 127 | array([[[[19/24, 1], 128 | [163/144, 133/96]], 129 | 130 | [[19/24, 1], 131 | [163/144, 133/96]]], 132 | 133 | 134 | [[[19/24, 1], 135 | [163/144, 133/96]], 136 | 137 | [[19/24, 1], 138 | [163/144, 133/96]]]], dtype=rational) 139 | 140 | -------------------------------------------------------------------------------- /doc/newufunc.rst: -------------------------------------------------------------------------------- 1 | Creating New UFunc for Custom DType 2 | ----------------------------------- 3 | 4 | .. highlight:: c 5 | 6 | The next example shows how to create a new ufunc for the Rational dtype. The ufunc 7 | example creates a ufunc called 'numerator' which generates an array of numerator 8 | values based rational numbers from the input array. 9 | 10 | 1. A 1-d loop function is created as before which takes the numerator value from 11 | each element of the input array and stores it in the output array:: 12 | 13 | void rational_ufunc_numerator(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { 14 | npy_intp is = steps[0], os = steps[1], n = *dimensions; 15 | char *i = args[0], *o = args[1]; 16 | int k; 17 | for (k = 0; k < n; k++) { 18 | rational x = *(rational*)i; 19 | *(int64_t*)o = x.n; 20 | i += is; o += os; 21 | } 22 | } 23 | 24 | You can also use the c macro provided in Rational for generating the above function:: 25 | 26 | #define UNARY_UFUNC(name,type,exp) \ 27 | void rational_ufunc_##name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 28 | npy_intp is = steps[0], os = steps[1], n = *dimensions; \ 29 | char *i = args[0], *o = args[1]; \ 30 | int k; \ 31 | for (k = 0; k < n; k++) { \ 32 | rational x = *(rational*)i; \ 33 | *(type*)o = exp; \ 34 | i += is; o += os; \ 35 | } \ 36 | } 37 | 38 | and call it like so:: 39 | 40 | UNARY_UFUNC(numerator,int64_t,x.n) 41 | 42 | | 43 | 44 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, 45 | a new PyUFuncObject is created for the new 'numerator' ufunc using the 46 | PyUFunc_FromFuncAndData function:: 47 | 48 | PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)"numerator",(char*)"rational number numerator",0); 49 | 50 | In this use case, the first four parameters should be set to zero since we're 51 | creating a new ufunc without support for existing dtypes. The rest of the 52 | parameters: 53 | 54 | - number of inputs to function that the loop function calls for each pair of elements 55 | - number of outputs of loop function 56 | - name of the ufunc 57 | - documentation string describing the ufunc 58 | - unused; present for backwards compatibility 59 | 60 | | 61 | 62 | 3. The 1-d loop function is registered using the loop function and the 63 | PyUFuncObject created in step 2:: 64 | 65 | int _types[] = {npy_rational,NPY_INT64}; 66 | 67 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_numerator,_types,0)<0) { 68 | return; 69 | } 70 | 71 | | 72 | 73 | 4. Finally, a function called 'numerator' is added to the rational module which 74 | will call the numerator ufunc:: 75 | 76 | PyModule_AddObject(m,"numerator",(PyObject*)ufunc); 77 | 78 | | 79 | 80 | 5. Steps 2-4 can also be accomplished by using a c macro similar to the one 81 | provided with Rational:: 82 | 83 | #define NEW_UNARY_UFUNC(name,type,doc) { \ 84 | PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)#name,(char*)doc,0); \ 85 | if (!ufunc) { \ 86 | return; \ 87 | } \ 88 | int types[2] = {npy_rational,type}; \ 89 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,types,0)<0) { \ 90 | return; \ 91 | } \ 92 | PyModule_AddObject(m,#name,(PyObject*)ufunc); \ 93 | } 94 | 95 | and calling it like so:: 96 | 97 | NEW_UNARY_UFUNC(numerator,NPY_INT64,"rational number numerator"); 98 | 99 | | 100 | 101 | An example of using the numerator ufunc with the Rational dtype:: 102 | 103 | In [1]: import numpy as np 104 | 105 | In [2]: from rational import rational, numerator 106 | 107 | In [3]: r1=rational(1,2) 108 | 109 | In [4]: r2=rational(3,4) 110 | 111 | In [5]: r3=rational(5,6) 112 | 113 | In [6]: r4=rational(7,8) 114 | 115 | In [7]: a=np.array([r1,r2,r3,r4], dtype=rational) 116 | 117 | In [8]: numerator(a) 118 | Out[8]: array([1, 3, 5, 7]) 119 | 120 | -------------------------------------------------------------------------------- /doc/ufuncs-draft.txt: -------------------------------------------------------------------------------- 1 | Writing Your Own UFunc and Generalized UFunc For a Custom Data Type 2 | 3 | The following are several examples for creating your own ufuncs and generalized 4 | ufuncs for custom data types. All the examples use the custom dtype 'Rational' 5 | located in the numpy-dtypes repository on github.com 6 | 7 | 8 | Extending Existing UFunc for Custom DType 9 | 10 | The first example shows how to extend the existing 'add' ufunc for the Rational 11 | dtype. The add ufunc is extended for Rational dtypes using Rational's 12 | 'rational_add' function which takes two rational numbers and returns a rational 13 | object representing the sum of those two rational numbers: 14 | 15 | static NPY_INLINE rational 16 | rational_add(rational x, rational y) { 17 | // d(y) retrieves denominator for y 18 | return make_rational_fast((int64_t)x.n*d(y) + (int64_t)d(x)*y.n, (int64_t)d(x)*d(y)); 19 | } 20 | 21 | 22 | 1. A 1-d loop function is created which loops over each pair of elements from two 23 | 1-d arrays of rationals and calls rational_add for each pair: 24 | 25 | void rational_ufunc_add(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { 26 | npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; 27 | char *i0 = args[0], *i1 = args[1], *o = args[2]; 28 | int k; 29 | for (k = 0; k < n; k++) { 30 | rational x = *(rational*)i0; 31 | rational y = *(rational*)i1; 32 | *(rational*)o = rational_add(x,y); 33 | i0 += is0; i1 += is1; o += os; 34 | } 35 | } 36 | 37 | The loop function must have the exact signature as above. The function parameters are: 38 | 39 | char **args - array of pointers to the actual data for the input and output arrays. 40 | In this example there are three pointers: two pointers pointing to blocks of 41 | memory for the two input arrays, and one pointer pointing to the block of 42 | memory for the output array. The result of rational_add should be stored in 43 | the output array. 44 | 45 | dimensions - a pointer to the size of the dimension over which this function is looping 46 | 47 | steps - a pointer to the number of bytes to jump to get to the next element in this 48 | dimension for each of the input and output arguments 49 | 50 | data - arbitrary data (extra arguments, function names, etc.) that can be stored with 51 | the ufunc and will be passed in when it is called 52 | 53 | The Rational dtype has an example of a C macro which can be used to generate create the 54 | above function for different rational ufuncs: 55 | 56 | #define BINARY_UFUNC(name,intype0,intype1,outtype,exp) \ 57 | void name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 58 | npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; \ 59 | char *i0 = args[0], *i1 = args[1], *o = args[2]; \ 60 | int k; \ 61 | for (k = 0; k < n; k++) { \ 62 | intype0 x = *(intype0*)i0; \ 63 | intype1 y = *(intype1*)i1; \ 64 | *(outtype*)o = exp; \ 65 | i0 += is0; i1 += is1; o += os; \ 66 | } \ 67 | } 68 | 69 | #define RATIONAL_BINARY_UFUNC(name, type, exp) BINARY_UFUNC(rational_ufunc_##name, rational, rational, type, exp) 70 | 71 | which can be used like so: 72 | 73 | RATIONAL_BINARY_UFUNC(add, rational, rational_add(x,y)) 74 | 75 | with the following arguments: 76 | - name suffix of 1-d loop function (the generated loop function will have the name 77 | rational_ufunc_' 78 | - output type 79 | - expression to calculate the output value for each pair of input elements. In this 80 | example the expression is a call to the function rational_add. 81 | 82 | 83 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, a 84 | PyUFuncObject is obtained for the existing 'add' ufunc in the numpy module: 85 | 86 | PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,"add"); 87 | 88 | 89 | 3. The 1-d loop function is registered using the PyUFuncObject obtained in step 2: 90 | 91 | int types[] = {npy_rational,npy_rational,npy_rational}; 92 | 93 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_add,types,0) < 0) { 94 | return; 95 | } 96 | 97 | The function parameters are: 98 | - pointer to PyUFuncObject obtained in step 2 99 | - custom rational dtype id (obtained when dtype is registered with call to PyArray_RegisterDataType) 100 | - 1-d loop function 101 | - array of input and output type ids (in this case two input rational types and one 102 | output rational type) 103 | - pointer to arbitrary data that will be passed to 1-d loop function 104 | 105 | 106 | 4. Steps 2-3 can also be accomplished by using a c MACRO similar to the one 107 | provided with Rational: 108 | 109 | #define REGISTER_UFUNC(name,...) { \ 110 | PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,#name); \ 111 | if (!ufunc) { \ 112 | return; \ 113 | } \ 114 | int _types[] = __VA_ARGS__; \ 115 | if (sizeof(_types)/sizeof(int)!=ufunc->nargs) { \ 116 | PyErr_Format(PyExc_AssertionError,"ufunc %s takes %d arguments, our loop takes %ld",#name,ufunc->nargs,sizeof(_types)/sizeof(int)); \ 117 | return; \ 118 | } \ 119 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,_types,0)<0) { \ 120 | return; \ 121 | } \ 122 | } 123 | #define REGISTER_UFUNC_BINARY_RATIONAL(name) REGISTER_UFUNC(name,{npy_rational,npy_rational,npy_rational}) 124 | 125 | REGISTER_UFUNC_BINARY_RATIONAL(add) 126 | 127 | 128 | 129 | Creating New UFunc for Custom DType 130 | 131 | The next example shows how to create a new ufunc for the Rational dtype. The ufunc 132 | example creates a ufunc called 'numerator' which generates an array of numerator 133 | values based rational numbers from the input array. 134 | 135 | 136 | 1. A 1-d loop function is created as before which takes the numerator value from 137 | each element of the input array and stores it in the output array: 138 | 139 | void rational_ufunc_numerator(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { 140 | npy_intp is = steps[0], os = steps[1], n = *dimensions; 141 | char *i = args[0], *o = args[1]; 142 | int k; 143 | for (k = 0; k < n; k++) { 144 | rational x = *(rational*)i; 145 | *(int64_t*)o = x.n; 146 | i += is; o += os; 147 | } 148 | } 149 | 150 | You can also use the c MACRO provided in Rational for generating the above function: 151 | 152 | #define UNARY_UFUNC(name,type,exp) \ 153 | void rational_ufunc_##name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 154 | npy_intp is = steps[0], os = steps[1], n = *dimensions; \ 155 | char *i = args[0], *o = args[1]; \ 156 | int k; \ 157 | for (k = 0; k < n; k++) { \ 158 | rational x = *(rational*)i; \ 159 | *(type*)o = exp; \ 160 | i += is; o += os; \ 161 | } \ 162 | } 163 | 164 | UNARY_UFUNC(numerator,int64_t,x.n) 165 | 166 | 167 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, 168 | a new PyUFuncObject is created for the new 'numerator' ufunc using the 169 | PyUFunc_FromFuncAndData function: 170 | 171 | PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)"numerator",(char*)"rational number numerator",0); 172 | 173 | In this use case, the first four parameters should be set to zero since we're 174 | creating a new ufunc without support for existing dtypes. The rest of the 175 | parameters: 176 | 177 | - number of inputs to function that the loop function calls for each pair of elements 178 | - number of outputs of loop function 179 | - name of the ufunc 180 | - documentation string describing the ufunc 181 | - unused; present for backwards compatibility 182 | 183 | 184 | 3. The 1-d loop function is registered using the loop function and the 185 | PyUFuncObject created in step 2: 186 | 187 | int _types[] = {npy_rational,NPY_INT64}; 188 | 189 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_numerator,_types,0)<0) { 190 | return; 191 | } 192 | 193 | 194 | 4. Finally, a function called 'numerator' is added to the rational module which 195 | will call the numerator ufunc: 196 | 197 | PyModule_AddObject(m,"numerator",(PyObject*)ufunc); 198 | 199 | 200 | 5. Steps 2-4 can also be accomplished by using a c MACRO similar to the one 201 | provided with Rational: 202 | 203 | #define NEW_UNARY_UFUNC(name,type,doc) { \ 204 | PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)#name,(char*)doc,0); \ 205 | if (!ufunc) { \ 206 | return; \ 207 | } \ 208 | int types[2] = {npy_rational,type}; \ 209 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,types,0)<0) { \ 210 | return; \ 211 | } \ 212 | PyModule_AddObject(m,#name,(PyObject*)ufunc); \ 213 | } 214 | 215 | NEW_UNARY_UFUNC(numerator,NPY_INT64,"rational number numerator"); 216 | 217 | 218 | Creating New Generalized UFunc for Custom DType 219 | 220 | The next example shows how to create a new generalized ufunc for the Rational dtype. 221 | The gufunc example creates a gufunc 'matrix_multiply' which loops over a pair of 222 | vectors or matrices and performs a matrix multiply on each pair of matrix elements. 223 | 224 | 225 | 1. A loop function is created to loop through the outer or loop dimensions, performing a 226 | matrix multiply operation on the core dimensions for each loop: 227 | 228 | static void 229 | rational_gufunc_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) 230 | { 231 | // outer dimensions counter 232 | npy_intp N_; 233 | 234 | // length of flattened outer dimensions 235 | npy_intp dN = dimensions[0]; 236 | 237 | // striding over flattened outer dimensions for input and output arrays 238 | npy_intp s0 = steps[0]; 239 | npy_intp s1 = steps[1]; 240 | npy_intp s2 = steps[2]; 241 | 242 | // loop through outer dimensions, performing matrix multiply on core dimensions for each loop 243 | for (N_ = 0; N_ < dN; N_++, args[0] += s0, args[1] += s1, args[2] += s2) { 244 | rational_matrix_multiply(args, dimensions+1, steps+3); 245 | } 246 | } 247 | 248 | If the input matrices have more than one outer dimension, the outer dimensions are flattened from the 249 | perspective of the loop function. 250 | 251 | The matrix multiply function: 252 | 253 | static NPY_INLINE void 254 | rational_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps) 255 | { 256 | // pointers to data for input and output arrays 257 | char *ip1 = args[0]; 258 | char *ip2 = args[1]; 259 | char *op = args[2]; 260 | 261 | // lengths of core dimensions 262 | npy_intp dm = dimensions[0]; 263 | npy_intp dn = dimensions[1]; 264 | npy_intp dp = dimensions[2]; 265 | 266 | // striding over core dimensions 267 | npy_intp is1_m = steps[0]; 268 | npy_intp is1_n = steps[1]; 269 | npy_intp is2_n = steps[2]; 270 | npy_intp is2_p = steps[3]; 271 | npy_intp os_m = steps[4]; 272 | npy_intp os_p = steps[5]; 273 | 274 | // core dimensions counters 275 | npy_intp m, n, p; 276 | 277 | // calculate dot product for each row/column vector pair 278 | for (m = 0; m < dm; m++) { 279 | for (p = 0; p < dp; p++) { 280 | npyrational_dot(ip1, is1_n, ip2, is2_n, op, dn, NULL); 281 | 282 | ip2 += is2_p; 283 | op += os_p; 284 | } 285 | 286 | ip2 -= is2_p * p; 287 | op -= os_p * p; 288 | 289 | ip1 += is1_m; 290 | op += os_m; 291 | } 292 | } 293 | 294 | 295 | 2. In the 'initrational' function used to initialize the Rational dtype with numpy, 296 | a new PyUFuncObject is created for the new 'matrix_multiply' generalized ufunc using the 297 | PyUFunc_FromFuncAndDataAndSignature function: 298 | 299 | PyObject* gufunc = PyUFunc_FromFuncAndDataAndSignature(0,0,0,0,2,1,PyUFunc_None,(char*)"matrix_multiply",(char*)"return result of multiplying two matrices of rationals",0,"(m,n),(n,p)->(m,p)"); 300 | 301 | This is identical to the PyUFunc_FromFuncAndData function used to create a ufunc object in the examples above, 302 | with the addition of a ufunc signature describing the core dimensions of the input and output arrays. In this example, 303 | the generalized ufunc operates on pairs of matrices with dimensions (m,n) and (n,p), producing an 304 | output matrix of dimensions (m,p). 305 | 306 | 3. The loop function is registered using the loop function and the PyUFuncObject created in step 2: 307 | 308 | int types2[3] = {npy_rational,npy_rational,npy_rational}; 309 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)gufunc,npy_rational,rational_gufunc_matrix_multiply,types2,0) < 0) { 310 | return; 311 | } 312 | 313 | 4. Finally, a function called 'matrix_multiply' is added to the rational module which 314 | will call the numerator ufunc: 315 | 316 | 317 | PyModule_AddObject(m,"matrix_multiply",(PyObject*)gufunc); 318 | 319 | 320 | -------------------------------------------------------------------------------- /npytypes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numpy/numpy-dtypes/0ab71b10403daf407b0e6cbaf12c6eee83c27a8d/npytypes/__init__.py -------------------------------------------------------------------------------- /npytypes/quaternion/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.so 3 | build 4 | -------------------------------------------------------------------------------- /npytypes/quaternion/README: -------------------------------------------------------------------------------- 1 | This Python module adds a quaternion dtype to NumPy. 2 | 3 | To build: 4 | 5 | $ python setup.py build 6 | 7 | To install (may require administrator rights): 8 | 9 | # python setup.py install 10 | 11 | Example: 12 | 13 | >>> import numpy as np 14 | >>> import quaternion 15 | >>> np.quaternion(1,0,0,0) 16 | quaternion(1, 0, 0, 0) 17 | >>> q1 = np.quaternion(1,2,3,4) 18 | >>> q2 = np.quaternion(5,6,7,8) 19 | >>> q1 * q2 20 | quaternion(-60, 12, 30, 24) 21 | >>> a = np.array([q1, q2]) 22 | >>> a 23 | array([quaternion(1, 2, 3, 4), quaternion(5, 6, 7, 8)], dtype=quaternion) 24 | >>> exp(a) 25 | array([quaternion(1.69392, -0.78956, -1.18434, -1.57912), 26 | quaternion(138.909, -25.6861, -29.9671, -34.2481)], dtype=quaternion) 27 | 28 | The following ufuncs are implemented: 29 | add, subtract, multiply, divide, log, exp, power, negative, conjugate, 30 | copysign, equal, not_equal, less, less_equal, isnan, isinf, isfinite, absolute 31 | 32 | Quaternion components are stored as doubles. 33 | 34 | Comparison operations follow the same lexicographic ordering as tuples. 35 | 36 | The unary tests isnan and isinf return true if they would return true for any 37 | individual component; isfinite returns true if it would return true for all 38 | components. 39 | 40 | Real types may be cast to quaternions, giving quaternions with zero for all 41 | three imaginary components. Complex types may also be cast to quaternions, 42 | with their single imaginary component becoming the first imaginary component of 43 | the quaternion. Quaternions may not be cast to real or complex types. 44 | 45 | Written at the SciPy 2011 sprints, with help from Mark Weibe. 46 | 47 | The basic structure of this package is copied from Mark's half-precision 48 | floating point package: https://github.com/m-paradox/numpy_half - without this 49 | and Mark's help I would have had little chance of figuring out how to do this! 50 | -------------------------------------------------------------------------------- /npytypes/quaternion/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from npytypes.quaternion.numpy_quaternion import quaternion 4 | from npytypes.quaternion.info import __doc__ 5 | 6 | __all__ = ['quaternion'] 7 | 8 | if np.__dict__.get('quaternion') is not None: 9 | raise RuntimeError('The NumPy package already has a quaternion type') 10 | 11 | np.quaternion = quaternion 12 | np.typeDict['quaternion'] = np.dtype(quaternion) 13 | -------------------------------------------------------------------------------- /npytypes/quaternion/info.py: -------------------------------------------------------------------------------- 1 | __doc_title__ = "Quaternion dtype for NumPy" 2 | __doc__ = "Adds a quaternion dtype to NumPy." 3 | -------------------------------------------------------------------------------- /npytypes/quaternion/numpy_quaternion.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Quaternion type for NumPy 3 | * Copyright (c) 2011 Martin Ling 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of the NumPy Developers nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTERS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | #define NPY_NO_DEPRECATED_API NPY_API_VERSION 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "structmember.h" 37 | #include "numpy/npy_3kcompat.h" 38 | 39 | #include "quaternion.h" 40 | 41 | typedef struct { 42 | PyObject_HEAD 43 | quaternion obval; 44 | } PyQuaternionScalarObject; 45 | 46 | PyMemberDef PyQuaternionArrType_members[] = { 47 | {"real", T_DOUBLE, offsetof(PyQuaternionScalarObject, obval.w), READONLY, 48 | "The real component of the quaternion"}, 49 | {"w", T_DOUBLE, offsetof(PyQuaternionScalarObject, obval.w), READONLY, 50 | "The real component of the quaternion"}, 51 | {"x", T_DOUBLE, offsetof(PyQuaternionScalarObject, obval.x), READONLY, 52 | "The first imaginary component of the quaternion"}, 53 | {"y", T_DOUBLE, offsetof(PyQuaternionScalarObject, obval.y), READONLY, 54 | "The second imaginary component of the quaternion"}, 55 | {"z", T_DOUBLE, offsetof(PyQuaternionScalarObject, obval.z), READONLY, 56 | "The third imaginary component of the quaternion"}, 57 | {NULL} 58 | }; 59 | 60 | static PyObject * 61 | PyQuaternionArrType_get_components(PyObject *self, void *closure) 62 | { 63 | quaternion *q = &((PyQuaternionScalarObject *)self)->obval; 64 | PyObject *tuple = PyTuple_New(4); 65 | PyTuple_SET_ITEM(tuple, 0, PyFloat_FromDouble(q->w)); 66 | PyTuple_SET_ITEM(tuple, 1, PyFloat_FromDouble(q->x)); 67 | PyTuple_SET_ITEM(tuple, 2, PyFloat_FromDouble(q->y)); 68 | PyTuple_SET_ITEM(tuple, 3, PyFloat_FromDouble(q->z)); 69 | return tuple; 70 | } 71 | 72 | static PyObject * 73 | PyQuaternionArrType_get_imag(PyObject *self, void *closure) 74 | { 75 | quaternion *q = &((PyQuaternionScalarObject *)self)->obval; 76 | PyObject *tuple = PyTuple_New(3); 77 | PyTuple_SET_ITEM(tuple, 0, PyFloat_FromDouble(q->x)); 78 | PyTuple_SET_ITEM(tuple, 1, PyFloat_FromDouble(q->y)); 79 | PyTuple_SET_ITEM(tuple, 2, PyFloat_FromDouble(q->z)); 80 | return tuple; 81 | } 82 | 83 | PyGetSetDef PyQuaternionArrType_getset[] = { 84 | {"components", PyQuaternionArrType_get_components, NULL, 85 | "The components of the quaternion as a (w,x,y,z) tuple", NULL}, 86 | {"imag", PyQuaternionArrType_get_imag, NULL, 87 | "The imaginary part of the quaternion as an (x,y,z) tuple", NULL}, 88 | {NULL} 89 | }; 90 | 91 | PyTypeObject PyQuaternionArrType_Type = { 92 | #if defined(NPY_PY3K) 93 | PyVarObject_HEAD_INIT(NULL, 0) 94 | #else 95 | PyObject_HEAD_INIT(NULL) 96 | 0, /* ob_size */ 97 | #endif 98 | "quaternion.quaternion", /* tp_name*/ 99 | sizeof(PyQuaternionScalarObject), /* tp_basicsize*/ 100 | 0, /* tp_itemsize */ 101 | 0, /* tp_dealloc */ 102 | 0, /* tp_print */ 103 | 0, /* tp_getattr */ 104 | 0, /* tp_setattr */ 105 | #if defined(NPY_PY3K) 106 | 0, /* tp_reserved */ 107 | #else 108 | 0, /* tp_compare */ 109 | #endif 110 | 0, /* tp_repr */ 111 | 0, /* tp_as_number */ 112 | 0, /* tp_as_sequence */ 113 | 0, /* tp_as_mapping */ 114 | 0, /* tp_hash */ 115 | 0, /* tp_call */ 116 | 0, /* tp_str */ 117 | 0, /* tp_getattro */ 118 | 0, /* tp_setattro */ 119 | 0, /* tp_as_buffer */ 120 | 0, /* tp_flags */ 121 | 0, /* tp_doc */ 122 | 0, /* tp_traverse */ 123 | 0, /* tp_clear */ 124 | 0, /* tp_richcompare */ 125 | 0, /* tp_weaklistoffset */ 126 | 0, /* tp_iter */ 127 | 0, /* tp_iternext */ 128 | 0, /* tp_methods */ 129 | PyQuaternionArrType_members, /* tp_members */ 130 | PyQuaternionArrType_getset, /* tp_getset */ 131 | 0, /* tp_base */ 132 | 0, /* tp_dict */ 133 | 0, /* tp_descr_get */ 134 | 0, /* tp_descr_set */ 135 | 0, /* tp_dictoffset */ 136 | 0, /* tp_init */ 137 | 0, /* tp_alloc */ 138 | 0, /* tp_new */ 139 | 0, /* tp_free */ 140 | 0, /* tp_is_gc */ 141 | 0, /* tp_bases */ 142 | 0, /* tp_mro */ 143 | 0, /* tp_cache */ 144 | 0, /* tp_subclasses */ 145 | 0, /* tp_weaklist */ 146 | 0, /* tp_del */ 147 | #if PY_VERSION_HEX >= 0x02060000 148 | 0, /* tp_version_tag */ 149 | #endif 150 | }; 151 | 152 | static PyArray_ArrFuncs _PyQuaternion_ArrFuncs; 153 | PyArray_Descr *quaternion_descr; 154 | 155 | static PyObject * 156 | QUATERNION_getitem(char *ip, PyArrayObject *ap) 157 | { 158 | quaternion q; 159 | PyObject *tuple; 160 | 161 | if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) { 162 | q = *((quaternion *)ip); 163 | } 164 | else { 165 | PyArray_Descr *descr; 166 | descr = PyArray_DescrFromType(NPY_DOUBLE); 167 | descr->f->copyswap(&q.w, ip, !PyArray_ISNOTSWAPPED(ap), NULL); 168 | descr->f->copyswap(&q.x, ip+8, !PyArray_ISNOTSWAPPED(ap), NULL); 169 | descr->f->copyswap(&q.y, ip+16, !PyArray_ISNOTSWAPPED(ap), NULL); 170 | descr->f->copyswap(&q.z, ip+24, !PyArray_ISNOTSWAPPED(ap), NULL); 171 | Py_DECREF(descr); 172 | } 173 | 174 | tuple = PyTuple_New(4); 175 | PyTuple_SET_ITEM(tuple, 0, PyFloat_FromDouble(q.w)); 176 | PyTuple_SET_ITEM(tuple, 1, PyFloat_FromDouble(q.x)); 177 | PyTuple_SET_ITEM(tuple, 2, PyFloat_FromDouble(q.y)); 178 | PyTuple_SET_ITEM(tuple, 3, PyFloat_FromDouble(q.z)); 179 | 180 | return tuple; 181 | } 182 | 183 | static int QUATERNION_setitem(PyObject *op, char *ov, PyArrayObject *ap) 184 | { 185 | quaternion q; 186 | 187 | if (PyArray_IsScalar(op, Quaternion)) { 188 | q = ((PyQuaternionScalarObject *)op)->obval; 189 | } 190 | else { 191 | q.w = PyFloat_AsDouble(PyTuple_GetItem(op, 0)); 192 | q.x = PyFloat_AsDouble(PyTuple_GetItem(op, 1)); 193 | q.y = PyFloat_AsDouble(PyTuple_GetItem(op, 2)); 194 | q.z = PyFloat_AsDouble(PyTuple_GetItem(op, 3)); 195 | } 196 | if (PyErr_Occurred()) { 197 | if (PySequence_Check(op)) { 198 | PyErr_Clear(); 199 | PyErr_SetString(PyExc_ValueError, 200 | "setting an array element with a sequence."); 201 | } 202 | return -1; 203 | } 204 | if (ap == NULL || PyArray_ISBEHAVED(ap)) 205 | *((quaternion *)ov)=q; 206 | else { 207 | PyArray_Descr *descr; 208 | descr = PyArray_DescrFromType(NPY_DOUBLE); 209 | descr->f->copyswap(ov, &q.w, !PyArray_ISNOTSWAPPED(ap), NULL); 210 | descr->f->copyswap(ov+8, &q.x, !PyArray_ISNOTSWAPPED(ap), NULL); 211 | descr->f->copyswap(ov+16, &q.y, !PyArray_ISNOTSWAPPED(ap), NULL); 212 | descr->f->copyswap(ov+24, &q.z, !PyArray_ISNOTSWAPPED(ap), NULL); 213 | Py_DECREF(descr); 214 | } 215 | 216 | return 0; 217 | } 218 | 219 | static void 220 | QUATERNION_copyswap(quaternion *dst, quaternion *src, 221 | int swap, void *NPY_UNUSED(arr)) 222 | { 223 | PyArray_Descr *descr; 224 | descr = PyArray_DescrFromType(NPY_DOUBLE); 225 | descr->f->copyswapn(dst, sizeof(double), src, sizeof(double), 4, swap, NULL); 226 | Py_DECREF(descr); 227 | } 228 | 229 | static void 230 | QUATERNION_copyswapn(quaternion *dst, npy_intp dstride, 231 | quaternion *src, npy_intp sstride, 232 | npy_intp n, int swap, void *NPY_UNUSED(arr)) 233 | { 234 | PyArray_Descr *descr; 235 | descr = PyArray_DescrFromType(NPY_DOUBLE); 236 | descr->f->copyswapn(&dst->w, dstride, &src->w, sstride, n, swap, NULL); 237 | descr->f->copyswapn(&dst->x, dstride, &src->x, sstride, n, swap, NULL); 238 | descr->f->copyswapn(&dst->y, dstride, &src->y, sstride, n, swap, NULL); 239 | descr->f->copyswapn(&dst->z, dstride, &src->z, sstride, n, swap, NULL); 240 | Py_DECREF(descr); 241 | } 242 | 243 | static int 244 | QUATERNION_compare (quaternion *pa, quaternion *pb, PyArrayObject *NPY_UNUSED(ap)) 245 | { 246 | quaternion a = *pa, b = *pb; 247 | npy_bool anan, bnan; 248 | int ret; 249 | 250 | anan = quaternion_isnan(a); 251 | bnan = quaternion_isnan(b); 252 | 253 | if (anan) { 254 | ret = bnan ? 0 : -1; 255 | } else if (bnan) { 256 | ret = 1; 257 | } else if(quaternion_less(a, b)) { 258 | ret = -1; 259 | } else if(quaternion_less(b, a)) { 260 | ret = 1; 261 | } else { 262 | ret = 0; 263 | } 264 | 265 | return ret; 266 | } 267 | 268 | static int 269 | QUATERNION_argmax(quaternion *ip, npy_intp n, npy_intp *max_ind, PyArrayObject *NPY_UNUSED(aip)) 270 | { 271 | npy_intp i; 272 | quaternion mp = *ip; 273 | 274 | *max_ind = 0; 275 | 276 | if (quaternion_isnan(mp)) { 277 | /* nan encountered; it's maximal */ 278 | return 0; 279 | } 280 | 281 | for (i = 1; i < n; i++) { 282 | ip++; 283 | /* 284 | * Propagate nans, similarly as max() and min() 285 | */ 286 | if (!(quaternion_less_equal(*ip, mp))) { /* negated, for correct nan handling */ 287 | mp = *ip; 288 | *max_ind = i; 289 | if (quaternion_isnan(mp)) { 290 | /* nan encountered, it's maximal */ 291 | break; 292 | } 293 | } 294 | } 295 | return 0; 296 | } 297 | 298 | static npy_bool 299 | QUATERNION_nonzero (char *ip, PyArrayObject *ap) 300 | { 301 | quaternion q; 302 | if (ap == NULL || PyArray_ISBEHAVED_RO(ap)) { 303 | q = *(quaternion *)ip; 304 | } 305 | else { 306 | PyArray_Descr *descr; 307 | descr = PyArray_DescrFromType(NPY_DOUBLE); 308 | descr->f->copyswap(&q.w, ip, !PyArray_ISNOTSWAPPED(ap), NULL); 309 | descr->f->copyswap(&q.x, ip+8, !PyArray_ISNOTSWAPPED(ap), NULL); 310 | descr->f->copyswap(&q.y, ip+16, !PyArray_ISNOTSWAPPED(ap), NULL); 311 | descr->f->copyswap(&q.z, ip+24, !PyArray_ISNOTSWAPPED(ap), NULL); 312 | Py_DECREF(descr); 313 | } 314 | return (npy_bool) !quaternion_equal(q, (quaternion) {0,0,0,0}); 315 | } 316 | 317 | static void 318 | QUATERNION_fillwithscalar(quaternion *buffer, npy_intp length, quaternion *value, void *NPY_UNUSED(ignored)) 319 | { 320 | npy_intp i; 321 | quaternion val = *value; 322 | 323 | for (i = 0; i < length; ++i) { 324 | buffer[i] = val; 325 | } 326 | } 327 | 328 | #define MAKE_T_TO_QUATERNION(TYPE, type) \ 329 | static void \ 330 | TYPE ## _to_quaternion(type *ip, quaternion *op, npy_intp n, \ 331 | PyArrayObject *NPY_UNUSED(aip), PyArrayObject *NPY_UNUSED(aop)) \ 332 | { \ 333 | while (n--) { \ 334 | op->w = (double)(*ip++); \ 335 | op->x = 0; \ 336 | op->y = 0; \ 337 | op->z = 0; \ 338 | } \ 339 | } 340 | 341 | MAKE_T_TO_QUATERNION(FLOAT, npy_float); 342 | MAKE_T_TO_QUATERNION(DOUBLE, npy_double); 343 | MAKE_T_TO_QUATERNION(LONGDOUBLE, npy_longdouble); 344 | MAKE_T_TO_QUATERNION(BOOL, npy_bool); 345 | MAKE_T_TO_QUATERNION(BYTE, npy_byte); 346 | MAKE_T_TO_QUATERNION(UBYTE, npy_ubyte); 347 | MAKE_T_TO_QUATERNION(SHORT, npy_short); 348 | MAKE_T_TO_QUATERNION(USHORT, npy_ushort); 349 | MAKE_T_TO_QUATERNION(INT, npy_int); 350 | MAKE_T_TO_QUATERNION(UINT, npy_uint); 351 | MAKE_T_TO_QUATERNION(LONG, npy_long); 352 | MAKE_T_TO_QUATERNION(ULONG, npy_ulong); 353 | MAKE_T_TO_QUATERNION(LONGLONG, npy_longlong); 354 | MAKE_T_TO_QUATERNION(ULONGLONG, npy_ulonglong); 355 | 356 | #define MAKE_CT_TO_QUATERNION(TYPE, type) \ 357 | static void \ 358 | TYPE ## _to_quaternion(type *ip, quaternion *op, npy_intp n, \ 359 | PyArrayObject *NPY_UNUSED(aip), PyArrayObject *NPY_UNUSED(aop)) \ 360 | { \ 361 | while (n--) { \ 362 | op->w = (double)(*ip++); \ 363 | op->x = (double)(*ip++); \ 364 | op->y = 0; \ 365 | op->z = 0; \ 366 | } \ 367 | } 368 | 369 | MAKE_CT_TO_QUATERNION(CFLOAT, npy_double); 370 | MAKE_CT_TO_QUATERNION(CDOUBLE, npy_double); 371 | MAKE_CT_TO_QUATERNION(CLONGDOUBLE, npy_longdouble); 372 | 373 | static void register_cast_function(int sourceType, int destType, PyArray_VectorUnaryFunc *castfunc) 374 | { 375 | PyArray_Descr *descr = PyArray_DescrFromType(sourceType); 376 | PyArray_RegisterCastFunc(descr, destType, castfunc); 377 | PyArray_RegisterCanCast(descr, destType, NPY_NOSCALAR); 378 | Py_DECREF(descr); 379 | } 380 | 381 | static PyObject * 382 | quaternion_arrtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 383 | { 384 | quaternion q; 385 | 386 | if (!PyArg_ParseTuple(args, "dddd", &q.w, &q.x, &q.y, &q.z)) 387 | return NULL; 388 | 389 | return PyArray_Scalar(&q, quaternion_descr, NULL); 390 | } 391 | 392 | static PyObject * 393 | gentype_richcompare(PyObject *self, PyObject *other, int cmp_op) 394 | { 395 | PyObject *arr, *ret; 396 | 397 | arr = PyArray_FromScalar(self, NULL); 398 | if (arr == NULL) { 399 | return NULL; 400 | } 401 | ret = Py_TYPE(arr)->tp_richcompare(arr, other, cmp_op); 402 | Py_DECREF(arr); 403 | return ret; 404 | } 405 | 406 | static long 407 | quaternion_arrtype_hash(PyObject *o) 408 | { 409 | quaternion q = ((PyQuaternionScalarObject *)o)->obval; 410 | long value = 0x456789; 411 | value = (10000004 * value) ^ _Py_HashDouble(q.w); 412 | value = (10000004 * value) ^ _Py_HashDouble(q.x); 413 | value = (10000004 * value) ^ _Py_HashDouble(q.y); 414 | value = (10000004 * value) ^ _Py_HashDouble(q.z); 415 | if (value == -1) 416 | value = -2; 417 | return value; 418 | } 419 | 420 | static PyObject * 421 | quaternion_arrtype_repr(PyObject *o) 422 | { 423 | char str[128]; 424 | quaternion q = ((PyQuaternionScalarObject *)o)->obval; 425 | sprintf(str, "quaternion(%g, %g, %g, %g)", q.w, q.x, q.y, q.z); 426 | return PyUString_FromString(str); 427 | } 428 | 429 | static PyObject * 430 | quaternion_arrtype_str(PyObject *o) 431 | { 432 | char str[128]; 433 | quaternion q = ((PyQuaternionScalarObject *)o)->obval; 434 | sprintf(str, "quaternion(%g, %g, %g, %g)", q.w, q.x, q.y, q.z); 435 | return PyString_FromString(str); 436 | } 437 | 438 | static PyMethodDef QuaternionMethods[] = { 439 | {NULL, NULL, 0, NULL} 440 | }; 441 | 442 | #define UNARY_UFUNC(name, ret_type)\ 443 | static void \ 444 | quaternion_##name##_ufunc(char** args, npy_intp* dimensions,\ 445 | npy_intp* steps, void* data) {\ 446 | char *ip1 = args[0], *op1 = args[1];\ 447 | npy_intp is1 = steps[0], os1 = steps[1];\ 448 | npy_intp n = dimensions[0];\ 449 | npy_intp i;\ 450 | for(i = 0; i < n; i++, ip1 += is1, op1 += os1){\ 451 | const quaternion in1 = *(quaternion *)ip1;\ 452 | *((ret_type *)op1) = quaternion_##name(in1);};} 453 | 454 | UNARY_UFUNC(isnan, npy_bool) 455 | UNARY_UFUNC(isinf, npy_bool) 456 | UNARY_UFUNC(isfinite, npy_bool) 457 | UNARY_UFUNC(absolute, npy_double) 458 | UNARY_UFUNC(log, quaternion) 459 | UNARY_UFUNC(exp, quaternion) 460 | UNARY_UFUNC(negative, quaternion) 461 | UNARY_UFUNC(conjugate, quaternion) 462 | 463 | #define BINARY_GEN_UFUNC(name, func_name, arg_type, ret_type)\ 464 | static void \ 465 | quaternion_##func_name##_ufunc(char** args, npy_intp* dimensions,\ 466 | npy_intp* steps, void* data) {\ 467 | char *ip1 = args[0], *ip2 = args[1], *op1 = args[2];\ 468 | npy_intp is1 = steps[0], is2 = steps[1], os1 = steps[2];\ 469 | npy_intp n = dimensions[0];\ 470 | npy_intp i;\ 471 | for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, op1 += os1){\ 472 | const quaternion in1 = *(quaternion *)ip1;\ 473 | const arg_type in2 = *(arg_type *)ip2;\ 474 | *((ret_type *)op1) = quaternion_##func_name(in1, in2);};}; 475 | 476 | #define BINARY_UFUNC(name, ret_type)\ 477 | BINARY_GEN_UFUNC(name, name, quaternion, ret_type) 478 | #define BINARY_SCALAR_UFUNC(name, ret_type)\ 479 | BINARY_GEN_UFUNC(name, name##_scalar, npy_double, ret_type) 480 | 481 | BINARY_UFUNC(add, quaternion) 482 | BINARY_UFUNC(subtract, quaternion) 483 | BINARY_UFUNC(multiply, quaternion) 484 | BINARY_UFUNC(divide, quaternion) 485 | BINARY_UFUNC(power, quaternion) 486 | BINARY_UFUNC(copysign, quaternion) 487 | BINARY_UFUNC(equal, npy_bool) 488 | BINARY_UFUNC(not_equal, npy_bool) 489 | BINARY_UFUNC(less, npy_bool) 490 | BINARY_UFUNC(less_equal, npy_bool) 491 | 492 | BINARY_SCALAR_UFUNC(multiply, quaternion) 493 | BINARY_SCALAR_UFUNC(divide, quaternion) 494 | BINARY_SCALAR_UFUNC(power, quaternion) 495 | 496 | #if defined(NPY_PY3K) 497 | static struct PyModuleDef moduledef = { 498 | PyModuleDef_HEAD_INIT, 499 | "numpy_quaternion", 500 | NULL, 501 | -1, 502 | QuaternionMethods, 503 | NULL, 504 | NULL, 505 | NULL, 506 | NULL 507 | }; 508 | #endif 509 | 510 | #if defined(NPY_PY3K) 511 | PyMODINIT_FUNC PyInit_numpy_quaternion(void) { 512 | #else 513 | PyMODINIT_FUNC initnumpy_quaternion(void) { 514 | #endif 515 | 516 | PyObject *m; 517 | int quaternionNum; 518 | PyObject* numpy = PyImport_ImportModule("numpy"); 519 | PyObject* numpy_dict = PyModule_GetDict(numpy); 520 | int arg_types[3]; 521 | 522 | #if defined(NPY_PY3K) 523 | m = PyModule_Create(&moduledef); 524 | #else 525 | m = Py_InitModule("numpy_quaternion", QuaternionMethods); 526 | #endif 527 | 528 | if (!m) { 529 | return NULL; 530 | } 531 | 532 | /* Make sure NumPy is initialized */ 533 | import_array(); 534 | import_umath(); 535 | 536 | /* Register the quaternion array scalar type */ 537 | #if defined(NPY_PY3K) 538 | PyQuaternionArrType_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; 539 | #else 540 | PyQuaternionArrType_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES; 541 | #endif 542 | PyQuaternionArrType_Type.tp_new = quaternion_arrtype_new; 543 | PyQuaternionArrType_Type.tp_richcompare = gentype_richcompare; 544 | PyQuaternionArrType_Type.tp_hash = quaternion_arrtype_hash; 545 | PyQuaternionArrType_Type.tp_repr = quaternion_arrtype_repr; 546 | PyQuaternionArrType_Type.tp_str = quaternion_arrtype_str; 547 | PyQuaternionArrType_Type.tp_base = &PyGenericArrType_Type; 548 | if (PyType_Ready(&PyQuaternionArrType_Type) < 0) { 549 | PyErr_Print(); 550 | PyErr_SetString(PyExc_SystemError, "could not initialize PyQuaternionArrType_Type"); 551 | return NULL; 552 | } 553 | 554 | /* The array functions */ 555 | PyArray_InitArrFuncs(&_PyQuaternion_ArrFuncs); 556 | _PyQuaternion_ArrFuncs.getitem = (PyArray_GetItemFunc*)QUATERNION_getitem; 557 | _PyQuaternion_ArrFuncs.setitem = (PyArray_SetItemFunc*)QUATERNION_setitem; 558 | _PyQuaternion_ArrFuncs.copyswap = (PyArray_CopySwapFunc*)QUATERNION_copyswap; 559 | _PyQuaternion_ArrFuncs.copyswapn = (PyArray_CopySwapNFunc*)QUATERNION_copyswapn; 560 | _PyQuaternion_ArrFuncs.compare = (PyArray_CompareFunc*)QUATERNION_compare; 561 | _PyQuaternion_ArrFuncs.argmax = (PyArray_ArgFunc*)QUATERNION_argmax; 562 | _PyQuaternion_ArrFuncs.nonzero = (PyArray_NonzeroFunc*)QUATERNION_nonzero; 563 | _PyQuaternion_ArrFuncs.fillwithscalar = (PyArray_FillWithScalarFunc*)QUATERNION_fillwithscalar; 564 | 565 | /* The quaternion array descr */ 566 | quaternion_descr = PyObject_New(PyArray_Descr, &PyArrayDescr_Type); 567 | quaternion_descr->typeobj = &PyQuaternionArrType_Type; 568 | quaternion_descr->kind = 'q'; 569 | quaternion_descr->type = 'j'; 570 | quaternion_descr->byteorder = '='; 571 | quaternion_descr->type_num = 0; /* assigned at registration */ 572 | quaternion_descr->elsize = 8*4; 573 | quaternion_descr->alignment = 8; 574 | quaternion_descr->subarray = NULL; 575 | quaternion_descr->fields = NULL; 576 | quaternion_descr->names = NULL; 577 | quaternion_descr->f = &_PyQuaternion_ArrFuncs; 578 | 579 | Py_INCREF(&PyQuaternionArrType_Type); 580 | quaternionNum = PyArray_RegisterDataType(quaternion_descr); 581 | 582 | if (quaternionNum < 0) 583 | return NULL; 584 | 585 | register_cast_function(NPY_BOOL, quaternionNum, (PyArray_VectorUnaryFunc*)BOOL_to_quaternion); 586 | register_cast_function(NPY_BYTE, quaternionNum, (PyArray_VectorUnaryFunc*)BYTE_to_quaternion); 587 | register_cast_function(NPY_UBYTE, quaternionNum, (PyArray_VectorUnaryFunc*)UBYTE_to_quaternion); 588 | register_cast_function(NPY_SHORT, quaternionNum, (PyArray_VectorUnaryFunc*)SHORT_to_quaternion); 589 | register_cast_function(NPY_USHORT, quaternionNum, (PyArray_VectorUnaryFunc*)USHORT_to_quaternion); 590 | register_cast_function(NPY_INT, quaternionNum, (PyArray_VectorUnaryFunc*)INT_to_quaternion); 591 | register_cast_function(NPY_UINT, quaternionNum, (PyArray_VectorUnaryFunc*)UINT_to_quaternion); 592 | register_cast_function(NPY_LONG, quaternionNum, (PyArray_VectorUnaryFunc*)LONG_to_quaternion); 593 | register_cast_function(NPY_ULONG, quaternionNum, (PyArray_VectorUnaryFunc*)ULONG_to_quaternion); 594 | register_cast_function(NPY_LONGLONG, quaternionNum, (PyArray_VectorUnaryFunc*)LONGLONG_to_quaternion); 595 | register_cast_function(NPY_ULONGLONG, quaternionNum, (PyArray_VectorUnaryFunc*)ULONGLONG_to_quaternion); 596 | register_cast_function(NPY_FLOAT, quaternionNum, (PyArray_VectorUnaryFunc*)FLOAT_to_quaternion); 597 | register_cast_function(NPY_DOUBLE, quaternionNum, (PyArray_VectorUnaryFunc*)DOUBLE_to_quaternion); 598 | register_cast_function(NPY_LONGDOUBLE, quaternionNum, (PyArray_VectorUnaryFunc*)LONGDOUBLE_to_quaternion); 599 | register_cast_function(NPY_CFLOAT, quaternionNum, (PyArray_VectorUnaryFunc*)CFLOAT_to_quaternion); 600 | register_cast_function(NPY_CDOUBLE, quaternionNum, (PyArray_VectorUnaryFunc*)CDOUBLE_to_quaternion); 601 | register_cast_function(NPY_CLONGDOUBLE, quaternionNum, (PyArray_VectorUnaryFunc*)CLONGDOUBLE_to_quaternion); 602 | 603 | #define REGISTER_UFUNC(name)\ 604 | PyUFunc_RegisterLoopForType((PyUFuncObject *)PyDict_GetItemString(numpy_dict, #name),\ 605 | quaternion_descr->type_num, quaternion_##name##_ufunc, arg_types, NULL) 606 | 607 | #define REGISTER_SCALAR_UFUNC(name)\ 608 | PyUFunc_RegisterLoopForType((PyUFuncObject *)PyDict_GetItemString(numpy_dict, #name),\ 609 | quaternion_descr->type_num, quaternion_##name##_scalar_ufunc, arg_types, NULL) 610 | 611 | /* quat -> bool */ 612 | arg_types[0] = quaternion_descr->type_num; 613 | arg_types[1] = NPY_BOOL; 614 | 615 | REGISTER_UFUNC(isnan); 616 | REGISTER_UFUNC(isinf); 617 | REGISTER_UFUNC(isfinite); 618 | /* quat -> double */ 619 | arg_types[1] = NPY_DOUBLE; 620 | 621 | REGISTER_UFUNC(absolute); 622 | 623 | /* quat -> quat */ 624 | arg_types[1] = quaternion_descr->type_num; 625 | 626 | REGISTER_UFUNC(log); 627 | REGISTER_UFUNC(exp); 628 | REGISTER_UFUNC(negative); 629 | REGISTER_UFUNC(conjugate); 630 | 631 | /* quat, quat -> bool */ 632 | 633 | arg_types[2] = NPY_BOOL; 634 | 635 | REGISTER_UFUNC(equal); 636 | REGISTER_UFUNC(not_equal); 637 | REGISTER_UFUNC(less); 638 | REGISTER_UFUNC(less_equal); 639 | 640 | /* quat, double -> quat */ 641 | 642 | arg_types[1] = NPY_DOUBLE; 643 | arg_types[2] = quaternion_descr->type_num; 644 | 645 | REGISTER_SCALAR_UFUNC(multiply); 646 | REGISTER_SCALAR_UFUNC(divide); 647 | REGISTER_SCALAR_UFUNC(power); 648 | 649 | /* quat, quat -> quat */ 650 | 651 | arg_types[1] = quaternion_descr->type_num; 652 | 653 | REGISTER_UFUNC(add); 654 | REGISTER_UFUNC(subtract); 655 | REGISTER_UFUNC(multiply); 656 | REGISTER_UFUNC(divide); 657 | REGISTER_UFUNC(power); 658 | REGISTER_UFUNC(copysign); 659 | 660 | PyModule_AddObject(m, "quaternion", (PyObject *)&PyQuaternionArrType_Type); 661 | 662 | return m; 663 | } 664 | -------------------------------------------------------------------------------- /npytypes/quaternion/quaternion.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Quaternion math implementation 3 | * Copyright (c) 2011 Martin Ling 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of the NumPy Developers nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTERS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | #include "quaternion.h" 30 | #include "math.h" 31 | 32 | #define _QUAT_EPS 1e-6 33 | 34 | int 35 | quaternion_isnonzero(quaternion q) 36 | { 37 | return q.w != 0 && q.x != 0 && q.y != 0 && q.z != 0; 38 | } 39 | 40 | int 41 | quaternion_isnan(quaternion q) 42 | { 43 | return isnan(q.w) || isnan(q.x) || isnan(q.y) || isnan(q.z); 44 | } 45 | 46 | int 47 | quaternion_isinf(quaternion q) 48 | { 49 | return isinf(q.w) || isinf(q.x) || isinf(q.y) || isnan(q.z); 50 | } 51 | 52 | int 53 | quaternion_isfinite(quaternion q) 54 | { 55 | return isfinite(q.w) && isfinite(q.x) && isfinite(q.y) && isfinite(q.z); 56 | } 57 | 58 | double 59 | quaternion_absolute(quaternion q) 60 | { 61 | return sqrt(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z); 62 | } 63 | 64 | quaternion 65 | quaternion_add(quaternion q1, quaternion q2) 66 | { 67 | return (quaternion) { 68 | q1.w+q2.w, 69 | q1.x+q2.x, 70 | q1.y+q2.y, 71 | q1.z+q2.z, 72 | }; 73 | } 74 | 75 | quaternion 76 | quaternion_subtract(quaternion q1, quaternion q2) 77 | { 78 | return (quaternion) { 79 | q1.w-q2.w, 80 | q1.x-q2.x, 81 | q1.y-q2.y, 82 | q1.z-q2.z, 83 | }; 84 | } 85 | 86 | quaternion 87 | quaternion_multiply(quaternion q1, quaternion q2) 88 | { 89 | return (quaternion) { 90 | q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z, 91 | q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y, 92 | q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x, 93 | q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w, 94 | }; 95 | } 96 | 97 | quaternion 98 | quaternion_divide(quaternion q1, quaternion q2) 99 | { 100 | double s = q2.w*q2.w + q2.x*q2.x + q2.y*q2.y + q2.z*q2.z; 101 | return (quaternion) { 102 | ( q1.w*q2.w + q1.x*q2.x + q1.y*q2.y + q1.z*q2.z) / s, 103 | (- q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y) / s, 104 | (- q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x) / s, 105 | (- q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w) / s 106 | }; 107 | } 108 | 109 | quaternion 110 | quaternion_multiply_scalar(quaternion q, double s) 111 | { 112 | return (quaternion) {s*q.w, s*q.x, s*q.y, s*q.z}; 113 | } 114 | 115 | quaternion 116 | quaternion_divide_scalar(quaternion q, double s) 117 | { 118 | return (quaternion) {q.w/s, q.x/s, q.y/s, q.z/s}; 119 | } 120 | 121 | quaternion 122 | quaternion_log(quaternion q) 123 | { 124 | double sumvsq = q.x*q.x + q.y*q.y + q.z*q.z; 125 | double vnorm = sqrt(sumvsq); 126 | if (vnorm > _QUAT_EPS) { 127 | double m = sqrt(q.w*q.w + sumvsq); 128 | double s = acos(q.w/m) / vnorm; 129 | return (quaternion) {log(m), s*q.x, s*q.y, s*q.z}; 130 | } else { 131 | return (quaternion) {0, 0, 0, 0}; 132 | } 133 | } 134 | 135 | quaternion 136 | quaternion_exp(quaternion q) 137 | { 138 | double vnorm = sqrt(q.x*q.x + q.y*q.y + q.z*q.z); 139 | if (vnorm > _QUAT_EPS) { 140 | double s = sin(vnorm) / vnorm; 141 | double e = exp(q.w); 142 | return (quaternion) {e*cos(vnorm), e*s*q.x, e*s*q.y, e*s*q.z}; 143 | } else { 144 | return (quaternion) {exp(q.w), 0, 0, 0}; 145 | } 146 | } 147 | 148 | quaternion 149 | quaternion_power(quaternion q, quaternion p) 150 | { 151 | return quaternion_exp(quaternion_multiply(quaternion_log(q), p)); 152 | } 153 | 154 | quaternion 155 | quaternion_power_scalar(quaternion q, double p) 156 | { 157 | return quaternion_exp(quaternion_multiply_scalar(quaternion_log(q), p)); 158 | } 159 | 160 | quaternion 161 | quaternion_negative(quaternion q) 162 | { 163 | return (quaternion) {-q.w, -q.x, -q.y, -q.z}; 164 | } 165 | 166 | quaternion 167 | quaternion_conjugate(quaternion q) 168 | { 169 | return (quaternion) {q.w, -q.x, -q.y, -q.z}; 170 | } 171 | 172 | quaternion 173 | quaternion_copysign(quaternion q1, quaternion q2) 174 | { 175 | return (quaternion) { 176 | copysign(q1.w, q2.w), 177 | copysign(q1.x, q2.x), 178 | copysign(q1.y, q2.y), 179 | copysign(q1.z, q2.z) 180 | }; 181 | } 182 | 183 | int 184 | quaternion_equal(quaternion q1, quaternion q2) 185 | { 186 | return 187 | !quaternion_isnan(q1) && 188 | !quaternion_isnan(q2) && 189 | q1.w == q2.w && 190 | q1.x == q2.x && 191 | q1.y == q2.y && 192 | q1.z == q2.z; 193 | } 194 | 195 | int 196 | quaternion_not_equal(quaternion q1, quaternion q2) 197 | { 198 | return !quaternion_equal(q1, q2); 199 | } 200 | 201 | int 202 | quaternion_less(quaternion q1, quaternion q2) 203 | { 204 | return 205 | (!quaternion_isnan(q1) && 206 | !quaternion_isnan(q2)) && ( 207 | q1.w != q2.w ? q1.w < q2.w : 208 | q1.x != q2.x ? q1.x < q2.x : 209 | q1.y != q2.y ? q1.y < q2.y : 210 | q1.z != q2.z ? q1.z < q2.z : 0); 211 | } 212 | 213 | int 214 | quaternion_less_equal(quaternion q1, quaternion q2) 215 | { 216 | return 217 | (!quaternion_isnan(q1) && 218 | !quaternion_isnan(q2)) && ( 219 | q1.w != q2.w ? q1.w < q2.w : 220 | q1.x != q2.x ? q1.x < q2.x : 221 | q1.y != q2.y ? q1.y < q2.y : 222 | q1.z != q2.z ? q1.z < q2.z : 1); 223 | } 224 | -------------------------------------------------------------------------------- /npytypes/quaternion/quaternion.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Quaternion math implementation 3 | * Copyright (c) 2011 Martin Ling 4 | * 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of the NumPy Developers nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTERS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | #ifndef __QUATERNION_H__ 30 | #define __QUATERNION_H__ 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | typedef struct { 37 | double w; 38 | double x; 39 | double y; 40 | double z; 41 | } quaternion; 42 | 43 | int quaternion_isnonzero(quaternion q); 44 | int quaternion_isnan(quaternion q); 45 | int quaternion_isinf(quaternion q); 46 | int quaternion_isfinite(quaternion q); 47 | double quaternion_absolute(quaternion q); 48 | quaternion quaternion_add(quaternion q1, quaternion q2); 49 | quaternion quaternion_subtract(quaternion q1, quaternion q2); 50 | quaternion quaternion_multiply(quaternion q1, quaternion q2); 51 | quaternion quaternion_divide(quaternion q1, quaternion q2); 52 | quaternion quaternion_multiply_scalar(quaternion q, double s); 53 | quaternion quaternion_divide_scalar(quaternion q, double s); 54 | quaternion quaternion_log(quaternion q); 55 | quaternion quaternion_exp(quaternion q); 56 | quaternion quaternion_power(quaternion q, quaternion p); 57 | quaternion quaternion_power_scalar(quaternion q, double p); 58 | quaternion quaternion_negative(quaternion q); 59 | quaternion quaternion_conjugate(quaternion q); 60 | quaternion quaternion_copysign(quaternion q1, quaternion q2); 61 | int quaternion_equal(quaternion q1, quaternion q2); 62 | int quaternion_not_equal(quaternion q1, quaternion q2); 63 | int quaternion_less(quaternion q1, quaternion q2); 64 | int quaternion_less_equal(quaternion q1, quaternion q2); 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /npytypes/rational/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011, Geoffrey Irving, Eugene d'Eon. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 3. The name of the author may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 19 | NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /npytypes/rational/Makefile: -------------------------------------------------------------------------------- 1 | all: rational.so 2 | 3 | rational.so: rational.c 4 | python setup.py build 5 | cp build/lib.*/rational.so . 6 | 7 | .PHONY: clean test 8 | 9 | clean: 10 | rm -f *.o *.E *.so 11 | -------------------------------------------------------------------------------- /npytypes/rational/README.md: -------------------------------------------------------------------------------- 1 | Test code for user defined types in Numpy 2 | ========================================= 3 | 4 | This code implements fixed precision rational numbers exposed to Python as 5 | a test of user-defined type support in Numpy. 6 | 7 | ### Dependencies 8 | 9 | * [python](http://python.org) 10 | * [numpy](http://numpy.scipy.org): Requires a patched version (see below) 11 | * [py.test](http://pytest.org): To run tests 12 | 13 | On a Mac, these can be obtained through [MacPorts](http://www.macports.org) via 14 | 15 | sudo port install py26-numpy py26-py 16 | 17 | The original version of this code exposed several bugs in the (hopefully) old 18 | version of numpy, so if it fails try upgrading numpy. My branch which fixed 19 | all but one of the bugs uncovered is here: 20 | 21 | https://github.com/girving/numpy/tree/fixuserloops 22 | 23 | These changes should be incorporated in the main numpy git repo fairly soon. 24 | 25 | ### Usage 26 | 27 | To build and run the tests, do 28 | 29 | make 30 | py.test 31 | -------------------------------------------------------------------------------- /npytypes/rational/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from npytypes.rational.rational import denominator, gcd, lcm, numerator, rational 4 | from npytypes.rational.info import __doc__ 5 | 6 | __all__ = ['denominator', 'gcd', 'lcm', 'numerator', 'rational'] 7 | 8 | if np.__dict__.get('rational') is not None: 9 | raise RuntimeError('The NumPy package already has a rational type') 10 | 11 | np.rational = rational 12 | np.typeDict['rational'] = np.dtype(rational) 13 | -------------------------------------------------------------------------------- /npytypes/rational/info.py: -------------------------------------------------------------------------------- 1 | __doc_title__ = "Rational dtype for NumPy" 2 | __doc__ = "Adds a rational dtype to NumPy." 3 | -------------------------------------------------------------------------------- /npytypes/rational/rational.c: -------------------------------------------------------------------------------- 1 | /* Fixed size rational numbers exposed to Python */ 2 | 3 | #define NPY_NO_DEPRECATED_API NPY_API_VERSION 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "numpy/npy_3kcompat.h" 12 | 13 | /* Relevant arithmetic exceptions */ 14 | 15 | /* Uncomment the following line to work around a bug in numpy */ 16 | /* #define ACQUIRE_GIL */ 17 | 18 | static void 19 | set_overflow(void) { 20 | #ifdef ACQUIRE_GIL 21 | /* Need to grab the GIL to dodge a bug in numpy */ 22 | PyGILState_STATE state = PyGILState_Ensure(); 23 | #endif 24 | if (!PyErr_Occurred()) { 25 | PyErr_SetString(PyExc_OverflowError, 26 | "overflow in rational arithmetic"); 27 | } 28 | #ifdef ACQUIRE_GIL 29 | PyGILState_Release(state); 30 | #endif 31 | } 32 | 33 | static void 34 | set_zero_divide(void) { 35 | #ifdef ACQUIRE_GIL 36 | /* Need to grab the GIL to dodge a bug in numpy */ 37 | PyGILState_STATE state = PyGILState_Ensure(); 38 | #endif 39 | if (!PyErr_Occurred()) { 40 | PyErr_SetString(PyExc_ZeroDivisionError, 41 | "zero divide in rational arithmetic"); 42 | } 43 | #ifdef ACQUIRE_GIL 44 | PyGILState_Release(state); 45 | #endif 46 | } 47 | 48 | /* Integer arithmetic utilities */ 49 | 50 | static NPY_INLINE int32_t 51 | safe_neg(int32_t x) { 52 | if (x==(int32_t)1<<31) { 53 | set_overflow(); 54 | } 55 | return -x; 56 | } 57 | 58 | static NPY_INLINE int32_t 59 | safe_abs32(int32_t x) { 60 | if (x>=0) { 61 | return x; 62 | } 63 | int32_t nx = -x; 64 | if (nx<0) { 65 | set_overflow(); 66 | } 67 | return nx; 68 | } 69 | 70 | static NPY_INLINE int64_t 71 | safe_abs64(int64_t x) { 72 | if (x>=0) { 73 | return x; 74 | } 75 | int64_t nx = -x; 76 | if (nx<0) { 77 | set_overflow(); 78 | } 79 | return nx; 80 | } 81 | 82 | static NPY_INLINE int64_t 83 | gcd(int64_t x, int64_t y) { 84 | x = safe_abs64(x); 85 | y = safe_abs64(y); 86 | if (x < y) { 87 | int64_t t = x; 88 | x = y; 89 | y = t; 90 | } 91 | while (y) { 92 | x = x%y; 93 | int64_t t = x; 94 | x = y; 95 | y = t; 96 | } 97 | return x; 98 | } 99 | 100 | static NPY_INLINE int64_t 101 | lcm(int64_t x, int64_t y) { 102 | if (!x || !y) { 103 | return 0; 104 | } 105 | x /= gcd(x,y); 106 | int64_t lcm = x*y; 107 | if (lcm/y!=x) { 108 | set_overflow(); 109 | } 110 | return safe_abs64(lcm); 111 | } 112 | 113 | /* Fixed precision rational numbers */ 114 | 115 | typedef struct { 116 | /* numerator */ 117 | int32_t n; 118 | /* 119 | * denominator minus one: numpy.zeros() uses memset(0) for non-object 120 | * types, so need to ensure that rational(0) has all zero bytes 121 | */ 122 | int32_t dmm; 123 | } rational; 124 | 125 | static NPY_INLINE rational 126 | make_rational_int(int64_t n) { 127 | rational r = {n,0}; 128 | if (r.n != n) { 129 | set_overflow(); 130 | } 131 | return r; 132 | } 133 | 134 | static rational 135 | make_rational_slow(int64_t n_, int64_t d_) { 136 | rational r = {0}; 137 | if (!d_) { 138 | set_zero_divide(); 139 | } 140 | else { 141 | int64_t g = gcd(n_,d_); 142 | n_ /= g; 143 | d_ /= g; 144 | r.n = n_; 145 | int32_t d = d_; 146 | if (r.n!=n_ || d!=d_) { 147 | set_overflow(); 148 | } 149 | else { 150 | if (d <= 0) { 151 | d = -d; 152 | r.n = safe_neg(r.n); 153 | } 154 | r.dmm = d-1; 155 | } 156 | } 157 | return r; 158 | } 159 | 160 | static NPY_INLINE int32_t 161 | d(rational r) { 162 | return r.dmm+1; 163 | } 164 | 165 | /* Assumes d_ > 0 */ 166 | static rational 167 | make_rational_fast(int64_t n_, int64_t d_) { 168 | int64_t g = gcd(n_,d_); 169 | n_ /= g; 170 | d_ /= g; 171 | rational r; 172 | r.n = n_; 173 | r.dmm = d_-1; 174 | if (r.n!=n_ || r.dmm+1!=d_) { 175 | set_overflow(); 176 | } 177 | return r; 178 | } 179 | 180 | static NPY_INLINE rational 181 | rational_negative(rational r) { 182 | rational x; 183 | x.n = safe_neg(r.n); 184 | x.dmm = r.dmm; 185 | return x; 186 | } 187 | 188 | static NPY_INLINE rational 189 | rational_add(rational x, rational y) { 190 | /* 191 | * Note that the numerator computation can never overflow int128_t, 192 | * since each term is strictly under 2**128/4 (since d > 0). 193 | */ 194 | return make_rational_fast((int64_t)x.n*d(y)+(int64_t)d(x)*y.n,(int64_t)d(x)*d(y)); 195 | } 196 | 197 | static NPY_INLINE rational 198 | rational_subtract(rational x, rational y) { 199 | /* We're safe from overflow as with + */ 200 | return make_rational_fast((int64_t)x.n*d(y)-(int64_t)d(x)*y.n,(int64_t)d(x)*d(y)); 201 | } 202 | 203 | static NPY_INLINE rational 204 | rational_multiply(rational x, rational y) { 205 | /* We're safe from overflow as with + */ 206 | return make_rational_fast((int64_t)x.n*y.n,(int64_t)d(x)*d(y)); 207 | } 208 | 209 | static NPY_INLINE rational 210 | rational_divide(rational x, rational y) { 211 | return make_rational_slow((int64_t)x.n*d(y),(int64_t)d(x)*y.n); 212 | } 213 | 214 | static NPY_INLINE int64_t 215 | rational_floor(rational x) { 216 | /* Always round down */ 217 | if (x.n>=0) { 218 | return x.n/d(x); 219 | } 220 | /* 221 | * This can be done without casting up to 64 bits, but it requires 222 | * working out all the sign cases 223 | */ 224 | return -((-(int64_t)x.n+d(x)-1)/d(x)); 225 | } 226 | 227 | static NPY_INLINE int64_t 228 | rational_ceil(rational x) { 229 | return -rational_floor(rational_negative(x)); 230 | } 231 | 232 | static NPY_INLINE rational 233 | rational_remainder(rational x, rational y) { 234 | return rational_subtract(x, rational_multiply(y,make_rational_int( 235 | rational_floor(rational_divide(x,y))))); 236 | } 237 | 238 | static NPY_INLINE rational 239 | rational_abs(rational x) { 240 | rational y; 241 | y.n = safe_abs32(x.n); 242 | y.dmm = x.dmm; 243 | return y; 244 | } 245 | 246 | static NPY_INLINE int64_t 247 | rational_rint(rational x) { 248 | /* 249 | * Round towards nearest integer, moving exact half integers towards 250 | * zero 251 | */ 252 | int32_t d_ = d(x); 253 | return (2*(int64_t)x.n+(x.n<0?-d_:d_))/(2*(int64_t)d_); 254 | } 255 | 256 | static NPY_INLINE int 257 | rational_sign(rational x) { 258 | return x.n<0?-1:x.n==0?0:1; 259 | } 260 | 261 | static NPY_INLINE rational 262 | rational_inverse(rational x) { 263 | rational y = {0}; 264 | if (!x.n) { 265 | set_zero_divide(); 266 | } 267 | else { 268 | y.n = d(x); 269 | int32_t d = x.n; 270 | if (d <= 0) { 271 | d = safe_neg(d); 272 | y.n = -y.n; 273 | } 274 | y.dmm = d-1; 275 | } 276 | return y; 277 | } 278 | 279 | static NPY_INLINE int 280 | rational_eq(rational x, rational y) { 281 | /* 282 | * Since we enforce d > 0, and store fractions in reduced form, 283 | * equality is easy. 284 | */ 285 | return x.n==y.n && x.dmm==y.dmm; 286 | } 287 | 288 | static NPY_INLINE int 289 | rational_ne(rational x, rational y) { 290 | return !rational_eq(x,y); 291 | } 292 | 293 | static NPY_INLINE int 294 | rational_lt(rational x, rational y) { 295 | return (int64_t)x.n*d(y) < (int64_t)y.n*d(x); 296 | } 297 | 298 | static NPY_INLINE int 299 | rational_gt(rational x, rational y) { 300 | return rational_lt(y,x); 301 | } 302 | 303 | static NPY_INLINE int 304 | rational_le(rational x, rational y) { 305 | return !rational_lt(y,x); 306 | } 307 | 308 | static NPY_INLINE int 309 | rational_ge(rational x, rational y) { 310 | return !rational_lt(x,y); 311 | } 312 | 313 | static NPY_INLINE int32_t 314 | rational_int(rational x) { 315 | return x.n/d(x); 316 | } 317 | 318 | static NPY_INLINE double 319 | rational_double(rational x) { 320 | return (double)x.n/d(x); 321 | } 322 | 323 | static NPY_INLINE int 324 | rational_nonzero(rational x) { 325 | return x.n!=0; 326 | } 327 | 328 | static int 329 | scan_rational(const char** s, rational* x) { 330 | long n,d; 331 | int offset; 332 | if (sscanf(*s,"%ld%n",&n,&offset)<=0) { 333 | return 0; 334 | } 335 | const char* ss = *s+offset; 336 | if (*ss!='/') { 337 | *s = ss; 338 | *x = make_rational_int(n); 339 | return 1; 340 | } 341 | ss++; 342 | if (sscanf(ss,"%ld%n",&d,&offset)<=0 || d<=0) { 343 | return 0; 344 | } 345 | *s = ss+offset; 346 | *x = make_rational_slow(n,d); 347 | return 1; 348 | } 349 | 350 | /* Expose rational to Python as a numpy scalar */ 351 | 352 | typedef struct { 353 | PyObject_HEAD; 354 | rational r; 355 | } PyRational; 356 | 357 | static PyTypeObject PyRational_Type; 358 | 359 | static NPY_INLINE int 360 | PyRational_Check(PyObject* object) { 361 | return PyObject_IsInstance(object,(PyObject*)&PyRational_Type); 362 | } 363 | 364 | static PyObject* 365 | PyRational_FromRational(rational x) { 366 | PyRational* p = (PyRational*)PyRational_Type.tp_alloc(&PyRational_Type,0); 367 | if (p) { 368 | p->r = x; 369 | } 370 | return (PyObject*)p; 371 | } 372 | 373 | static PyObject* 374 | pyrational_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { 375 | if (kwds && PyDict_Size(kwds)) { 376 | PyErr_SetString(PyExc_TypeError, 377 | "constructor takes no keyword arguments"); 378 | return 0; 379 | } 380 | Py_ssize_t size = PyTuple_GET_SIZE(args); 381 | if (size>2) { 382 | PyErr_SetString(PyExc_TypeError, 383 | "expected rational or numerator and optional denominator"); 384 | return 0; 385 | } 386 | PyObject* x[2] = {PyTuple_GET_ITEM(args,0),PyTuple_GET_ITEM(args,1)}; 387 | if (size==1) { 388 | if (PyRational_Check(x[0])) { 389 | Py_INCREF(x[0]); 390 | return x[0]; 391 | } 392 | else if (PyString_Check(x[0])) { 393 | const char* s = PyString_AS_STRING(x[0]); 394 | rational x; 395 | if (scan_rational(&s,&x)) { 396 | const char* p; 397 | for (p = s; *p; p++) { 398 | if (!isspace(*p)) { 399 | goto bad; 400 | } 401 | } 402 | return PyRational_FromRational(x); 403 | } 404 | bad: 405 | PyErr_Format(PyExc_ValueError, 406 | "invalid rational literal '%s'",s); 407 | return 0; 408 | } 409 | } 410 | long n[2]={0,1}; 411 | int i; 412 | for (i=0;iob_type->tp_name); 420 | } 421 | return 0; 422 | } 423 | /* Check that we had an exact integer */ 424 | PyObject* y = PyInt_FromLong(n[i]); 425 | if (!y) { 426 | return 0; 427 | } 428 | int eq = PyObject_RichCompareBool(x[i],y,Py_EQ); 429 | Py_DECREF(y); 430 | if (eq<0) { 431 | return 0; 432 | } 433 | if (!eq) { 434 | PyErr_Format(PyExc_TypeError, 435 | "expected integer %s, got %s", 436 | (i ? "denominator" : "numerator"), 437 | x[i]->ob_type->tp_name); 438 | return 0; 439 | } 440 | } 441 | rational r = make_rational_slow(n[0],n[1]); 442 | if (PyErr_Occurred()) { 443 | return 0; 444 | } 445 | return PyRational_FromRational(r); 446 | } 447 | 448 | /* 449 | * Returns Py_NotImplemented on most conversion failures, or raises an 450 | * overflow error for too long ints 451 | */ 452 | #define AS_RATIONAL(dst,object) \ 453 | rational dst = {0}; \ 454 | if (PyRational_Check(object)) { \ 455 | dst = ((PyRational*)object)->r; \ 456 | } \ 457 | else { \ 458 | long n_ = PyInt_AsLong(object); \ 459 | if (n_==-1 && PyErr_Occurred()) { \ 460 | if (PyErr_ExceptionMatches(PyExc_TypeError)) { \ 461 | PyErr_Clear(); \ 462 | Py_INCREF(Py_NotImplemented); \ 463 | return Py_NotImplemented; \ 464 | } \ 465 | return 0; \ 466 | } \ 467 | PyObject* y_ = PyInt_FromLong(n_); \ 468 | if (!y_) { \ 469 | return 0; \ 470 | } \ 471 | int eq_ = PyObject_RichCompareBool(object,y_,Py_EQ); \ 472 | Py_DECREF(y_); \ 473 | if (eq_<0) { \ 474 | return 0; \ 475 | } \ 476 | if (!eq_) { \ 477 | Py_INCREF(Py_NotImplemented); \ 478 | return Py_NotImplemented; \ 479 | } \ 480 | dst = make_rational_int(n_); \ 481 | } 482 | 483 | static PyObject* 484 | pyrational_richcompare(PyObject* a, PyObject* b, int op) { 485 | AS_RATIONAL(x,a); 486 | AS_RATIONAL(y,b); 487 | int result = 0; 488 | #define OP(py,op) case py: result = rational_##op(x,y); break; 489 | switch (op) { 490 | OP(Py_LT,lt) 491 | OP(Py_LE,le) 492 | OP(Py_EQ,eq) 493 | OP(Py_NE,ne) 494 | OP(Py_GT,gt) 495 | OP(Py_GE,ge) 496 | }; 497 | #undef OP 498 | return PyBool_FromLong(result); 499 | } 500 | 501 | static PyObject* 502 | pyrational_repr(PyObject* self) { 503 | rational x = ((PyRational*)self)->r; 504 | if (d(x)!=1) { 505 | return PyUString_FromFormat( 506 | "rational(%ld,%ld)",(long)x.n,(long)d(x)); 507 | } 508 | else { 509 | return PyUString_FromFormat( 510 | "rational(%ld)",(long)x.n); 511 | } 512 | } 513 | 514 | static PyObject* 515 | pyrational_str(PyObject* self) { 516 | rational x = ((PyRational*)self)->r; 517 | if (d(x)!=1) { 518 | return PyString_FromFormat( 519 | "%ld/%ld",(long)x.n,(long)d(x)); 520 | } 521 | else { 522 | return PyString_FromFormat( 523 | "%ld",(long)x.n); 524 | } 525 | } 526 | 527 | static long 528 | pyrational_hash(PyObject* self) { 529 | rational x = ((PyRational*)self)->r; 530 | /* Use a fairly weak hash as Python expects */ 531 | long h = 131071*x.n+524287*x.dmm; 532 | /* Never return the special error value -1 */ 533 | return h==-1?2:h; 534 | } 535 | 536 | #define RATIONAL_BINOP_2(name,exp) \ 537 | static PyObject* \ 538 | pyrational_##name(PyObject* a, PyObject* b) { \ 539 | AS_RATIONAL(x,a); \ 540 | AS_RATIONAL(y,b); \ 541 | rational z = exp; \ 542 | if (PyErr_Occurred()) { \ 543 | return 0; \ 544 | } \ 545 | return PyRational_FromRational(z); \ 546 | } 547 | #define RATIONAL_BINOP(name) RATIONAL_BINOP_2(name,rational_##name(x,y)) 548 | RATIONAL_BINOP(add) 549 | RATIONAL_BINOP(subtract) 550 | RATIONAL_BINOP(multiply) 551 | RATIONAL_BINOP(divide) 552 | RATIONAL_BINOP(remainder) 553 | RATIONAL_BINOP_2(floor_divide,make_rational_int(rational_floor(rational_divide(x,y)))) 554 | 555 | #define RATIONAL_UNOP(name,type,exp,convert) \ 556 | static PyObject* \ 557 | pyrational_##name(PyObject* self) { \ 558 | rational x = ((PyRational*)self)->r; \ 559 | type y = exp; \ 560 | if (PyErr_Occurred()) { \ 561 | return 0; \ 562 | } \ 563 | return convert(y); \ 564 | } 565 | RATIONAL_UNOP(negative,rational,rational_negative(x),PyRational_FromRational) 566 | RATIONAL_UNOP(absolute,rational,rational_abs(x),PyRational_FromRational) 567 | RATIONAL_UNOP(int,long,rational_int(x),PyInt_FromLong) 568 | RATIONAL_UNOP(float,double,rational_double(x),PyFloat_FromDouble) 569 | 570 | static PyObject* 571 | pyrational_positive(PyObject* self) { 572 | Py_INCREF(self); 573 | return self; 574 | } 575 | 576 | static int 577 | pyrational_nonzero(PyObject* self) { 578 | rational x = ((PyRational*)self)->r; 579 | return rational_nonzero(x); 580 | } 581 | 582 | static PyNumberMethods pyrational_as_number = { 583 | pyrational_add, /* nb_add */ 584 | pyrational_subtract, /* nb_subtract */ 585 | pyrational_multiply, /* nb_multiply */ 586 | pyrational_divide, /* nb_divide */ 587 | pyrational_remainder, /* nb_remainder */ 588 | 0, /* nb_divmod */ 589 | 0, /* nb_power */ 590 | pyrational_negative, /* nb_negative */ 591 | pyrational_positive, /* nb_positive */ 592 | pyrational_absolute, /* nb_absolute */ 593 | pyrational_nonzero, /* nb_nonzero */ 594 | 0, /* nb_invert */ 595 | 0, /* nb_lshift */ 596 | 0, /* nb_rshift */ 597 | 0, /* nb_and */ 598 | 0, /* nb_xor */ 599 | 0, /* nb_or */ 600 | 0, /* nb_coerce */ 601 | pyrational_int, /* nb_int */ 602 | pyrational_int, /* nb_long */ 603 | pyrational_float, /* nb_float */ 604 | 0, /* nb_oct */ 605 | 0, /* nb_hex */ 606 | 607 | 0, /* nb_inplace_add */ 608 | 0, /* nb_inplace_subtract */ 609 | 0, /* nb_inplace_multiply */ 610 | 0, /* nb_inplace_divide */ 611 | 0, /* nb_inplace_remainder */ 612 | 0, /* nb_inplace_power */ 613 | 0, /* nb_inplace_lshift */ 614 | 0, /* nb_inplace_rshift */ 615 | 0, /* nb_inplace_and */ 616 | 0, /* nb_inplace_xor */ 617 | 0, /* nb_inplace_or */ 618 | 619 | pyrational_floor_divide, /* nb_floor_divide */ 620 | pyrational_divide, /* nb_true_divide */ 621 | 0, /* nb_inplace_floor_divide */ 622 | 0, /* nb_inplace_true_divide */ 623 | 0, /* nb_index */ 624 | }; 625 | 626 | static PyObject* 627 | pyrational_n(PyObject* self, void* closure) { 628 | return PyInt_FromLong(((PyRational*)self)->r.n); 629 | } 630 | 631 | static PyObject* 632 | pyrational_d(PyObject* self, void* closure) { 633 | return PyInt_FromLong(d(((PyRational*)self)->r)); 634 | } 635 | 636 | static PyGetSetDef pyrational_getset[] = { 637 | {(char*)"n",pyrational_n,0,(char*)"numerator",0}, 638 | {(char*)"d",pyrational_d,0,(char*)"denominator",0}, 639 | {0} /* sentinel */ 640 | }; 641 | 642 | static PyTypeObject PyRational_Type = { 643 | #if defined(NPY_PY3K) 644 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 645 | #else 646 | PyObject_HEAD_INIT(&PyType_Type) 647 | 0, /* ob_size */ 648 | #endif 649 | "rational", /* tp_name */ 650 | sizeof(PyRational), /* tp_basicsize */ 651 | 0, /* tp_itemsize */ 652 | 0, /* tp_dealloc */ 653 | 0, /* tp_print */ 654 | 0, /* tp_getattr */ 655 | 0, /* tp_setattr */ 656 | #if defined(NPY_PY3K) 657 | 0, /* tp_reserved */ 658 | #else 659 | 0, /* tp_compare */ 660 | #endif 661 | pyrational_repr, /* tp_repr */ 662 | &pyrational_as_number, /* tp_as_number */ 663 | 0, /* tp_as_sequence */ 664 | 0, /* tp_as_mapping */ 665 | pyrational_hash, /* tp_hash */ 666 | 0, /* tp_call */ 667 | pyrational_str, /* tp_str */ 668 | 0, /* tp_getattro */ 669 | 0, /* tp_setattro */ 670 | 0, /* tp_as_buffer */ 671 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ 672 | "Fixed precision rational numbers", /* tp_doc */ 673 | 0, /* tp_traverse */ 674 | 0, /* tp_clear */ 675 | pyrational_richcompare, /* tp_richcompare */ 676 | 0, /* tp_weaklistoffset */ 677 | 0, /* tp_iter */ 678 | 0, /* tp_iternext */ 679 | 0, /* tp_methods */ 680 | 0, /* tp_members */ 681 | pyrational_getset, /* tp_getset */ 682 | 0, /* tp_base */ 683 | 0, /* tp_dict */ 684 | 0, /* tp_descr_get */ 685 | 0, /* tp_descr_set */ 686 | 0, /* tp_dictoffset */ 687 | 0, /* tp_init */ 688 | 0, /* tp_alloc */ 689 | pyrational_new, /* tp_new */ 690 | 0, /* tp_free */ 691 | 0, /* tp_is_gc */ 692 | 0, /* tp_bases */ 693 | 0, /* tp_mro */ 694 | 0, /* tp_cache */ 695 | 0, /* tp_subclasses */ 696 | 0, /* tp_weaklist */ 697 | 0, /* tp_del */ 698 | #if PY_VERSION_HEX >= 0x02060000 699 | 0, /* tp_version_tag */ 700 | #endif 701 | }; 702 | 703 | /* Numpy support */ 704 | 705 | static PyObject* 706 | npyrational_getitem(void* data, void* arr) { 707 | rational r; 708 | memcpy(&r,data,sizeof(rational)); 709 | return PyRational_FromRational(r); 710 | } 711 | 712 | static int 713 | npyrational_setitem(PyObject* item, void* data, void* arr) { 714 | rational r; 715 | if (PyRational_Check(item)) { 716 | r = ((PyRational*)item)->r; 717 | } 718 | else { 719 | long n = PyInt_AsLong(item); 720 | if (n==-1 && PyErr_Occurred()) { 721 | return -1; 722 | } 723 | PyObject* y = PyInt_FromLong(n); 724 | if (!y) { 725 | return -1; 726 | } 727 | int eq = PyObject_RichCompareBool(item,y,Py_EQ); 728 | Py_DECREF(y); 729 | if (eq<0) { 730 | return -1; 731 | } 732 | if (!eq) { 733 | PyErr_Format(PyExc_TypeError, 734 | "expected rational, got %s", item->ob_type->tp_name); 735 | return -1; 736 | } 737 | r = make_rational_int(n); 738 | } 739 | memcpy(data,&r,sizeof(rational)); 740 | return 0; 741 | } 742 | 743 | static NPY_INLINE void 744 | byteswap(int32_t* x) { 745 | char* p = (char*)x; 746 | size_t i; 747 | for (i = 0; i < sizeof(*x)/2; i++) { 748 | int j = sizeof(*x)-1-i; 749 | char t = p[i]; 750 | p[i] = p[j]; 751 | p[j] = t; 752 | } 753 | } 754 | 755 | static void 756 | npyrational_copyswapn(void* dst_, npy_intp dstride, void* src_, 757 | npy_intp sstride, npy_intp n, int swap, void* arr) { 758 | char *dst = (char*)dst_, *src = (char*)src_; 759 | if (!src) { 760 | return; 761 | } 762 | npy_intp i; 763 | if (swap) { 764 | for (i = 0; i < n; i++) { 765 | rational* r = (rational*)(dst+dstride*i); 766 | memcpy(r,src+sstride*i,sizeof(rational)); 767 | byteswap(&r->n); 768 | byteswap(&r->dmm); 769 | } 770 | } 771 | else if (dstride == sizeof(rational) && sstride == sizeof(rational)) { 772 | memcpy(dst, src, n*sizeof(rational)); 773 | } 774 | else { 775 | for (i = 0; i < n; i++) { 776 | memcpy(dst + dstride*i, src + sstride*i, sizeof(rational)); 777 | } 778 | } 779 | } 780 | 781 | static void 782 | npyrational_copyswap(void* dst, void* src, int swap, void* arr) { 783 | if (!src) { 784 | return; 785 | } 786 | rational* r = (rational*)dst; 787 | memcpy(r,src,sizeof(rational)); 788 | if (swap) { 789 | byteswap(&r->n); 790 | byteswap(&r->dmm); 791 | } 792 | } 793 | 794 | static int 795 | npyrational_compare(const void* d0, const void* d1, void* arr) { 796 | rational x = *(rational*)d0, 797 | y = *(rational*)d1; 798 | return rational_lt(x,y)?-1:rational_eq(x,y)?0:1; 799 | } 800 | 801 | #define FIND_EXTREME(name,op) \ 802 | static int \ 803 | npyrational_##name(void* data_, npy_intp n, npy_intp* max_ind, void* arr) { \ 804 | if (!n) { \ 805 | return 0; \ 806 | } \ 807 | const rational* data = (rational*)data_; \ 808 | npy_intp best_i = 0; \ 809 | rational best_r = data[0]; \ 810 | npy_intp i; \ 811 | for (i = 1; i < n; i++) { \ 812 | if (rational_##op(data[i],best_r)) { \ 813 | best_i = i; \ 814 | best_r = data[i]; \ 815 | } \ 816 | } \ 817 | *max_ind = best_i; \ 818 | return 0; \ 819 | } 820 | FIND_EXTREME(argmin,lt) 821 | FIND_EXTREME(argmax,gt) 822 | 823 | static void 824 | npyrational_dot(void* ip0_, npy_intp is0, void* ip1_, npy_intp is1, 825 | void* op, npy_intp n, void* arr) { 826 | rational r = {0}; 827 | const char *ip0 = (char*)ip0_, *ip1 = (char*)ip1_; 828 | npy_intp i; 829 | for (i = 0; i < n; i++) { 830 | r = rational_add(r,rational_multiply(*(rational*)ip0,*(rational*)ip1)); 831 | ip0 += is0; 832 | ip1 += is1; 833 | } 834 | *(rational*)op = r; 835 | } 836 | 837 | static npy_bool 838 | npyrational_nonzero(void* data, void* arr) { 839 | rational r; 840 | memcpy(&r,data,sizeof(r)); 841 | return rational_nonzero(r)?NPY_TRUE:NPY_FALSE; 842 | } 843 | 844 | static int 845 | npyrational_fill(void* data_, npy_intp length, void* arr) { 846 | rational* data = (rational*)data_; 847 | rational delta = rational_subtract(data[1],data[0]); 848 | rational r = data[1]; 849 | npy_intp i; 850 | for (i = 2; i < length; i++) { 851 | r = rational_add(r,delta); 852 | data[i] = r; 853 | } 854 | return 0; 855 | } 856 | 857 | static int 858 | npyrational_fillwithscalar(void* buffer_, npy_intp length, 859 | void* value, void* arr) { 860 | rational r = *(rational*)value; 861 | rational* buffer = (rational*)buffer_; 862 | npy_intp i; 863 | for (i = 0; i < length; i++) { 864 | buffer[i] = r; 865 | } 866 | return 0; 867 | } 868 | 869 | static PyArray_ArrFuncs npyrational_arrfuncs; 870 | 871 | typedef struct { char c; rational r; } align_test; 872 | 873 | PyArray_Descr npyrational_descr = { 874 | PyObject_HEAD_INIT(0) 875 | &PyRational_Type, /* typeobj */ 876 | 'V', /* kind */ 877 | 'r', /* type */ 878 | '=', /* byteorder */ 879 | /* 880 | * For now, we need NPY_NEEDS_PYAPI in order to make numpy detect our 881 | * exceptions. This isn't technically necessary, 882 | * since we're careful about thread safety, and hopefully future 883 | * versions of numpy will recognize that. 884 | */ 885 | NPY_NEEDS_PYAPI | NPY_USE_GETITEM | NPY_USE_SETITEM, /* hasobject */ 886 | 0, /* type_num */ 887 | sizeof(rational), /* elsize */ 888 | offsetof(align_test,r), /* alignment */ 889 | 0, /* subarray */ 890 | 0, /* fields */ 891 | 0, /* names */ 892 | &npyrational_arrfuncs, /* f */ 893 | }; 894 | 895 | #define DEFINE_CAST(From,To,statement) \ 896 | static void \ 897 | npycast_##From##_##To(void* from_, void* to_, npy_intp n, void* fromarr, void* toarr) { \ 898 | const From* from = (From*)from_; \ 899 | To* to = (To*)to_; \ 900 | npy_intp i; \ 901 | for (i = 0; i < n; i++) { \ 902 | From x = from[i]; \ 903 | statement \ 904 | to[i] = y; \ 905 | } \ 906 | } 907 | #define DEFINE_INT_CAST(bits) \ 908 | DEFINE_CAST(int##bits##_t,rational,rational y = make_rational_int(x);) \ 909 | DEFINE_CAST(rational,int##bits##_t,int32_t z = rational_int(x); int##bits##_t y = z; if (y != z) set_overflow();) 910 | DEFINE_INT_CAST(8) 911 | DEFINE_INT_CAST(16) 912 | DEFINE_INT_CAST(32) 913 | DEFINE_INT_CAST(64) 914 | DEFINE_CAST(rational,float,double y = rational_double(x);) 915 | DEFINE_CAST(rational,double,double y = rational_double(x);) 916 | DEFINE_CAST(npy_bool,rational,rational y = make_rational_int(x);) 917 | DEFINE_CAST(rational,npy_bool,npy_bool y = rational_nonzero(x);) 918 | 919 | #define BINARY_UFUNC(name,intype0,intype1,outtype,exp) \ 920 | void name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 921 | npy_intp is0 = steps[0], is1 = steps[1], os = steps[2], n = *dimensions; \ 922 | char *i0 = args[0], *i1 = args[1], *o = args[2]; \ 923 | int k; \ 924 | for (k = 0; k < n; k++) { \ 925 | intype0 x = *(intype0*)i0; \ 926 | intype1 y = *(intype1*)i1; \ 927 | *(outtype*)o = exp; \ 928 | i0 += is0; i1 += is1; o += os; \ 929 | } \ 930 | } 931 | #define RATIONAL_BINARY_UFUNC(name,type,exp) BINARY_UFUNC(rational_ufunc_##name,rational,rational,type,exp) 932 | RATIONAL_BINARY_UFUNC(add,rational,rational_add(x,y)) 933 | RATIONAL_BINARY_UFUNC(subtract,rational,rational_subtract(x,y)) 934 | RATIONAL_BINARY_UFUNC(multiply,rational,rational_multiply(x,y)) 935 | RATIONAL_BINARY_UFUNC(divide,rational,rational_divide(x,y)) 936 | RATIONAL_BINARY_UFUNC(remainder,rational,rational_remainder(x,y)) 937 | RATIONAL_BINARY_UFUNC(floor_divide,rational,make_rational_int(rational_floor(rational_divide(x,y)))) 938 | PyUFuncGenericFunction rational_ufunc_true_divide = rational_ufunc_divide; 939 | RATIONAL_BINARY_UFUNC(minimum,rational,rational_lt(x,y)?x:y) 940 | RATIONAL_BINARY_UFUNC(maximum,rational,rational_lt(x,y)?y:x) 941 | RATIONAL_BINARY_UFUNC(equal,npy_bool,rational_eq(x,y)) 942 | RATIONAL_BINARY_UFUNC(not_equal,npy_bool,rational_ne(x,y)) 943 | RATIONAL_BINARY_UFUNC(less,npy_bool,rational_lt(x,y)) 944 | RATIONAL_BINARY_UFUNC(greater,npy_bool,rational_gt(x,y)) 945 | RATIONAL_BINARY_UFUNC(less_equal,npy_bool,rational_le(x,y)) 946 | RATIONAL_BINARY_UFUNC(greater_equal,npy_bool,rational_ge(x,y)) 947 | 948 | BINARY_UFUNC(gcd_ufunc,int64_t,int64_t,int64_t,gcd(x,y)) 949 | BINARY_UFUNC(lcm_ufunc,int64_t,int64_t,int64_t,lcm(x,y)) 950 | 951 | #define UNARY_UFUNC(name,type,exp) \ 952 | void rational_ufunc_##name(char** args, npy_intp* dimensions, npy_intp* steps, void* data) { \ 953 | npy_intp is = steps[0], os = steps[1], n = *dimensions; \ 954 | char *i = args[0], *o = args[1]; \ 955 | int k; \ 956 | for (k = 0; k < n; k++) { \ 957 | rational x = *(rational*)i; \ 958 | *(type*)o = exp; \ 959 | i += is; o += os; \ 960 | } \ 961 | } 962 | UNARY_UFUNC(negative,rational,rational_negative(x)) 963 | UNARY_UFUNC(absolute,rational,rational_abs(x)) 964 | UNARY_UFUNC(floor,rational,make_rational_int(rational_floor(x))) 965 | UNARY_UFUNC(ceil,rational,make_rational_int(rational_ceil(x))) 966 | UNARY_UFUNC(trunc,rational,make_rational_int(x.n/d(x))) 967 | UNARY_UFUNC(square,rational,rational_multiply(x,x)) 968 | UNARY_UFUNC(rint,rational,make_rational_int(rational_rint(x))) 969 | UNARY_UFUNC(sign,rational,make_rational_int(rational_sign(x))) 970 | UNARY_UFUNC(reciprocal,rational,rational_inverse(x)) 971 | UNARY_UFUNC(numerator,int64_t,x.n) 972 | UNARY_UFUNC(denominator,int64_t,d(x)) 973 | 974 | static NPY_INLINE void 975 | rational_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps) 976 | { 977 | /* pointers to data for input and output arrays */ 978 | char *ip1 = args[0]; 979 | char *ip2 = args[1]; 980 | char *op = args[2]; 981 | 982 | /* lengths of core dimensions */ 983 | npy_intp dm = dimensions[0]; 984 | npy_intp dn = dimensions[1]; 985 | npy_intp dp = dimensions[2]; 986 | 987 | /* striding over core dimensions */ 988 | npy_intp is1_m = steps[0]; 989 | npy_intp is1_n = steps[1]; 990 | npy_intp is2_n = steps[2]; 991 | npy_intp is2_p = steps[3]; 992 | npy_intp os_m = steps[4]; 993 | npy_intp os_p = steps[5]; 994 | 995 | /* core dimensions counters */ 996 | npy_intp m, p; 997 | 998 | /* calculate dot product for each row/column vector pair */ 999 | for (m = 0; m < dm; m++) { 1000 | for (p = 0; p < dp; p++) { 1001 | npyrational_dot(ip1, is1_n, ip2, is2_n, op, dn, NULL); 1002 | 1003 | /* advance to next column of 2nd input array and output array */ 1004 | ip2 += is2_p; 1005 | op += os_p; 1006 | } 1007 | 1008 | /* reset to first column of 2nd input array and output array */ 1009 | ip2 -= is2_p * p; 1010 | op -= os_p * p; 1011 | 1012 | /* advance to next row of 1st input array and output array */ 1013 | ip1 += is1_m; 1014 | op += os_m; 1015 | } 1016 | } 1017 | 1018 | 1019 | static void 1020 | rational_gufunc_matrix_multiply(char **args, npy_intp *dimensions, npy_intp *steps, void *NPY_UNUSED(func)) 1021 | { 1022 | /* outer dimensions counter */ 1023 | npy_intp N_; 1024 | 1025 | /* length of flattened outer dimensions */ 1026 | npy_intp dN = dimensions[0]; 1027 | 1028 | /* striding over flattened outer dimensions for input and output arrays */ 1029 | npy_intp s0 = steps[0]; 1030 | npy_intp s1 = steps[1]; 1031 | npy_intp s2 = steps[2]; 1032 | 1033 | /* loop through outer dimensions, performing matrix multiply on core dimensions for each loop */ 1034 | for (N_ = 0; N_ < dN; N_++, args[0] += s0, args[1] += s1, args[2] += s2) { 1035 | rational_matrix_multiply(args, dimensions+1, steps+3); 1036 | } 1037 | } 1038 | 1039 | 1040 | PyMethodDef module_methods[] = { 1041 | {0} /* sentinel */ 1042 | }; 1043 | 1044 | #if defined(NPY_PY3K) 1045 | static struct PyModuleDef moduledef = { 1046 | PyModuleDef_HEAD_INIT, 1047 | "rational", 1048 | NULL, 1049 | -1, 1050 | module_methods, 1051 | NULL, 1052 | NULL, 1053 | NULL, 1054 | NULL 1055 | }; 1056 | #endif 1057 | 1058 | #if defined(NPY_PY3K) 1059 | PyMODINIT_FUNC PyInit_rational(void) { 1060 | #else 1061 | PyMODINIT_FUNC initrational(void) { 1062 | #endif 1063 | 1064 | PyObject *m; 1065 | 1066 | import_array(); 1067 | if (PyErr_Occurred()) { 1068 | return NULL; 1069 | } 1070 | import_umath(); 1071 | if (PyErr_Occurred()) { 1072 | return NULL; 1073 | } 1074 | PyObject* numpy_str = PyUString_FromString("numpy"); 1075 | if (!numpy_str) { 1076 | return NULL; 1077 | } 1078 | PyObject* numpy = PyImport_Import(numpy_str); 1079 | Py_DECREF(numpy_str); 1080 | if (!numpy) { 1081 | return NULL; 1082 | } 1083 | 1084 | /* Can't set this until we import numpy */ 1085 | PyRational_Type.tp_base = &PyGenericArrType_Type; 1086 | 1087 | /* Initialize rational type object */ 1088 | if (PyType_Ready(&PyRational_Type) < 0) { 1089 | return NULL; 1090 | } 1091 | 1092 | /* Initialize rational descriptor */ 1093 | PyArray_InitArrFuncs(&npyrational_arrfuncs); 1094 | npyrational_arrfuncs.getitem = npyrational_getitem; 1095 | npyrational_arrfuncs.setitem = npyrational_setitem; 1096 | npyrational_arrfuncs.copyswapn = npyrational_copyswapn; 1097 | npyrational_arrfuncs.copyswap = npyrational_copyswap; 1098 | npyrational_arrfuncs.compare = npyrational_compare; 1099 | npyrational_arrfuncs.argmin = npyrational_argmin; 1100 | npyrational_arrfuncs.argmax = npyrational_argmax; 1101 | npyrational_arrfuncs.dotfunc = npyrational_dot; 1102 | npyrational_arrfuncs.nonzero = npyrational_nonzero; 1103 | npyrational_arrfuncs.fill = npyrational_fill; 1104 | npyrational_arrfuncs.fillwithscalar = npyrational_fillwithscalar; 1105 | /* Left undefined: scanfunc, fromstr, sort, argsort */ 1106 | Py_TYPE(&npyrational_descr) = &PyArrayDescr_Type; 1107 | int npy_rational = PyArray_RegisterDataType(&npyrational_descr); 1108 | if (npy_rational<0) { 1109 | return NULL; 1110 | } 1111 | 1112 | /* Support dtype(rational) syntax */ 1113 | if (PyDict_SetItemString(PyRational_Type.tp_dict,"dtype",(PyObject*)&npyrational_descr)<0) { 1114 | return NULL; 1115 | } 1116 | 1117 | /* Register casts to and from rational */ 1118 | #define REGISTER_CAST(From,To,from_descr,to_typenum,safe) \ 1119 | PyArray_Descr* from_descr_##From##_##To = (from_descr); \ 1120 | if (PyArray_RegisterCastFunc(from_descr_##From##_##To,(to_typenum),npycast_##From##_##To)<0) { \ 1121 | return NULL; \ 1122 | } \ 1123 | if (safe && PyArray_RegisterCanCast(from_descr_##From##_##To,(to_typenum),NPY_NOSCALAR)<0) { \ 1124 | return NULL; \ 1125 | } 1126 | #define REGISTER_INT_CASTS(bits) \ 1127 | REGISTER_CAST(int##bits##_t,rational,PyArray_DescrFromType(NPY_INT##bits),npy_rational,1) \ 1128 | REGISTER_CAST(rational,int##bits##_t,&npyrational_descr,NPY_INT##bits,0) 1129 | REGISTER_INT_CASTS(8) 1130 | REGISTER_INT_CASTS(16) 1131 | REGISTER_INT_CASTS(32) 1132 | REGISTER_INT_CASTS(64) 1133 | REGISTER_CAST(rational,float,&npyrational_descr,NPY_FLOAT,0) 1134 | REGISTER_CAST(rational,double,&npyrational_descr,NPY_DOUBLE,1) 1135 | REGISTER_CAST(npy_bool,rational,PyArray_DescrFromType(NPY_BOOL),npy_rational,1) 1136 | REGISTER_CAST(rational,npy_bool,&npyrational_descr,NPY_BOOL,0) 1137 | 1138 | /* Register ufuncs */ 1139 | #define REGISTER_UFUNC(name,...) { \ 1140 | PyUFuncObject* ufunc = (PyUFuncObject*)PyObject_GetAttrString(numpy,#name); \ 1141 | if (!ufunc) { \ 1142 | return NULL; \ 1143 | } \ 1144 | int _types[] = __VA_ARGS__; \ 1145 | if (sizeof(_types)/sizeof(int)!=ufunc->nargs) { \ 1146 | PyErr_Format(PyExc_AssertionError,"ufunc %s takes %d arguments, our loop takes %ld",#name,ufunc->nargs,sizeof(_types)/sizeof(int)); \ 1147 | return NULL; \ 1148 | } \ 1149 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,_types,0)<0) { \ 1150 | return NULL; \ 1151 | } \ 1152 | } 1153 | #define REGISTER_UFUNC_BINARY_RATIONAL(name) REGISTER_UFUNC(name,{npy_rational,npy_rational,npy_rational}) 1154 | #define REGISTER_UFUNC_BINARY_COMPARE(name) REGISTER_UFUNC(name,{npy_rational,npy_rational,NPY_BOOL}) 1155 | #define REGISTER_UFUNC_UNARY(name) REGISTER_UFUNC(name,{npy_rational,npy_rational}) 1156 | /* Binary */ 1157 | REGISTER_UFUNC_BINARY_RATIONAL(add) 1158 | REGISTER_UFUNC_BINARY_RATIONAL(subtract) 1159 | REGISTER_UFUNC_BINARY_RATIONAL(multiply) 1160 | REGISTER_UFUNC_BINARY_RATIONAL(divide) 1161 | REGISTER_UFUNC_BINARY_RATIONAL(remainder) 1162 | REGISTER_UFUNC_BINARY_RATIONAL(true_divide) 1163 | REGISTER_UFUNC_BINARY_RATIONAL(floor_divide) 1164 | REGISTER_UFUNC_BINARY_RATIONAL(minimum) 1165 | REGISTER_UFUNC_BINARY_RATIONAL(maximum) 1166 | /* Comparisons */ 1167 | REGISTER_UFUNC_BINARY_COMPARE(equal) 1168 | REGISTER_UFUNC_BINARY_COMPARE(not_equal) 1169 | REGISTER_UFUNC_BINARY_COMPARE(less) 1170 | REGISTER_UFUNC_BINARY_COMPARE(greater) 1171 | REGISTER_UFUNC_BINARY_COMPARE(less_equal) 1172 | REGISTER_UFUNC_BINARY_COMPARE(greater_equal) 1173 | /* Unary */ 1174 | REGISTER_UFUNC_UNARY(negative) 1175 | REGISTER_UFUNC_UNARY(absolute) 1176 | REGISTER_UFUNC_UNARY(floor) 1177 | REGISTER_UFUNC_UNARY(ceil) 1178 | REGISTER_UFUNC_UNARY(trunc) 1179 | REGISTER_UFUNC_UNARY(rint) 1180 | REGISTER_UFUNC_UNARY(square) 1181 | REGISTER_UFUNC_UNARY(reciprocal) 1182 | REGISTER_UFUNC_UNARY(sign) 1183 | 1184 | /* Create module */ 1185 | #if defined(NPY_PY3K) 1186 | m = PyModule_Create(&moduledef); 1187 | #else 1188 | m = Py_InitModule("rational", module_methods); 1189 | #endif 1190 | 1191 | if (!m) { 1192 | return NULL; 1193 | } 1194 | 1195 | /* Add rational type */ 1196 | Py_INCREF(&PyRational_Type); 1197 | PyModule_AddObject(m,"rational",(PyObject*)&PyRational_Type); 1198 | 1199 | /* Create matrix multiply generalized ufunc */ 1200 | PyObject* gufunc = PyUFunc_FromFuncAndDataAndSignature(0,0,0,0,2,1,PyUFunc_None,(char*)"matrix_multiply",(char*)"return result of multiplying two matrices of rationals",0,"(m,n),(n,p)->(m,p)"); 1201 | if (!gufunc) { 1202 | return NULL; 1203 | } 1204 | int types2[3] = {npy_rational,npy_rational,npy_rational}; 1205 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)gufunc,npy_rational,rational_gufunc_matrix_multiply,types2,0) < 0) { 1206 | return NULL; 1207 | } 1208 | PyModule_AddObject(m,"matrix_multiply",(PyObject*)gufunc); 1209 | 1210 | /* Create numerator and denominator ufuncs */ 1211 | #define NEW_UNARY_UFUNC(name,type,doc) { \ 1212 | PyObject* ufunc = PyUFunc_FromFuncAndData(0,0,0,0,1,1,PyUFunc_None,(char*)#name,(char*)doc,0); \ 1213 | if (!ufunc) { \ 1214 | return NULL; \ 1215 | } \ 1216 | int types[2] = {npy_rational,type}; \ 1217 | if (PyUFunc_RegisterLoopForType((PyUFuncObject*)ufunc,npy_rational,rational_ufunc_##name,types,0)<0) { \ 1218 | return NULL; \ 1219 | } \ 1220 | PyModule_AddObject(m,#name,(PyObject*)ufunc); \ 1221 | } 1222 | NEW_UNARY_UFUNC(numerator,NPY_INT64,"rational number numerator"); 1223 | NEW_UNARY_UFUNC(denominator,NPY_INT64,"rational number denominator"); 1224 | 1225 | /* Create gcd and lcm ufuncs */ 1226 | #define GCD_LCM_UFUNC(name,type,doc) { \ 1227 | static const PyUFuncGenericFunction func[1] = {name##_ufunc}; \ 1228 | static const char types[3] = {type,type,type}; \ 1229 | static void* data[1] = {0}; \ 1230 | PyObject* ufunc = PyUFunc_FromFuncAndData((PyUFuncGenericFunction*)func,data,(char*)types,1,2,1,PyUFunc_One,(char*)#name,(char*)doc,0); \ 1231 | if (!ufunc) { \ 1232 | return NULL; \ 1233 | } \ 1234 | PyModule_AddObject(m,#name,(PyObject*)ufunc); \ 1235 | } 1236 | GCD_LCM_UFUNC(gcd,NPY_INT64,"greatest common denominator of two integers"); 1237 | GCD_LCM_UFUNC(lcm,NPY_INT64,"least common multiple of two integers"); 1238 | 1239 | return m; 1240 | } 1241 | -------------------------------------------------------------------------------- /npytypes/rational/test_rational.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import division 4 | from numpy import * 5 | from numpy.testing import assert_ 6 | from rational import * 7 | 8 | R = rational 9 | 10 | def test_misc(): 11 | x = R() 12 | y = R(7) 13 | z = R(-6,-10) 14 | assert_(not x) 15 | assert_(y and z) 16 | assert_(z.n is 3) 17 | assert_(z.d is 5) 18 | assert_(str(y)=='7') 19 | assert_(str(z)=='3/5') 20 | assert_(repr(y)=='rational(7)') 21 | assert_(repr(z)=='rational(3,5)') 22 | 23 | def test_parse(): 24 | assert_(rational("4")==4) 25 | assert_(rational(" -4 ")==-4) 26 | assert_(rational("3/5")==R(3,5)) 27 | assert_(rational(" -3/5 ")==R(-3,5)) 28 | for s in '-4 5','1/0','1/-1','1/': 29 | try: 30 | rational(s) 31 | assert_(False) 32 | except ValueError: 33 | pass 34 | 35 | def test_compare(): 36 | random.seed(1262081) 37 | for _ in xrange(100): 38 | xn,yn = random.randint(-10,10,2) 39 | xd,yd = random.randint(1,10,2) 40 | x,y = R(xn,xd),R(yn,yd) 41 | assert_(bool(x)==bool(xn)) 42 | assert_((x==y)==(xn*yd==yn*xd)) 43 | assert_((xy)==(xn*yd>yn*xd)) 45 | assert_((x<=y)==(xn*yd<=yn*xd)) 46 | assert_((x>=y)==(xn*yd>=yn*xd)) 47 | # Not true in general, but should be for this sample size 48 | assert_((hash(x)==hash(y))==(x==y)) 49 | 50 | def test_arithmetic(): 51 | random.seed(1262081) 52 | for _ in xrange(100): 53 | xn,yn,zn = random.randint(-100,100,3) 54 | xd,yd,zd = [n if n else 1 for n in random.randint(-100,100,3)] 55 | x,y,z = R(xn,xd),R(yn,yd),R(zn,zd) 56 | assert_(-x==R(-xn,xd)) 57 | assert_(+x is x) 58 | assert_(--x==x) 59 | assert_(x+y==R(xn*yd+yn*xd,xd*yd)) 60 | assert_(x+y==x--y==R(xn*yd+yn*xd,xd*yd)) 61 | assert_(-x+y==-(x-y)) 62 | assert_((x+y)+z==x+(y+z)) 63 | assert_(x*y==R(xn*yn,xd*yd)) 64 | assert_((x*y)*z==x*(y*z)) 65 | assert_(-(x*y)==(-x)*y) 66 | assert_(x*y==y*x) 67 | assert_(x*(y+z)==x*y+x*z) 68 | if y: 69 | assert_(x/y==R(xn*yd,xd*yn)) 70 | assert_(x/y*y==x) 71 | assert_(x//y==xn*yd//(xd*yn)) 72 | assert_(x%y==x-x//y*y) 73 | assert_(x+7==7+x==x+R(7)) 74 | assert_(x*7==7*x==x*R(7)) 75 | assert_(int(x)==int(xn/xd)) 76 | assert_(allclose(float(x),xn/xd)) 77 | assert_(abs(x)==R(abs(xn),abs(xd))) 78 | # TODO: test floor, ceil, abs 79 | 80 | def test_errors(): 81 | # Check invalid constructions 82 | for args in (R(3,2),4),(1.2,),(1,2,3): 83 | try: 84 | R(*args) 85 | assert_(False) 86 | except TypeError: 87 | pass 88 | for args in (1<<80,),(2,1<<80): 89 | try: 90 | R(*args) 91 | assert_(False) 92 | except OverflowError: 93 | pass 94 | # Check for zero divisions 95 | try: 96 | R(1,0) 97 | assert_(False) 98 | except ZeroDivisionError: 99 | pass 100 | try: 101 | R(7)/R() 102 | assert_(False) 103 | except ZeroDivisionError: 104 | pass 105 | # Check for LONG_MIN overflows 106 | for args in (-1<<63,-1),(1<<63,): 107 | try: 108 | R(*args) 109 | assert_(False) 110 | except OverflowError: 111 | pass 112 | # Check for overflow in addition 113 | r = R(1<<30) 114 | try: 115 | r+r 116 | assert_(False) 117 | except OverflowError: 118 | pass 119 | # Check for overflow in multiplication 120 | # Twin primes from http://primes.utm.edu/lists/small/10ktwins.txt 121 | p = R(1262081,1262083) 122 | r = p 123 | for _ in xrange(int(log(2.**31)/log(r.d))-1): 124 | r *= p 125 | try: 126 | r*p 127 | assert_(False) 128 | except OverflowError: 129 | pass 130 | # Float/rational arithmetic should fail 131 | for x,y in (.2,R(3,2)),(R(3,2),.2): 132 | try: 133 | x+y 134 | assert_(False) 135 | except TypeError: 136 | pass 137 | 138 | def test_numpy_basic(): 139 | d = dtype(rational) 140 | assert_(d.itemsize==8) 141 | x = zeros(5,d) 142 | assert_(type(x[2]) is rational) 143 | assert_(x[3]==0) 144 | assert_(ones(5,d)[3]==1) 145 | x[2] = 2 146 | assert_(x[2]==2) 147 | x[3] = R(4,5) 148 | assert_(5*x[3]==4) 149 | try: 150 | x[4] = 1.2 151 | assert_(False) 152 | except TypeError: 153 | pass 154 | i = arange(R(1,3),R(5,3),R(1,3)) 155 | assert_(i.dtype is d) 156 | assert_(all(i==[R(1,3),R(2,3),R(3,3),R(4,3)])) 157 | assert_(numerator(i).dtype==denominator(i).dtype==dtype(int64)) 158 | assert_(all(numerator(i)==[1,2,1,4])) 159 | assert_(all(denominator(i)==[3,3,1,3])) 160 | y = zeros(4,d) 161 | y[1:3] = i[1:3] # Test unstride copyswapn 162 | assert_(all(y==[0,R(2,3),R(3,3),0])) 163 | assert_(all(nonzero(y)[0]==(1,2))) 164 | y[::3] = i[:2] # Test strided copyswapn 165 | assert_(all(y==[R(1,3),R(2,3),R(3,3),R(2,3)])) 166 | assert_(searchsorted(arange(0,20),R(7,2))==4) # Test compare 167 | assert_(argmin(y)==0) 168 | assert_(argmax(y)==2) 169 | assert_(y.min()==R(1,3)) 170 | assert_(y.max()==1) 171 | assert_(dot(i,y)==R(22,9)) 172 | y[:] = 7 # Test fillwithscalar 173 | assert_(all(y==7)) 174 | 175 | def test_numpy_cast(): 176 | r = arange(R(10,3),step=R(1,3),dtype=rational) 177 | # Check integer to rational conversion 178 | for T in int8,int32,int64: 179 | n = arange(10,dtype=T) 180 | assert_(all(n.astype(rational)==3*r)) 181 | assert_(all(n+r==4*r)) 182 | # Check rational to integer conversion 183 | assert_(all(r.astype(int)==r.astype(float).astype(int))) 184 | # Check detection of overflow during casts 185 | for x in array(1<<40),array([1<<40]): 186 | try: 187 | x.astype(int64).astype(rational) 188 | assert_(False) 189 | except OverflowError: 190 | pass 191 | # Check conversion to and from floating point 192 | for T in float,double: 193 | f = arange(10,dtype=float)/3 194 | assert_(allclose(r.astype(float),f)) 195 | rf = r+f 196 | assert_(rf.dtype==dtype(float)) 197 | assert_(allclose(rf,2*f)) 198 | try: 199 | f.astype(rational) 200 | assert_(False) 201 | except ValueError: 202 | pass 203 | 204 | def test_numpy_ufunc(): 205 | d = dtype(rational) 206 | # Exhaustively check arithmetic for all small numerators and denominators 207 | N = arange(-10,10).reshape(-1,1) 208 | D = arange(1,10).reshape(1,-1) 209 | x = (N.astype(rational)/D).reshape(-1,1) 210 | y = x.reshape(1,-1) 211 | s = y+(y==0) 212 | xf = x.astype(float) 213 | for f in add,subtract,multiply,minimum,maximum,divide,true_divide: 214 | z = s if f in (divide,true_divide) else y 215 | fxy = f(x,z) 216 | assert_(fxy.dtype is d) 217 | assert_(allclose(fxy,f(xf,z))) 218 | assert_(all(x//s==floor(x/s))) 219 | assert_(all(x%s==x-x//s*s)) 220 | xn,yn = numerator(x),numerator(y) 221 | xd,yd = denominator(x),denominator(y) 222 | for f in equal,not_equal,less,greater,less_equal,greater_equal: 223 | assert_(all(f(x,y)==f(xn*yd,yn*xd))) 224 | for f in negative,absolute,floor,ceil,trunc,square,sign: 225 | fx = f(x) 226 | assert_(fx.dtype is d) 227 | assert_(allclose(fx,f(xf))) 228 | assert_(all(denominator(rint(x))==1)) 229 | assert_(all(absolute(rint(x)-x)<=R(1,2))) 230 | assert_(all(reciprocal(s)*s==1)) 231 | # Check that missing ufuncs promote to float 232 | r = array([R(5,3)]) 233 | assert_(r.dtype==dtype(rational)) 234 | assert_(sin(r).dtype==dtype(float)) 235 | 236 | def test_gcd_lcm(): 237 | x = arange(-10,10).reshape(-1,1) 238 | y = x.reshape(1,-1) 239 | z = x.reshape(-1,1,1) 240 | g = gcd(x,y) 241 | l = lcm(x,y) 242 | assert_(all(g*l==absolute(x*y))) 243 | assert_(all(gcd(x,lcm(y,z))==lcm(gcd(x,y),gcd(x,z)))) 244 | assert_(all(gcd(2,[1,2,3,4,5,6])==[1,2,1,2,1,2])) 245 | assert_(all(lcm(2,[1,2,3,4,5,6])==[2,2,6,4,10,6])) 246 | assert_(lcm.reduce(arange(1,10))==2520) 247 | 248 | def test_numpy_errors(): 249 | # Check that exceptions inside ufuncs are detected 250 | r = array([1<<30]).astype(rational) 251 | try: 252 | r+r 253 | assert_(False) 254 | except OverflowError: 255 | pass 256 | r = zeros(3,rational) 257 | try: 258 | reciprocal(r) 259 | assert_(False) 260 | except ZeroDivisionError: 261 | pass 262 | 263 | if __name__=='__main__': 264 | test_parse() 265 | test_numpy_cast() 266 | test_numpy_basic() 267 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup, Extension 2 | import numpy as np 3 | 4 | ext_modules = [] 5 | 6 | ext = Extension('npytypes.rational.rational', 7 | sources=['npytypes/rational/rational.c'], 8 | include_dirs=[np.get_include()]) 9 | ext_modules.append(ext) 10 | 11 | ext = Extension('npytypes.quaternion.numpy_quaternion', 12 | sources=['npytypes/quaternion/quaternion.c', 13 | 'npytypes/quaternion/numpy_quaternion.c'], 14 | include_dirs=[np.get_include()], 15 | extra_compile_args=['-std=c99']) 16 | ext_modules.append(ext) 17 | 18 | setup(name='npytypes', 19 | version='0.1', 20 | description='NumPy type extensions', 21 | packages=['npytypes', 22 | 'npytypes.quaternion', 23 | 'npytypes.rational' 24 | ], 25 | ext_modules=ext_modules) 26 | --------------------------------------------------------------------------------