{"id":13688864,"url":"https://github.com/WarrenWeckesser/ufunclab","last_synced_at":"2025-05-01T20:31:05.079Z","repository":{"id":40240817,"uuid":"209852656","full_name":"WarrenWeckesser/ufunclab","owner":"WarrenWeckesser","description":"NumPy ufuncs and utilities.","archived":false,"fork":false,"pushed_at":"2025-03-17T14:52:30.000Z","size":1391,"stargazers_count":52,"open_issues_count":3,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-07T08:32:57.469Z","etag":null,"topics":["numpy","python","ufunc"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WarrenWeckesser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-20T18:07:06.000Z","updated_at":"2025-03-17T14:52:33.000Z","dependencies_parsed_at":"2023-09-22T04:51:17.825Z","dependency_job_id":"2354a501-442f-4956-9d01-a8c17584b898","html_url":"https://github.com/WarrenWeckesser/ufunclab","commit_stats":{"total_commits":597,"total_committers":2,"mean_commits":298.5,"dds":"0.0016750418760469454","last_synced_commit":"7818f5a94dd42e2c49bc222b233cb0ada1f4f847"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WarrenWeckesser%2Fufunclab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WarrenWeckesser%2Fufunclab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WarrenWeckesser%2Fufunclab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WarrenWeckesser%2Fufunclab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WarrenWeckesser","download_url":"https://codeload.github.com/WarrenWeckesser/ufunclab/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251940560,"owners_count":21668555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["numpy","python","ufunc"],"created_at":"2024-08-02T15:01:25.472Z","updated_at":"2025-05-01T20:31:05.058Z","avatar_url":"https://github.com/WarrenWeckesser.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"ufunclab\n========\n\nSome NumPy `ufuncs`, and some related tools.\n\nLinks to reference material related to NumPy's C API for ufuncs\nand gufuncs are given [below](#resources).\n\n\nBuilding and installing\n-----------------------\n\n`ufunclab` uses `meson-python` as its build system.  Currently no\npre-built wheels are provided, so to install it, you will have to check\nout the git repository, and in the top-level directory, run\n\n    pip install .\n\nin the terminal or command prompt.  To run the unit tests after\ninstalling, run\n\n    pytest --pyargs ufunclab\n\nTo build `ufunclab`, a C99-compatible C compiler and a C++17-compatible C++\ncompiler are required.\n\nThe unit tests require [pytest](https://docs.pytest.org/).\n\nThe test suite is run as a GitHub actions workflow with Python versions\n3.9 to 3.13 and with several recent releases of NumPy.\n\n\nWhat's in ufunclab?\n-------------------\n\n***Element-wise ufuncs***\n\nMost of the element-wise ufuncs are implemented by writing the core\ncalculation as a templated C++ function, and using some Python code to\nautomate the generation of all the necessary boilerplate and wrappers\nthat implement a ufunc around the core calculation.  The exceptions\nare `debye1`, `logfactorial`, `log1p`, `loggamma1p`, `issnan`, and\n`cabssq`, which are implemented in C, with all boilerplate code written\n\"by hand\" in the C file.\n\n| Function                                      | Description                                                   |\n| --------                                      | -----------                                                   |\n| [`logfactorial`](#logfactorial)               | Log of the factorial of integers                              |\n| [`issnan`](#issnan)                           | Like `isnan`, but for signaling nans only.                    |\n| [`next_less`](#next_less)                     | Equivalent to `np.nextafter(x, -inf)`                         |\n| [`next_greater`](#next_greater)               | Equivalent to `np.nextafter(x, inf)`                          |\n| [`abs_squared`](#abs_squared)                 | Squared absolute value                                        |\n| [`cabssq`](#cabssq)                           | Squared absolute value for complex input only                 |\n| [`deadzone`](#deadzone)                       | Deadzone function                                             |\n| [`step`](#step)                               | Step function                                                 |\n| [`linearstep`](#linearstep)                   | Piecewise linear step function                                |\n| [`smoothstep3`](#smoothstep3)                 | Smooth step using a cubic polynomial                          |\n| [`invsmoothstep3`](#invsmoothstep3)           | Inverse of `smoothstep3`                                      |\n| [`smoothstep5`](#smoothstep5)                 | Smooth step using a degree 5 polynomial                       |\n| [`trapezoid_pulse`](#trapezoid_pulse)         | Trapezoid pulse function                                      |\n| [`pow1pm1`](#pow1pm1)                         | Compute `(1 + x)**y - 1`                                      |\n| [`debye1`](#debye1)                           | Compute the Debye function D₁(x)                              |\n| [`expint1`](#expint1)                         | Exponential integral E₁ for real inputs                       |\n| [`log1p_doubledouble`](#log1p_doubledouble)   | `log(1 + z)` for complex z.                                   |\n| [`log1p_theorem4`](#log1p_theorem4)           | `log(1 + z)` for complex z.                                   |\n| [`logexpint1`](#logexpint1)                   | Logarithm of the exponential integral E₁                      |\n| [`loggamma1p`](#loggamma1p)                   | Logarithm of gamma(1 + x) for real x \u003e -1.                    |\n| [`logistic`](#logistic)                       | The standard logistic sigmoid function                        |\n| [`logistic_deriv`](#logistic_deriv)           | Derivative of the standard logistic sigmoid function          |\n| [`log_logistic`](#log_logistic)               | Logarithm of the standard logistic sigmoid function           |\n| [`swish`](#swish)                             | The 'swish' function--a smoothed ramp                         |\n| [`hyperbolic_ramp`](#hyperbolic_ramp)         | A smoothed ramp whose graph is a hyperbola                    |\n| [`exponential_ramp`](#exponential_ramp)       | A smoothed ramp with exponential convergence to asymptotes    |\n| [`yeo_johnson`](#yeo_johnson)                 | Yeo-Johnson transformation                                    |\n| [`inv_yeo_johnson`](#inv_yeo_johnson)         | Inverse of the Yeo-Johnson transformation                     |\n| [`erfcx`](#erfcx)                             | Scaled complementary error function                           |\n| [Normal distribution functions](#normal)      | Functions for the normal distribution: cdf, sf, logcdf, logsf |\n| [Semivariograms](#semivariograms)             | Several semivariograms used in kriging interpolation          |\n\n***Generalized ufuncs***\n\nNote, for anyone looking at the source code: some of these implementations\nare in C and use the legacy NumPy templating language (look for filenames\nthat end in `.src`); others use templated C++ functions combined with code\ngeneration tools that can be found in `tools/cxxgen`.  The `.src` files are\nprocessed with the script in `ufunclab/tools/conv_template.py`.\n\n*Functions that reduce a one-dimensional array to one or two numbers.*\n\n| Function                                        | Description                                                 |\n| --------                                        | -----------                                                 |\n| [`first`](#first)                               | First value that matches a target comparison                |\n| [`argfirst`](#argfirst)                         | Index of the first occurrence of a target comparison        |\n| [`argmin`](#argmin)                             | Like `numpy.argmin`, but a gufunc                           |\n| [`argmax`](#argmax)                             | Like `numpy.argmax`, but a gufunc                           |\n| [`minmax`](#minmax)                             | Minimum and maximum                                         |\n| [`argminmax`](#argminmax)                       | Indices of the min and the max                              |\n| [`min_argmin`](#min_argmin)                     | Minimum value and its index                                 |\n| [`max_argmax`](#max_argmax)                     | Maximum value and its index                                 |\n| [`searchsortedl`](#searchsortedl)               | Find position for given element in sorted seq.              |\n| [`searchsortedr`](#searchsortedr)               | Find position for given element in sorted seq.              |\n| [`peaktopeak`](#peaktopeak)                     | Alternative to `numpy.ptp`                                  |\n| [`all_same`](#all_same)                         | Check all values are the same                               |\n| [`gmean`](#gmean)                               | Geometric mean                                              |\n| [`hmean`](#hmean)                               | Harmonic mean                                               |\n| [`meanvar`](#meanvar)                           | Mean and variance                                           |\n| [`mad`](#mad)                                   | Mean absolute difference (MAD)                              |\n| [`rmad`](#rmad)                                 | Relative mean absolute difference (RMAD)                    |\n| [`gini`](#gini)                                 | Gini coefficient                                            |\n| [`rms`](#rms)                                   | Root-mean-square for real and complex inputs                |\n| [`vnorm`](#vnorm)                               | Vector norm                                                 |\n| [`percentileofscore`](#percentileofscore)       | Percentile of score (like `scipy.stats.percentileofscore`)  |\n\n*Functions that reduce two one-dimensional arrays to a number.*\n\n| Function                                        | Description                                           |\n| --------                                        | -----------                                           |\n| [`vdot`](#vdot)                                 | Vector dot product for real floating point arrays     |\n| [`pearson_corr`](#pearson_corr)                 | Pearson's product-moment correlation coefficient      |\n| [`wjaccard`](#wjaccard)                         | Weighted Jaccard index.                               |\n\n*Functions that transform a one-dimensional array to another one-dimensional array.*\n\n| Function                                        | Description                                           |\n| --------                                        | -----------                                           |\n| [`fillnan1d`](#fillnan1d)                       | Replace `nan` using linear interpolation              |\n| [`backlash`](#backlash)                         | Backlash operator                                     |\n| [`backlash_sum`](#backlash_sum)                 | Sum backlash operators (Prandtl-Ishlinskii)           |\n| [`hysteresis_relay`](#hysteresis_relay)         | Relay with hysteresis (Schmitt trigger)               |\n| [`sosfilter`](#sosfilter)                       | SOS (second order sections) linear filter             |\n| [`sosfilter_ic`](#sosfilter_ic)                 | SOS linear filter with initial condition              |\n| [`sosfilter_ic_contig`](#sosfilter_ic_contig)   | SOS linear filter with contiguous array inputs        |\n\n*Other generalized ufuncs.*\n\n| Function                                        | Description                                           |\n| --------                                        | -----------                                           |\n| [`cross2`](#cross2)                             | 2-d vector cross product (returns scalar)             |\n| [`cross3`](#cross3)                             | 3-d vector cross product                              |\n| [`linear_interp1d`](#linear_interp1d)           | Linear interpolation, like `numpy.interp`             |\n| [`tri_area`](#tri_area)                         | Area of triangles in n-dimensional space              |\n| [`tri_area_indexed`](#tri_area_indexed)         | Area of triangles in n-dimensional space              |\n| [`multivariate_logbeta`](#multivariate_logbeta) | Logarithm of the multivariate beta function           |\n\n*Wrapped generalized ufuncs*\n\nThese are Python functions that wrap a gufunc.  The wrapper allows the\nfunction to provide a capability that is not possible with a gufunc.\n\n| Function                                        | Description                                            |\n| --------                                        | -----------                                            |\n| [`bincount`](#bincount)                         | Like `np.bincount`, but gufunc-based                   |\n| [`convert_to_base`](#convert_to_base)           | Convert an integer to a given base.                    |\n| [`nextn_greater`](#nextn_greater)               | Next n values greater than the given x.                |\n| [`nextn_less`](#nextn_less)                     | Next n values less than the given x.                   |\n| [`one_hot`](#one_hot)                           | Create 1-d array that is 1 at index k and 0 elsewhere. |\n\n*Other tools*\n\n| Function                                | Description                                           |\n| --------                                | -----------                                           |\n| [`gendot`](#gendot)                     | Create a new gufunc that composes two ufuncs          |\n| [`ufunc_inspector`](#ufunc_inspector)   | Display ufunc information                             |\n\n-----\n\n#### `logfactorial`\n\n`logfactorial` is a ufunc that computes the natural logarithm of the\nfactorial of the nonnegative integer x.  (`nan` is returned for negative\ninput.)\n\nFor example,\n```\n\u003e\u003e\u003e from ufunclab import logfactorial\n\n\u003e\u003e\u003e logfactorial([1, 10, 100, 1000])\narray([   0.        ,   15.10441257,  363.73937556, 5912.12817849])\n```\n\n#### `issnan`\n\n`issnan` is an element-wise ufunc with a single input that acts like\nthe standard `isnan` function, but it returns True only for\n[*signaling* nans](https://en.wikipedia.org/wiki/NaN#Signaling_NaN).\n\nThe current implementation only handles the floating point types `np.float16`,\n`np.float32` and `np.float64`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import issnan\n\u003e\u003e\u003e x = np.array([12.5, 0.0, np.inf, 999.0, np.nan], dtype=np.float32)\n```\nPut a signaling nan in `x[1]`. (The nan in `x[4]` is a quiet nan, and\nwe'll leave it that way.)\n```\n\u003e\u003e\u003e v = x.view(np.uint32)\n\u003e\u003e\u003e v[1] = 0b0111_1111_1000_0000_0000_0000_0000_0011\n\u003e\u003e\u003e x\narray([ 12.5,   nan,   inf, 999. ,   nan], dtype=float32)\n\u003e\u003e\u003e np.isnan(x)\narray([False,  True, False, False,  True])\n```\nNote that NumPy displays both quiet and signaling nans as just `nan`,\nand `np.isnan(x)` returns True for both quiet and signaling nans (as\nit should).\n\n`issnan(x)` indicates which values are signaling nans:\n```\n\u003e\u003e\u003e issnan(x)\narray([False,  True, False, False, False])\n```\n\n#### `next_less`\n\n`next_less` is an element-wise ufunc with a single input that\nis equivalent to `np.nextafter` with the second argument set to `-inf`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import next_less\n\u003e\u003e\u003e next_less(np.array([-12.5, 0, 1, 1000], dtype=np.float32))\narray([-1.2500001e+01, -1.4012985e-45,  9.9999994e-01,  9.9999994e+02],\n      dtype=float32)\n```\n\n#### `next_greater`\n\n`next_greater` is an element-wise ufunc with a single input that\nis equivalent to `np.nextafter` with the second argument set to `inf`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import next_greater\n\u003e\u003e\u003e next_greater(np.array([-12.5, 0, 1, 1000], dtype=np.float32))\narray([-1.24999990e+01,  1.40129846e-45,  1.00000012e+00,  1.00000006e+03],\n      dtype=float32)\n```\n\n#### `abs_squared`\n\n`abs_squared(z)` computes the squared absolute value of `z`.\nThis is an element-wise ufunc with types `'f-\u003ef'`, `'d-\u003ed'`,\n`'g-\u003eg'`, `'F-\u003ef'`, `'D-\u003ed'`, and `'G-\u003eg'`.  For real input,\nthe result is `z**2`.  For complex input, it is\n`z.real**2 + z.imag**2`, which is equivalent to `z*conj(z)`,\nwhere `conj(z)` is the complex conjugate of `z`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import abs_squared\n\n\u003e\u003e\u003e abs_squared.types\n['f-\u003ef', 'd-\u003ed', 'g-\u003eg', 'F-\u003ef', 'D-\u003ed', 'G-\u003eg']\n\n\u003e\u003e\u003e x = np.array([-1.5, 3.0, 9.0, -10.0], dtype=np.float32)\n\u003e\u003e\u003e abs_squared(x)\narray([  2.25,   9.  ,  81.  , 100.  ], dtype=float32)\n\n\u003e\u003e\u003e z = np.array([-3+4j, -1, 1j, 13, 0.5-1.5j])\n\u003e\u003e\u003e abs_squared(z)\narray([ 25. ,   1. ,   1. , 169. ,   2.5])\n```\n\n#### `cabssq`\n\n`cabssq(z)` computes the squared absolute value of `z` for complex input only.\nThis is the same calculation as `abs_squared`, but the implementation is\ndifferent.  `cabssq` is implemented in C with the inner loop functions\nimplemented \"by hand\", with no C++ or NumPy templating.  `cabssq` is generally\nfaster than `abs_squared`, because it avoids some of the overhead that occurs\nin the code generated in the implementation of `abs_squared`, and it allows\nthe compiler to optimize the code more effectively.\n\n#### `deadzone`\n\n`deadzone(x, low, high)` is a ufunc with three inputs and one output.\nIt computes the \"deadzone\" response of a signal:\n\n           { 0         if low \u003c= x \u003c= high\n    f(x) = { x - low   if x \u003c low\n           { x - high  if x \u003e high\n\nThe function is similar to the\n[deadzone block](https://www.mathworks.com/help/simulink/slref/deadzone.html)\nof Matlab's Simulink library.  The function is also known as\na *soft threshold*.\n\nHere's a plot of `deadzone(x, -0.25, 0.5)`:\n\n![Deadzone plot1](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/deadzone_demo1.png)\n\nThe script `deadzone_demo2.py` in the `examples` directory generates\nthe plot\n\n![Deadzone plot2](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/deadzone_demo2.png)\n\n#### `step`\n\nThe ufunc `step(x, a, flow, fa, fhigh)` returns `flow` for\n`x \u003c a`, `fhigh` for `x \u003e a`, and `fa` for `x = a`.\n\nThe Heaviside function can be implemented as `step(x, 0, 0, 0.5, 1)`.\n\nThe script `step_demo.py` in the `examples` directory generates\nthe plot\n\n![step plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/step_demo.png)\n\n\n#### `linearstep`\n\nThe ufunc `linearstep(x, a, b, fa, fb)` returns `fa` for\n`x \u003c= a`, `fb` for `x \u003e= b`, and uses linear interpolation\nfrom `fa` to `fb` in the interval `a \u003c x \u003c b`.\n\nThe script `linearstep_demo.py` in the `examples` directory generates\nthe plot\n\n![linearstep plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/linearstep_demo.png)\n\n#### `smoothstep3`\n\nThe ufunc `smoothstep3(x, a, b, fa, fb)` returns `fa` for\n`x \u003c= a`, `fb` for `x \u003e= b`, and uses a cubic polynomial in\nthe interval `a \u003c x \u003c b` to smoothly transition from `fa` to `fb`.\n\nThe script `smoothstep3_demo.py` in the `examples` directory generates\nthe plot\n\n![smoothstep3 plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/smoothstep3_demo.png)\n\n#### `invsmoothstep3`\n\nThe ufunc `invsmoothstep3(y, a, b, fa, fb)` is the inverse of\n`smoothstep3(x, a, b, fa, fb)`.\n\nThe script `invsmoothstep3_demo.py` in the `examples` directory generates\nthe plot\n\n![invsmoothstep3 plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/invsmoothstep3_demo.png)\n\n\n#### `smoothstep5`\n\nThe function `smoothstep5(x, a, b, fa, fb)` returns `fa` for\n`x \u003c= a`, `fb` for `x \u003e= b`, and uses a degree 5 polynomial in\nthe interval `a \u003c x \u003c b` to smoothly transition from `fa` to `fb`.\n\nThe script `smoothstep5_demo.py` in the `examples` directory generates\nthe plot\n\n![smoothstep5 plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/smoothstep5_demo.png)\n\n#### `trapezoid_pulse`\n\n`trapezoid_pulse(x, a, b, c, d, amp)` is a ufunc that computes\na trapezoid pulse.  The function is 0 for `x` \u003c= `a` or `x` \u003e= `d`,\n`amp` for `b` \u003c= `x` \u003c= `c`, and a linear ramp in the intervals\n`[a, b]` and `[c, d]`.\n\nHere's a plot of `trapezoid_pulse(x, 1, 3, 4, 5, 2)` (generated by\nthe script `trapezoid_pulse_demo.py` in the `examples` directory):\n\n![trapezoid_pulse plot1](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/trapezoid_pulse_demo.png)\n\n#### `pow1pm1`\n\n`pow1pm1(x, y)` computes `(1 + x)**y - 1` for `x \u003e= -1`.\n\nThe calculation is formulated to avoid loss of precision when\n`(1 + x)**y` is close to 1.\n\n```\n\u003e\u003e\u003e from ufunclab import pow1pm1\n\nThe following result provides full machine precision:\n\n\u003e\u003e\u003e x = -0.125\n\u003e\u003e\u003e y = 3.25e-12\n\u003e\u003e\u003e pow1pm1(x, y)\n-4.3397702602960437e-13\n\nThe naive calculation provides less than six digits of precision:\n\n\u003e\u003e\u003e (1 + x)**y - 1\n-4.339861803259737e-13\n\n```\n\n#### `debye1`\n\n`debye1(x)` computes the Debye function D₁(x).\n\nSee the wikipedia article https://en.wikipedia.org/wiki/Debye_function\nfor more details.\n\n```\n\u003e\u003e\u003e from ufunclab import debye1\n\n\u003e\u003e\u003e debye1([-2, -1.5, 0, 0.25, 0.5, 2.5, 50, 100])\narray([1.60694728, 1.43614531, 1.        , 0.93923503, 0.88192716,\n       0.53878957, 0.03289868, 0.01644934])\n\n```\n\nThe script `debye1_demo.py` in the `examples` directory generates\nthe plot\n\n![debye1 plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/debye1_demo.png)\n\n\n#### `expint1`\n\n`expint1(x)` computes the exponential integral E₁ for the real input x.\n\n```\n\u003e\u003e\u003e from ufunclab import expint1\n\u003e\u003e\u003e expint1([0.25, 2.5, 25])\narray([1.04428263e+00, 2.49149179e-02, 5.34889976e-13])\n```\n\n#### `log1p_doubledouble`\n\n`log1p_doubledouble(z)` computes `log(1 + z)` for complex `z`.  This is an alternative\nto `numpy.log1p` and `scipy.special.log1p`.\n\nThe function uses double-double numbers for some intermediate calculations to avoid\nloss of precision near the circle |z + 1| = 1 in the complex plane.\n\n```\n\u003e\u003e\u003e from ufunclab import log1p_doubledouble\n\u003e\u003e\u003e z = -0.57113-0.90337j\n\u003e\u003e\u003e log1p_doubledouble(z)\n(3.4168883248419116e-06-1.1275564209486122j)\n```\n\nCurrently, the only \"inner loop\" implemented for this ufunc is for\nthe data type `np.complex128`, so it will always return a complex result:\n\n```\n\u003e\u003e\u003e log1p_doubledouble.types\n['D-\u003eD']\n\u003e\u003e\u003e log1p_doubledouble([-6e-3, 0, 1e-12, 5e-8, 0.25, 1])\narray([-6.01807233e-03+0.j,  0.00000000e+00+0.j,  1.00000000e-12+0.j,\n        4.99999988e-08+0.j,  2.23143551e-01+0.j,  6.93147181e-01+0.j])\n```\n\n#### `log1p_theorem4`\n\n`log1p_theorem4(z)` computes `log(1 + z)` for complex `z`.  This is an alternative\nto `numpy.log1p` and `scipy.special.log1p`.\n\nThe function uses the \"trick\" given as Theorem 4 of Goldberg's paper \"What every\ncomputer scientist should know about floating-point arithmetic\".\n\nThe precision provided by this function depends on the precision of the function\n`clog` (complex logarithm) provided by the underlying C math library that is used\nto build `ufunclab`.\n\n```\n\u003e\u003e\u003e from ufunclab import log1p_theorem4\n\u003e\u003e\u003e z = -0.57113-0.90337j\n\u003e\u003e\u003e log1p_theorem4(z)\n(3.4168883248419116e-06-1.1275564209486122j)\n```\n\nCurrently, the only \"inner loop\" implemented for this ufunc is for\nthe data type `np.complex128`, so it will always return a complex result:\n\n```\n\u003e\u003e\u003e log1p_theorem4.types\n['D-\u003eD']\n\u003e\u003e\u003e log1p_theorem4([-6e-3, 0, 1e-12, 5e-8, 0.25, 1])\narray([-6.01807233e-03+0.j,  0.00000000e+00+0.j,  1.00000000e-12+0.j,\n        4.99999988e-08+0.j,  2.23143551e-01+0.j,  6.93147181e-01+0.j])\n```\n\n#### `logexpint1`\n\n`logexpint1(x)` computes the logarithm of the exponential integral E₁ for the real input x.\n\n`expint1(x)` underflows to 0 for sufficiently large x:\n\n```\n\u003e\u003e\u003e from ufunclab import expint1, logexpint1\n\u003e\u003e\u003e expint1([650, 700, 750, 800])\narray([7.85247922e-286, 1.40651877e-307, 0.00000000e+000, 0.00000000e+000])\n```\n\n`logexpint1` avoids the underflow by computing the logarithm of the value:\n\n```\n\u003e\u003e\u003e logexpint1([650, 700, 750, 800])\narray([-656.47850729, -706.55250586, -756.62140388, -806.68585939])\n```\n\n\n#### `loggamma1p`\n\n`loggamma1p` computes `log(gamma(1 + x))` for real `x` \u003e -1.\nIt avoids the loss of precision in the expression `1 + x` that occurs\nwhen `x` is very small.\n\n```\n\u003e\u003e\u003e from ufunclab import loggamma1p\n\u003e\u003e\u003e x = -3e-11\n\u003e\u003e\u003e loggamma1p(x)\n1.7316469947786207e-11\n```\n\nThat result is accurate to machine precision.  The naive calculation\nloses precision in the sum `1 + x`; in the following result that uses\n`math.lgamma`, only about five digits are correct:\n\n```\n\u003e\u003e\u003e import math\n\u003e\u003e\u003e math.lgamma(1 + x)\n1.7316037492776104e-11\n```\n\n\n#### `logistic`\n\n`logistic(x)` computes the standard logistic sigmoid function.\n\nThe script `logistic_demo.py` in the `examples` directory generates\nthis plot:\n\n![logistic plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/logistic_demo.png)\n\n\n#### `logistic_deriv`\n\n`logistic_deriv(x)` computes the derivative of the standard logistic sigmoid\nfunction.\n\nSee `logistic` (above) for a plot.\n\n\n#### `log_logistic`\n\n`log_logistic(x)` computes the logarithm of the standard logistic sigmoid\nfunction.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import log_logistic\n\n\u003e\u003e\u003e x = np.array([-800, -500, -0.5, 10, 250, 500])\n\u003e\u003e\u003e log_logistic(x)\narray([-8.00000000e+002, -5.00000000e+002, -9.74076984e-001,\n       -4.53988992e-005, -2.66919022e-109, -7.12457641e-218])\n```\n\nCompare that to the output of `log(expit(x))`, which triggers a warning\nand loses all precision for inputs with large magnitudes:\n\n```\n\u003e\u003e\u003e from scipy.special import expit\n\u003e\u003e\u003e np.log(expit(x))\n\u003cstdin\u003e:1: RuntimeWarning: divide by zero encountered in log\narray([           -inf, -5.00000000e+02, -9.74076984e-01,\n       -4.53988992e-05,  0.00000000e+00,  0.00000000e+00])\n```\n\n\n#### `swish`\n\n`swish(x, beta)` computes `x * logistic(beta*x)`, where `logistic(x)`\nis the standard logistic sigmoid function.  The function is a type\nof smoothed ramp.\n\n![swish plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/swish_demo.png)\n\n\n#### `hyperbolic_ramp`\n\n`hyperbolic_ramp(x, a)` computes the function\n\n    hyperbolic_ramp(x, a) = (x + sqrt(x*x + 4*a*a))/2\n\nIt is a smoothed ramp function.  The scaling of the parameters is chosen\nso that `hyperbolic_ramp(0, a)` is `a`.\n\n![hyperbolic_ramp plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/hyperbolic_ramp_demo.png)\n\n\n#### `exponential_ramp`\n\n`exponential_ramp(x, a)` computes the function\n\n    exponential_ramp(x, a) = a*log_2(1 + 2**(x/a))\n\nIt is a smoothed ramp function that converges exponentially fast to\nthe asymptotes.  The scaling of the parameters is chosen so that\n`exponential_ramp(0, a)` is `a`.\n\nThe function is also known as the \"softplus\" function.\n\n![exponential_ramp plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/exponential_ramp_demo.png)\n\n\n#### `yeo_johnson`\n\n`yeo_johnson` computes the Yeo-Johnson transform.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import yeo_johnson\n\n\u003e\u003e\u003e yeo_johnson([-1.5, -0.5, 2.8, 7, 7.1], 2.3)\narray([-0.80114069, -0.38177502,  8.93596922, 51.4905317 , 52.99552905])\n\n\u003e\u003e\u003e yeo_johnson.outer([-1.5, -0.5, 2.8, 7, 7.1], [-2.5, 0.1, 2.3, 3])\narray([[-13.50294123,  -2.47514321,  -0.80114069,  -0.6       ],\n       [ -1.15561576,  -0.61083954,  -0.38177502,  -0.33333333],\n       [  0.38578977,   1.42821388,   8.93596922,  17.95733333],\n       [  0.39779029,   2.31144413,  51.4905317 , 170.33333333],\n       [  0.39785786,   2.32674755,  52.99552905, 176.81366667]])\n```\n\n#### `inv_yeo_johnson`\n\n`inv_yeo_johnson` computes the inverse of the Yeo-Johnson transform.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import inv_yeo_johnson, yeo_johnson\n\n\u003e\u003e\u003e x = inv_yeo_johnson([-1.5, -0.5, 2.8, 7, 7.1], 2.5)\n\u003e\u003e\u003e x\narray([-15.        ,  -0.77777778,   1.29739671,   2.21268904,\n         2.22998502])\n\u003e\u003e\u003e yeo_johnson(x, 2.5)\narray([-1.5, -0.5,  2.8,  7. ,  7.1])\n```\n\n#### `erfcx`\n\n`erfcx(x)` computes the scaled complementary error function,\n`exp(x**2) * erfc(x)`.  The function is implemented for NumPy types\n`float32`, `float64` and `longdouble` (also known as `float128`):\n\n```\n\u003e\u003e\u003e from ufunclab import erfcx\n\u003e\u003e\u003e erfcx.types\n['f-\u003ef', 'd-\u003ed', 'g-\u003eg']\n```\n\nThis example is run on a platform where the `longdouble` type\ncorresponds to a `float128` with 80 bits of precision:\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e b = np.longdouble('1.25e2000')\n\u003e\u003e\u003e x = np.array([-40, -1.0, 0, 2.5, 3000.0, b])\n\u003e\u003e\u003e x\narray([-4.00e+0001, -1.00e+0000,  0.00e+0000,  1.00e+0003,  1.25e+2000],\n      dtype=float128)\n\u003e\u003e\u003e erfcx(x)\narray([1.48662366e+0695, 5.00898008e+0000, 1.00000000e+0000,\n       5.64189301e-0004, 4.51351667e-2001], dtype=float128)\n```\n\n#### Normal\n\nThe submodule `normal` defines several functions for the standard normal\nprobability distrbution.\n\n\n##### `normal.cdf`\n\n`normal.cdf(x)` computes the cumulative distribution function of the standard\nnormal distribution.\n\n\n##### `normal.logcdf`\n\n`normal.logcdf(x)` computes the natural logarithm of the CDF of the standard\nnormal distribution.\n\n\n##### `normal.sf`\n\n`normal.sf(x)` computes the survival function of the standard normal distribution.\nThis function is also known as the complementary CDF, and is often abbreviated\nas `ccdf`.\n\n\n##### `normal.logsf`\n\n`normal.logsf(x)` computes the natural logarithm of the survival function of the\nstandard normal distribution.\n\n\n#### Semivariograms\n\nThe submodule `ufunclab.semivar` defines the ufuncs `exponential`, `linear`,\n`spherical` and `parabolic`.  The script `semivar_demo.py` in the `examples`\ndirectory generates the following plot.\n\n![semivariogram plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/semivar_demo.png)\n\n\n##### `semivar.exponential`\n\n`semivar.exponential(h, nugget, sill, rng)` computes the exponential semivariogram.\n\n\n##### `semivar.linear`\n\n`semivar.linear(h, nugget, sill, rng)` computes the linear semivariogram.\n\n\n##### `semivar.spherical`\n\n`semivar.spherical(h, nugget, sill, rng)` computes the spherical semivariogram.\n\n##### `semivar.parabolic`\n\n`semivar.parabolic(h, nugget, sill, rng)` computes the parabolic semivariogram.\n\n-----\n\n#### `first`\n\n`first` is a gufunc with signature `(n),(),(),()-\u003e()` that returns the first\nvalue that matches a given comparison.  The function signature is\n`first(x, op, target, otherwise)`, where `op` is one of the values in\n`ufunclab.op` that specifies the comparison to be made. `otherwise` is the\nvalue to be returned if no value in `x` satisfies the given relation with\n`target`.\n\nFind the first nonzero value in `a`:\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import first, op\n\n\u003e\u003e\u003e a = np.array([0, 0, 0, 0, 0, -0.5, 0, 1, 0.1])\n\u003e\u003e\u003e first(a, op.NE, 0.0, 0.0)\n-0.5\n```\n\nFind the first value in each row of `b` that is less than 0.\nIf there is no such value, return 0:\n\n```\n\u003e\u003e\u003e b = np.array([[10, 23, -10, 0, -9],\n...               [18, 28, 42, 33, 71],\n...               [17, 29, 16, 14, -7]], dtype=np.int8)\n...\n\u003e\u003e\u003e first(b, op.LT, 0, 0)\narray([-10,   0,  -7], dtype=int8)\n```\n\nThe function stops at the first occurrence, so it will be faster when the\ncondition is met at the beginning of the array than when the condition\ndoesn't occur until near the end of the array.\n\nIn the following, the condition occurs in the last element of ``x``, and\nthe result of the ``timeit`` call is 0.6 seconds.\n\n```\n\u003e\u003e\u003e from timeit import timeit\n\u003e\u003e\u003e x = np.ones(100000)\n\u003e\u003e\u003e x[-1] = 0\n\u003e\u003e\u003e first(x, op.LT, 1.0, np.nan)\n0.0\n\u003e\u003e\u003e timeit('first(x, op.LT, 1.0, np.nan)', number=20000, globals=globals())\n0.6052625320153311\n```\n\nThe time is reduced to 0.05 seconds when the first occurrence of the\ncondition is in the first element of ``x``.\n\n```\n\u003e\u003e\u003e x[0] = -1.0\n\u003e\u003e\u003e first(x, op.LT, 1.0, np.nan)\n-1.0\n\u003e\u003e\u003e timeit('first(x, op.LT, 1.0, np.nan)', number=20000, globals=globals())\n0.049594153999350965\n```\n\n#### `argfirst`\n\n`argfirst` is a gufunc (signature `(n),(),()-\u003e()`) that finds the index of\nthe first true value of a comparison of an array with a target value.  If no\nvalue is found, -1 is return.  Some examples follow.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import argfirst, op\n```\n\nFind the index of the first occurrence of 0 in `x`:\n\n```\n\u003e\u003e\u003e x = np.array([10, 35, 19, 0, -1, 24, 0])\n\u003e\u003e\u003e argfirst(x, op.EQ, 0)\n3\n```\n\nFind the index of the first nonzero value in `a`:\n\n```\n\u003e\u003e\u003e a = np.array([0, 0, 0, 0, 0, -0.5, 0, 1, 0.1])\n\u003e\u003e\u003e argfirst(a, op.NE, 0.0)\n5\n```\n\n`argfirst` is a gufunc, so it can handle higher-dimensional\narray arguments, and among its gufunc-related parameters is\n`axis`.  By default, the gufunc operates along the last axis.\nFor example, here we find the location of the first nonzero\nelement in each row of `b`:\n\n```\n\u003e\u003e\u003e b = np.array([[0, 8, 0, 0], [0, 0, 0, 0], [0, 0, 9, 2]],\n...              dtype=np.uint8)\n\u003e\u003e\u003e b\narray([[0, 8, 0, 0],\n       [0, 0, 0, 0],\n       [0, 0, 9, 2]])\n\u003e\u003e\u003e argfirst(b, op.NE, np.uint8(0))\narray([ 1, -1,  2])\n```\n\nIf we give the argument `axis=0`, we tell `argfirst` to\noperate along the first axis, which in this case is the\ncolumns:\n\n```\n\u003e\u003e\u003e argfirst(b, op.NE, np.uint8(0), axis=0)\narray([-1,  0,  2,  2])\n```\n\n#### `argmin`\n\n`argmin` is a `gufunc` with signature `(n)-\u003e()` that is similar to `numpy.argmin`.\n\n```\n\u003e\u003e\u003e from ufunclab import argmin\n\u003e\u003e\u003e x = np.array([[11, 10, 10, 23, 31],\n...               [19, 20, 21, 22, 22],\n...               [16, 15, 16, 14, 14]])\n\u003e\u003e\u003e argmin(x, axis=1)  # same as argmin(x)\narray([1, 0, 3])\n\u003e\u003e\u003e argmin(x, axis=0)\narray([0, 0, 0, 2, 2])\n```\n\n#### `argmax`\n\n`argmax` is a `gufunc` with signature `(n)-\u003e()` that is similar to `numpy.argmax`.\n\n```\n\u003e\u003e\u003e from ufunclab import argmax\n\u003e\u003e\u003e x = np.array([[11, 10, 10, 23, 31],\n...               [19, 20, 21, 22, 22],\n...               [16, 15, 16, 14, 14]])\n\u003e\u003e\u003e argmax(x, axis=1)  # same as argmax(x)\narray([4, 3, 0])\n\u003e\u003e\u003e argmax(x, axis=0)\narray([1, 1, 1, 0, 0])\n```\n\n#### `minmax`\n\n`minmax` is a `gufunc` (signature `(n)-\u003e(2)`) that simultaneously computes\nthe minimum and maximum of a NumPy array.\n\nThe function handles the standard integer and floating point types, and\nobject arrays. The function will not accept complex arrays, nor arrays with\nthe data types `datetime64` or `timedelta64`.  Also, the function does not\nimplement any special handling of `nan`, so the behavior of this function\nwith arrays containing `nan` is *undefined*.\n\nFor an input with more than one dimension, `minmax` is applied to the\nlast axis.  For example, if `a` has shape (L, M, N), then `minmax(a)` has\nshape (L, M, 2).\n\n```\n\u003e\u003e\u003e x = np.array([5, -10, -25, 99, 100, 10], dtype=np.int8)\n\u003e\u003e\u003e minmax(x)\narray([-25, 100], dtype=int8)\n\n\u003e\u003e\u003e np.random.seed(12345)\n\u003e\u003e\u003e y = np.random.randint(-1000, 1000, size=(3, 3, 5)).astype(np.float32)\n\u003e\u003e\u003e y\narray([[[-518.,  509.,  309., -871.,  444.],\n        [ 449., -618.,  381., -454.,  565.],\n        [-231.,  142.,  393.,  339., -346.]],\n\n       [[-895.,  115., -241.,  398.,  232.],\n        [-118., -287., -733.,  101.,  674.],\n        [-919.,  746., -834., -737., -957.]],\n\n       [[-769., -977.,   53.,  -48.,  463.],\n        [ 311., -299., -647.,  883., -145.],\n        [-964., -424., -613., -236.,  148.]]], dtype=float32)\n\n\u003e\u003e\u003e mm = minmax(y)\n\u003e\u003e\u003e mm\narray([[[-871.,  509.],\n        [-618.,  565.],\n        [-346.,  393.]],\n\n       [[-895.,  398.],\n        [-733.,  674.],\n        [-957.,  746.]],\n\n       [[-977.,  463.],\n        [-647.,  883.],\n        [-964.,  148.]]], dtype=float32)\n\n\u003e\u003e\u003e mm.shape\n(3, 3, 2)\n\n\u003e\u003e\u003e z = np.array(['foo', 'xyz', 'bar', 'abc', 'def'], dtype=object)\n\u003e\u003e\u003e minmax(z)\narray(['abc', 'xyz'], dtype=object)\n\n\u003e\u003e\u003e from fractions import Fraction\n\u003e\u003e\u003e f = np.array([Fraction(1, 3), Fraction(3, 5),\n...               Fraction(22, 7), Fraction(5, 2)], dtype=object)\n\u003e\u003e\u003e minmax(f)\narray([Fraction(1, 3), Fraction(22, 7)], dtype=object)\n\n```\n\n#### `argminmax`\n\n`argminmax` is a `gufunc` (signature `(n)-\u003e(2)`) that simultaneously\ncomputes the `argmin` and `argmax` of a NumPy array.\n\n```\n\u003e\u003e\u003e y = np.array([[-518,  509,  309, -871,  444,  449, -618,  381],\n...               [-454,  565, -231,  142,  393,  339, -346, -895],\n...               [ 115, -241,  398,  232, -118, -287, -733,  101]],\n...              dtype=np.float32)\n\u003e\u003e\u003e argminmax(y)\narray([[3, 1],\n       [7, 1],\n       [6, 2]])\n\u003e\u003e\u003e argminmax(y, axes=[0, 0])\narray([[0, 2, 1, 0, 2, 2, 2, 1],\n       [2, 1, 2, 2, 0, 0, 1, 0]])\n```\n\n#### `min_argmin`\n\n`min_argmin` is a gufunc (signature `(n)-\u003e(),()`) that returns both\nthe extreme value and the index of the extreme value.\n\n```\n\u003e\u003e\u003e x = np.array([[ 1, 10, 18, 17, 11],\n...               [15, 11,  0,  4,  8],\n...               [10, 10, 12, 11, 11]])\n\u003e\u003e\u003e min_argmin(x, axis=1)\n(array([ 1,  0, 10]), array([0, 2, 0]))\n```\n\n#### `max_argmax`\n\n`max_argmax` is a gufunc (signature `(n)-\u003e(),()`) that returns both\nthe extreme value and the index of the extreme value.\n\n```\n\u003e\u003e\u003e x = np.array([[ 1, 10, 18, 17, 11],\n...               [15, 11,  0,  4,  8],\n...               [10, 10, 12, 11, 11]])\n\u003e\u003e\u003e max_argmax(x, axis=1)\n(array([18, 15, 12]), array([2, 0, 2]))\n\n\u003e\u003e\u003e from fractions import Fraction as F\n\u003e\u003e\u003e y = np.array([F(2, 3), F(3, 4), F(2, 7), F(2, 5)])\n\u003e\u003e\u003e max_argmax(y)\n(Fraction(3, 4), 1)\n```\n\n#### `searchsortedl`\n\n`searchsortedl` is a gufunc with signature `(n),()-\u003e()`.  The function\nis equivalent to `numpy.searchsorted` with `side='left'`, but as a gufunc,\nit supports broadcasting of its arguments.  (Note that `searchsortedl`\ndoes not provide the `sorter` parameter.)\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import searchsortedl\n\u003e\u003e\u003e searchsortedl([1, 1, 2, 3, 5, 8, 13, 21], [1, 4, 15, 99])\narray([0, 4, 7, 8])\n\u003e\u003e\u003e arr = np.array([[1, 1, 2, 3, 5, 8, 13, 21],\n...                 [1, 1, 1, 1, 2, 2, 10, 10]])\n\u003e\u003e\u003e searchsortedl(arr, [7, 8])\narray([5, 6])\n\u003e\u003e\u003e searchsortedl(arr, [[2], [5]])\narray([[2, 4],\n       [4, 6]])\n```\n\n#### `searchsortedr`\n\n`searchsortedr` is a gufunc with signature `(n),()-\u003e()`.  The function\nis equivalent to `numpy.searchsorted` with `side='right'`, but as a gufunc,\nit supports broadcasting of its arguments.  (Note that `searchsortedr`\ndoes not provide the `sorter` parameter.)\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import searchsortedr\n\u003e\u003e\u003e searchsortedr([1, 1, 2, 3, 5, 8, 13, 21], [1, 4, 15, 99])\narray([2, 4, 7, 8])\n\u003e\u003e\u003e arr = np.array([[1, 1, 2, 3, 5, 8, 13, 21],\n...                 [1, 1, 1, 1, 2, 2, 10, 10]])\n\u003e\u003e\u003e searchsortedr(arr, [7, 8])\narray([5, 6])\n\u003e\u003e\u003e searchsortedr(arr, [[2], [5]])\narray([[3, 6],\n       [5, 6]])\n```\n\n\n#### `peaktopeak`\n\n`peaktopeak` is a `gufunc` (signature `(n)-\u003e()`) that computes the\npeak-to-peak range of a NumPy array.  It is like the `ptp` method\nof a NumPy array, but when the input is signed integers, the output\nis an unsigned integer with the same bit width.\n\nThe function does not accept complex arrays.  Also, the function does not\nimplement any special handling of `nan`, so the behavior of this function\nwith arrays containing `nan` is undefined (i.e. it might not do what you\nwant, and the behavior might change in the next update of the software).\n\n```\n\u003e\u003e\u003e x = np.array([85, 125, 0, -75, -50], dtype=np.int8)\n\u003e\u003e\u003e p = peaktopeak(x)\n\u003e\u003e\u003e p\n200\n\u003e\u003e\u003e type(p)\nnumpy.uint8\n```\n\nCompare that to the `ptp` method, which returns a value with the\nsame data type as the input:\n\n```\n\u003e\u003e\u003e q = x.ptp()\n\u003e\u003e\u003e q\n-56\n\u003e\u003e\u003e type(q)\nnumpy.int8\n\n```\n\n`f` is an object array of `Fraction`s and has shape (2, 4).\n\n```\n\u003e\u003e\u003e from fractions import Fraction\n\u003e\u003e\u003e f = np.array([[Fraction(1, 3), Fraction(3, 5),\n...                Fraction(22, 7), Fraction(5, 2)],\n...               [Fraction(-2, 9), Fraction(1, 3),\n...                Fraction(2, 3), Fraction(5, 9)]], dtype=object)\n\u003e\u003e\u003e peaktopeak(x)\narray([Fraction(59, 21), Fraction(8, 9)], dtype=object)\n\n```\n\n\n#### `all_same`\n\n`all_same` is a gufunc (signature `(n)-\u003e()`) that tests that all the\nvalues in the array along the given axis are the same.\n\n(Note: handling of `datetime64`, `timedelta64` and complex data types\nare not implemented yet.)\n\n```\n\u003e\u003e\u003e x = np.array([[3, 2, 2, 3, 2, 2, 3, 1, 3],\n...               [1, 2, 2, 2, 2, 2, 3, 1, 1],\n...               [2, 3, 3, 1, 2, 3, 3, 1, 2]])\n\n\u003e\u003e\u003e all_same(x, axis=0)\narray([False, False, False, False,  True, False,  True,  True, False])\n\n\u003e\u003e\u003e all_same(x, axis=1)\narray([False, False, False])\n```\n\nObject arrays are handled.\n\n```\n\u003e\u003e\u003e a = np.array([[None, \"foo\", 99], [None, \"bar\", \"abc\"]])\n\u003e\u003e\u003e a\narray([[None, 'foo', 99],\n       [None, 'bar', 'abc']], dtype=object)\n\n\u003e\u003e\u003e all_same(a, axis=0)\narray([ True, False, False])\n```\n\n#### `gmean`\n\n`gmean` is a gufunc (signature `(n)-\u003e()`) that computes the\n[geometric mean](https://en.wikipedia.org/wiki/Geometric_mean).\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import gmean\n\n\u003e\u003e\u003e x = np.array([1, 2, 3, 5, 8], dtype=np.uint8)\n\u003e\u003e\u003e gmean(x)\n2.992555739477689\n\n\u003e\u003e\u003e y = np.arange(1, 16).reshape(3, 5)\n\u003e\u003e\u003e y\narray([[ 1,  2,  3,  4,  5],\n       [ 6,  7,  8,  9, 10],\n       [11, 12, 13, 14, 15]])\n\n\u003e\u003e\u003e gmean(y, axis=1)\narray([ 2.60517108,  7.87256685, 12.92252305])\n```\n\n#### `hmean`\n\n`hmean` is a gufunc (signature `(n)-\u003e()`) that computes the\n[harmonic mean](https://en.wikipedia.org/wiki/Harmonic_mean).\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import hmean\n\n\u003e\u003e\u003e x = np.array([1, 2, 3, 5, 8], dtype=np.uint8)\n\u003e\u003e\u003e hmean(x)\n2.316602316602317\n\n\u003e\u003e\u003e y = np.arange(1, 16).reshape(3, 5)\n\u003e\u003e\u003e y\narray([[ 1,  2,  3,  4,  5],\n       [ 6,  7,  8,  9, 10],\n       [11, 12, 13, 14, 15]])\n\n\u003e\u003e\u003e hmean(y, axis=1)\narray([ 2.18978102,  7.74431469, 12.84486077])\n```\n\n#### `meanvar`\n\n`meanvar` is a gufunc (signature `(n),()-\u003e(2)`) that computes both\nthe mean and variance in one function call.\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import meanvar\n\n\u003e\u003e\u003e meanvar([1, 2, 4, 5], 0)  # Use ddof=0.\narray([3. , 2.5])\n```\n\nApply `meanvar` with `ddof=1` to the rows of a 2-d array.\nThe output has shape `(4, 2)`; the first column holds the\nmeans, and the second column holds the variances.\n\n\n```\n\u003e\u003e\u003e x = np.array([[1, 4, 4, 2, 1, 1, 2, 7],\n...               [0, 0, 9, 4, 1, 0, 0, 1],\n...               [8, 3, 3, 3, 3, 3, 3, 3],\n...               [5, 5, 5, 5, 5, 5, 5, 5]])\n\n\u003e\u003e\u003e meanvar(x, 1)  # Use ddof=1.\narray([[ 2.75 ,  4.5  ],\n       [ 1.875, 10.125],\n       [ 3.625,  3.125],\n       [ 5.   ,  0.   ]])\n```\n\nCompare to the results of `numpy.mean` and `numpy.var`:\n\n```\n\u003e\u003e\u003e np.mean(x, axis=1)\narray([2.75 , 1.875, 3.625, 5.   ])\n\n\u003e\u003e\u003e np.var(x, ddof=1, axis=1)\narray([ 4.5  , 10.125,  3.125,  0.   ])\n```\n\n#### `mad`\n\n`mad(x, unbiased)` computes the [mean absolute difference](https://en.wikipedia.org/wiki/Mean_absolute_difference)\nof a 1-d array (gufunc signature is `(n),()-\u003e()`).  When the second parameter\nis False,  `mad` is the standard calculation (sum of the absolute differences\ndivided by `n**2`).  When the second parameter is True, `mad` is the unbiased\nestimator (sum of the absolute differences divided by `n*(n-1)`).\n\nFor example,\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import mad\n\n\u003e\u003e\u003e x = np.array([1.0, 1.0, 2.0, 3.0, 5.0, 8.0])\n\n\u003e\u003e\u003e mad(x, False)\n2.6666666666666665\n\n\u003e\u003e\u003e y = np.linspace(0, 1, 21).reshape(3, 7)**2\n\u003e\u003e\u003e y\narray([[0.    , 0.0025, 0.01  , 0.0225, 0.04  , 0.0625, 0.09  ],\n       [0.1225, 0.16  , 0.2025, 0.25  , 0.3025, 0.36  , 0.4225],\n       [0.49  , 0.5625, 0.64  , 0.7225, 0.81  , 0.9025, 1.    ]])\n\n\u003e\u003e\u003e mad(y, False, axis=1)\narray([0.03428571, 0.11428571, 0.19428571])\n```\n\nWhen the second parameter is `True`, the calculation is the unbiased\nestimate of the mean absolute difference.\n\n```\n\u003e\u003e\u003e mad(x, True)\n3.2\n\n\u003e\u003e\u003e mad(y, True, axis=1)\narray([0.04      , 0.13333333, 0.22666667])\n```\n\n#### `rmad`\n\n`rmad(x, unbiased)` computes the relative mean absolute difference (gufunc\nsignature is `(n),()-\u003e()`).\n\n`rmad` is twice the [Gini coefficient](https://en.wikipedia.org/wiki/Gini_coefficient).\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import rmad\n\n\u003e\u003e\u003e x = np.array([1.0, 1.0, 2.0, 3.0, 5.0, 8.0])\n\n\u003e\u003e\u003e rmad(x, False)\n0.7999999999999999\n\n\u003e\u003e\u003e y = np.linspace(0, 1, 21).reshape(3, 7)**2\n\u003e\u003e\u003e y\narray([[0.    , 0.0025, 0.01  , 0.0225, 0.04  , 0.0625, 0.09  ],\n       [0.1225, 0.16  , 0.2025, 0.25  , 0.3025, 0.36  , 0.4225],\n       [0.49  , 0.5625, 0.64  , 0.7225, 0.81  , 0.9025, 1.    ]])\n\n\u003e\u003e\u003e rmad(y, False, axis=1)\narray([1.05494505, 0.43956044, 0.26523647])\n```\n\nWhen the second parameter is `True`, the calculation is based on the\nunbiased estimate of the mean absolute difference (MAD).\n\n```\n\u003e\u003e\u003e rmad(x, True)\n0.96\n\n\u003e\u003e\u003e rmad(y, True, axis=1)\narray([1.23076923, 0.51282051, 0.30944255])\n```\n\n#### `gini`\n\n`gini(x, unbiased)` is a gufunc with signature `(n),()-\u003e()` that computes the\nGini coefficient of the data in `x`.\n\n```\n\u003e\u003e\u003e from ufunclab import gini\n\n\u003e\u003e\u003e gini([1, 2, 3, 4], False)\n0.25\n\n\u003e\u003e\u003e income = [20, 30, 40, 50, 60, 70, 80, 90, 120, 150]\n\u003e\u003e\u003e gini(income, False)\n0.3028169014084507\n```\n\nWhen the second parameter is `True`, the calculation is based on the\nunbiased estimate of the mean absolute difference (MAD).\n\n```\n\u003e\u003e\u003e gini([1, 2, 3, 4], True)\n0.33333333333333337\n\n\u003e\u003e\u003e income = [20, 30, 40, 50, 60, 70, 80, 90, 120, 150]\n\u003e\u003e\u003e gini(income, True)\n0.3364632237871674\n```\n\n#### `rms`\n\n`rms(x)` computes the root-mean-square value for a collection of values.\nIt is a gufunc with signature `(n)-\u003e()`.  The implementation is for\nfloat and complex types; integer types are cast to float.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import rms\n\u003e\u003e\u003e x = np.array([1, 2, -1, 0, 3, 2, -1, 0, 1])\n\u003e\u003e\u003e rms(x)\n1.5275252316519468\n\nCompare to:\n\n\u003e\u003e\u003e np.sqrt(np.mean(x**2))\n1.5275252316519468\n\nA complex example:\n\n\u003e\u003e\u003e z = np.array([1-1j, 2+1.5j, -3-2j, 0.5+1j, 2.5j], dtype=np.complex64)\n\u003e\u003e\u003e rms(z)\n2.3979158\n\nAn equivalent NumPy expression:\n\n\u003e\u003e\u003e np.sqrt(np.mean(z.real**2 + z.imag**2))\n2.3979158\n\n```\n\n#### `vnorm`\n\n`vnorm(x, p)` computes the vector p-norm of 1D arrays.  It is a gufunc with\nsignatue `(n), () -\u003e ()`.\n\nFor example, to compute the 2-norm of [3, 4]:\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import vnorm\n\n\u003e\u003e\u003e vnorm([3, 4], 2)\n5.0\n```\n\nCompute the p-norm of [3, 4] for several values of p:\n\n```\n\u003e\u003e\u003e vnorm([3, 4], [1, 2, 3, np.inf])\narray([7.        , 5.        , 4.49794145, 4.        ])\n```\n\nCompute the 2-norm of four 2-d vectors:\n\n```\n\u003e\u003e\u003e vnorm([[3, 4], [5, 12], [0, 1], [1, 1]], 2)\narray([ 5.        , 13.        ,  1.        ,  1.41421356])\n```\n\nFor the same vectors, compute the p-norm for p = [1, 2, inf]:\n\n```\n\u003e\u003e\u003e vnorm([[3, 4], [5, 12], [0, 1], [1, 1]], [[1], [2], [np.inf]])\narray([[ 7.        , 17.        ,  1.        ,  2.        ],\n       [ 5.        , 13.        ,  1.        ,  1.41421356],\n       [ 4.        , 12.        ,  1.        ,  1.        ]])\n```\n\n`vnorm` handles complex numbers. Here we compute the norm of `z`\nwith orders 1, 2, 3, and inf.  (Note that `abs(z)` is [2, 5, 0, 14].)\n\n```\n\u003e\u003e\u003e z = np.array([-2j, 3+4j, 0, 14])\n\u003e\u003e\u003e vnorm(z, [1, 2, 3, np.inf])\narray([21.        , 15.        , 14.22263137, 14.        ])\n```\n\n### `percentileofscore`\n\n`percentileofscore(x, score, kind)`, a gufunc with signature `(n),(),()-\u003e()`,\nperforms the same calculation as `scipy.stats.percentileofscore`.  As a gufunc,\nit broadcasts all of its arguments, including `kind`.\n\nUnlike the `kind` parameter of `scipy.stats.percentilescore`, here the third\nargument is required, and it must be an integer type.  The allowed values\nare `ufunclab.op.KIND_RANK`, `ufunclab.op.KIND_WEAK`, `ufunclab.op.KIND_STRICT`\nand `ufunclab.op.KIND_MEAN`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import percentileofscore, op\n\n\u003e\u003e\u003e a = [1, 2, 3, 3, 4]\n\u003e\u003e\u003e percentileofscore(a, [2, 2.5, 3, 3.5], op.KIND_RANK)\narray([40., 40., 70., 80.])\n```\n\nWith broadcasting, all four kinds of the calculation can be performed with\none function call.\n\n```\n\u003e\u003e\u003e percentileofscore(a, 3, [op.KIND_RANK, op.KIND_WEAK, op.KIND_STRICT, op.KIND_MEAN])\narray([70., 80., 40., 60.])\n```\n\nA more common application of broadcasting might be to compute the percentile\nof a score in several arrays.\n\n```\n\u003e\u003e\u003e rng = np.random.default_rng(121263137472525314065)\n\u003e\u003e\u003e x = rng.integer(0, 20, size=(3, 16))\narray([[19,  0, 13,  0,  6, 19, 17,  9,  0,  9, 11, 14,  6, 15, 18,  3],\n       [ 7,  0, 16,  3,  5,  1,  1, 16,  0, 16,  3, 14,  5,  2, 10,  6],\n       [17, 10,  5, 17,  8, 11, 13, 18, 15,  7,  3,  3,  6,  0, 17, 15]])\n```\n\nGet the percentile of 10 in each row of x:\n\n```\n\u003e\u003e\u003e percentileofscore(x, 10, op.KIND_RANK)\narray([50., 75., 50.])\n```\n\nGet the percentiles of 9 and 10 for each row.  For broadcasting, the `score`\nhas shape (2, 1).\n\n```\n\u003e\u003e\u003e percentileofscore(x, [[9], [10]], op.KIND_RANK)\narray([[46.875, 68.75 , 43.75 ],\n       [50.   , 75.   , 50.   ]])\n```\n\n#### `vdot`\n\n`vdot(x, y)` is the vector dot product of the real floating point vectors\n`x` and `y`.  It is a gufunc with signature `(n),(n)-\u003e()`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import vdot\n\n\u003e\u003e\u003e x = np.array([[1, -2, 3],\n...               [4, 5, 6]])\n\u003e\u003e\u003e y = np.array([[-1, 0, 3],\n                  [1, 1, 1]])\n\n\u003e\u003e\u003e vdot(x, y)  # Default axis is -1.\narray([ 8., 15.])\n\n\u003e\u003e\u003e vdot(x, y, axis=0)\narray([ 3.,  5., 15.])\n```\n\n#### `pearson_corr`\n\n`pearson_corr(x, y)` computes Pearson's product-moment correlation coefficient.\nIt is a gufunc with shape signature `(n),(n)-\u003e()`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import pearson_corr\n\n\u003e\u003e\u003e x = np.array([1.0, 2.0, 3.5, 7.0, 8.5, 10.0, 11.0])\n\u003e\u003e\u003e y = np.array([10, 11.5, 11.4, 13.6, 15.1, 16.7, 15.0])\n\u003e\u003e\u003e pearson_corr(x, y)\n0.9506381287828245\n```\n\nIn the following example, a trivial dimension is added to the array `a` before\npassing it to `pearson_corr`, so the inputs are compatible for broadcasting.\nThe correlation coefficient of each row of `a` with each row of `b` is computed,\ngiving a result with shape (3, 2).\n\n```\n\u003e\u003e\u003e a = np.array([[2, 3, 1, 3, 5, 8, 8, 9],\n...               [3, 3, 1, 2, 2, 4, 4, 5],\n...               [2, 5, 1, 2, 2, 3, 3, 8]])\n\u003e\u003e\u003e b = np.array([[9, 8, 8, 7, 4, 4, 1, 2],\n...               [8, 9, 9, 6, 5, 7, 3, 4]])\n\u003e\u003e\u003e pearson_corr(np.expand_dims(a, 1), b)\narray([[-0.92758645, -0.76815464],\n       [-0.65015428, -0.53015896],\n       [-0.43575108, -0.32925148]])\n```\n\n#### `wjaccard`\n\n`wjaccard` is a gufunc with shape signature `(n),(n)-\u003e()` that computes\nthe weighted Jaccard index, which is defined to be\n\n    Jw(x, y) = min(x, y).sum() / max(x, y).sum()\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import wjaccard\n\n\u003e\u003e\u003e x = np.array([0.9, 1.0, 0.7, 0.0, 0.8, 0.6])\n\u003e\u003e\u003e y = np.array([0.3, 1.0, 0.9, 0.6, 1.0, 0.2])\n\u003e\u003e\u003e wjaccard(x, y)\n0.6\n```\n#### `nextn_greater`\n\n`nextn_greater(x, n, out=None, axis=-1)` is a Python function that wraps a\ngufunc with signature `()-\u003e(n)`.  Given a floating point scalar `x`, it\ncomputes the next `n` values greater than `x`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import nextn_greater\n\n\u003e\u003e\u003e x = np.float32(2.5)\n\u003e\u003e\u003e nextn_greater(x, 5)\narray([2.5000002, 2.5000005, 2.5000007, 2.500001 , 2.5000012],\n      dtype=float32)\n```\n\n#### `nextn_less`\n\n`nextn_less(x, n, out=None, axis=-1)` is a Python function that wraps a\ngufunc with signature `()-\u003e(n)`.  Given a floating point scalar `x`, it\ncomputes the next `n` values less than `x`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import nextn_less\n\n\u003e\u003e\u003e x = np.float32(2.5)\n\u003e\u003e\u003e nextn_less(x, 5)\narray([2.4999998, 2.4999995, 2.4999993, 2.499999 , 2.4999988],\n      dtype=float32)\n```\n\n#### `one_hot`\n\n`one_hot(k, n, out=None, axis=-1)` is a Python function that wraps\na gufunc with signature `()-\u003e(n)`.  Given integers `k` and `n`,\nit returns a 1-d integer array with length `n`, where the value is\n1 at index `k` and 0 elsewhere.  If `k` is less than 0 or greater\nthan `n - 1`, the array will be all zeros.\n\n```\n\u003e\u003e\u003e from ufunclab import one_hot\n\n\u003e\u003e\u003e one_hot(3, 10)\narray([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])\n\n\u003e\u003e\u003e one_hot([3, 7, 8], 10)\narray([[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],\n       [0, 0, 0, 0, 0, 0, 0, 1, 0, 0],\n       [0, 0, 0, 0, 0, 0, 0, 0, 1, 0]])\n```\n\n#### `cross2`\n\n`cross2(u, v)` is a gufunc with signature `(2),(2)-\u003e()`.  It computes\nthe 2-d cross product that returns a scalar.  That is, `cross2([u0, u1], [v0, v1])`\nis `u0*v1 - u1*v0`.  The calculation is the same as that of `numpy.cross`,\nbut `cross2` is restricted to 2-d inputs.\n\nFor example,\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import cross2\n\n\u003e\u003e\u003e cross2([1, 2], [5, 3])\n-7\n\n\u003e\u003e\u003e cross2([[1, 2], [6, 0]], [[5, 3], [2, 3]])\narray([-7, 18])\n\n```\n\nIn the following, `a` and `b` are object arrays; `a` has shape (2,),\nand `b` has shape (3, 2).  The result of ``cross2(a, b)`` has shape\n(3,).\n\n```\n\u003e\u003e\u003e from fractions import Fraction as F\n\n\u003e\u003e\u003e a = np.array([F(1, 3), F(2, 7)])\n\u003e\u003e\u003e b = np.array([[F(7, 4), F(6, 7)], [F(2, 5), F(-3, 7)], [1, F(1, 4)]])\n\u003e\u003e\u003e cross2(a, b)\narray([Fraction(-3, 14), Fraction(-9, 35), Fraction(-17, 84)],\n      dtype=object)\n```\n\n#### `cross3`\n\n`cross3(u, v)` is a gufunc with signature `(3),(3)-\u003e(3)`.  It computes\nthe 3-d vector cross product (like `numpy.cross`, but specialized to the\ncase of 3-d vectors only).\n\nFor example,\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import cross3\n\n\u003e\u003e\u003e u = np.array([1, 2, 3])\n\u003e\u003e\u003e v = np.array([2, 2, -1])\n\n\u003e\u003e\u003e cross3(u, v)\narray([-8,  7, -2])\n```\n\nIn the following, `x` has shape (5, 3), and `y` has shape (2, 1, 3).\nThe result of `cross3(x, y)` has shape (2, 5, 3).\n\n```\n\u003e\u003e\u003e x = np.arange(15).reshape(5, 3)\n\u003e\u003e\u003e y = np.round(10*np.sin(np.linspace(0, 2, 6))).reshape(2, 1, 3)\n\n\u003e\u003e\u003e x\narray([[ 0,  1,  2],\n       [ 3,  4,  5],\n       [ 6,  7,  8],\n       [ 9, 10, 11],\n       [12, 13, 14]])\n\n\u003e\u003e\u003e y\narray([[[ 0.,  4.,  7.]],\n\n       [[ 9., 10.,  9.]]])\n\n\u003e\u003e\u003e cross3(x, y)\narray([[[ -1.,   0.,   0.],\n        [  8., -21.,  12.],\n        [ 17., -42.,  24.],\n        [ 26., -63.,  36.],\n        [ 35., -84.,  48.]],\n\n       [[-11.,  18.,  -9.],\n        [-14.,  18.,  -6.],\n        [-17.,  18.,  -3.],\n        [-20.,  18.,   0.],\n        [-23.,  18.,   3.]]])\n```\n\n#### `tri_area`\n\n`tri_area(p)` is a gufunc with signature `(3, n) -\u003e ()`.  It computes the\narea of a triangle defined by three points in n-dimensional space.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import tri_area\n\n`p` has shape (2, 3, 4). It contains the vertices\nof two triangles in 4-dimensional space.\n\n\u003e\u003e\u003e p = np.array([[[0.0, 0.0, 0.0, 6.0],\n                   [1.0, 2.0, 3.0, 6.0],\n                   [0.0, 2.0, 2.0, 6.0]],\n                  [[1.5, 1.0, 2.5, 2.0],\n                   [4.0, 1.0, 0.0, 2.5],\n                   [2.0, 1.0, 2.0, 2.5]]])\n\u003e\u003e\u003e tri_area(p)\narray([1.73205081, 0.70710678])\n```\n\n#### `tri_area_indexed`\n\n`tri_area_indexed(p, i)` is a gufunc with signature `(m, n),(3) -\u003e ()`.\nIt computes the area of a triangle defined by three points in n-dimensional\nspace.  The first argument, `p`, is an array with shape `(m, n)` holding `m`\npoints in n-dimensional space.  The second argument, `i`, is a 1-d array with\nlength three that holds indices into `p`.  The core calculation is equivalent\nto `tri_area(p[i])`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import tri_area_indexed, tri_area\n\n\u003e\u003e\u003e p = np.array([[0.0, 0.0, 0.0, 6.0],\n                  [1.0, 2.0, 3.0, 6.0],\n                  [0.0, 2.0, 2.0, 6.0],\n                  [1.5, 1.0, 2.5, 2.0],\n                  [4.0, 1.0, 0.0, 2.5],\n                  [2.0, 1.0, 2.0, 2.5]])\n\u003e\u003e\u003e tri_area_indexed(p, [0, 2, 3])\n6.224949798994367\n\u003e\u003e\u003e tri_area(p[[0, 2, 3]])\n6.224949798994367\n\nCompute the areas of several triangles formed from points in `p`.\nNote that the last two are the same triangles.\n\n\u003e\u003e\u003e tri_area_indexed(p, [[0, 2, 3], [1, 3, 4], [3, 4, 5], [-1, -2, -3]])\narray([6.2249498 , 7.46449931, 0.70710678, 0.70710678])\n```\n\n#### `fillnan1d`\n\n`fillnan1d(x)` is a gufunc with signature `(n)-\u003e(n)`.  It uses linear\ninterpolation to replace occurrences of `nan` in `x`.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import fillnan1d\n\n\u003e\u003e\u003e x = np.array([1.0, 2.0, np.nan, np.nan, 3.5, 5.0, np.nan, 7.5])\n\u003e\u003e\u003e fillnan1d(x)\narray([1.  , 2.  , 2.5 , 3.  , 3.5 , 5.  , 6.25, 7.5 ])\n```\n\n`nan` values at the ends of `x` are replaced with the nearest non-`nan`\nvalue:\n\n```\n\u003e\u003e\u003e x = np.array([np.nan, 2.0, np.nan, 5.0, np.nan, np.nan])\n\u003e\u003e\u003e fillnan1d(x)\narray([2. , 2. , 3.5, 5. , 5. , 5. ])\n```\n\nThis plot of the result of applying `fillnan1d(x)` to a bigger sample is\ngenerated by the script `examples/fillnan1d_demo.py`:\n\n![fillnan1d plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/fillnan1d_demo.png)\n\n#### `linear_interp1d`\n\n`linear_interp1d(x, xp, fp)` is a gufunc with signature `(),(n),(n)-\u003e()`.  It\nis like `numpy.interp`, but as a gufunc, it will broadcast its arguments.\n\nCurrently the function provides just the basic interpolation function.  It does\nnot extrapolate, so any `x` values outside of the interval `[xp[0], xp[-1]]` will\nresult in `nan`.  The `period` option of `numpy.interp` is also not implemented.\n\nThe function has a special code path in which the computation of the indices\nwhere the values in `x` lie in `xp` is done once for each input.  To activate\nthis code path, the appropriate shapes of the input arrays must be used.  Some\nfamiliarity with the broadcasting behavior of gufuncs is necessary to make the\nmost of this fast code path.  The arrays must be shaped so that in the internal\ngufunc loop, the strides associated with the `x` and `xp` arrays are 0.\n\nSome examples of the use of `linear_interp1d` follow.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import linear_interp1d\n```\n\nThis example is the same as `numpy.interp`.  `xp` and `fp` are the known values,\nand we want to evaluate the interpolated function at `x = [0, 0.25, 1, 2, 5, 6]`.\n\n```\n\u003e\u003e\u003e xp = np.array([0, 1, 3, 5, 8])\n\u003e\u003e\u003e fp = np.array([10, 15, 15, 20, 80])\n\u003e\u003e\u003e x = np.array([0, 0.25, 1, 2, 5, 6.5])\n\n\u003e\u003e\u003e linear_interp1d(x, xp, fp)\narray([10.  , 11.25, 15.  , 15.  , 20.  , 50.  ])\n\n\u003e\u003e\u003e np.interp(x, xp, fp)\narray([10.  , 11.25, 15.  , 15.  , 20.  , 50.  ])\n```\n\nWith broadcasting, we can interpolate multiple functions (i.e. multiple 1-d\narrays) in one call.\n\nFor example, in the array `fp3` below, we want to treat each column as a\nseparate function to be interpolated (note that the first column is the\nsame as `fp` above):\n\n```\n\u003e\u003e\u003e fp3 = np.array([[10, 15, 10],\n...                 [15, 14, 20],\n...                 [15, 13, 40],\n...                 [20, 12, 10],\n...                 [80, 11, 0]])\n```\n\nWe can do this with `linear_interp1d`, but we have to adjust the shapes\nof the inputs to broadcast correctly:\n\n```\n\u003e\u003e\u003e linear_interp1d(x[:, None], xp, fp3.T)\narray([[10.  , 15.  , 10.  ],\n       [11.25, 14.75, 12.5 ],\n       [15.  , 14.  , 20.  ],\n       [15.  , 13.5 , 30.  ],\n       [20.  , 12.  , 10.  ],\n       [50.  , 11.5 ,  5.  ]])\n```\n\nThe script `linear_interp1d_demo.py` in the `examples` directory\nprovides a demonstration of the use of `linear_interp1d` to interpolate\na parametric curve in two dimensions using arclength as the parameter.\nIt generates the following plot:\n\n![linear_interp1d plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/linear_interp1d_demo.png)\n\n\n#### `backlash`\n\n`backlash(x, deadband, initial)`, a gufunc with signature `(n),(),()-\u003e(n)`,\ncomputes the \"backlash\" response of a signal; see the Wikipedia article\n[Backlash (engineering)](https://en.wikipedia.org/wiki/Backlash_(engineering)).\nThe function emulates the\n[backlash block](https://www.mathworks.com/help/simulink/slref/backlash.html)\nof Matlab's Simulink library.\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import backlash\n\n\u003e\u003e\u003e x = np.array([0, 0.5, 1, 1.1, 1.0, 1.5, 1.4, 1.2, 0.5])\n\u003e\u003e\u003e deadband = 0.4\n\u003e\u003e\u003e initial = 0\n\n\u003e\u003e\u003e backlash(x, deadband, initial)\narray([0. , 0.3, 0.8, 0.9, 0.9, 1.3, 1.3, 1.3, 0.7])\n```\n\nThe script `backlash_demo.py` in the `examples` directory generates\nthe plot\n\n![Backlash plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/backlash_demo.png)\n\n#### `backlash_sum`\n\n`backlash_sum` computes the linear combination of `m` `backlash` operations.\nThis is known as the Prandtl-Ishlinskii hysteresis model.  The function is a gufunc\nwith signature `(n),(m),(m),(m)-\u003e(n),(m)`.  The second return value is the final value of\neach of the backlash processes.\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import backlash_sum\n\n\u003e\u003e\u003e x = np.array([0.0, 0.2, 0.5, 1.1, 1.25, 1.0, 0.2, -1])\n\nHere the weights are all the same and happen to sum to 1, but that\nis not required in general.\n\n\u003e\u003e\u003e w = np.array([0.25, 0.25, 0.25, 0.25])\n\u003e\u003e\u003e deadband = np.array([0.2, 0.4, 0.6, 0.8])\n\u003e\u003e\u003e initial = np.zeros(4)\n\n\u003e\u003e\u003e y, final = backlash_sum(x, w, deadband, initial)\n\u003e\u003e\u003e y\narray([ 0.    ,  0.025 ,  0.25  ,  0.85  ,  1.    ,  0.9875,  0.45  , -0.75  ])\n```\n\n-----\n\nAnother example is in the script `backlash_sum_demo.py` in the `examples`\ndirectory. It passes two cycles of a sine wave with amplitude 2 through a\nPrandtl-Ishlinskii model with three backlash operations.  The weights are\n`w = [0.125, 0.25, 0.25]`, the deadband values are `[0.5, 1.0, 1.5]`, and\nthe initial values are all zero.\n\n![Backlash_sum plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/backlash_sum_demo_x_vs_t.png)\n\n![Backlash_sum plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/backlash_sum_demo_y_vs_x.png)\n\n#### `hysteresis_relay`\n\n`hysteresis_relay(x, low_threshold, high_threshold, low_value, high_value, init)`,\na gufunc with signature `(n),(),(),(),(),()-\u003e(n)`, passes `x` through a relay with\nhysteresis (like a [Schmitt trigger](https://en.wikipedia.org/wiki/Schmitt_trigger)).\nThe function is similar to the\n[relay block](https://www.mathworks.com/help/simulink/slref/relay.html)\nof Matlab's Simulink library.\n\nThe script `hysteresis_relay_demo.py` in the `examples` directory generates\nthe plot\n\n![hysteresis_replay plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/hysteresis_relay_demo.png)\n\n#### `sosfilter`\n\n`sosfilter(sos, x)` is a gufunc with signature `(m,6),(n)-\u003e(n)`.\nThe function applies a discrete time linear filter to the input\narray `x`.  The array `sos` with shape `(m,6)` represents the\nlinear filter using the *second order sections* format.\n\nThe function is like `scipy.signal.sosfilt`, but this version does\nnot accept the `zi` parameter.  See `sosfilter_ic` for a function\nthat accepts `zi`.\n\nThe script `sosfilter_demo.py` in the `examples` directory generates\nthe plot\n\n![sosfilter plot](https://github.com/WarrenWeckesser/ufunclab/blob/main/examples/sosfilter_demo.png)\n\n#### `sosfilter_ic`\n\n`sosfilter_ic(sos, x, zi)` is a gufunc with signature\n`(m,6),(n),(m,2)-\u003e(n),(m,2)`.  Like `sosfilter`, the function applies\na discrete time linear filter to the input array `x`.  The array `sos`\nwith shape `(m,6)` represents the linear filter using the *second order\nsections* format.\n\nThis function is like `scipy.signal.sosfilt`, but for `sosfilter_ic`,\nthe `zi` parameter is *required*.  Also, because `sosfilter_ic` is a gufunc,\nit uses the gufunc rules for broadcasting.  `scipy.signal.sosfilt` handles\nbroadcasting of the `zi` parameter differently.\n\n#### `sosfilter_ic_contig`\n\n`sosfilter_ic_contig(sos, x, zi)` is a gufunc with signature\n`(m,6),(n),(m,2)-\u003e(n),(m,2)`.  This function has the same inputs and\nperforms the same calculation as `sosfilter_ic`, but it assumes that the\narray inputs are all C-contiguous.  It does not verify this; if an array\ninput is *not* C-contiguous, the results will be incorrect, and the program\nmight crash.\n\n#### `multivariate_logbeta`\n\n`multivariate_logbeta(x)` is a gufunc with signature `(n)-\u003e()` that computes\nthe logarithm of the multivariate beta function.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import multivariate_logbeta\n\n\u003e\u003e\u003e x = np.array([1, 2.5, 7.25, 3])\n\u003e\u003e\u003e multivariate_logbeta(x)\n-13.87374699005739\n\nCompare to\n\n\u003e\u003e\u003e from scipy.special import gammaln\n\u003e\u003e\u003e gammaln(x).sum() - gammaln(x.sum())\n-13.87374699005739\n\n```\n\n#### `bincount`\n\n`bincount(x, m=None, weights=None, out=None, axis=-1)` is a Python function\nthat wraps two gufuncs, one with shape signature `(n)-\u003e(m)` (no weights) and\nthe other with shape signature `(n),(n)-\u003e(m)`.  The function is like\n`np.bincount`, but it accepts n-dimensional arrays.  When the input has more\nthan one dimension, the operation is applied along the given axis.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import bincount\n```\n\nCreate an array to work with.  `x` is an array with shape `(3, 12)`.\n\n```\n\u003e\u003e\u003e rng = np.random.default_rng(121263137472525314065)\n\u003e\u003e\u003e x = rng.integers(0, 8, size=(3, 12))\n\u003e\u003e\u003e x\narray([[7, 0, 5, 0, 2, 7, 7, 3, 0, 3, 4, 5],\n       [2, 6, 7, 1, 3, 0, 6, 1, 2, 0, 0, 6],\n       [0, 6, 1, 5, 2, 1, 4, 2, 6, 4, 2, 6]])\n```\n\nBy default, `bincount` operates along the last axis.  The default\nvalue of `m` is one more than maximum value in `x`, so in this case\nthe output length of the counts will be 8.  That is, the output\narray will have shape `(3, 8)`.\n\n```\n\u003e\u003e\u003e bincount(x)\narray([[3, 0, 1, 2, 1, 2, 0, 3],\n       [3, 2, 2, 1, 0, 0, 3, 1],\n       [1, 2, 3, 0, 2, 1, 3, 0]], dtype=uint64)\n```\n\nIf we given a value for `m` that is larger than 8, the final values\nwill be 0.\n\n```\n\u003e\u003e\u003e bincount(x, 10)\narray([[3, 0, 1, 2, 1, 2, 0, 3, 0, 0],\n       [3, 2, 2, 1, 0, 0, 3, 1, 0, 0],\n       [1, 2, 3, 0, 2, 1, 3, 0, 0, 0]], dtype=uint64)\n```\n\nIf the given value of `m` is smaller than `np.max(x) + 1`, the values\ngreater than or equal to `m` are ignored.\n\n```\n\u003e\u003e\u003e bincount(x, 4)\narray([[3, 0, 1, 2],\n       [3, 2, 2, 1],\n       [1, 2, 3, 0]], dtype=uint64)\n```\n\nThe `axis` parameter selects the axis of `x` along which `bincount`\nis applied.  In the following example, since `x` has shape `(3, 12)`,\nthe output has shape `(8, 12)` when `axis=0` is given.\n\n```\n\u003e\u003e\u003e bincount(x, axis=0)\narray([[1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0],\n       [0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0],\n       [1, 0, 0, 0, 2, 0, 0, 1, 1, 0, 1, 0],\n       [0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0],\n       [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0],\n       [0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1],\n       [0, 2, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2],\n       [1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0]], dtype=uint64)\n```\n\nSome examples with weights.\n\n```\n\u003e\u003e\u003e x = np.array([3, 4, 5, 1, 1, 0, 4])\n\u003e\u003e\u003e w = np.array([1.0, 0.25, 1.5, 0.5, 0.75, 1.0, 1.5])\n\u003e\u003e\u003e bincount(x, weights=w)\narray([1.  , 1.25, 0.  , 1.  , 1.75, 1.5 ])\n\n\u003e\u003e\u003e x = np.array([[1, 0, 2, 2],\n...               [0, 0, 0, 2]])\n\u003e\u003e\u003e w = np.array([0.25, 0.75, 0.75, 0.5])\n\u003e\u003e\u003e bincount(x, weights=w)\narray([[0.75, 0.25, 1.25],\n       [1.75, 0.  , 0.5 ]])\n```\n\nThe `weights` array can be integer, float, double, complex float or\ncomplex double.\n\n```\n\u003e\u003e\u003e x = np.array([[1, 0, 2, 2, 1, 4],\n...               [0, 0, 0, 2, 3, 3]])\n\u003e\u003e\u003e w = np.array([0.25-1j, 0.75+3j, 0.75+0.5j, 0.5+1j, 1.0, -3j],\n...              dtype=np.complex64)\n\u003e\u003e\u003e bincount(x, weights=w)\narray([[0.75+3.j , 1.25-1.j , 1.25+1.5j, 0.  +0.j , 0.  -3.j ],\n       [1.75+2.5j, 0.  +0.j , 0.5 +1.j , 1.  -3.j , 0.  +0.j ]],\n      dtype=complex64)\n```\n\n#### `convert_to_base`\n\n`convert_to_base(k, base, ndigits, out=None, axis=-1)` is a Python function\nthat wraps a gufunc.  The function converts an integer to a given base, using\n`ndigits` \"digits\".  The output \"digits\" are the coefficients of powers of\n`base` that sum to `k`.\n\nA gufunc cannot accept an argument such as `ndigits` that determines the\nsize of one of the outputs dimensions.  This function is a wrapper of a\ngufunc with signature `(),()-\u003e(n)`. The wrapper interprets the inputs to\nproduce an `out` array of the appropriate shape that is passed to the gufunc.\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import convert_to_base\n\n\u003e\u003e\u003e convert_to_base(1249, 8, ndigits=4)\narray([1, 4, 3, 2])\n\nThat result follows from 1249 = 1*8**0 + 4*8**1 + 3*8**2 + 2*8**3.\n\nBroadcasting applies to `k` and `base`:\n\n\u003e\u003e\u003e x = np.array([10, 24, 85])    # shape is (3,)\n\u003e\u003e\u003e base = np.array([[8], [16]])  # shape is (2, 1)\n\u003e\u003e\u003e convert_to_base(x, base, ndigits=4)  # output shape is (2, 3, 4)\narray([[[ 2,  1,  0,  0],\n        [ 0,  3,  0,  0],\n        [ 5,  2,  1,  0]],\n       [[10,  0,  0,  0],\n        [ 8,  1,  0,  0],\n        [ 5,  5,  0,  0]]])\n```\n\n#### `gendot`\n\n`gendot` creates a new gufunc (with signature `(n),(n)-\u003e()`) that is\nthe composition of two ufuncs.  The first ufunc must be an element-wise\nufunc with two inputs and one output.  The second must be either another\nelement-wise ufunc with two inputs and one output, or a gufunc with\nsignature `(n)-\u003e()`.\n\nThe name `gendot` is from \"generalized dot product\".  The standard\ndot product is the composition of element-wise multiplication and\nreduction with addition.  The `prodfunc` and `sumfunc` arguments of\n`gendot` take the place of multiplication and addition.\n\nFor example, to take the element-wise minimum of two 1-d arrays,\nand then take the maximum of the result:\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import gendot\n\n\u003e\u003e\u003e minmaxdot = gendot(np.minimum, np.maximum)\n\n\u003e\u003e\u003e a = np.array([1.0, 2.5, 0.3, 1.9, 3.0, 1.8])\n\u003e\u003e\u003e b = np.array([0.5, 1.1, 0.9, 2.1, 0.3, 3.0])\n\u003e\u003e\u003e minmaxdot(a, b)\n1.9\n```\n\n`minmaxdot` is a gufunc with signature `(n),(n)-\u003e()`;  the type\nsignatures of the gufunc loop functions were derived by matching\nthe signatures of the ufunc loop functions for `np.minimum` and\n`np.maximum`:\n\n```\n\u003e\u003e\u003e minmaxdot.signature\n'(n),(n)-\u003e()'\n\n\u003e\u003e\u003e print(minmaxdot.types)\n['??-\u003e?', 'bb-\u003eb', 'BB-\u003eB', 'hh-\u003eh', 'HH-\u003eH', 'ii-\u003ei', 'II-\u003eI', 'll-\u003el',\n 'LL-\u003eL', 'qq-\u003eq', 'QQ-\u003eQ', 'ee-\u003ee', 'ff-\u003ef', 'dd-\u003ed', 'gg-\u003eg', 'FF-\u003eF',\n 'DD-\u003eD', 'GG-\u003eG', 'mm-\u003em', 'MM-\u003eM']\n```\n\n`gendot` is experimental, and might not be useful in many applications.\nWe could do the same calculation as `minmaxdot` with, for example,\n`np.maximum.reduce(np.minimum(a, b))`, and in fact, the pure NumPy\nversion is faster than `minmaxdot(a, b)` for large (and even moderately\nsized) 1-d arrays.  An advantage of the `gendot` gufunc is that it does\nnot create an intermediate array when broadcasting takes place.  For\nexample, with inputs `x` and `y` with shapes `(20, 10000000)` and\n`(10, 1, 10000000)`, the equivalent of `minmaxdot(x, y)` can be computed\nwith `np.maximum.reduce(np.minimum(x, y), axis=-1)`, but `np.minimum(x, y)`\ncreates an array with shape `(10, 20, 10000000)`.  Computing the result\nwith `minmaxdot(x, y)` does not create the temporary intermediate array.\n\n#### `ufunc_inspector`\n\n`ufunc_inspector(func)` prints information about a NumPy ufunc.\n\nFor example,\n\n```\n\u003e\u003e\u003e import numpy as np\n\u003e\u003e\u003e from ufunclab import ufunc_inspector\n\u003e\u003e\u003e np.__version__\n'1.24.0'\n\u003e\u003e\u003e ufunc_inspector(np.hypot)\n'hypot' is a ufunc.\nnin = 2, nout = 1\nntypes = 5\nloop types:\n  0: ( 23,  23) -\u003e  23  (ee-\u003ee)  PyUFunc_ee_e_As_ff_f\n  1: ( 11,  11) -\u003e  11  (ff-\u003ef)  PyUFunc_ff_f\n  2: ( 12,  12) -\u003e  12  (dd-\u003ed)  PyUFunc_dd_d\n  3: ( 13,  13) -\u003e  13  (gg-\u003eg)  PyUFunc_gg_g\n  4: ( 17,  17) -\u003e  17  (OO-\u003eO)  PyUFunc_OO_O_method\n```\n\n(The output will likely change as the code develops.)\n\n```\n\u003e\u003e\u003e ufunc_inspector(np.sqrt)\n'sqrt' is a ufunc.\nnin = 1, nout = 1\nntypes = 10\nloop types:\n  0:   23 -\u003e  23  (e-\u003ee)  PyUFunc_e_e_As_f_f\n  1:   11 -\u003e  11  (f-\u003ef)  not generic (or not in the checked generics)\n  2:   12 -\u003e  12  (d-\u003ed)  not generic (or not in the checked generics)\n  3:   11 -\u003e  11  (f-\u003ef)  PyUFunc_f_f\n  4:   12 -\u003e  12  (d-\u003ed)  PyUFunc_d_d\n  5:   13 -\u003e  13  (g-\u003eg)  PyUFunc_g_g\n  6:   14 -\u003e  14  (F-\u003eF)  PyUFunc_F_F\n  7:   15 -\u003e  15  (D-\u003eD)  PyUFunc_D_D\n  8:   16 -\u003e  16  (G-\u003eG)  PyUFunc_G_G\n  9:   17 -\u003e  17  (O-\u003eO)  PyUFunc_O_O_method\n\n\u003e\u003e\u003e ufunc_inspector(np.add)\n'add' is a ufunc.\nnin = 2, nout = 1\nntypes = 22\nloop types:\n  0: (  0,   0) -\u003e   0  (??-\u003e?)  not generic (or not in the checked generics)\n  1: (  1,   1) -\u003e   1  (bb-\u003eb)  not generic (or not in the checked generics)\n  2: (  2,   2) -\u003e   2  (BB-\u003eB)  not generic (or not in the checked generics)\n  3: (  3,   3) -\u003e   3  (hh-\u003eh)  not generic (or not in the checked generics)\n  4: (  4,   4) -\u003e   4  (HH-\u003eH)  not generic (or not in the checked generics)\n  5: (  5,   5) -\u003e   5  (ii-\u003ei)  not generic (or not in the checked generics)\n  6: (  6,   6) -\u003e   6  (II-\u003eI)  not generic (or not in the checked generics)\n  7: (  7,   7) -\u003e   7  (ll-\u003el)  not generic (or not in the checked generics)\n  8: (  8,   8) -\u003e   8  (LL-\u003eL)  not generic (or not in the checked generics)\n  9: (  9,   9) -\u003e   9  (qq-\u003eq)  not generic (or not in the checked generics)\n 10: ( 10,  10) -\u003e  10  (QQ-\u003eQ)  not generic (or not in the checked generics)\n 11: ( 23,  23) -\u003e  23  (ee-\u003ee)  not generic (or not in the checked generics)\n 12: ( 11,  11) -\u003e  11  (ff-\u003ef)  not generic (or not in the checked generics)\n 13: ( 12,  12) -\u003e  12  (dd-\u003ed)  not generic (or not in the checked generics)\n 14: ( 13,  13) -\u003e  13  (gg-\u003eg)  not generic (or not in the checked generics)\n 15: ( 14,  14) -\u003e  14  (FF-\u003eF)  not generic (or not in the checked generics)\n 16: ( 15,  15) -\u003e  15  (DD-\u003eD)  not generic (or not in the checked generics)\n 17: ( 16,  16) -\u003e  16  (GG-\u003eG)  not generic (or not in the checked generics)\n 18: ( 21,  22) -\u003e  21  (Mm-\u003eM)  not generic (or not in the checked generics)\n 19: ( 22,  22) -\u003e  22  (mm-\u003em)  not generic (or not in the checked generics)\n 20: ( 22,  21) -\u003e  21  (mM-\u003eM)  not generic (or not in the checked generics)\n 21: ( 17,  17) -\u003e  17  (OO-\u003eO)  PyUFunc_OO_O\n```\n\n\n### Resources\n\nHere's a collection of resources for learning about the C API for ufuncs.\n\n* [Universal functions (ufunc)](https://numpy.org/devdocs/reference/ufuncs.html)\n* [UFunc API](https://numpy.org/devdocs/reference/c-api/ufunc.html)\n* [Generalized Universal Function API](https://numpy.org/devdocs/reference/c-api/generalized-ufuncs.html)\n* [NEP 5 — Generalized Universal Functions](https://numpy.org/neps/nep-0005-generalized-ufuncs.html)\n* [NEP 20 — Expansion of Generalized Universal Function Signatures](https://numpy.org/neps/nep-0020-gufunc-signature-enhancement.html)\n* [Universal functions](https://numpy.org/devdocs/dev/internals.code-explanations.html#universal-functions),\n  part of the [NumPy C Code Explanations](https://numpy.org/devdocs/dev/internals.code-explanations.html#c-code-explanations)\n  * In particular, the section\n    [\"Function call\"](https://numpy.org/devdocs/dev/internals.code-explanations.html#function-call)\n    explains when the GIL is released.\n* Some relevant NumPy source code, if you want to dive deep:\n  * `PyUFuncObject` along with related C types and macros are defined in\n   [`numpy/numpy/_core/include/numpy/ufuncobject.h`](https://github.com/numpy/numpy/blob/main/numpy/_core/include/numpy/ufuncobject.h).\n  * `PyUFunc_FromFuncAndData` and `PyUFunc_FromFuncAndDataAndSignatureAndIdentity`\n    are defined in the file [`numpy/numpy/core/_src/umath/ufunc_object.c`](https://github.com/numpy/numpy/blob/main/numpy/_core/src/umath/ufunc_object.c).\n* Section of the [SciPy Lecture Notes](https://scipy-lectures.org/index.html) on ufuncs:\n  * [2.2.2 Universal Functions](https://scipy-lectures.org/advanced/advanced_numpy/index.html#universal-functions)\n* [Data Type API](https://numpy.org/doc/stable/reference/c-api/dtype.html) --\n  a handy reference.\n* *NumPy's distutils module and the corresponding template processor are deprecated.  Do not use in new code!*\n  When implementing inner loops for many NumPy dtypes, the\n  [NumPy distutils](https://numpy.org/doc/stable/reference/distutils_guide.html)\n  [template preprocessor](https://numpy.org/doc/stable/reference/distutils_guide.html#conversion-of-src-files-using-templates)\n  is a useful tool. (See the [\"Other files\"](https://numpy.org/doc/stable/reference/distutils_guide.html#other-files)\n  section for the syntax that would be used in, say, a C file.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWarrenWeckesser%2Fufunclab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWarrenWeckesser%2Fufunclab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWarrenWeckesser%2Fufunclab/lists"}