From 30354b99bad312f2759c3b2635ec95dd7f97cddb Mon Sep 17 00:00:00 2001 From: LunaStev Date: Thu, 29 Oct 2026 00:41:23 +0900 Subject: [PATCH 1/3] docs: standardize API contracts and add numerical task guides --- .github/workflows/docs.yml | 8 + .github/workflows/python-wheels.yml | 4 +- .github/workflows/validate.yml | 4 +- docs/contributing.md | 36 +- docs/explanation/numerical-conventions.md | 91 ++ docs/getting-started/choose-an-operation.md | 45 + docs/guides/borrowed-storage.md | 84 ++ docs/guides/calculus.md | 62 ++ docs/guides/least-squares.md | 63 ++ docs/guides/matrix-calculation.md | 57 + docs/guides/statistics.md | 45 + docs/index.md | 5 +- docs/javascripts/mathjax.js | 13 + docs/reference/c.md | 1045 +++++++++++++++++-- docs/reference/python.md | 788 ++++++++++++-- docs/reference/qr.md | 264 +++++ docs/reference/wave.md | 355 ++++++- mkdocs.yml | 14 + tools/build_docs.py | 4 + tools/check_api_docs.py | 147 ++- tools/haskell_reference.py | 201 +++- 21 files changed, 3082 insertions(+), 253 deletions(-) create mode 100644 docs/explanation/numerical-conventions.md create mode 100644 docs/getting-started/choose-an-operation.md create mode 100644 docs/guides/borrowed-storage.md create mode 100644 docs/guides/calculus.md create mode 100644 docs/guides/least-squares.md create mode 100644 docs/guides/matrix-calculation.md create mode 100644 docs/guides/statistics.md create mode 100644 docs/javascripts/mathjax.js create mode 100644 docs/reference/qr.md diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 181d7c1..9ad78a0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,6 +36,14 @@ jobs: run: | python -m pip install -r requirements-docs.txt python tools/build_docs.py + - name: Verify Python documentation examples against the released package + run: | + python - <<'PY' + import pathlib, subprocess, sys, tomllib + version = tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'] + subprocess.run([sys.executable, '-m', 'pip', 'install', f'hmath-python[numpy,cffi]=={version}'], check=True) + PY + python tools/check_docs_examples.py - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: path: site diff --git a/.github/workflows/python-wheels.yml b/.github/workflows/python-wheels.yml index ddb0f33..7dbd531 100644 --- a/.github/workflows/python-wheels.yml +++ b/.github/workflows/python-wheels.yml @@ -4,9 +4,9 @@ name: Python wheels on: push: branches: [master] - paths-ignore: ['docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] + paths-ignore: ['.github/workflows/docs.yml', 'docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] pull_request: - paths-ignore: ['docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] + paths-ignore: ['.github/workflows/docs.yml', 'docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] workflow_call: outputs: version: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 789342f..a4242c3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -4,9 +4,9 @@ name: Validate on: push: branches: [master] - paths-ignore: ['docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] + paths-ignore: ['.github/workflows/docs.yml', 'docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] pull_request: - paths-ignore: ['docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] + paths-ignore: ['.github/workflows/docs.yml', 'docs/**', 'mkdocs.yml', 'requirements-docs.txt', 'tools/build_docs.py', 'tools/check_api_docs.py', 'tools/check_haskell_doc_examples.py', 'tools/check_docs_links.py', 'tools/haskell_reference.py', 'README.md'] workflow_dispatch: inputs: full-matrix: diff --git a/docs/contributing.md b/docs/contributing.md index cdd3ed0..d23795f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -22,7 +22,8 @@ put detailed instructions here and link to the authoritative explanation. ```sh python -m pip install -r requirements-docs.txt -python -m mkdocs serve +python tools/build_docs.py +python -m mkdocs serve --config-file build-docs/mkdocs.generated.yml ``` To build the site together with the complete Haskell API reference, install @@ -36,3 +37,36 @@ CI builds with strict validation and deploys the generated `site/` directory to GitHub Pages after changes reach `master`. Do not commit generated HTML. Use runnable examples with expected results; distinguish available interfaces from future or Haskell-only features. + +## Reference writing standard + +Follow the [numpydoc section conventions](https://numpydoc.readthedocs.io/en/latest/format.html) +for Python reference content and [Haddock conventions](https://haskell-haddock.readthedocs.io/latest/markup.html) +for Haskell source documentation. The website uses Markdown; adopting these +content conventions does not require changing its site generator. + +Each Python, C, or Wave entry needs a one-line purpose, exact signature, +`Parameters` table with one row per argument in declaration order, a `Returns` +section, and relevant `See also` links. Include types, defaults, units, shapes, +bounds, ownership, and failure conditions where they apply. Use `None.` for +an empty parameter list. Properties have no call parentheses. Explain errors +with the actual exception/status/constructor, rather than “may fail”. + +Add examples for operations that need an explanation of usage. Show expected +results, resource cleanup, and meaningful failure cases. For numerical +algorithms, explain the equation, assumptions, stopping criteria, and accuracy +limitations; keep implementation history out of user instructions. A simple +getter does not need a paper citation or an artificial standalone example. + +Haskell module and area indexes are generated from declarations and authored +Haddock prose, including constructors, record fields, and class methods. +The API check includes symbolic function names as well as ordinary identifiers. +Keep standalone task guides distinct from reference contracts and conceptual +explanations, following [Diátaxis](https://diataxis.fr/start-here/). + +The documentation workflow checks source signatures and parameter order, +reference coverage, local file/fragment links, Python examples against the +released wheel, and complete Haskell example programs against this checkout. +Run the C/Wave example programs with the native build when changing them. +The home-page version is substituted from `pyproject.toml` during generation; +documentation changes alone do not require a package release. diff --git a/docs/explanation/numerical-conventions.md b/docs/explanation/numerical-conventions.md new file mode 100644 index 0000000..93c0e1c --- /dev/null +++ b/docs/explanation/numerical-conventions.md @@ -0,0 +1,91 @@ +# Numerical conventions + +This page explains how to interpret numerical inputs and results. A function's +reference entry takes precedence when it defines a more specific contract. + +## Numeric types and finite values + +Python, C, and Wave tensor kernels use IEEE 754 binary64 (`float`, `double`, +and `f64`). Checked native kernels reject non-finite inputs/results. This +does not make floating-point arithmetic exact: rounding and cancellation +still occur for accepted finite values. + +Haskell functions have the numeric type shown in their signature. Generic +`Num`, `Fractional`, or `Floating` functions inherit the selected type's +arithmetic. For example, bounded integers can overflow. A generic type +constraint alone does not promise finite-result validation. + +## Shapes and storage + +Row-major order means consecutive elements in a row are adjacent. For +`[[1, 2], [3, 4]]`, flat values are `[1, 2, 3, 4]`. + +| Shape | Meaning | Logical element count | +| --- | --- | --- | +| `()` | Scalar | 1 | +| `(n,)` | Vector | n | +| `(m, n)` | Matrix | m × n | +| `(0, n)` | Empty matrix with n columns | 0 | + +The native ABI bounds rank at 8 and logical element count at 1,048,576. +These are native-interface limits, not universal limits for every Haskell +module. Native strides count elements; NumPy strides count bytes. Storage +span and logical element count can differ for a strided view. + +## Compare approximate results + +Use exact equality for dimensions, counts, and deliberately exact small +examples. For general floating-point results, compare with explicit +absolute and relative tolerances: + +$$ +|x-y| \leq \max(\mathrm{atol},\ \mathrm{rtol}\,\max(|x|,|y|)). +$$ + +This is the symmetric rule used by Python's `math.isclose`; a particular +solver may use a different stopping rule. Choose tolerances for the scale +of your data and the required accuracy rather than copying a universal +constant. Absolute tolerance matters when the expected result is near zero. + +```python +import math +from hmath import Tensor + +with Tensor([0.1, 0.2]) as samples: + assert math.isclose(samples.sum(), 0.3, rel_tol=1e-12, abs_tol=1e-15) +``` + +## Residual, conditioning, and rank + +For a proposed solution of $A x = b$, the residual is +$r = A x - b$. A small residual checks that the computed vector nearly +satisfies the supplied equation. It does not by itself establish a small +solution error: an ill-conditioned problem can amplify small perturbations +in the data. + +QR rank is a numerical decision controlled by a relative pivot tolerance. +`qrRank` is an estimate, not an exact algebraic proof. A rank-deficient +factorization can be inspected, while the QR least-squares solver rejects +it. See [QR contracts](../reference/qr.md) for the exact threshold and shapes. + +## Errors and stopping conditions + +| Interface | Failure reporting | What to inspect | +| --- | --- | --- | +| Python | `HMathError` or a documented Python exception | Exception type, `.status`, message | +| C | Checked status and thread-local diagnostic | Status immediately, then error message on the same thread | +| Wave | Wrapper/native status | Status; native message can be stale after wrapper-only rejection | +| Haskell checked API | `Either MathError a` | `Left` constructor or accepted `Right` result | +| Haskell legacy pure API | Function-specific precondition or pure error | Reference entry before calling | + +An accepted result is not a universal accuracy guarantee. For iterative +methods, inspect the reported stopping reason and residual/error estimate. +An evaluation or iteration budget limits work; it is not a requested error +bound. Calculus `estimateError` values are numerical estimates, not rigorous +bounds on every possible callback. + +## Further reading + +- [SciPy linear solve](https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.solve.html): shape contracts and singular/ill-conditioned systems. +- [GNU Scientific Library linear algebra](https://www.gnu.org/software/gsl/doc/html/linalg.html): decompositions, rank, and numerical tolerances. +- [Python `math.isclose`](https://docs.python.org/3/library/math.html#math.isclose): approximate comparison semantics. diff --git a/docs/getting-started/choose-an-operation.md b/docs/getting-started/choose-an-operation.md new file mode 100644 index 0000000..a11d672 --- /dev/null +++ b/docs/getting-started/choose-an-operation.md @@ -0,0 +1,45 @@ +# Choose an operation + +Start with the problem you want to solve, then select an available interface. +The Python wheel provides the native scalar/tensor interface. The Haskell +library additionally supplies the broader numerical algorithms below. + +## Python, C, and Wave + +| Task | Python | C | Wave | +| --- | --- | --- | --- | +| Add scalars | `add(a, b)` | `hmath_scalar_add` | `scalar_add` | +| Construct a matrix | `Tensor.matrix` | `hmath_matrix_copy` | `matrix_copy` | +| Sum all elements | `tensor.sum()` | `hmath_tensor_sum` | `tensor_sum` | +| Sum corresponding products | `a.dot(b)` | `hmath_tensor_dot` | `tensor_dot` | +| Add corresponding elements | `a + b` | `hmath_tensor_add` | `tensor_add` | +| Multiply two matrices | `a @ b` | `hmath_tensor_matmul` | `tensor_matmul` | +| Exchange NumPy arrays | `Tensor.from_numpy`, `tensor.numpy` | Storage/DLPack ABI | Use the C ABI | +| Reinterpret a contiguous shape | `tensor.reshape` | `hmath_tensor_reshape` | Use the C ABI | + +`dot` reduces every corresponding product to a scalar at any rank. Matrix +multiplication requires rank two and matching inner dimensions. Elementwise +addition requires identical shapes. Broadcasting and batched matrix products +are not supported by these wrappers. + +Read the [Python](../reference/python.md), [C](../reference/c.md), or +[Wave](../reference/wave.md) reference for complete contracts. The +[matrix calculation guide](../guides/matrix-calculation.md) demonstrates the +three operations on the same inputs. + +## Haskell numerical algorithms + +| Task | Start with | Guide / reference | +| --- | --- | --- | +| Solve a square linear system | `HMath.Matrix.LU.solve` | [Linear algebra API](../reference/haskell.md) | +| Fit an overdetermined linear model | `HMath.Matrix.QR.leastSquares` | [Least squares](../guides/least-squares.md) | +| Regularize a linear fit | `HMath.Matrix.QR.ridgeSolve` | [QR contracts](../reference/qr.md) | +| Estimate a derivative | `HMath.Calculus.derivative` | [Calculus](../guides/calculus.md) | +| Integrate a smooth finite-interval function | `HMath.Calculus.integrateComposite` | [Calculus](../guides/calculus.md) | +| Request adaptive integration | `HMath.Calculus.adaptiveIntegrate` | [Calculus](../guides/calculus.md) | +| Compute descriptive statistics | `HMath.Statistics` | [Summarize data](../guides/statistics.md) | +| Explore optimization, probability, graphs, or transforms | Select a numerical area | [Haskell module index](../reference/haskell.md) | + +These functions require Haskell imports; they are not additional Python +functions. Check [numerical conventions](../explanation/numerical-conventions.md) +before selecting tolerances or interpreting convergence. diff --git a/docs/guides/borrowed-storage.md b/docs/guides/borrowed-storage.md new file mode 100644 index 0000000..b7c455c --- /dev/null +++ b/docs/guides/borrowed-storage.md @@ -0,0 +1,84 @@ +# Borrow storage safely in C + +Use `hmath_tensor_borrow` when copying a read-only binary64 buffer is +undesirable. The caller must guarantee valid aligned storage, its readable +span, and immutability through **every alias** until the last storage owner +is released. See [C integration](c.md) for compiler and linker setup. + +## Give the tensor a cleanup callback + +This complete example allocates three values, borrows them, reshapes the +tensor without copying, and checks that cleanup happens only after both +handles are released. Metadata and ownership operations need no runtime +initialization. + +```c +#include +#include +#include + +struct storage_owner { + double *values; + unsigned releases; +}; + +static void release_storage(void *userdata, const double *data) { + struct storage_owner *owner = userdata; + assert(data == owner->values); + free(owner->values); + owner->values = NULL; + owner->releases++; +} + +int main(void) { + struct storage_owner owner = {malloc(3 * sizeof(double)), 0}; + hmath_tensor *vector = NULL, *matrix = NULL; + const size_t vector_shape[] = {3}, matrix_shape[] = {1, 3}; + hmath_status status; + if (!owner.values) return 1; + owner.values[0] = 1; owner.values[1] = 2; owner.values[2] = 3; + status = hmath_tensor_borrow(owner.values, 3, vector_shape, NULL, 1, + release_storage, &owner, &vector); + if (status != HMATH_OK) { + free(owner.values); /* Failed construction did not consume storage. */ + return 1; + } + status = hmath_tensor_reshape(vector, matrix_shape, 2, &matrix); + hmath_tensor_release(vector); + if (status != HMATH_OK) return 1; /* Final vector release already cleaned up. */ + assert(owner.releases == 0); + hmath_tensor_release(matrix); + assert(owner.releases == 1); + return 0; +} +``` + +The expected exit status is zero. The `userdata` object lives on the stack +until both handles have been released. A real application must retain its +owner object for at least as long as any handle or exported view can survive. + +## Check strided layouts + +For nonnegative element strides $s_i$ and dimensions $d_i$, a nonempty +tensor reaches a storage span of + +$$ +1+\sum_i(d_i-1)s_i. +$$ + +`storage_count` must cover that span, including gaps; it is not simply the +product of dimensions. C strides are measured in **doubles**, not bytes. +Passing NULL strides selects contiguous row-major storage. An empty tensor +does not read any values. Rank zero is a scalar and needs one value. + +## Avoid lifetime errors + +The deleter runs synchronously on the final releasing thread, exactly once +for successfully borrowed storage. It must not throw across C or invoke +runtime shutdown. Failed construction leaves ownership with the caller. +`hmath_tensor_data` returns a borrowed pointer and does not create another +owner; never free that pointer separately. + +See [the C reference](../reference/c.md), +[architecture and ownership](../explanation/architecture.md), and +[NumPy borrowing](numpy.md) for related contracts. diff --git a/docs/guides/calculus.md b/docs/guides/calculus.md new file mode 100644 index 0000000..85c6699 --- /dev/null +++ b/docs/guides/calculus.md @@ -0,0 +1,62 @@ +# Differentiate and integrate in Haskell + +Use `HMath.Calculus` for checked finite differences and numerical quadrature +of `Double` callbacks. Callbacks must terminate and be side-effect free. +These operations are Haskell APIs; they are not Python tensor methods. + +## Use an explicit step and panel count + +Estimate the derivative of $x^2$ at 3 and its integral from 0 to 1: + +```haskell +import qualified HMath.Calculus as Calculus + +main :: IO () +main = do + let derivative = Calculus.derivative Calculus.Central 1e-5 (\x -> x*x) 3 + integral = Calculus.integrateComposite Calculus.Simpson 100 (\x -> x*x) 0 1 + expect expected result = case result of + Left err -> fail (show err) + Right actual -> + if abs (actual - expected) <= 1e-8 + then pure () + else fail ("Unexpected estimate: " ++ show actual) + expect 6 derivative + expect (1/3) integral + putStrLn "derivative and integral checks passed" +``` + +The expected output is `derivative and integral checks passed`. +The differentiation step must be positive, finite, and representable at +the evaluation point. Making it arbitrarily small can worsen cancellation. +Simpson's panel count must be positive and even; `ThreeEighths` requires a +multiple of three. A fixed-panel calculation does not return an error bound. + +## Request adaptive refinement + +```haskell +import qualified HMath.Calculus as Calculus + +main :: IO () +main = case Calculus.adaptiveIntegrate Calculus.defaultTolerance (\x -> x*x) 0 1 of + Left err -> fail (show err) + Right report -> do + if abs (Calculus.estimateValue report - 1/3) <= 1e-9 + && Calculus.estimateError report >= 0 + && Calculus.evaluations report <= 100000 + then putStrLn "adaptive integration check passed" + else fail (show report) +``` + +The default adaptive tolerance uses `1e-10` absolute, `1e-8` relative, and +100,000 evaluations. Its stopping target is +$\max(\mathrm{absoluteTolerance},\ \mathrm{relativeTolerance}\,|\mathrm{estimateValue}|)$. +Absolute tolerance must be positive; relative tolerance must be nonnegative; +both must be finite. A nonempty interval needs at least 15 evaluations. + +If refinement cannot meet the target within the budget, the adaptive +integrator returns `Left (NonConvergence ...)`. Non-finite sampled values +also fail. `estimateError` is an estimate from the approximation rule, +not a rigorous bound for arbitrary callbacks. Inspect the full +[Calculus API](../reference/haskell.md) and +[numerical conventions](../explanation/numerical-conventions.md). diff --git a/docs/guides/least-squares.md b/docs/guides/least-squares.md new file mode 100644 index 0000000..8c00a6d --- /dev/null +++ b/docs/guides/least-squares.md @@ -0,0 +1,63 @@ +# Solve least-squares problems in Haskell + +Use `HMath.Matrix.QR.leastSquares` to fit coefficients when there are at +least as many observations as coefficients and the design matrix has full +column rank. This guide requires the Haskell source environment described +in [building from source](build.md); the Python wrapper does not expose QR. + +## Fit a line + +Fit $y = c_0 + c_1 t$ to the observations $(0,1)$, $(1,3)$, $(2,5)$. +Each row of the design matrix is `[1, t]`. Solve + +$$ +\min_c \|A c-y\|_2^2,\qquad +A=\begin{bmatrix}1&0\\1&1\\1&2\end{bmatrix},\quad +y=\begin{bmatrix}1\\3\\5\end{bmatrix}. +$$ + +```haskell +import qualified HMath.Matrix as Matrix +import qualified HMath.Matrix.QR as QR + +main :: IO () +main = case Matrix.fromListChecked 3 2 [1, 0, 1, 1, 1, 2 :: Double] + >>= (\a -> QR.leastSquares a [1, 3, 5]) of + Left err -> fail (show err) + Right coefficients -> do + let close a b = abs (a - b) <= 1e-10 + if length coefficients == 2 && and (zipWith close coefficients [1, 2]) + then putStrLn "intercept=1, slope=2" + else fail ("Unexpected coefficients: " ++ show coefficients) +``` + +The expected output is `intercept=1, slope=2`. The assertion compares with +a tolerance because QR uses floating-point arithmetic. + +## Choose factorization or direct solve + +| Need | Operation | +| --- | --- | +| One right-hand side | `leastSquares a b` | +| Several RHS columns | `leastSquaresMatrix a b` | +| Reuse one factorization | `qrDecompose a`, then `solveQR` or `solveMatrixQR` | +| Inspect column rank | `qrRank factors` and `qrPermutation factors` | +| Add a squared-coefficient penalty | `ridgeSolve lambda a b` | + +QR uses column pivoting, so the factorization describes reordered columns. +The solve functions return coefficients in the **original** column order. +HMath uses augmented QR for ridge regression and does not form normal +equations. The intercept is penalized too if it is a column of the supplied +design matrix; no column receives an automatic exemption. + +## Handle failure + +Underdetermined systems and rank-deficient design matrices are rejected by +the QR least-squares solve. Wrong RHS length, invalid tolerance, non-finite +data, and non-finite arithmetic also have checked failures. Inspect `Left` +instead of substituting a zero coefficient vector. + +For the exact signatures, defaults, matrix shapes, rank threshold, and error +constructors, see [QR contracts](../reference/qr.md). A small residual alone +does not establish a small coefficient error; see +[conditioning](../explanation/numerical-conventions.md#residual-conditioning-and-rank). diff --git a/docs/guides/matrix-calculation.md b/docs/guides/matrix-calculation.md new file mode 100644 index 0000000..7261dac --- /dev/null +++ b/docs/guides/matrix-calculation.md @@ -0,0 +1,57 @@ +# Validate a matrix calculation + +Use this guide when you have two matrices and need to choose between +elementwise addition, a scalar dot reduction, and matrix multiplication. +It uses the installed Python package and requires no optional dependencies. + +## Choose the calculation + +For two 2-by-2 matrices, all three operations are valid but produce different +results: + +```python +from hmath import Tensor + +with Tensor.matrix([1, 2, 3, 4], 2, 2) as a: + with Tensor.matrix([5, 6, 7, 8], 2, 2) as b: + with a + b as elementwise: + assert elementwise.values() == [6.0, 8.0, 10.0, 12.0] + assert a.dot(b) == 70.0 + with a @ b as product: + assert product.shape == (2, 2) + assert product.values() == [19.0, 22.0, 43.0, 50.0] +``` + +Matrix multiplication computes + +$$ +C_{ij} = \sum_{k=0}^{p-1} A_{ik} B_{kj}, +\qquad A\in\mathbb{R}^{m\times p},\ B\in\mathbb{R}^{p\times n}. +$$ + +For a dot reduction, matching element positions are multiplied and all +products are added to one scalar. Neither operation broadcasts inputs. + +## Handle incompatible dimensions + +```python +from hmath import HMathError, Tensor + +with Tensor.matrix([1, 2, 3, 4, 5, 6], 2, 3) as a: + with Tensor.matrix([1, 2, 3, 4], 2, 2) as b: + try: + invalid = a @ b + except HMathError as error: + assert error.status == 2 # HMATH_SHAPE_ERROR + else: + invalid.close() + raise AssertionError("Inner dimensions must agree") +``` + +All owned results have their own lifetime. A `with` block closes its result +even if a later assertion or operation fails. Inputs remain unchanged. + +For general floating-point data, use a scale-appropriate approximate +comparison instead of the exact small-integer assertions in this example. +See [numerical conventions](../explanation/numerical-conventions.md), +[Python API](../reference/python.md), and [NumPy interoperability](numpy.md). diff --git a/docs/guides/statistics.md b/docs/guides/statistics.md new file mode 100644 index 0000000..b5f8f42 --- /dev/null +++ b/docs/guides/statistics.md @@ -0,0 +1,45 @@ +# Summarize data in Haskell + +Use `HMath.Statistics` for basic descriptive statistics over finite, +nonempty lists. These legacy pure functions return a number directly and +raise a pure error on an empty list. Validate that precondition before calling. +The Python binding currently exposes tensor reductions rather than these APIs. + +## Compute a population summary + +```haskell +import qualified HMath.Statistics as Statistics + +main :: IO () +main = do + let samples = [1, 3 :: Double] + if null samples then fail "A summary needs at least one observation" else pure () + if Statistics.mean samples == 2 + && Statistics.variance samples == 1 + && Statistics.standardDeviation samples == 1 + && Statistics.median samples == 2 + then putStrLn "mean=2, population variance=1, median=2" + else fail "Unexpected summary" +``` + +The expected output is `mean=2, population variance=1, median=2`. +These particular results are exactly representable; general floating-point +data needs approximate comparison. + +## Interpret the denominator + +`variance` computes population variance: + +$$ +\mu=\frac{1}{n}\sum_{i=1}^{n}x_i,\qquad +\mathrm{variance}(x)=\frac{1}{n}\sum_{i=1}^{n}(x_i-\mu)^2. +$$ + +It divides by $n$, not $n-1$. Do not label this result an unbiased sample +variance. `standardDeviation` is the square root of that population variance. +For even-length input, `median` averages the two central sorted values. + +These generic functions do not provide a universal overflow or numerical +stability guarantee. Use the [Statistics API](../reference/haskell.md) for +signatures and [numerical conventions](../explanation/numerical-conventions.md) +for interpreting finite arithmetic and error behavior. diff --git a/docs/index.md b/docs/index.md index 5638b0a..cfe8f21 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,6 +7,9 @@ Start with [installation](getting-started/installation.md), then follow [your first calculation](getting-started/first-calculation.md). Use the [Python reference](reference/python.md) to look up individual operations. +[Choose an operation](getting-started/choose-an-operation.md) to find the +right interface and function for your calculation. + ## Choose an interface | Interface | Scope | Entry point | @@ -26,6 +29,6 @@ being available in Haskell does not imply that a Python wrapper exists. - **Reference** describes signatures, results, constraints, and errors. - **Explanation** describes architecture, ownership, and compatibility. -This documentation accompanies HMath 1.0.1. Linux x86-64 is the supported +This documentation accompanies HMath @HMATH_VERSION@. Linux x86-64 is the supported binary distribution target. See [release compatibility](explanation/releases.md) for Python versions, ABI compatibility, and the 1.0.0 installation defect. diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js new file mode 100644 index 0000000..e6ad5d1 --- /dev/null +++ b/docs/javascripts/mathjax.js @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } +}; diff --git a/docs/reference/c.md b/docs/reference/c.md index 92a2e68..79c494b 100644 --- a/docs/reference/c.md +++ b/docs/reference/c.md @@ -4,6 +4,55 @@ Include `hmath.h` for the stable ABI-1 interface and `hmath_dlpack.h` for DLPack exchange. The ABI epoch is independent of package release numbers. The [C integration guide](../guides/c.md) gives a complete compilable client. +## Function index + +| API | Purpose | +| --- | --- | +| [hmath_abi_version](#hmath_abi_version) | Query the C ABI epoch. | +| [hmath_library_version](#hmath_library_version) | Query the package version for diagnostics. | +| [hmath_build_info](#hmath_build_info) | Describe the native build configuration. | +| [hmath_backend_version](#hmath_backend_version) | Describe the backend used by native tensor kernels. | +| [hmath_last_error_code](#hmath_last_error_code) | Read this thread’s most recent checked status. | +| [hmath_last_error_message](#hmath_last_error_message) | Read this thread’s most recent checked diagnostic. | +| [hmath_status_string](#hmath_status_string) | Describe a status code without changing the thread diagnostic. | +| [hmath_init](#hmath_init) | Initialize the process-owned Haskell runtime. | +| [hmath_runtime_shutdown](#hmath_runtime_shutdown) | Request final shutdown after all numerical users have finished. | +| [hmath_shutdown](#hmath_shutdown) | Invoke the legacy shutdown wrapper. | +| [hmath_context_create](#hmath_context_create) | Create a cancellation context. | +| [hmath_context_retain](#hmath_context_retain) | Acquire one additional reference to a cancellation context. | +| [hmath_context_release](#hmath_context_release) | Release one owned cancellation-context reference. | +| [hmath_context_cancel](#hmath_context_cancel) | Request cooperative cancellation. | +| [hmath_context_reset](#hmath_context_reset) | Clear cancellation after the context becomes idle. | +| [hmath_tensor_copy](#hmath_tensor_copy) | Copy row-major values into a new tensor. | +| [hmath_tensor_borrow](#hmath_tensor_borrow) | Create an immutable tensor over caller-provided storage. | +| [hmath_vector_copy](#hmath_vector_copy) | Copy values into a rank-one tensor. | +| [hmath_matrix_copy](#hmath_matrix_copy) | Copy row-major values into a rank-two tensor. | +| [hmath_tensor_retain](#hmath_tensor_retain) | Acquire one additional reference to an existing tensor. | +| [hmath_tensor_release](#hmath_tensor_release) | Release one owned tensor reference. | +| [hmath_tensor_rank](#hmath_tensor_rank) | Query the number of dimensions. | +| [hmath_tensor_count](#hmath_tensor_count) | Query the logical element count. | +| [hmath_tensor_metadata](#hmath_tensor_metadata) | Copy dimensions and element strides to caller-owned arrays. | +| [hmath_tensor_copy_to](#hmath_tensor_copy_to) | Copy logical values to independent row-major storage. | +| [hmath_tensor_data](#hmath_tensor_data) | Borrow a read-only pointer to underlying tensor storage. | +| [hmath_tensor_reshape](#hmath_tensor_reshape) | Create a new handle sharing contiguous storage with a new shape. | +| [hmath_buffer_alloc](#hmath_buffer_alloc) | Allocate aligned storage using the HMath allocator. | +| [hmath_buffer_free](#hmath_buffer_free) | Free storage allocated by hmath_buffer_alloc. | +| [hmath_scalar_add](#hmath_scalar_add) | Add two finite real scalars. | +| [hmath_tensor_sum](#hmath_tensor_sum) | Reduce all logical elements to one scalar. | +| [hmath_tensor_dot](#hmath_tensor_dot) | Reduce corresponding products over identically shaped tensors. | +| [hmath_tensor_add](#hmath_tensor_add) | Add identically shaped tensors element by element. | +| [hmath_tensor_matmul](#hmath_tensor_matmul) | Multiply two dense rank-two matrices. | +| [hmath_tensor_map](#hmath_tensor_map) | Apply a synchronous checked callback to each logical element. | +| [hmath_batch_sum](#hmath_batch_sum) | Sum multiple tensors with explicit partial progress. | +| [hmath_add_f64](#hmath_add_f64) | Add scalars using the legacy numerical interface. | +| [hmath_factorial](#hmath_factorial) | Compute a legacy bounded integer factorial. | +| [hmath_mean_f64](#hmath_mean_f64) | Compute a legacy arithmetic mean. | +| [hmath_dot_f64](#hmath_dot_f64) | Compute a legacy dot product of flat arrays. | +| [hmath_dlpack_export](#hmath_dlpack_export) | Export a writable legacy DLPack copy. | +| [hmath_dlpack_import](#hmath_dlpack_import) | Consume a supported legacy DLPack tensor on successful import. | +| [hmath_dlpack_export_versioned](#hmath_dlpack_export_versioned) | Export a retained read-only versioned DLPack view. | +| [hmath_dlpack_import_versioned](#hmath_dlpack_import_versioned) | Consume a supported versioned DLPack tensor on success. | + ## Types, bounds, and ownership `hmath_tensor` and `hmath_context` are opaque reference-counted handles. @@ -22,7 +71,7 @@ the actual allocation behind a supplied buffer. Checked functions return `hmath_status`. `HMATH_OK` (0) is success; otherwise read `hmath_last_error_message()` immediately on the same thread. The status -[table](python.md#native-status-codes) lists every code. Kernel failures can +[table](#native-status-codes) lists every code. Kernel failures can include runtime unavailable, cancellation, contained Haskell exceptions, and non-finite arithmetic. Constructors clear their output handle to NULL on error; scalar outputs are written only on success. Never pass a live owned handle as @@ -34,350 +83,1000 @@ kernel context disables cancellation. Cancellation is cooperative before/after bounded Haskell kernels, and between callback elements. It does not forcibly interrupt a running Haskell kernel. +## Native status codes + +| Code | Name | Meaning | +| --- | --- | --- | +| 0 | `HMATH_OK` | Success | +| 1 | `HMATH_INVALID_ARGUMENT` | Invalid argument | +| 2 | `HMATH_SHAPE_ERROR` | Invalid or incompatible dimensions | +| 3 | `HMATH_OVERFLOW` | Arithmetic or size overflow | +| 4 | `HMATH_ALLOCATION_FAILED` | Allocation failed | +| 5 | `HMATH_RUNTIME_ERROR` | Runtime unavailable | +| 6 | `HMATH_HASKELL_EXCEPTION` | Contained Haskell exception | +| 7 | `HMATH_CANCELLED` | Cooperative cancellation | +| 8 | `HMATH_BOUNDS_ERROR` | Buffer or metadata bounds error | +| 9 | `HMATH_BUSY` | Active work prevents shutdown | +| 10 | `HMATH_UNSUPPORTED` | Unsupported operation | + ## Version and diagnostics ### `hmath_abi_version` +Query the C ABI epoch. + `uint32_t hmath_abi_version(void)` -**Parameters:** none. **Returns:** ABI epoch, currently 1. Compare against +#### Parameters + +None. + +#### Returns + +ABI epoch, currently 1. Compare against `HMATH_ABI_VERSION` before using the interface. No initialization or allocation is needed. Example: `if (hmath_abi_version() != HMATH_ABI_VERSION) return 1;`. +#### See also + +[hmath_library_version](#hmath_library_version), [hmath_init](#hmath_init). + ### `hmath_library_version` +Query the package version for diagnostics. + `const char *hmath_library_version(void)` -**Parameters:** none. **Returns:** a borrowed, immutable, NUL-terminated package -version string, such as `"1.0.1"`. Do not free it. No runtime initialization +#### Parameters + +None. + +#### Returns + +a borrowed, immutable, NUL-terminated package +version string. Do not free it. No runtime initialization is needed. Use for diagnostics rather than ABI negotiation. +#### See also + +[hmath_abi_version](#hmath_abi_version), [hmath_build_info](#hmath_build_info). + ### `hmath_build_info` +Describe the native build configuration. + `const char *hmath_build_info(void)` -**Parameters:** none. **Returns:** a borrowed static description of the native +#### Parameters + +None. + +#### Returns + +a borrowed static description of the native build (C11, threaded GHC runtime, binary64, rank bound). Do not free or parse it as a stable machine-readable schema. Example: `puts(hmath_build_info());`. +#### See also + +[hmath_backend_version](#hmath_backend_version), [hmath_abi_version](#hmath_abi_version). + ### `hmath_backend_version` +Describe the backend used by native tensor kernels. + `const char *hmath_backend_version(void)` -**Parameters:** none. **Returns:** a borrowed static description of the backend +#### Parameters + +None. + +#### Returns + +a borrowed static description of the backend used by the C tensor kernels. This describes those kernels, independently of Haskell's separately selected BLAS/LAPACK providers. Do not free it. +#### See also + +[hmath_build_info](#hmath_build_info). + ### `hmath_last_error_code` +Read this thread’s most recent checked status. + `hmath_status hmath_last_error_code(void)` -**Parameters:** none. **Returns:** the current thread's last checked status. +#### Parameters + +None. + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: the current thread's last checked status. Queries, retain/release, and cancellation do not clear it. Successful checked calls replace it with `HMATH_OK`. It does not return another thread's failure. +#### See also + +[hmath_last_error_message](#hmath_last_error_message), [hmath_status_string](#hmath_status_string). + ### `hmath_last_error_message` +Read this thread’s most recent checked diagnostic. + `const char *hmath_last_error_message(void)` -**Parameters:** none. **Returns:** a borrowed thread-local NUL-terminated error +#### Parameters + +None. + +#### Returns + +a borrowed thread-local NUL-terminated error message, valid until the next checked operation on this thread. Copy it before another checked call if it must survive. Do not free it. +#### See also + +[hmath_last_error_code](#hmath_last_error_code), [hmath_status_string](#hmath_status_string). + ### `hmath_status_string` +Describe a status code without changing the thread diagnostic. + `const char *hmath_status_string(hmath_status status)` -**Parameters:** a status code. **Returns:** its borrowed static generic label; +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `status` | `hmath_status` | Status value; unknown values have a generic label. | + +#### Returns + +its borrowed static generic label; unknown codes return `"unknown status"`. This does not replace the more specific thread-local diagnostic. Example: `puts(hmath_status_string(HMATH_SHAPE_ERROR));`. +#### See also + +[hmath_last_error_message](#hmath_last_error_message), [hmath_last_error_code](#hmath_last_error_code). + ## Runtime ### `hmath_init` +Initialize the process-owned Haskell runtime. + `int hmath_init(void)` -**Parameters:** none. **Returns:** `HMATH_INIT_OK` (0) on successful or repeated +#### Parameters + +None. + +#### Returns + +`HMATH_INIT_OK` (0) on successful or repeated initialization; `HMATH_INIT_ALREADY_SHUT_DOWN` (1) after final shutdown. HMath owns its GHC runtime: do not combine it with independently initialized GHC libraries. Call before numerical kernels: `if (hmath_init() != 0) return 1;`. +#### See also + +[hmath_abi_version](#hmath_abi_version), [hmath_runtime_shutdown](#hmath_runtime_shutdown). + ### `hmath_runtime_shutdown` +Request final shutdown after all numerical users have finished. + `hmath_status hmath_runtime_shutdown(void)` -**Parameters:** none. **Returns:** `HMATH_OK` after final shutdown or `HMATH_BUSY` +#### Parameters + +None. + +#### Returns + +`HMATH_OK` after final shutdown or `HMATH_BUSY` while a checked/legacy call or callback is active. Join workers, then retry a busy result. Successful shutdown is irreversible; only the process owner should invoke it. Release owned resources before shutdown. +#### See also + +[hmath_init](#hmath_init), [hmath_shutdown](#hmath_shutdown). + ### `hmath_shutdown` +Invoke the legacy shutdown wrapper. + `void hmath_shutdown(void)` -**Parameters:** none. **Returns:** nothing. Legacy wrapper around final shutdown; +#### Parameters + +None. + +#### Returns + +nothing. Legacy wrapper around final shutdown; if busy, leaves the runtime running and records the error. Prefer the checked `hmath_runtime_shutdown()` for new clients; otherwise inspect the last error. +#### See also + +[hmath_runtime_shutdown](#hmath_runtime_shutdown), [hmath_init](#hmath_init). + ## Cancellation contexts ### `hmath_context_create` +Create a cancellation context. + `hmath_status hmath_context_create(hmath_context **out)` -**Parameters:** non-NULL output slot. **Returns:** `HMATH_OK` and one owned, -initially uncancelled context. **Errors:** invalid output pointer or allocation +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `out` | `hmath_context **` | Writable non-NULL slot for one owned context; NULL on failure. | + +#### Returns + +`HMATH_OK` and one owned, +initially uncancelled context. + +#### Errors + +invalid output pointer or allocation failure (NULL result). Example: `hmath_context *ctx = NULL;` followed by `hmath_context_create(&ctx)`; release the context after its users finish. +#### See also + +[hmath_context_release](#hmath_context_release), [hmath_context_cancel](#hmath_context_cancel). + ### `hmath_context_retain` +Acquire one additional reference to a cancellation context. + `void hmath_context_retain(hmath_context *context)` -**Parameters:** a live context or NULL. **Returns:** nothing; adds one owned +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Live context, or NULL for a no-op. | + +#### Returns + +nothing; adds one owned reference, or does nothing for NULL. Retain before giving a second owner access. This does not reset cancellation or update the last diagnostic. +#### See also + +[hmath_context_release](#hmath_context_release), [hmath_context_create](#hmath_context_create). + ### `hmath_context_release` +Release one owned cancellation-context reference. + `void hmath_context_release(hmath_context *context)` -**Parameters:** an owned context reference or NULL. **Returns:** nothing; +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Owned reference, or NULL for a no-op. | + +#### Returns + +nothing; destroys it on final release. Every successful create/retain needs one release. Do not release the final reference while a kernel still uses this context. +#### See also + +[hmath_context_retain](#hmath_context_retain), [hmath_context_create](#hmath_context_create). + ### `hmath_context_cancel` +Request cooperative cancellation. + `void hmath_context_cancel(hmath_context *context)` -**Parameters:** a live context or NULL. **Returns:** nothing. Atomically requests +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Live context retained throughout cancellation, or NULL. | + +#### Returns + +nothing. Atomically requests cancellation of current/future work using this context. NULL is a no-op. It is safe to request cancellation from another thread; the caller must keep its reference alive. Does not clear the error message. +#### See also + +[hmath_context_reset](#hmath_context_reset), [hmath_context_create](#hmath_context_create). + ### `hmath_context_reset` +Clear cancellation after the context becomes idle. + `void hmath_context_reset(hmath_context *context)` -**Parameters:** a live context or NULL. **Returns:** nothing. Clear the cancelled +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Live context with no active users, or NULL. | + +#### Returns + +nothing. Clear the cancelled flag for reuse, **only after no operation is using the context**. NULL is a no-op. Reset does not restart a shut down runtime. +#### See also + +[hmath_context_cancel](#hmath_context_cancel), [hmath_context_create](#hmath_context_create). + ## Tensor construction ### `hmath_tensor_copy` +Copy row-major values into a new tensor. + `hmath_status hmath_tensor_copy(const double *data, size_t count, const size_t *shape, size_t rank, hmath_tensor **out)` -**Parameters:** `count` readable finite doubles, `rank` dimensions, and a non-NULL +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `const double *` | Readable finite double values; NULL only when count is zero. | +| `count` | `size_t` | Number of input doubles; must equal the shape product. | +| `shape` | `const size_t *` | Array of rank dimensions; may be NULL for rank zero. | +| `rank` | `size_t` | Number of dimensions, from zero through HMATH_MAX_RANK. | +| `out` | `hmath_tensor **` | Writable non-NULL handle slot; receives one owned tensor on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: one owned contiguous row-major copy. The caller retains ownership +of the input. + +#### Errors + +invalid pointers/data, shape/count mismatch, exceeded +bounds, or allocation failure. See the complete example below. + +#### Notes + +`count` readable finite doubles, `rank` dimensions, and a non-NULL output slot. `count` must equal the shape product; NULL data is allowed only for zero elements, and NULL shape for rank zero. -**Returns:** one owned contiguous row-major copy. The caller retains ownership -of the input. **Errors:** invalid pointers/data, shape/count mismatch, exceeded -bounds, or allocation failure. See the complete example below. + +#### See also + +[hmath_tensor_borrow](#hmath_tensor_borrow), [hmath_vector_copy](#hmath_vector_copy), [hmath_matrix_copy](#hmath_matrix_copy). ### `hmath_tensor_borrow` +Create an immutable tensor over caller-provided storage. + `hmath_status hmath_tensor_borrow(const double *data, size_t storage_count, const size_t *shape, const size_t *strides, size_t rank, hmath_buffer_deleter deleter, void *userdata, hmath_tensor **out)` -**Parameters:** aligned immutable storage containing `storage_count` readable -doubles; shape and optional element strides (NULL means row-major); optional -`deleter(userdata, data)` and its borrowed userdata; output slot. All reachable -offsets must fit the storage. Keep data and userdata live/immutable until release. -**Returns:** one owned zero-copy tensor. On final storage release, invokes the +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `const double *` | Aligned finite storage; immutable until final storage release. | +| `storage_count` | `size_t` | Readable storage span in doubles, including gaps between strided elements. | +| `shape` | `const size_t *` | Array of rank bounded dimensions. | +| `strides` | `const size_t *` | Array of rank nonnegative strides in elements; NULL selects row-major. | +| `rank` | `size_t` | Number of dimensions, at most HMATH_MAX_RANK. | +| `deleter` | `hmath_buffer_deleter` | Optional final-storage callback; NULL leaves cleanup to the caller. | +| `userdata` | `void *` | Borrowed callback state, alive until the deleter runs. | +| `out` | `hmath_tensor **` | Writable non-NULL handle slot; one owned reference on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: one owned zero-copy tensor. On final storage release, invokes the deleter exactly once on the releasing thread. The callback must not throw or -call shutdown. **Errors:** invalid/non-finite storage, shape/span/budget violation, +call shutdown. + +#### Errors + +invalid/non-finite storage, shape/span/budget violation, or allocation failure. Failure does not consume input ownership or call the deleter. Example: borrow a static `const double[3]` with shape `{3}`, NULL strides/deleter. +#### Notes + +aligned immutable storage containing `storage_count` readable +doubles; shape and optional element strides (NULL means row-major); optional +`deleter(userdata, data)` and its borrowed userdata; output slot. All reachable +offsets must fit the storage. Keep data and userdata live/immutable until release. + +#### See also + +[hmath_tensor_copy](#hmath_tensor_copy), [hmath_tensor_release](#hmath_tensor_release), [hmath_tensor_reshape](#hmath_tensor_reshape). + ### `hmath_vector_copy` +Copy values into a rank-one tensor. + `hmath_status hmath_vector_copy(const double *data, size_t count, hmath_vector **out)` -**Parameters:** `count` readable finite doubles and output slot. -**Returns:** a new owned contiguous tensor with shape `(count)`. -**Errors:** the same input, budget, and allocation errors as `hmath_tensor_copy`. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `const double *` | Readable finite doubles; NULL is allowed for zero elements. | +| `count` | `size_t` | Number of values and vector length. | +| `out` | `hmath_vector **` | Writable non-NULL vector handle slot; one owned reference on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned contiguous tensor with shape `(count)`. + +#### Errors + +the same input, budget, and allocation errors as `hmath_tensor_copy`. Example: `hmath_vector_copy(values, 3, &vector)`. +#### See also + +[hmath_tensor_copy](#hmath_tensor_copy), [hmath_matrix_copy](#hmath_matrix_copy). + ### `hmath_matrix_copy` +Copy row-major values into a rank-two tensor. + `hmath_status hmath_matrix_copy(const double *data, size_t count, size_t rows, size_t columns, hmath_matrix **out)` -**Parameters:** flat row-major data, its count, two dimensions, and output slot. -**Returns:** a new owned `(rows, columns)` tensor. -**Errors:** construction errors, including `count != rows * columns`. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `const double *` | Readable finite doubles in row-major order. | +| `count` | `size_t` | Number of values; must equal rows * columns. | +| `rows` | `size_t` | Nonnegative row count within the element budget. | +| `columns` | `size_t` | Nonnegative column count within the element budget. | +| `out` | `hmath_matrix **` | Writable non-NULL matrix handle slot; one owned reference on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned `(rows, columns)` tensor. + +#### Errors + +construction errors, including `count != rows * columns`. Example: `hmath_matrix_copy(values, 6, 2, 3, &matrix)`. +#### See also + +[hmath_tensor_copy](#hmath_tensor_copy), [hmath_tensor_matmul](#hmath_tensor_matmul). + ## Tensor ownership and inspection ### `hmath_tensor_retain` +Acquire one additional reference to an existing tensor. + `void hmath_tensor_retain(hmath_tensor *tensor)` -**Parameters:** a live tensor or NULL. **Returns:** nothing; adds one owned +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `hmath_tensor *` | Live tensor retained throughout the call, or NULL. | + +#### Returns + +nothing; adds one owned reference for a new owner, or does nothing for NULL. It copies no values. Balance each retain with a release. Does not change the last diagnostic. +#### See also + +[hmath_tensor_release](#hmath_tensor_release), [hmath_tensor_data](#hmath_tensor_data). + ### `hmath_tensor_release` +Release one owned tensor reference. + `void hmath_tensor_release(hmath_tensor *tensor)` -**Parameters:** an owned reference or NULL. **Returns:** nothing; final release +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `hmath_tensor *` | One owned reference, or NULL; never release a non-NULL reference twice. | + +#### Returns + +nothing; final release frees the handle and, when no storage owners remain, its data/deleter. NULL is allowed; releasing the same non-NULL reference twice is invalid. Reset your variable to NULL after release to avoid accidental reuse. +#### See also + +[hmath_tensor_retain](#hmath_tensor_retain), [hmath_tensor_borrow](#hmath_tensor_borrow). + ### `hmath_tensor_rank` +Query the number of dimensions. + `size_t hmath_tensor_rank(const hmath_tensor *tensor)` -**Parameters:** a live tensor or NULL. **Returns:** its dimension count; NULL +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live tensor, or NULL to obtain zero. | + +#### Returns + +its dimension count; NULL returns zero. A real rank-zero scalar also returns zero: this is not an error check. No initialization or ownership transfer occurs. +#### See also + +[hmath_tensor_metadata](#hmath_tensor_metadata), [hmath_tensor_count](#hmath_tensor_count). + ### `hmath_tensor_count` +Query the logical element count. + `size_t hmath_tensor_count(const hmath_tensor *tensor)` -**Parameters:** a live tensor or NULL. **Returns:** its logical element count; +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live tensor, or NULL to obtain zero. | + +#### Returns + +its logical element count; NULL returns zero. Scalars count as one; empty tensors count as zero. The count can differ from the storage span for strided input. +#### See also + +[hmath_tensor_rank](#hmath_tensor_rank), [hmath_tensor_copy_to](#hmath_tensor_copy_to). + ### `hmath_tensor_metadata` +Copy dimensions and element strides to caller-owned arrays. + `hmath_status hmath_tensor_metadata(const hmath_tensor *tensor, size_t *shape, size_t *strides, size_t capacity)` -**Parameters:** live tensor, two caller-owned output arrays, and capacity in -entries (at least rank). Both arrays may be NULL for rank zero. -**Returns:** `HMATH_OK` after writing dimensions and element strides. -**Errors:** invalid tensor/arrays or `HMATH_BOUNDS_ERROR` for insufficient capacity. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live tensor. | +| `shape` | `size_t *` | Writable array of at least rank entries; NULL allowed for rank zero. | +| `strides` | `size_t *` | Writable array of at least rank entries; NULL allowed for rank zero. | +| `capacity` | `size_t` | Capacity of each output array in size_t entries. | + +#### Returns + +`HMATH_OK` after writing dimensions and element strides. + +#### Errors + +invalid tensor/arrays or `HMATH_BOUNDS_ERROR` for insufficient capacity. Example: use `size_t shape[HMATH_MAX_RANK], strides[HMATH_MAX_RANK];`. +#### Notes + +live tensor, two caller-owned output arrays, and capacity in +entries (at least rank). Both arrays may be NULL for rank zero. + +#### See also + +[hmath_tensor_data](#hmath_tensor_data), [hmath_tensor_rank](#hmath_tensor_rank). + ### `hmath_tensor_copy_to` +Copy logical values to independent row-major storage. + `hmath_status hmath_tensor_copy_to(const hmath_tensor *tensor, double *out, size_t capacity)` -**Parameters:** live tensor and aligned destination with at least `count` -entries. The output must not overlap input storage; NULL is allowed for empty -output. **Returns:** a logical row-major copy in the caller's buffer. -**Errors:** invalid/overlapping buffer or insufficient capacity. No ownership +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live source tensor. | +| `out` | `double *` | Aligned destination, non-overlapping with source storage; NULL allowed for empty output. | +| `capacity` | `size_t` | Destination capacity in doubles, at least the logical element count. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a logical row-major copy in the caller's buffer. + +#### Errors + +invalid/overlapping buffer or insufficient capacity. No ownership transfer occurs. Example: copy a six-element tensor into `double out[6]`. +#### Notes + +live tensor and aligned destination with at least `count` +entries. The output must not overlap input storage; NULL is allowed for empty +output. + +#### See also + +[hmath_tensor_data](#hmath_tensor_data), [hmath_tensor_copy](#hmath_tensor_copy). + ### `hmath_tensor_data` +Borrow a read-only pointer to underlying tensor storage. + `hmath_status hmath_tensor_data(const hmath_tensor *tensor, const double **data, size_t *storage_count)` -**Parameters:** live tensor and two non-NULL output slots. -**Returns:** a **borrowed read-only** storage pointer and underlying storage +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live tensor whose storage must remain retained while the pointer is used. | +| `data` | `const double **` | Writable non-NULL slot for a borrowed storage pointer; do not free the pointer. | +| `storage_count` | `size_t *` | Writable non-NULL slot for the storage span in doubles. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a **borrowed read-only** storage pointer and underlying storage size. Use metadata strides for logical indexing; the storage need not be compact. The pointer is valid only while storage ownership is retained. Do not free it. -**Errors:** NULL tensor or output slots. No allocation or copying occurs. + +#### Errors + +NULL tensor or output slots. No allocation or copying occurs. + +#### See also + +[hmath_tensor_metadata](#hmath_tensor_metadata), [hmath_tensor_retain](#hmath_tensor_retain). ### `hmath_tensor_reshape` +Create a new handle sharing contiguous storage with a new shape. + `hmath_status hmath_tensor_reshape(const hmath_tensor *tensor, const size_t *shape, size_t rank, hmath_tensor **out)` -**Parameters:** live contiguous tensor, new dimensions, and output slot. -**Returns:** a new owned handle sharing storage with the original. -**Errors:** invalid shape, changed element count, non-contiguous input, or +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live contiguous source tensor. | +| `shape` | `const size_t *` | New dimensions with the same element count; NULL for rank zero. | +| `rank` | `size_t` | New dimension count, at most HMATH_MAX_RANK. | +| `out` | `hmath_tensor **` | Writable non-NULL slot for a new independently owned handle. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned handle sharing storage with the original. + +#### Errors + +invalid shape, changed element count, non-contiguous input, or allocation failure. No `-1` dimension inference. Example: reshape a length-six vector to shape `{2, 3}`; both handles require independent release. +#### See also + +[hmath_tensor_copy](#hmath_tensor_copy), [hmath_tensor_metadata](#hmath_tensor_metadata). + ## Raw allocation ### `hmath_buffer_alloc` +Allocate aligned storage using the HMath allocator. + `hmath_status hmath_buffer_alloc(size_t bytes, size_t alignment, void **out)` -**Parameters:** requested byte count, power-of-two alignment in -`[sizeof(void *), 4096]`, and non-NULL output slot. Zero bytes is valid. -**Returns:** one owned aligned allocation; release **only** with -`hmath_buffer_free`. **Errors:** invalid alignment, size exceeding +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `bytes` | `size_t` | Requested bytes, at most HMATH_MAX_ELEMENTS * sizeof(double); zero is valid. | +| `alignment` | `size_t` | Power of two from sizeof(void *) through 4096. | +| `out` | `void **` | Writable non-NULL slot; release the result only with hmath_buffer_free. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: one owned aligned allocation; release **only** with +`hmath_buffer_free`. + +#### Errors + +invalid alignment, size exceeding `HMATH_MAX_ELEMENTS * sizeof(double)`, arithmetic overflow, or allocation failure. Example: `hmath_buffer_alloc(3 * sizeof(double), 64, &buffer)`. +#### Notes + +requested byte count, power-of-two alignment in +`[sizeof(void *), 4096]`, and non-NULL output slot. Zero bytes is valid. + +#### See also + +[hmath_buffer_free](#hmath_buffer_free), [hmath_tensor_borrow](#hmath_tensor_borrow). + ### `hmath_buffer_free` +Free storage allocated by hmath_buffer_alloc. + `void hmath_buffer_free(void *buffer)` -**Parameters:** a pointer returned by `hmath_buffer_alloc`, or NULL. -**Returns:** nothing; frees that allocation. Never pass a tensor's borrowed data +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `buffer` | `void *` | Owned allocation from hmath_buffer_alloc, or NULL. | + +#### Returns + +nothing; frees that allocation. Never pass a tensor's borrowed data pointer or an allocation from another allocator. Free each non-NULL allocation once. +#### See also + +[hmath_buffer_alloc](#hmath_buffer_alloc). + ## Checked numerical kernels ### `hmath_scalar_add` +Add two finite real scalars. + `hmath_status hmath_scalar_add(hmath_context *context, double a, double b, double *out)` -**Parameters:** optional context, two finite scalars, and aligned output pointer. -**Returns:** `HMATH_OK` with their sum in `*out`. -**Errors:** invalid/non-finite input, non-finite result, or common kernel failures. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `a` | `double` | First finite double. | +| `b` | `double` | Second finite double. | +| `out` | `double *` | Aligned writable scalar slot; written only on success. | + +#### Returns + +`HMATH_OK` with their sum in `*out`. + +#### Errors + +invalid/non-finite input, non-finite result, or common kernel failures. Example: `hmath_scalar_add(NULL, 2.0, 3.0, &result)` writes `5.0` on success. +#### See also + +[hmath_add_f64](#hmath_add_f64), [hmath_last_error_message](#hmath_last_error_message). + ### `hmath_tensor_sum` +Reduce all logical elements to one scalar. + `hmath_status hmath_tensor_sum(hmath_context *context, const hmath_tensor *tensor, double *out)` -**Parameters:** optional context, live tensor, aligned scalar output. -**Returns:** sum of all logical elements; empty sum is zero. -**Errors:** invalid arguments, non-finite arithmetic, or common kernel failures. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `tensor` | `const hmath_tensor *` | Live input tensor of any rank. | +| `out` | `double *` | Aligned writable scalar slot; written only on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: sum of all logical elements; empty sum is zero. + +#### Errors + +invalid arguments, non-finite arithmetic, or common kernel failures. Neither input nor ownership is modified. See the example below. +#### See also + +[hmath_tensor_dot](#hmath_tensor_dot), [hmath_batch_sum](#hmath_batch_sum). + ### `hmath_tensor_dot` +Reduce corresponding products over identically shaped tensors. + `hmath_status hmath_tensor_dot(hmath_context *context, const hmath_tensor *a, const hmath_tensor *b, double *out)` -**Parameters:** optional context, two live identically shaped tensors, scalar output. -**Returns:** sum of corresponding products across all dimensions, zero for -matching empty tensors. **Errors:** invalid arguments, unequal shapes, +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `a` | `const hmath_tensor *` | Live first tensor. | +| `b` | `const hmath_tensor *` | Live second tensor with exactly the same shape. | +| `out` | `double *` | Aligned writable scalar slot; written only on success. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: sum of corresponding products across all dimensions, zero for +matching empty tensors. + +#### Errors + +invalid arguments, unequal shapes, non-finite arithmetic, or common kernel failures. This is not matrix multiplication. +#### See also + +[hmath_tensor_sum](#hmath_tensor_sum), [hmath_tensor_matmul](#hmath_tensor_matmul). + ### `hmath_tensor_add` +Add identically shaped tensors element by element. + `hmath_status hmath_tensor_add(hmath_context *context, const hmath_tensor *a, const hmath_tensor *b, hmath_tensor **out)` -**Parameters:** optional context, identically shaped live tensors, output slot. -**Returns:** a new owned elementwise sum; inputs unchanged. No broadcasting. -**Errors:** invalid arguments, shape mismatch, allocation/arithmetic or common +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `a` | `const hmath_tensor *` | Live first tensor. | +| `b` | `const hmath_tensor *` | Live second tensor of identical shape; no broadcasting. | +| `out` | `hmath_tensor **` | Writable non-NULL slot for a new owned tensor. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned elementwise sum; inputs unchanged. No broadcasting. + +#### Errors + +invalid arguments, shape mismatch, allocation/arithmetic or common kernel failures. For `[1, 2]` and `[3, 4]`, the result contains `[4, 6]`. +#### See also + +[hmath_tensor_matmul](#hmath_tensor_matmul), [hmath_tensor_copy](#hmath_tensor_copy). + ### `hmath_tensor_matmul` +Multiply two dense rank-two matrices. + `hmath_status hmath_tensor_matmul(hmath_context *context, const hmath_tensor *a, const hmath_tensor *b, hmath_tensor **out)` -**Parameters:** optional context, rank-two `(m,k)` and `(k,n)` tensors, output slot. -**Returns:** a new owned `(m,n)` matrix product. Work must not exceed +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `a` | `const hmath_tensor *` | Live left matrix of shape (m, k). | +| `b` | `const hmath_tensor *` | Live right matrix of shape (k, n). | +| `out` | `hmath_tensor **` | Writable non-NULL slot for a new owned matrix of shape (m, n). | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned `(m,n)` matrix product. Work must not exceed 16,777,216 scalar multiply-add steps. -**Errors:** invalid arguments, rank/inner-dimension mismatch, work/size bound, + +#### Errors + +invalid arguments, rank/inner-dimension mismatch, work/size bound, allocation/arithmetic, or common kernel failures. Inputs remain unchanged. +#### See also + +[hmath_tensor_dot](#hmath_tensor_dot), [hmath_matrix_copy](#hmath_matrix_copy). + ### `hmath_tensor_map` +Apply a synchronous checked callback to each logical element. + `hmath_status hmath_tensor_map(hmath_context *context, const hmath_tensor *tensor, hmath_map_callback callback, void *userdata, hmath_tensor **out)` -**Parameters:** optional context, live input, callback, borrowed userdata, output. -The callback receives `(double value, size_t index, void *userdata, double *result)` -in logical row-major order, writes one finite result, and returns a status. -**Returns:** a new owned tensor of the original shape only if every callback -succeeds. **Errors:** callback status, invalid callback/input, cancellation, +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `tensor` | `const hmath_tensor *` | Live input tensor. | +| `callback` | `hmath_map_callback` | Non-NULL callback: write one finite result and return a status; never throw across C. | +| `userdata` | `void *` | Borrowed callback state, alive until this synchronous call returns. | +| `out` | `hmath_tensor **` | Writable non-NULL slot; receives a new owned tensor only after every callback succeeds. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: a new owned tensor of the original shape only if every callback +succeeds. + +#### Errors + +callback status, invalid callback/input, cancellation, non-finite output, allocation, or runtime failure. Partial results are discarded. Callbacks are synchronous, may reenter checked calls, must not throw across C, and must not shut down the runtime. Userdata need only outlive this call. +#### Notes + +optional context, live input, callback, borrowed userdata, output. +The callback receives `(double value, size_t index, void *userdata, double *result)` +in logical row-major order, writes one finite result, and returns a status. + +#### See also + +[hmath_context_cancel](#hmath_context_cancel), [hmath_tensor_sum](#hmath_tensor_sum). + ### `hmath_batch_sum` +Sum multiple tensors with explicit partial progress. + `hmath_status hmath_batch_sum(hmath_context *context, const hmath_tensor *const *inputs, size_t count, double *out, size_t *completed)` -**Parameters:** optional context, arrays of `count` live tensor pointers and -scalar output slots, and aligned non-NULL progress pointer. `count <= 4096`. -**Returns:** `HMATH_OK` after all reductions; `*completed == count`. -**Errors:** invalid arrays/bounds or a reduction failure. Unlike atomic tensor +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `context` | `hmath_context *` | Optional cancellation context; NULL disables cancellation. | +| `inputs` | `const hmath_tensor *const *` | Array of count live tensor pointers; NULL allowed for zero count. | +| `count` | `size_t` | Number of tensors, at most 4096. | +| `out` | `double *` | Writable array of count scalar slots; completed entries remain valid on failure. | +| `completed` | `size_t *` | Aligned writable non-NULL size_t slot recording completed reductions. | + +#### Returns + +`HMATH_OK` after all reductions; `*completed == count`. + +#### Errors + +invalid arrays/bounds or a reduction failure. Unlike atomic tensor kernels, entries `out[0 .. *completed)` remain valid after failure; later output slots are untouched. Count zero permits NULL input/output arrays. Initialize progress storage even for an empty batch: `size_t completed = 0;`. +#### Notes + +optional context, arrays of `count` live tensor pointers and +scalar output slots, and aligned non-NULL progress pointer. `count <= 4096`. + +#### See also + +[hmath_tensor_sum](#hmath_tensor_sum), [hmath_context_cancel](#hmath_context_cancel). + ## Legacy numerical interface Prefer checked functions when available. Legacy numerical calls still require @@ -387,36 +1086,94 @@ counts must fit Haskell `Int`. ### `hmath_add_f64` +Add scalars using the legacy numerical interface. + `double hmath_add_f64(double a, double b)` -**Parameters:** two doubles. **Returns:** their legacy scalar sum. Invalid +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `double` | First double. | +| `b` | `double` | Second double. | + +#### Returns + +their legacy scalar sum. Invalid lifecycle returns NaN. Unlike the checked interface, this retains legacy floating-point behavior without a status result. Example: `hmath_add_f64(2, 3)`. +#### See also + +[hmath_scalar_add](#hmath_scalar_add), [hmath_init](#hmath_init). + ### `hmath_factorial` +Compute a legacy bounded integer factorial. + `long long hmath_factorial(int n)` -**Parameters:** integer `n` in `[0,20]`. -**Returns:** `n!`, including `0! == 1`; `-1` outside the range or with invalid +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `n` | `int` | Integer from zero through twenty; other inputs return -1. | + +#### Returns + +`n!`, including `0! == 1`; `-1` outside the range or with invalid lifecycle. Example: `hmath_factorial(5)` returns 120. +#### See also + +[hmath_init](#hmath_init), [hmath_last_error_code](#hmath_last_error_code). + ### `hmath_mean_f64` +Compute a legacy arithmetic mean. + `double hmath_mean_f64(const double *values, size_t count)` -**Parameters:** a readable double array and count. **Returns:** arithmetic mean; +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `values` | `const double *` | Array of count readable doubles; NULL yields NaN. | +| `count` | `size_t` | Number of values, representable as Haskell Int; zero yields NaN. | + +#### Returns + +arithmetic mean; empty or NULL input, or invalid lifecycle, returns NaN. Example: `{2,4}` gives 3. No handle ownership is transferred. +#### See also + +[hmath_tensor_sum](#hmath_tensor_sum), [hmath_init](#hmath_init). + ### `hmath_dot_f64` +Compute a legacy dot product of flat arrays. + `double hmath_dot_f64(const double *a, const double *b, size_t count)` -**Parameters:** two readable double arrays of equal declared count. -**Returns:** sum of products; empty input gives zero, nonempty NULL input or +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `const double *` | First array of count readable doubles. | +| `b` | `const double *` | Second array of count readable doubles. | +| `count` | `size_t` | Common array length, representable as Haskell Int; zero returns zero. | + +#### Returns + +sum of products; empty input gives zero, nonempty NULL input or invalid lifecycle gives NaN. Example: `{1,2}` dotted with `{3,4}` gives 11. +#### See also + +[hmath_tensor_dot](#hmath_tensor_dot), [hmath_init](#hmath_init). + ## DLPack exchange Include `hmath_dlpack.h` and the producer's official `dlpack/dlpack.h` for the @@ -426,45 +1183,129 @@ and unknown versioned flags are rejected. Imported storage must remain immutable ### `hmath_dlpack_export` +Export a writable legacy DLPack copy. + `hmath_status hmath_dlpack_export(const hmath_tensor *tensor, struct DLManagedTensor **out)` -**Parameters:** live input and non-NULL managed-tensor output slot. -**Returns:** an owned legacy DLPack **copy**, because legacy DLPack cannot express +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live input tensor. | +| `out` | `struct DLManagedTensor **` | Writable non-NULL slot for a managed tensor; invoke its deleter exactly once. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: an owned legacy DLPack **copy**, because legacy DLPack cannot express read-only storage. The recipient must invoke its deleter exactly once. The HMath input can then be released independently. -**Errors:** invalid input/output, size/layout, or allocation failure. + +#### Errors + +invalid input/output, size/layout, or allocation failure. + +#### See also + +[hmath_dlpack_export_versioned](#hmath_dlpack_export_versioned), [hmath_dlpack_import](#hmath_dlpack_import). ### `hmath_dlpack_import` +Consume a supported legacy DLPack tensor on successful import. + `hmath_status hmath_dlpack_import(struct DLManagedTensor *tensor, size_t storage_elements, hmath_tensor **out)` -**Parameters:** producer-owned managed tensor, count of readable double elements -**after `byte_offset`**, and output slot. The caller guarantees this count; -DLPack itself carries no allocation size. -**Returns:** one owned HMath tensor and consumes managed-tensor ownership **only +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `struct DLManagedTensor *` | Producer-owned CPU binary64 managed tensor; ownership transfers only on success. | +| `storage_elements` | `size_t` | Guaranteed readable doubles after byte_offset; DLPack does not provide allocation size. | +| `out` | `hmath_tensor **` | Writable non-NULL slot for one owned HMath tensor. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: one owned HMath tensor and consumes managed-tensor ownership **only on success**; final HMath storage release calls the producer's deleter. -**Errors:** invalid/unsupported metadata, insufficient storage span, non-finite + +#### Errors + +invalid/unsupported metadata, insufficient storage span, non-finite data, bounds, or allocation failure. On failure the caller still owns the managed tensor. +#### Notes + +producer-owned managed tensor, count of readable double elements +**after `byte_offset`**, and output slot. The caller guarantees this count; +DLPack itself carries no allocation size. + +#### See also + +[hmath_dlpack_import_versioned](#hmath_dlpack_import_versioned), [hmath_tensor_release](#hmath_tensor_release). + ### `hmath_dlpack_export_versioned` +Export a retained read-only versioned DLPack view. + `hmath_status hmath_dlpack_export_versioned(const hmath_tensor *tensor, struct DLManagedTensorVersioned **out)` -**Parameters:** live input and output slot. -**Returns:** an owned versioned zero-copy DLPack view with the read-only flag set. +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `const hmath_tensor *` | Live input tensor. | +| `out` | `struct DLManagedTensorVersioned **` | Writable non-NULL slot for a retained managed view; invoke its deleter exactly once. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: an owned versioned zero-copy DLPack view with the read-only flag set. It retains storage independently of the original HMath handle; invoke the -managed view's deleter exactly once. **Errors:** invalid arguments or allocation failure. +managed view's deleter exactly once. + +#### Errors + +invalid arguments or allocation failure. + +#### See also + +[hmath_dlpack_export](#hmath_dlpack_export), [hmath_dlpack_import_versioned](#hmath_dlpack_import_versioned). ### `hmath_dlpack_import_versioned` +Consume a supported versioned DLPack tensor on success. + `hmath_status hmath_dlpack_import_versioned(struct DLManagedTensorVersioned *tensor, size_t storage_elements, hmath_tensor **out)` -**Parameters:** versioned managed tensor, readable element count after byte offset, -and output slot. **Returns:** one owned HMath tensor, consuming producer ownership -only on success. **Errors:** legacy import validation failures plus unsupported +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `struct DLManagedTensorVersioned *` | Producer-owned supported managed tensor; ownership transfers only on success. | +| `storage_elements` | `size_t` | Guaranteed readable doubles after byte_offset. | +| `out` | `hmath_tensor **` | Writable non-NULL slot for one owned HMath tensor. | + +#### Returns + +Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. + +On success: one owned HMath tensor, consuming producer ownership +only on success. + +#### Errors + +legacy import validation failures plus unsupported DLPack version or flags. Do not mutate the imported storage through any alias while the resulting storage is retained. +#### See also + +[hmath_dlpack_import](#hmath_dlpack_import), [hmath_tensor_release](#hmath_tensor_release). + ## Complete checked example ```c diff --git a/docs/reference/python.md b/docs/reference/python.md index b444684..edb61f3 100644 --- a/docs/reference/python.md +++ b/docs/reference/python.md @@ -2,7 +2,45 @@ `hmath` is the primary interface. Calculations use finite IEEE 754 binary64 values. Tensors are immutable and do not broadcast implicitly. NumPy is needed -only for the conversion methods. All examples below run in documentation checks. +only for the conversion methods. Python code blocks run in documentation checks; +the final-shutdown example is displayed separately. + +## Function index + +| API | Purpose | +| --- | --- | +| [hmath.initialize](#hmathinitialize) | Initialize the shared numerical runtime. | +| [hmath.add](#hmathadda-b) | Add two real scalars. | +| [hmath.shutdown](#hmathshutdown) | Permanently shut down the process-owned runtime. | +| [hmath.HMathError](#hmathhmatherrorstatus) | Represent a failed native checked operation. | +| [hmath.Tensor](#hmathtensorvalues-shapenone) | Copy flat values into a new immutable tensor. | +| [hmath.Tensor.scalar](#hmathtensorscalarvalue) | Construct a rank-zero tensor. | +| [hmath.Tensor.matrix](#hmathtensormatrixvalues-rows-columns) | Construct a rank-two matrix from flat row-major values. | +| [hmath.Tensor.from_numpy](#hmathtensorfrom_numpyarray-copytrue) | Convert an array to a tensor, copying or borrowing its storage. | +| [hmath.Tensor.shape](#hmathtensorshape) | Read the tensor’s dimensions. | +| [hmath.Tensor.values](#hmathtensorvalues) | Copy logical elements to a flat Python list. | +| [hmath.Tensor.numpy](#hmathtensornumpycopytrue) | Return a NumPy copy or a read-only view. | +| [hmath.Tensor.sum](#hmathtensorsum) | Sum all tensor elements to one scalar. | +| [hmath.Tensor.dot](#hmathtensordotother) | Reduce corresponding products of two identically shaped tensors. | +| [hmath.Tensor.__add__](#hmathtensor__add__other-a-b) | Add two tensors element by element. | +| [hmath.Tensor.__matmul__](#hmathtensor__matmul__other-a-b) | Multiply two rank-two matrices. | +| [hmath.Tensor.reshape](#hmathtensorreshapeshape) | Create a new shape over the same contiguous storage. | +| [hmath.Tensor.close](#hmathtensorclose) | Release this tensor handle deterministically. | +| [hmath.Tensor.__enter__](#hmathtensor__enter__) | Enter a context that owns this tensor handle. | +| [hmath.Tensor.__exit__](#hmathtensor__exit__args) | Close the tensor when leaving a context. | +| [hmath.Tensor.__del__](#hmathtensor__del__) | Release the handle as a garbage-collection fallback. | +| [hmath_cffi.add](#hmath_cffiadda-b) | Add two real scalars. | +| [hmath_cffi.HMathError](#hmath_cffihmatherrorstatus) | Represent a failed native checked operation. | +| [hmath_cffi.check](#hmath_cfficheckstatus) | Convert a raw native status into a Python exception. | +| [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone) | Copy flat values into a new immutable tensor. | +| [hmath_cffi.Tensor.values](#hmath_cffitensorvalues) | Copy logical elements to a flat Python list. | +| [hmath_cffi.Tensor.sum](#hmath_cffitensorsum) | Sum all tensor elements to one scalar. | +| [hmath_cffi.Tensor.dot](#hmath_cffitensordotother) | Reduce corresponding products of two identically shaped tensors. | +| [hmath_cffi.Tensor.__add__](#hmath_cffitensor__add__other-a-b) | Add two tensors element by element. | +| [hmath_cffi.Tensor.__matmul__](#hmath_cffitensor__matmul__other-a-b) | Multiply two rank-two matrices. | +| [hmath_cffi.Tensor.close](#hmath_cffitensorclose) | Release this tensor handle deterministically. | +| [hmath_cffi.Tensor.__enter__](#hmath_cffitensor__enter__) | Enter a context that owns this tensor handle. | +| [hmath_cffi.Tensor.__exit__](#hmath_cffitensor__exit__args) | Close the tensor when leaving a context. | ## Common contracts @@ -21,50 +59,122 @@ only for the conversion methods. All examples below run in documentation checks. ### `hmath.initialize()` -**Parameters:** none. Initialize the process-wide runtime; repeated calls are -allowed. Constructors and `add()` already do this automatically. -**Returns:** `None`. **Raises:** `RuntimeError` for ABI incompatibility or +Initialize the shared numerical runtime. + +#### Parameters + +None. + +#### Returns + +`None`. + +#### Raises + +`RuntimeError` for ABI incompatibility or an irreversibly shut down runtime. +#### Notes + +Initialize the process-wide runtime; repeated calls are +allowed. Constructors and `add()` already do this automatically. + +#### Examples + ```python from hmath import initialize initialize() initialize() ``` +#### See also + +[hmath.shutdown](#hmathshutdown), [hmath.add](#hmathadda-b). + ### `hmath.add(a, b)` -**Parameters:** two finite real scalars convertible to C `double`. -**Returns:** their sum as a Python `float`, initializing the runtime if needed. -**Raises:** `HMathError` for non-finite input/result, or runtime/conversion errors. +Add two real scalars. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `real scalar` | First finite value, convertible to C double. | +| `b` | `real scalar` | Second finite value, convertible to C double. | + +#### Returns + +their sum as a Python `float`, initializing the runtime if needed. + +#### Raises + +`HMathError` for non-finite input/result, or runtime/conversion errors. + +#### Examples ```python from hmath import add assert add(2, 3.5) == 5.5 ``` +#### See also + +[hmath.Tensor.__add__](#hmathtensor__add__other-a-b), [hmath.initialize](#hmathinitialize). + ### `hmath.shutdown()` -**Parameters:** none. Only the process owner should call this after joining -all numerical users and releasing their resources. Applications can instead -allow process exit to clean up the runtime. -**Returns:** `None`. **Raises:** `HMathError` with status 9 while work is active; +Permanently shut down the process-owned runtime. + +#### Parameters + +None. + +#### Returns + +`None`. + +#### Raises + +`HMathError` with status 9 while work is active; join workers and retry. Successful shutdown is **irreversible**. Example, as the last operation of a dedicated process (not run in shared tests): +#### Notes + +Only the process owner should call this after joining +all numerical users and releasing their resources. Applications can instead +allow process exit to clean up the runtime. + +#### Examples + ```text from hmath import shutdown shutdown() ``` +#### See also + +[hmath.initialize](#hmathinitialize), [hmath.Tensor.close](#hmathtensorclose). + ### `hmath.HMathError(status)` -**Parameters:** the integer status from a failed checked C call. -**Returns:** a `RuntimeError` subclass instance with `.status` and a snapshot +Represent a failed native checked operation. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `status` | `int` | Native status code; available as the exception’s status attribute. | + +#### Returns + +a `RuntimeError` subclass instance with `.status` and a snapshot of the calling thread's native diagnostic in `str(error)`. Catch exceptions raised by operations rather than constructing this manually. +#### Examples + ```python from hmath import HMathError, Tensor try: @@ -73,17 +183,41 @@ except HMathError as error: assert error.status == 2 ``` +#### See also + +[hmath.add](#hmathadda-b), [hmath.Tensor](#hmathtensorvalues-shapenone). + ## Construction ### `hmath.Tensor(values, shape=None)` -**Parameters:** `values`: finite numeric iterable in flat row-major order; +Copy flat values into a new immutable tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `values` | `iterable of real scalars` | Finite values in logical row-major order; at most 1,048,576 elements. | +| `shape` | `iterable of int or None; default None` | At most eight bounded nonnegative dimensions. None selects (len(values),). | + +#### Returns + +a new owned tensor. + +#### Raises + +`ValueError` for Python shape/size +bounds; `HMathError` for mismatched element count, non-finite input, or allocation +failure; ordinary numeric conversion errors may also occur. + +#### Notes + +`values`: finite numeric iterable in flat row-major order; `shape`: optional iterable of dimensions, defaulting to `(len(values),)`. Values are copied, so later changes to the input do not affect the tensor. At most 1,048,577 entries are consumed before oversized input is rejected. -**Returns:** a new owned tensor. **Raises:** `ValueError` for Python shape/size -bounds; `HMathError` for mismatched element count, non-finite input, or allocation -failure; ordinary numeric conversion errors may also occur. + +#### Examples ```python with Tensor([1, 2, 3, 4], (2, 2)) as matrix: @@ -91,10 +225,30 @@ with Tensor([1, 2, 3, 4], (2, 2)) as matrix: assert matrix.values() == [1., 2., 3., 4.] ``` +#### See also + +[hmath.Tensor.matrix](#hmathtensormatrixvalues-rows-columns), [hmath.Tensor.scalar](#hmathtensorscalarvalue), [hmath.Tensor.from_numpy](#hmathtensorfrom_numpyarray-copytrue). + ### `hmath.Tensor.scalar(value)` -**Parameters:** one finite real value. **Returns:** a new owned rank-zero tensor -with shape `()`. **Raises:** the constructor's conversion and native errors. +Construct a rank-zero tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `value` | `real scalar` | Single finite value; the resulting shape is (). | + +#### Returns + +a new owned rank-zero tensor +with shape `()`. + +#### Raises + +the constructor's conversion and native errors. + +#### Examples ```python with Tensor.scalar(7) as scalar: @@ -102,27 +256,76 @@ with Tensor.scalar(7) as scalar: assert scalar.values() == [7.0] ``` +#### See also + +[hmath.Tensor](#hmathtensorvalues-shapenone), [hmath.Tensor.matrix](#hmathtensormatrixvalues-rows-columns). + ### `hmath.Tensor.matrix(values, rows, columns)` -**Parameters:** flat row-major values and two nonnegative integer dimensions; +Construct a rank-two matrix from flat row-major values. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `values` | `iterable of real scalars` | Exactly rows * columns finite values. | +| `rows` | `int` | Nonnegative row count, at most 1,048,576; bool is rejected. | +| `columns` | `int` | Nonnegative column count, at most 1,048,576; bool is rejected. | + +#### Returns + +a new owned rank-two tensor. + +#### Raises + +constructor errors. + +#### Notes + +flat row-major values and two nonnegative integer dimensions; exactly `rows * columns` values are required. -**Returns:** a new owned rank-two tensor. **Raises:** constructor errors. + +#### Examples ```python with Tensor.matrix([1, 2, 3, 4, 5, 6], 2, 3) as matrix: assert matrix.shape == (2, 3) ``` +#### See also + +[hmath.Tensor](#hmathtensorvalues-shapenone), [hmath.Tensor.__matmul__](#hmathtensor__matmul__other-a-b). + ### `hmath.Tensor.from_numpy(array, copy=True)` -**Parameters:** `array`: NumPy array or array-like input; `copy`: whether to copy. +Convert an array to a tensor, copying or borrowing its storage. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `array` | `numpy.ndarray or array-like` | Input whose shape is retained; borrowing requirements are below. | +| `copy` | `bool; default True` | True converts to float64 and copies; False borrows eligible read-only storage. | + +#### Returns + +an owned tensor preserving shape. + +#### Raises + +`ImportError` without +NumPy, `ValueError` for borrowing/rank/size violations, or `HMathError` for native +storage validation, non-finite data, or allocation failures. + +#### Notes + +`array`: NumPy array or array-like input; `copy`: whether to copy. With `True`, convert to `float64` and copy in row-major order. With `False`, borrow aligned, native-endian, **read-only** `float64` storage with nonnegative strides divisible by eight bytes. The tensor keeps the array alive. The caller must prevent writes through every alias until all borrowed tensors/views die. -**Returns:** an owned tensor preserving shape. **Raises:** `ImportError` without -NumPy, `ValueError` for borrowing/rank/size violations, or `HMathError` for native -storage validation, non-finite data, or allocation failures. + +#### Examples ```python import numpy as np @@ -132,24 +335,59 @@ with Tensor.from_numpy(array, copy=False) as tensor: assert tensor.sum() == 10.0 ``` +#### See also + +[hmath.Tensor.numpy](#hmathtensornumpycopytrue), [hmath.Tensor](#hmathtensorvalues-shapenone), [hmath.Tensor.close](#hmathtensorclose). + ## Inspection and conversion ### `hmath.Tensor.shape` -**Parameters:** none; read-only property. **Returns:** a tuple of dimensions, -including `()` for scalars. **Raises:** `RuntimeError` after close or +Read the tensor’s dimensions. + +#### Parameters + +None. + +#### Returns + +a tuple of dimensions, +including `()` for scalars. + +#### Raises + +`RuntimeError` after close or `HMathError` if native metadata retrieval fails. +#### Examples + ```python with Tensor([], (0, 3)) as empty: assert empty.shape == (0, 3) ``` +#### See also + +[hmath.Tensor.reshape](#hmathtensorreshapeshape), [hmath.Tensor.values](#hmathtensorvalues). + ### `hmath.Tensor.values()` -**Parameters:** none. **Returns:** an independent flat `list[float]` in logical +Copy logical elements to a flat Python list. + +#### Parameters + +None. + +#### Returns + +an independent flat `list[float]` in logical row-major order, including for strided input; `[]` for empty tensors. -**Raises:** closed-handle or native copy errors. + +#### Raises + +closed-handle or native copy errors. + +#### Examples ```python with Tensor([2, 4]) as tensor: @@ -157,13 +395,36 @@ with Tensor([2, 4]) as tensor: values[0] = 99 # Independent list remains usable after close. ``` +#### See also + +[hmath.Tensor.numpy](#hmathtensornumpycopytrue), [hmath.Tensor.shape](#hmathtensorshape). + ### `hmath.Tensor.numpy(copy=True)` -**Parameters:** `copy`: `True` for a writable independent copy, `False` for a +Return a NumPy copy or a read-only view. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `copy` | `bool; default True` | True returns an independent writable copy; False returns a read-only view. | + +#### Returns + +`numpy.ndarray` of `float64`. + +#### Raises + +`ImportError` without NumPy, +closed-handle errors, or native metadata/data errors. + +#### Notes + +`copy`: `True` for a writable independent copy, `False` for a read-only native view with the tensor's shape and strides. Views retain storage and borrowed owners even after this tensor closes. Do not mutate borrowed aliases. -**Returns:** `numpy.ndarray` of `float64`. **Raises:** `ImportError` without NumPy, -closed-handle errors, or native metadata/data errors. + +#### Examples ```python with Tensor([1, 2, 3]) as tensor: @@ -173,38 +434,94 @@ assert not view.flags.writeable del view ``` +#### See also + +[hmath.Tensor.from_numpy](#hmathtensorfrom_numpyarray-copytrue), [hmath.Tensor.values](#hmathtensorvalues), [hmath.Tensor.close](#hmathtensorclose). + ## Calculations ### `hmath.Tensor.sum()` -**Parameters:** none. Sum every logical element, regardless of rank. -**Returns:** a `float`, or `0.0` for an empty tensor. -**Raises:** closed-handle errors or native arithmetic/runtime errors, including +Sum all tensor elements to one scalar. + +#### Parameters + +None. + +#### Returns + +a `float`, or `0.0` for an empty tensor. + +#### Raises + +closed-handle errors or native arithmetic/runtime errors, including non-finite results. +#### Examples + ```python with Tensor([1, 2, 3]) as tensor: assert tensor.sum() == 6.0 ``` +#### See also + +[hmath.Tensor.dot](#hmathtensordotother). + ### `hmath.Tensor.dot(other)` -**Parameters:** a live tensor with exactly the same shape. Sum corresponding -products over the entire tensor; this is a scalar reduction at every rank. -**Returns:** a `float`, or `0.0` for matching empty tensors. -**Raises:** `HMathError` for shape/arithmetic/runtime errors; closed operands +Reduce corresponding products of two identically shaped tensors. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live tensor with exactly the same shape as this tensor. | + +#### Returns + +a `float`, or `0.0` for matching empty tensors. + +#### Raises + +`HMathError` for shape/arithmetic/runtime errors; closed operands raise `RuntimeError`. +#### Notes + +a live tensor with exactly the same shape. Sum corresponding +products over the entire tensor; this is a scalar reduction at every rank. + +#### Examples + ```python with Tensor([1, 2, 3]) as a, Tensor([4, 5, 6]) as b: assert a.dot(b) == 32.0 ``` +#### See also + +[hmath.Tensor.sum](#hmathtensorsum), [hmath.Tensor.__matmul__](#hmathtensor__matmul__other-a-b). + ### `hmath.Tensor.__add__(other)` — `a + b` -**Parameters:** another live tensor with identical shape; no broadcasting. -**Returns:** a new owned tensor of elementwise sums. Inputs are unchanged. -**Raises:** native shape/allocation/arithmetic/runtime or closed-handle errors. +Add two tensors element by element. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live tensor of the same shape; broadcasting is unsupported. | + +#### Returns + +a new owned tensor of elementwise sums. Inputs are unchanged. + +#### Raises + +native shape/allocation/arithmetic/runtime or closed-handle errors. + +#### Examples ```python with Tensor([1, 2]) as a, Tensor([3, 4]) as b: @@ -212,13 +529,36 @@ with Tensor([1, 2]) as a, Tensor([3, 4]) as b: assert result.values() == [4., 6.] ``` +#### See also + +[hmath.Tensor.dot](#hmathtensordotother), [hmath.Tensor.__matmul__](#hmathtensor__matmul__other-a-b). + ### `hmath.Tensor.__matmul__(other)` — `a @ b` -**Parameters:** a live rank-two tensor. Shapes `(m, k)` and `(k, n)` produce +Multiply two rank-two matrices. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live (k, n) matrix when this tensor has shape (m, k). | + +#### Returns + +a new owned matrix. + +#### Raises + +native rank/inner-dimension, +size/work budget, allocation, arithmetic/runtime, or closed-handle errors. + +#### Notes + +a live rank-two tensor. Shapes `(m, k)` and `(k, n)` produce `(m, n)`. Work is bounded by **16,777,216** scalar multiply-add steps (`m*k*n`). This binding does not expose batched multiplication. -**Returns:** a new owned matrix. **Raises:** native rank/inner-dimension, -size/work budget, allocation, arithmetic/runtime, or closed-handle errors. + +#### Examples ```python with Tensor.matrix([1, 2, 3, 4], 2, 2) as a: @@ -226,14 +566,36 @@ with Tensor.matrix([1, 2, 3, 4], 2, 2) as a: assert squared.values() == [7., 10., 15., 22.] ``` +#### See also + +[hmath.Tensor.matrix](#hmathtensormatrixvalues-rows-columns), [hmath.Tensor.dot](#hmathtensordotother). + ### `hmath.Tensor.reshape(shape)` -**Parameters:** new dimensions with unchanged element count. Storage must be -contiguous. Negative dimensions, including `-1` for inference, are unsupported. -**Returns:** a new owned handle sharing storage; both handles can close independently. -**Raises:** `ValueError` for invalid dimensions, `HMathError` for incompatible +Create a new shape over the same contiguous storage. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `shape` | `iterable of int` | Nonnegative dimensions with unchanged element count; -1 inference is unsupported. | + +#### Returns + +a new owned handle sharing storage; both handles can close independently. + +#### Raises + +`ValueError` for invalid dimensions, `HMathError` for incompatible count or non-contiguous input, or `RuntimeError` after close. +#### Notes + +new dimensions with unchanged element count. Storage must be +contiguous. Negative dimensions, including `-1` for inference, are unsupported. + +#### Examples + ```python with Tensor([1, 2, 3, 4]) as flat: reshaped = flat.reshape((2, 2)) @@ -241,37 +603,95 @@ with reshaped: assert reshaped.shape == (2, 2) ``` +#### See also + +[hmath.Tensor.shape](#hmathtensorshape), [hmath.Tensor](#hmathtensorvalues-shapenone). + ## Lifetime protocol ### `hmath.Tensor.close()` -**Parameters:** none. Release this handle's native reference and borrowed owner. -**Returns:** `None`; repeated calls are harmless. Other retained handles/views +Release this tensor handle deterministically. + +#### Parameters + +None. + +#### Returns + +`None`; repeated calls are harmless. Other retained handles/views remain valid. Using this closed handle raises `RuntimeError`. +#### Examples + ```python tensor = Tensor([1]) tensor.close() tensor.close() ``` +#### See also + +[hmath.Tensor.__enter__](#hmathtensor__enter__), [hmath.Tensor.__exit__](#hmathtensor__exit__args), [hmath.Tensor.__del__](#hmathtensor__del__). + ### `hmath.Tensor.__enter__()` -**Parameters:** none. **Returns:** the same live tensor for a `with` block; -this creates no additional reference. **Raises:** `RuntimeError` after close. +Enter a context that owns this tensor handle. + +#### Parameters + +None. + +#### Returns + +the same live tensor for a `with` block; +this creates no additional reference. + +#### Raises + +`RuntimeError` after close. See the context-manager examples above. +#### See also + +[hmath.Tensor.close](#hmathtensorclose), [hmath.Tensor.__exit__](#hmathtensor__exit__args). + ### `hmath.Tensor.__exit__(*args)` -**Parameters:** Python's context-manager exception triple. -**Returns:** `None`, closing the tensor on normal or exceptional block exit +Close the tensor when leaving a context. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `args` | `exception triple` | Python supplies (exception type, exception value, traceback); exceptions are not suppressed. | + +#### Returns + +`None`, closing the tensor on normal or exceptional block exit without suppressing the exception. See the context-manager examples above. +#### See also + +[hmath.Tensor.close](#hmathtensorclose), [hmath.Tensor.__enter__](#hmathtensor__enter__). + ### `hmath.Tensor.__del__()` -**Parameters:** none. **Returns:** `None`, calling `close()` as a finalization +Release the handle as a garbage-collection fallback. + +#### Parameters + +None. + +#### Returns + +`None`, calling `close()` as a finalization fallback. Collection timing is unspecified; use `with` for deterministic cleanup. +#### See also + +[hmath.Tensor.close](#hmathtensorclose). + ## Optional cffi interface Install `hmath-python[cffi]` to use `hmath_cffi`. Both wrappers share the native @@ -280,34 +700,102 @@ runtime. Prevent concurrent close/use of a cffi tensor. NumPy conversion, ### `hmath_cffi.add(a, b)` -**Parameters:** two finite real scalars. **Returns:** their binary64 sum as a -Python float. **Raises:** `hmath_cffi.HMathError` for native failures or ordinary +Add two real scalars. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `real scalar` | First finite value, convertible to C double. | +| `b` | `real scalar` | Second finite value, convertible to C double. | + +#### Returns + +their binary64 sum as a +Python float. + +#### Raises + +`hmath_cffi.HMathError` for native failures or ordinary conversion errors. Import initializes the runtime and checks ABI 1. +#### Examples + ```python import hmath_cffi as hc assert hc.add(2, 3) == 5.0 ``` +#### See also + +[hmath.add](#hmathadda-b), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.HMathError(status)` -**Parameters:** native status integer. **Returns:** an exception with `.status` +Represent a failed native checked operation. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `status` | `int` | Native status code; available as the exception’s status attribute. | + +#### Returns + +an exception with `.status` and a snapshot of the current native error message. Catch this class for cffi errors; it is a separate class from `hmath.HMathError`. +#### See also + +[hmath.HMathError](#hmathhmatherrorstatus), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.check(status)` -**Parameters:** status from a raw checked C call. **Returns:** `None` for zero. -**Raises:** `hmath_cffi.HMathError` for nonzero. Call immediately after the raw +Convert a raw native status into a Python exception. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `status` | `int` | Status returned by the immediately preceding checked C call. | + +#### Returns + +`None` for zero. + +#### Raises + +`hmath_cffi.HMathError` for nonzero. Call immediately after the raw call so its thread-local error message has not been replaced. +#### See also + +[hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor(values, shape=None)` -**Parameters:** flat finite numeric iterable and optional dimensions, following -primary constructor limits. **Returns:** an owned cffi tensor with `.shape` -stored as a tuple. **Raises:** `ValueError` for Python bounds, +Copy flat values into a new immutable tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `values` | `iterable of real scalars` | Finite values in logical row-major order; at most 1,048,576 elements. | +| `shape` | `iterable of int or None; default None` | At most eight bounded nonnegative dimensions. None selects (len(values),). | + +#### Returns + +an owned cffi tensor with `.shape` +stored as a tuple. + +#### Raises + +`ValueError` for Python bounds, `hmath_cffi.HMathError` for native input/allocation errors, or conversion errors. +#### Examples + ```python with hc.Tensor([1, 2, 3, 4], (2, 2)) as a: assert a.shape == (2, 2) @@ -315,53 +803,181 @@ with hc.Tensor([1, 2, 3, 4], (2, 2)) as a: assert result.values() == [7., 10., 15., 22.] ``` +#### See also + +[hmath.Tensor](#hmathtensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose), [hmath_cffi.check](#hmath_cfficheckstatus). + ### `hmath_cffi.Tensor.values()` -**Parameters:** none. **Returns:** an independent row-major `list[float]`. -**Raises:** `RuntimeError` after close or `hmath_cffi.HMathError` on copy failure. +Copy logical elements to a flat Python list. + +#### Parameters + +None. + +#### Returns + +an independent row-major `list[float]`. + +#### Raises + +`RuntimeError` after close or `hmath_cffi.HMathError` on copy failure. Use `.values()` inside the context-manager example above. +#### See also + +[hmath.Tensor.values](#hmathtensorvalues), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor.sum()` -**Parameters:** none. **Returns:** scalar sum, or `0.0` for an empty tensor. -**Raises:** native arithmetic/runtime or closed-handle errors. For example, +Sum all tensor elements to one scalar. + +#### Parameters + +None. + +#### Returns + +scalar sum, or `0.0` for an empty tensor. + +#### Raises + +native arithmetic/runtime or closed-handle errors. For example, `hc.Tensor([1, 2])` sums to `3.0`; close the owner after use. +#### See also + +[hmath.Tensor.sum](#hmathtensorsum), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor.dot(other)` -**Parameters:** a live cffi tensor with identical shape. **Returns:** scalar sum -of elementwise products. **Raises:** native shape/arithmetic/runtime or +Reduce corresponding products of two identically shaped tensors. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live tensor with exactly the same shape as this tensor. | + +#### Returns + +scalar sum +of elementwise products. + +#### Raises + +native shape/arithmetic/runtime or closed-handle errors. The primary interface's dot example also applies to cffi. +#### See also + +[hmath.Tensor.dot](#hmathtensordotother), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor.__add__(other)` — `a + b` -**Parameters:** a live cffi tensor of identical shape. **Returns:** a new owned -elementwise sum without broadcasting. **Raises:** native shape/allocation/ +Add two tensors element by element. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live tensor of the same shape; broadcasting is unsupported. | + +#### Returns + +a new owned +elementwise sum without broadcasting. + +#### Raises + +native shape/allocation/ arithmetic/runtime or closed-handle errors. Use with `with a + b as result:`. +#### See also + +[hmath.Tensor.__add__](#hmathtensor__add__other-a-b), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor.__matmul__(other)` — `a @ b` -**Parameters:** a live cffi tensor with compatible rank-two dimensions. -**Returns:** a new owned matrix. **Raises:** the same shape/work-bound and +Multiply two rank-two matrices. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `other` | `Tensor` | Live (k, n) matrix when this tensor has shape (m, k). | + +#### Returns + +a new owned matrix. + +#### Raises + +the same shape/work-bound and native/lifecycle errors as primary `@`. See the executable cffi example above. +#### See also + +[hmath.Tensor.__matmul__](#hmathtensor__matmul__other-a-b), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + ### `hmath_cffi.Tensor.close()` -**Parameters:** none. **Returns:** `None`, releasing its cffi-managed reference. +Release this tensor handle deterministically. + +#### Parameters + +None. + +#### Returns + +`None`, releasing its cffi-managed reference. Repeated calls are harmless. Do not close concurrently with an operation on this tensor. Other independent owners remain valid. +#### See also + +[hmath.Tensor.close](#hmathtensorclose), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.check](#hmath_cfficheckstatus). + ### `hmath_cffi.Tensor.__enter__()` -**Parameters:** none. **Returns:** the same live tensor in `with`. -**Raises:** `RuntimeError` after close. No new native owner is created. +Enter a context that owns this tensor handle. + +#### Parameters + +None. + +#### Returns + +the same live tensor in `with`. + +#### Raises + +`RuntimeError` after close. No new native owner is created. + +#### See also + +[hmath.Tensor.__enter__](#hmathtensor__enter__), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). ### `hmath_cffi.Tensor.__exit__(*args)` -**Parameters:** Python's exception triple. **Returns:** `None`, closing this +Close the tensor when leaving a context. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `args` | `exception triple` | Python supplies (exception type, exception value, traceback); exceptions are not suppressed. | + +#### Returns + +`None`, closing this tensor on block exit without suppressing exceptions. -### Low-level cffi attributes +#### See also + +[hmath.Tensor.__exit__](#hmathtensor__exit__args), [hmath_cffi.Tensor](#hmath_cffitensorvalues-shapenone), [hmath_cffi.Tensor.close](#hmath_cffitensorclose). + +## Low-level cffi attributes `hmath_cffi.ffi` is the configured `cffi.FFI` object; `hmath_cffi.lib` is the loaded native handle exposing the declarations used by this wrapper. Raw calls @@ -371,16 +987,4 @@ is the construction/result shape tuple and must not be reassigned by callers. ## Native status codes -| Code | Name | Meaning | -| --- | --- | --- | -| 0 | `HMATH_OK` | Success | -| 1 | `HMATH_INVALID_ARGUMENT` | Invalid argument | -| 2 | `HMATH_SHAPE_ERROR` | Invalid or incompatible dimensions | -| 3 | `HMATH_OVERFLOW` | Arithmetic or size overflow | -| 4 | `HMATH_ALLOCATION_FAILED` | Allocation failed | -| 5 | `HMATH_RUNTIME_ERROR` | Runtime unavailable | -| 6 | `HMATH_HASKELL_EXCEPTION` | Contained Haskell exception | -| 7 | `HMATH_CANCELLED` | Cooperative cancellation | -| 8 | `HMATH_BOUNDS_ERROR` | Buffer or metadata bounds error | -| 9 | `HMATH_BUSY` | Active work prevents shutdown | -| 10 | `HMATH_UNSUPPORTED` | Unsupported operation | +See the canonical [C status table](c.md#native-status-codes). diff --git a/docs/reference/qr.md b/docs/reference/qr.md new file mode 100644 index 0000000..49f0526 --- /dev/null +++ b/docs/reference/qr.md @@ -0,0 +1,264 @@ +# QR contracts (Haskell) + +`HMath.Matrix.QR` provides column-pivoted Householder QR, full-column-rank +least-squares solves, and ridge regression. The interface is Haskell-only. +For a complete executable fitting example, see [least squares](../guides/least-squares.md). + +## Shapes and factorization + +For an $m\times n$ input matrix, `QR a` is opaque and contains factors +satisfying + +$$ +A[:,p]=Q R,\qquad Q\in\mathbb{R}^{m\times m},\quad +R\in\mathbb{R}^{m\times n}. +$$ + +`p` lists zero-based **original** column indices in pivoted order. `Q` is +square, not an economy-size factor. `R` is upper trapezoidal. The factorization +records deficient rank; the least-squares solve rejects it. Matrix construction +uses the separate checked [Matrix API](haskell.md). + +## Factorization + +### `qrDecompose` + +```haskell +qrDecompose :: (RealFloat a) => Matrix a -> Either MathError (QR a) +``` + +Compute column-pivoted Householder QR with the default relative rank tolerance. + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `a` | `Matrix a` | Finite m-by-n matrix; rectangular and zero-sized shapes are allowed. | + +#### Returns + +`Right factors`, containing square `Q`, trapezoidal `R`, column permutation, +and numerical rank. The default tolerance is `max(m,n) * machineEpsilon` +for the input's `RealFloat` type. This estimates numerical rank rather than +requiring exact-zero pivots. + +#### Errors + +Non-finite matrix entries return `Left (NonFiniteMatrixEntry (row, column))`. +Non-finite intermediate data can return `Left (NonFiniteVectorEntry index)`; +that index may describe a flattened working array rather than the original input. +Checked matrix-allocation shape/size failures propagate. A deficient rank +alone does not fail this factorization. + +#### See also + +[`qrDecomposeWithTolerance`](#qrdecomposewithtolerance), +[`qrRank`](#factor-inspection), [`solveQR`](#solveqr), +[executable example](../guides/least-squares.md#fit-a-line). + +### `qrDecomposeWithTolerance` + +```haskell +qrDecomposeWithTolerance :: (RealFloat a) => a -> Matrix a -> Either MathError (QR a) +``` + +Select the relative pivot threshold explicitly. + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `tol` | `a` | Finite nonnegative relative rank tolerance. | +| `a` | `Matrix a` | Finite m-by-n matrix. | + +#### Returns + +The same factors as `qrDecompose`, with rank determined from diagonal pivots. +Let $s=\max_i |R_{ii}|$. A pivot counts when it is nonzero and either +`tol == 0` or $|R_{ii}|/s>\mathrm{tol}$. Zero tolerance counts every nonzero +pivot; it does not remove roundoff. + +#### Errors + +`Left InvalidTolerance` for a negative, NaN, or infinite tolerance, plus the +finite-input/result and checked matrix errors of `qrDecompose`. + +#### See also + +[`qrDecompose`](#qrdecompose), +[rank and conditioning](../explanation/numerical-conventions.md#residual-conditioning-and-rank). + +## Factor inspection + +These accessors take one `QR a`, allocate no new factorization, and return +the stored component directly. Their signatures have no checked error result. + +| Signature | Returns | +| --- | --- | +| `qrQ :: QR a -> Matrix a` | Square m-by-m orthogonal factor. | +| `qrR :: QR a -> Matrix a` | Upper-trapezoidal m-by-n factor in pivoted column order. | +| `qrPermutation :: QR a -> [Int]` | n original column indices in pivoted order. | +| `qrRank :: QR a -> Int` | Numerical rank selected by the relative tolerance. | + +## Apply an orthogonal factor + +| Signature | Parameters | Returns / errors | +| --- | --- | --- | +| `applyQ :: (Num a) => QR a -> Matrix a -> Either MathError (Matrix a)` | Factors, then m-by-k matrix. | `Right (Q * matrix)`, shape m-by-k; checked dimension/size failures propagate. | +| `applyQT :: (Num a) => QR a -> Matrix a -> Either MathError (Matrix a)` | Factors, then m-by-k matrix. | `Right (transpose Q * matrix)`, shape m-by-k; checked dimension/size failures propagate. | + +These operations have a generic `Num` constraint. They do not add a +universal finite-result guarantee to the selected numeric type. + +## Least-squares solves + +### `solveQR` + +```haskell +solveQR :: (RealFloat a) => QR a -> [a] -> Either MathError [a] +``` + +Solve $\min_x\|Ax-b\|_2^2$ using an existing factorization. + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `f` | `QR a` | Factors of an m-by-n matrix, with m ≥ n and numerical rank n. | +| `b` | `[a]` | Exactly m finite right-hand-side values. | + +#### Returns + +`Right x` with n coefficients in the original, unpermuted column order. +The result for n = 0 is an empty list when the RHS shape is compatible. + +#### Errors + +- `InvalidArgument` for an underdetermined system. +- `RightHandSideMismatch expected actual` for the wrong RHS length. +- `RankDeficient rank` if the factorization has fewer than n independent columns. +- Non-finite matrix/vector/result and checked matrix errors propagate. + +#### See also + +[`leastSquares`](#leastsquares), [`solveMatrixQR`](#multiple-right-hand-sides), +[factor once for several solves](../guides/least-squares.md#choose-factorization-or-direct-solve). + +### `leastSquares` + +```haskell +leastSquares :: (RealFloat a) => Matrix a -> [a] -> Either MathError [a] +``` + +Factor and solve in one operation. + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `a` | `Matrix a` | Finite m-by-n design, with m ≥ n and full numerical column rank. | +| `b` | `[a]` | m finite observations. | + +#### Returns + +The n coefficients from `qrDecompose a` followed by `solveQR factors b`. +An intercept column must be supplied explicitly; none is inserted automatically. + +#### Errors + +The union of factorization and `solveQR` failures. There is no minimum-norm +fallback for underdetermined or deficient-rank input. + +#### See also + +[`solveQR`](#solveqr), [`ridgeSolve`](#ridgesolve), +[fit a line](../guides/least-squares.md#fit-a-line). + +### Multiple right-hand sides + +| Signature | Parameters | Returns / errors | +| --- | --- | --- | +| `leastSquaresMatrix :: (RealFloat a) => Matrix a -> Matrix a -> Either MathError (Matrix a)` | m-by-n design, then m-by-k RHS matrix. | n-by-k coefficients; factorization and solve errors propagate. | +| `solveMatrixQR :: (RealFloat a) => QR a -> Matrix a -> Either MathError (Matrix a)` | Full-column-rank factors, then finite m-by-k RHS. | n-by-k coefficients in original column order; same m ≥ n, rank, shape and numerical checks as `solveQR`. | + +Compatible zero-column RHS matrices return n-by-0 results. Rank and dimension +checks still apply before the empty result is produced. + +## Ridge regression + +### `ridgeSolve` + +```haskell +ridgeSolve :: (RealFloat a) => a -> Matrix a -> [a] -> Either MathError [a] +``` + +Minimize a squared residual plus a squared-coefficient penalty: + +$$ +\min_x\left(\|Ax-b\|_2^2+\lambda\|x\|_2^2\right). +$$ + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `lambda` | `a` | Finite nonnegative penalty coefficient. | +| `a` | `Matrix a` | Finite m-by-n design. | +| `b` | `[a]` | m finite RHS values. | + +#### Returns + +n coefficients in original column order. For positive lambda, the solver +uses augmented QR with design `[A; sqrt(lambda)*I]` and RHS `[b; 0]`. +Every supplied coefficient, including an intercept column, is penalized. +Zero lambda delegates to ordinary least squares and retains its requirements. + +#### Errors + +`DomainError` for negative/non-finite lambda, plus RHS, augmented-shape, +rank, and numerical errors from the checked construction/factorization/solve. +Positive lambda does not establish an unconditional finite-precision success guarantee. + +#### See also + +[`leastSquares`](#leastsquares), [`ridgeSolveMatrix`](#ridgesolvematrix), +[least-squares guide](../guides/least-squares.md). + +### `ridgeSolveMatrix` + +```haskell +ridgeSolveMatrix :: (RealFloat a) => a -> Matrix a -> Matrix a -> Either MathError (Matrix a) +``` + +#### Parameters + +| Argument | Type | Meaning | +| --- | --- | --- | +| `lambda` | `a` | Finite nonnegative coefficient applied to every RHS column. | +| `a` | `Matrix a` | Finite m-by-n design. | +| `b` | `Matrix a` | Finite m-by-k RHS matrix. | + +#### Returns + +An n-by-k coefficient matrix, using the same augmented QR and column ordering +as `ridgeSolve`. + +#### Errors + +The same validation and numerical failures as `ridgeSolve`, with a matrix +RHS row-count check. Zero lambda delegates to `leastSquaresMatrix`. + +#### See also + +[`ridgeSolve`](#ridgesolve), [`leastSquaresMatrix`](#multiple-right-hand-sides). + +## Numerical notes and references + +Column pivoting supports rank estimation but does not replace a condition +number analysis. The reported rank and residual depend on floating-point +scale and the selected tolerance. The implementation uses Householder +reflections; it does not form $A^T A$ for these solves. + +- [Full module declarations](haskell.md): all public Haskell modules and generated signatures. +- [Numerical conventions](../explanation/numerical-conventions.md): residuals and accuracy. +- [GSL QR documentation](https://www.gnu.org/software/gsl/doc/html/linalg.html#qr-decomposition): mathematical background; GSL-specific storage and API rules do not define HMath's interface. diff --git a/docs/reference/wave.md b/docs/reference/wave.md index 1df28b4..d7791e2 100644 --- a/docs/reference/wave.md +++ b/docs/reference/wave.md @@ -4,6 +4,26 @@ The Wave 0.2.1 binding targets **64-bit systems** where C `size_t` matches Wave `u64`. Import `bindings/wave/hmath.wave` beside your source as shown in the [Wave integration guide](../guides/wave.md). All numerical values are `f64`. +## Function index + +| API | Purpose | +| --- | --- | +| [initialize](#initialize-i32) | Initialize the shared runtime after checking the ABI. | +| [error_message](#error_message-ptri8) | Borrow the current thread’s native error message. | +| [scalar_add](#scalar_adda-f64-b-f64-output-ptrf64-i32) | Add two finite real scalars. | +| [tensor_empty](#tensor_empty-tensor) | Construct an empty wrapper without allocating a native tensor. | +| [tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32) | Copy row-major values into an owned tensor. | +| [vector_copy](#vector_copydata-ptrf64-count-u64-output-ptrtensor-i32) | Copy values into an owned rank-one tensor. | +| [matrix_copy](#matrix_copydata-ptrf64-count-u64-rows-u64-columns-u64-output-ptrtensor-i32) | Copy row-major values into an owned rank-two tensor. | +| [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32) | Acquire a separate owner for the same native tensor. | +| [tensor_close](#tensor_closetensor-ptrtensor) | Release one owned wrapper and clear its handle. | +| [tensor_count](#tensor_counttensor-tensor-u64) | Query the logical element count. | +| [tensor_values](#tensor_valuestensor-tensor-output-ptrf64-count-u64-i32) | Copy logical values into caller-owned storage. | +| [tensor_sum](#tensor_sumtensor-tensor-output-ptrf64-i32) | Sum all logical tensor elements. | +| [tensor_dot](#tensor_dota-tensor-b-tensor-output-ptrf64-i32) | Reduce corresponding products to one scalar. | +| [tensor_add](#tensor_adda-tensor-b-tensor-output-ptrtensor-i32) | Add identically shaped tensors element by element. | +| [tensor_matmul](#tensor_matmula-tensor-b-tensor-output-ptrtensor-i32) | Multiply two dense rank-two matrices. | + ## Shared rules `Tensor { handle: ptr }` owns one native reference. Treat owners linearly: @@ -12,7 +32,7 @@ plain struct assignment does **not** retain a second reference. Use failure. Keep all input handles live throughout a call. Status-returning wrappers return `i32`: zero means success. The C -[status table](python.md#native-status-codes) defines the other values. Read +[status table](c.md#native-status-codes) defines the other values. Read `error_message()` immediately after a failed native checked call. Wrapper-only validation (such as an occupied output slot or ABI mismatch) returns its code without replacing the native diagnostic, so the message may describe an earlier @@ -27,88 +47,268 @@ must obey the C ABI's rank 8, 1,048,576-element, finite-data, and storage bounds ### `initialize() -> i32` -**Parameters:** none. Check ABI 1 and initialize the shared runtime. -**Returns:** 0 on success, 10 for unsupported ABI, 5 after irreversible shutdown. +Initialize the shared runtime after checking the ABI. + +#### Parameters + +None. + +#### Returns + +0 on success, 10 for unsupported ABI, 5 after irreversible shutdown. Repeated initialization is allowed. Constructors and `scalar_add` call this automatically. It does not create a tensor or an ownership obligation. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `error_message() -> ptr` -**Parameters:** none. **Returns:** a borrowed, NUL-terminated thread-local +Borrow the current thread’s native error message. + +#### Parameters + +None. + +#### Returns + +a borrowed, NUL-terminated thread-local native error string. It remains valid until the next checked C call on this thread. Copy it before that call if it must survive; never free it. Wrapper-only validation does not set a fresh message; see the shared rules. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `scalar_add(a: f64, b: f64, output: ptr) -> i32` -**Parameters:** two finite scalars and a valid aligned scalar output pointer. -**Returns:** status 0 and their sum in `output` after automatic initialization. -**Errors:** invalid/non-finite input or result, initialization or native runtime +Add two finite real scalars. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `f64` | First finite scalar. | +| `b` | `f64` | Second finite scalar. | +| `output` | `ptr` | Aligned writable scalar slot; valid only on success. | + +#### Returns + +status 0 and their sum in `output` after automatic initialization. + +#### Errors + +invalid/non-finite input or result, initialization or native runtime failure. Example: `scalar_add(2.0, 3.0, &value)` writes 5.0 on success. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ## Construction and ownership ### `tensor_empty() -> Tensor` -**Parameters:** none. **Returns:** `Tensor { handle: null }`, with no native +Construct an empty wrapper without allocating a native tensor. + +#### Parameters + +None. + +#### Returns + +`Tensor { handle: null }`, with no native reference to release. Use this to initialize every result slot before passing its address to a constructor, clone, add, or matrix multiplication. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_copy(data: ptr, count: u64, shape: ptr, rank: u64, output: ptr) -> i32` -**Parameters:** `count` readable finite doubles, `rank` dimensions, and empty +Copy row-major values into an owned tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `ptr` | Readable finite f64 values; NULL only for zero count. | +| `count` | `u64` | Number of values, equal to the shape product. | +| `shape` | `ptr` | Array of rank dimensions; may be NULL for rank zero. | +| `rank` | `u64` | Dimension count, at most eight. | +| `output` | `ptr` | Non-NULL pointer to a wrapper initialized with tensor_empty(). | + +#### Returns + +status 0 and a new owned row-major copy; input ownership is unchanged. + +#### Errors + +occupied/NULL output, invalid storage/dimensions, count mismatch, +size bound, initialization, or allocation failure. + +#### Notes + +`count` readable finite doubles, `rank` dimensions, and empty output wrapper. Count must equal the dimension product. Rank zero contains one value; zero dimensions allow an empty tensor. Data must remain readable during this synchronous call. -**Returns:** status 0 and a new owned row-major copy; input ownership is unchanged. -**Errors:** occupied/NULL output, invalid storage/dimensions, count mismatch, -size bound, initialization, or allocation failure. + +#### See also + +[tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor), [error_message](#error_message-ptri8). ### `vector_copy(data: ptr, count: u64, output: ptr) -> i32` -**Parameters:** readable values and their count, plus empty output wrapper. -**Returns:** status 0 and a new owned rank-one tensor of length `count`. -**Errors:** the same construction errors as `tensor_copy`. +Copy values into an owned rank-one tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `ptr` | Readable finite f64 values; NULL only for zero count. | +| `count` | `u64` | Vector length, at most 1,048,576. | +| `output` | `ptr` | Non-NULL pointer to an empty result wrapper. | + +#### Returns + +status 0 and a new owned rank-one tensor of length `count`. + +#### Errors + +the same construction errors as `tensor_copy`. The values are copied, so the source can be reused once the call returns. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `matrix_copy(data: ptr, count: u64, rows: u64, columns: u64, output: ptr) -> i32` -**Parameters:** row-major values, count, two dimensions, empty output wrapper. -**Returns:** status 0 and a new owned rank-two matrix. -**Errors:** construction errors, including `count != rows * columns`. +Copy row-major values into an owned rank-two tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `data` | `ptr` | Readable finite row-major values. | +| `count` | `u64` | Number of values, equal to rows * columns. | +| `rows` | `u64` | Nonnegative bounded row count. | +| `columns` | `u64` | Nonnegative bounded column count. | +| `output` | `ptr` | Non-NULL pointer to an empty result wrapper. | + +#### Returns + +status 0 and a new owned rank-two matrix. + +#### Errors + +construction errors, including `count != rows * columns`. For `{1,2,3,4}`, dimensions 2 by 2 create rows `[1,2]` and `[3,4]`. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_clone(source: Tensor, output: ptr) -> i32` -**Parameters:** a live source and non-NULL empty result wrapper. -**Returns:** 0 and a second owned reference to the same immutable native tensor. +Acquire a separate owner for the same native tensor. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `source` | `Tensor` | Live source wrapper; remains owned by the caller. | +| `output` | `ptr` | Non-NULL pointer to an empty result wrapper. | + +#### Returns + +0 and a second owned reference to the same immutable native tensor. No values are copied. Both owners must be closed independently. -**Errors:** 1 for an empty source, NULL output, or occupied output slot. + +#### Errors + +1 for an empty source, NULL output, or occupied output slot. + +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor), [error_message](#error_message-ptri8). ### `tensor_close(tensor: ptr)` -**Parameters:** pointer to an owned or empty wrapper; NULL is also allowed. -**Returns:** nothing. Release its native reference and set the handle to NULL. +Release one owned wrapper and clear its handle. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `ptr` | Pointer to an owned or empty wrapper; NULL is a no-op. | + +#### Returns + +nothing. Release its native reference and set the handle to NULL. Repeated close is safe. Do not close the last owner concurrently with use. Cloned owners remain valid. Call this on all success and error cleanup paths. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [error_message](#error_message-ptri8). + ## Inspection ### `tensor_count(tensor: Tensor) -> u64` -**Parameters:** live or empty wrapper. -**Returns:** logical element count; empty/closed wrapper returns zero. A +Query the logical element count. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `Tensor` | Live or empty wrapper; an empty wrapper returns zero. | + +#### Returns + +logical element count; empty/closed wrapper returns zero. A rank-zero scalar counts as one. This is a query, not a status result; a zero count cannot distinguish an empty tensor from an empty wrapper. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_values(tensor: Tensor, output: ptr, count: u64) -> i32` -**Parameters:** live wrapper and aligned caller-owned output buffer; `count` +Copy logical values into caller-owned storage. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `Tensor` | Live source wrapper. | +| `output` | `ptr` | Aligned writable non-overlapping buffer; NULL for zero elements. | +| `count` | `u64` | Output capacity in f64 elements, at least tensor_count(tensor). | + +#### Returns + +0 after copying flat logical row-major values. + +#### Errors + +invalid handle/buffer, overlap, or insufficient capacity (8). +It neither consumes the tensor nor transfers storage ownership. + +#### Notes + +live wrapper and aligned caller-owned output buffer; `count` is capacity in doubles, at least `tensor_count(tensor)`. It must not overlap native input storage. A NULL buffer is allowed for zero elements. -**Returns:** 0 after copying flat logical row-major values. -**Errors:** invalid handle/buffer, overlap, or insufficient capacity (8). -It neither consumes the tensor nor transfers storage ownership. + +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). ## Calculations @@ -118,33 +318,106 @@ include unavailable runtime, allocation, and non-finite arithmetic. ### `tensor_sum(tensor: Tensor, output: ptr) -> i32` -**Parameters:** live input and aligned scalar output pointer. -**Returns:** 0 with the sum of every logical element, or zero for an empty tensor. -**Errors:** invalid pointers/handle or common native failures. +Sum all logical tensor elements. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `tensor` | `Tensor` | Live input wrapper. | +| `output` | `ptr` | Aligned writable scalar slot; valid only on success. | + +#### Returns + +0 with the sum of every logical element, or zero for an empty tensor. + +#### Errors + +invalid pointers/handle or common native failures. The input owner remains unchanged. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_dot(a: Tensor, b: Tensor, output: ptr) -> i32` -**Parameters:** live tensors with identical shapes and scalar output pointer. -**Returns:** 0 with the sum of elementwise products; matching empty tensors give -zero. **Errors:** shape mismatch, invalid pointers/handles, or common native +Reduce corresponding products to one scalar. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `Tensor` | Live first wrapper. | +| `b` | `Tensor` | Live second wrapper with identical shape. | +| `output` | `ptr` | Aligned writable scalar slot; valid only on success. | + +#### Returns + +0 with the sum of elementwise products; matching empty tensors give +zero. + +#### Errors + +shape mismatch, invalid pointers/handles, or common native failures. This is a scalar reduction, not matrix multiplication. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_add(a: Tensor, b: Tensor, output: ptr) -> i32` -**Parameters:** live tensors of identical shape and empty result wrapper. -**Returns:** 0 with a new owned elementwise sum. Inputs remain unchanged; there -is no broadcasting. **Errors:** occupied/NULL output, shape mismatch, invalid +Add identically shaped tensors element by element. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `Tensor` | Live first wrapper. | +| `b` | `Tensor` | Live second wrapper with identical shape; no broadcasting. | +| `output` | `ptr` | Non-NULL pointer to an empty result wrapper. | + +#### Returns + +0 with a new owned elementwise sum. Inputs remain unchanged; there +is no broadcasting. + +#### Errors + +occupied/NULL output, shape mismatch, invalid handles, or common native failures. Close the result independently of its inputs. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ### `tensor_matmul(a: Tensor, b: Tensor, output: ptr) -> i32` -**Parameters:** live rank-two matrices of shapes `(m,k)` and `(k,n)`, and empty -result wrapper. **Returns:** 0 with a new owned `(m,n)` dense product. -**Errors:** invalid/occupied output, rank/inner-dimension mismatch, element/work +Multiply two dense rank-two matrices. + +#### Parameters + +| Name | Type / default | Description | +| --- | --- | --- | +| `a` | `Tensor` | Live left matrix of shape (m, k). | +| `b` | `Tensor` | Live right matrix of shape (k, n). | +| `output` | `ptr` | Non-NULL pointer to an empty wrapper; result shape is (m, n). | + +#### Returns + +0 with a new owned `(m,n)` dense product. + +#### Errors + +invalid/occupied output, rank/inner-dimension mismatch, element/work bounds (at most 16,777,216 scalar multiply-add steps), or common native failures. Batched and broadcasted matrix products are not exposed by this wrapper. +#### See also + +[tensor_copy](#tensor_copydata-ptrf64-count-u64-shape-ptru64-rank-u64-output-ptrtensor-i32), [tensor_clone](#tensor_clonesource-tensor-output-ptrtensor-i32), [tensor_close](#tensor_closetensor-ptrtensor). + ## Example with cleanup Place this file beside the binding, then use the compiler/linker setup in the diff --git a/mkdocs.yml b/mkdocs.yml index c9fd90b..5ffadb1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,10 +27,15 @@ theme: name: Switch to light mode extra_css: - stylesheets/api.css +extra_javascript: + - javascripts/mathjax.js + - https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.js plugins: - search markdown_extensions: - admonition + - pymdownx.arithmatex: + generic: true - pymdownx.highlight: anchor_linenums: true - pymdownx.superfences @@ -38,20 +43,28 @@ markdown_extensions: check_paths: true - toc: permalink: true + toc_depth: 3 nav: - Overview: index.md - Getting started: - Installation: getting-started/installation.md - First calculation: getting-started/first-calculation.md + - Choose an operation: getting-started/choose-an-operation.md - How-to guides: - NumPy interoperability: guides/numpy.md + - Validate a matrix calculation: guides/matrix-calculation.md + - Solve least-squares problems (Haskell): guides/least-squares.md + - Differentiate and integrate (Haskell): guides/calculus.md + - Summarize data (Haskell): guides/statistics.md - C integration: guides/c.md + - Borrow storage safely (C): guides/borrowed-storage.md - Wave integration: guides/wave.md - Build from source: guides/build.md - Troubleshooting: guides/troubleshooting.md - Dense linear algebra: linear-algebra.md - Explanation: - Architecture and ownership: explanation/architecture.md + - Numerical conventions: explanation/numerical-conventions.md - Compatibility and releases: explanation/releases.md - Contributing: contributing.md - API Reference: @@ -59,3 +72,4 @@ nav: - C ABI: reference/c.md - Wave API: reference/wave.md - Haskell API: reference/haskell.md + - QR contracts (Haskell): reference/qr.md diff --git a/tools/build_docs.py b/tools/build_docs.py index e1ae34d..fdc5e8d 100644 --- a/tools/build_docs.py +++ b/tools/build_docs.py @@ -7,6 +7,7 @@ import sys from pathlib import Path +import tomllib import yaml from check_api_docs import check_reference, read_haddock from haskell_reference import generate_reference, repair_module_anchors @@ -49,6 +50,9 @@ if staging.exists(): shutil.rmtree(staging) shutil.copytree(root / "docs", staging) +version = tomllib.loads((root / "pyproject.toml").read_text())["project"]["version"] +for page in staging.rglob("*.md"): + page.write_text(page.read_text().replace("@HMATH_VERSION@", version)) config = yaml.safe_load((root / "mkdocs.yml").read_text()) config["docs_dir"] = str(staging) config["site_dir"] = str(root / "site") diff --git a/tools/check_api_docs.py b/tools/check_api_docs.py index 77b73f4..cd12a6f 100644 --- a/tools/check_api_docs.py +++ b/tools/check_api_docs.py @@ -31,7 +31,7 @@ def python_exports(path, module): return names -def check_entries(names, page, pattern): +def check_entries(names, page, pattern, parameters, signatures, defaults=None): text = page.read_text() sections = re.split(r"(?=^### )", text, flags=re.MULTILINE) covered = {} @@ -43,12 +43,95 @@ def check_entries(names, page, pattern): if missing: raise ValueError(f"{page}: missing entries: {', '.join(sorted(missing))}") for name in sorted(names): - section = covered[name] - if "**Parameters:**" not in section or "**Returns:**" not in section: - raise ValueError(f"{page}: {name} lacks parameter/return contracts") + section = re.split(r"^## ", covered[name], maxsplit=1, flags=re.MULTILINE)[0] + for title in ("Parameters", "Returns", "See also"): + if not re.search(rf"^#### {title}\n\n\S", section, re.MULTILINE): + raise ValueError(f"{page}: {name} lacks a nonempty {title} section") + parameter_section = section.split("#### Parameters\n", 1)[1].split("#### ", 1)[ + 0 + ] + rows = re.findall( + r"^\| `([^`]+)` \| `([^`]+)` \| (.+) \|$", parameter_section, re.MULTILINE + ) + if [row[0] for row in rows] != parameters[name]: + raise ValueError( + f"{page}: {name} parameter names/order differ from source: {parameters[name]}" + ) + if not parameters[name] and parameter_section.strip() != "None.": + raise ValueError(f"{page}: {name} must explicitly state no parameters") + for parameter, value in (defaults or {}).get(name, {}).items(): + declared_type = next(row[1] for row in rows if row[0] == parameter) + if not re.search( + r"\bdefault\s+" + re.escape(value) + r"(?:;|$)", declared_type + ): + raise ValueError( + f"{page}: {name}.{parameter} must document default {value}" + ) + compact = re.sub(r"\s+", "", section) + if re.sub(r"\s+", "", signatures[name]) not in compact: + raise ValueError(f"{page}: {name} signature differs from source") return len(names) +def python_signatures(): + """Validate documentation against AST without importing the native library.""" + parameters, signatures, defaults = {}, {}, {} + for module in ("hmath", "hmath_cffi"): + tree = ast.parse((ROOT / "bindings/python" / f"{module}.py").read_text()) + + def record(node, name): + args = node.args + parameters[name] = [ + arg.arg + for arg in args.posonlyargs + args.args + args.kwonlyargs + if arg.arg not in ("self", "cls") + ] + if args.vararg: + parameters[name].append(args.vararg.arg) + if args.kwarg: + parameters[name].append(args.kwarg.arg) + signature = ast.unparse(args) + signature = re.sub(r"^(?:self|cls)(?:,\s*)?", "", signature) + property_ = any( + isinstance(item, ast.Name) and item.id == "property" + for item in node.decorator_list + ) + signatures[name] = name if property_ else f"{name}({signature})" + positional = args.posonlyargs + args.args + defaults[name] = ( + { + arg.arg: ast.unparse(value) + for arg, value in zip( + positional[-len(args.defaults) :], args.defaults + ) + } + if args.defaults + else {} + ) + defaults[name].update( + { + arg.arg: ast.unparse(value) + for arg, value in zip(args.kwonlyargs, args.kw_defaults) + if value is not None + } + ) + + for node in tree.body: + if isinstance(node, ast.FunctionDef): + record(node, f"{module}.{node.name}") + elif isinstance(node, ast.ClassDef): + for member in node.body: + if isinstance(member, ast.FunctionDef): + name = f"{module}.{node.name}" + record( + member, + name + if member.name == "__init__" + else name + "." + member.name, + ) + return parameters, signatures, defaults + + def read_haddock(interface): result = subprocess.run( ["haddock", "--show-interface", str(interface)], @@ -65,7 +148,9 @@ def check_haskell(data): name for module in data["inst_ifaces"] for name in module["visible_exports"] - if name.startswith("$hmath-") and name.rsplit("$", 1)[1][0].islower() + if name.startswith("$hmath-") + and not name.rsplit("$", 1)[1][0].isupper() + and not name.rsplit("$", 1)[1].startswith(":") } documented = { name @@ -108,10 +193,14 @@ def check_reference(interface=None): python |= python_exports(ROOT / "bindings/python" / f"{module}.py", module) counts = { "Python": check_entries( - python, ROOT / "docs/reference/python.md", r"### `(hmath(?:_cffi)?\.[\w.]+)" + python, + ROOT / "docs/reference/python.md", + r"### `(hmath(?:_cffi)?\.[\w.]+)", + *python_signatures(), ) } c = set() + c_parameters, c_signatures = {}, {} for header in ("hmath.h", "hmath_dlpack.h"): c.update( re.findall( @@ -120,7 +209,31 @@ def check_reference(interface=None): re.MULTILINE, ) ) - counts["C"] = check_entries(c, ROOT / "docs/reference/c.md", r"### `(hmath_\w+)`") + for match in re.finditer( + r"^HMATH_API\s+([\w\s*]+?)\b(hmath_\w+)\s*\(([^;]*)\);", + (ROOT / "include" / header).read_text(), + re.MULTILINE, + ): + c_signatures[match[2]] = f"{match[1]}{match[2]}({match[3]})" + c_parameters[match[2]] = [ + re.search(r"(\w+)\s*$", arg)[1] + for arg in match[3].split(",") + if arg.strip() != "void" + ] + counts["C"] = check_entries( + c, + ROOT / "docs/reference/c.md", + r"### `(hmath_\w+)`", + c_parameters, + c_signatures, + ) + header = (ROOT / "include/hmath.h").read_text() + table = (ROOT / "docs/reference/c.md").read_text() + for status, value in re.findall( + r"\b(HMATH_(?:OK|[A-Z_]+))\s*=\s*(\d+)", header.split("} hmath_status;")[0] + ): + if f"| {value} | `{status}` |" not in table: + raise ValueError(f"C status table does not match {status} = {value}") wave = set( re.findall( r"^pub fun (\w+)\(", @@ -129,7 +242,25 @@ def check_reference(interface=None): ) ) counts["Wave"] = check_entries( - wave, ROOT / "docs/reference/wave.md", r"### `(\w+)\(" + wave, + ROOT / "docs/reference/wave.md", + r"### `(\w+)\(", + { + match[1]: re.findall(r"(\w+)\s*:", match[2]) + for match in re.finditer( + r"^pub fun (\w+)\((.*?)\)", + (ROOT / "bindings/wave/hmath.wave").read_text(), + re.MULTILINE | re.DOTALL, + ) + }, + { + match[1]: match[0].removeprefix("pub fun ").strip() + for match in re.finditer( + r"^pub fun (\w+)\([^)]*\)(?:\s*->\s*[^\s{]+)?", + (ROOT / "bindings/wave/hmath.wave").read_text(), + re.MULTILINE, + ) + }, ) if interface: functions, types = check_haskell(read_haddock(interface)) diff --git a/tools/haskell_reference.py b/tools/haskell_reference.py index 960c178..5539056 100644 --- a/tools/haskell_reference.py +++ b/tools/haskell_reference.py @@ -9,6 +9,91 @@ from pathlib import Path from urllib.parse import urlsplit +# Match the reader's numerical task before exposing the module hierarchy. +AREAS = { + "Foundations": ( + "HMath.API", + "HMath.Basic", + "HMath.Constants", + "HMath.Error", + "HMath.Numeric", + "HMath.Scalar", + "HMath.Trigonometry", + "HMath.Units", + ), + "Algebra and number theory": ( + "HMath.Algebra", + "HMath.Algorithms", + "HMath.Combinatorics", + "HMath.Complex", + "HMath.NumberTheory", + "HMath.Polynomial", + "HMath.Special", + ), + "Linear algebra": ( + "HMath.Backend", + "HMath.Linear", + "HMath.Matrix", + "HMath.Sparse", + "HMath.Vector", + ), + "Calculus and differential equations": ( + "HMath.Calculus", + "HMath.Interpolation", + "HMath.ODE", + "HMath.PDE", + "HMath.Root", + ), + "Optimization and differentiation": ("HMath.Autodiff", "HMath.Optimization"), + "Statistics and time series": ( + "HMath.Probability", + "HMath.Random", + "HMath.Statistics", + "HMath.TimeSeries", + ), + "Machine learning": ("HMath.DeepLearning", "HMath.MachineLearning"), + "Signals and transforms": ("HMath.Fourier", "HMath.Signal"), + "Geometry and images": ( + "HMath.Geometry", + "HMath.Image", + "HMath.Quaternion", + "HMath.Rotation", + ), + "Graphs and symbolic mathematics": ( + "HMath.DataStructure", + "HMath.Discrete", + "HMath.Expression", + "HMath.Graph", + "HMath.Symbolic", + ), + "Tensors and interchange": ("HMath.Interchange", "HMath.Tensor"), +} + + +def area_for(name): + if name == "HMath": + return "Foundations" + for area, prefixes in AREAS.items(): + if any(name == prefix or name.startswith(prefix + ".") for prefix in prefixes): + return area + raise ValueError(f"Assign the public module {name} to a documentation area") + + +def plain_summary(node): + """Use authored prose, preserving whole sentences rather than inventing text.""" + paragraph = next( + ( + item + for item in node.walk() + if item.tag == "p" and "caption" not in item.attrs.get("class", "").split() + ), + None, + ) + text = escape(" ".join((paragraph or node).text().split())) + # HTML table separators must not accidentally introduce another column. + return re.split(r"(?<=\.)\s+(?=[A-Z])", text, maxsplit=1)[0].replace("|", "|") + + VOID = { "area", "base", @@ -70,6 +155,46 @@ def handle_data(self, data): self.stack[-1].children.append(data) +def declaration_index(interface): + """Index nested constructors, class methods and fields as well as functions.""" + parents = { + id(child): node + for node in interface.walk() + for child in node.children + if isinstance(child, Node) + } + declarations, seen = [], set() + for node in interface.walk(): + target = node.attrs.get("id", "") + if ( + "def" not in node.attrs.get("class", "").split() + or not target.startswith(("v:", "t:")) + or target in seen + ): + continue + seen.add(target) + owner = parents.get(id(node)) + summary = "" + while owner and owner is not interface: + documentation = next( + ( + child + for child in owner.children + if isinstance(child, Node) + and "doc" in child.attrs.get("class", "").split() + ), + None, + ) + if documentation: + summary = plain_summary(documentation) + break + owner = parents.get(id(owner)) + declarations.append( + (node.text(), target, summary or "Declaration and type contract below.") + ) + return declarations + + def render(node, module_pages): if isinstance(node, str): return escape(node) @@ -145,6 +270,9 @@ def generate_reference(reference, docs, data, html_pages): destination = docs / "reference/haskell" destination.mkdir(parents=True, exist_ok=True) navigation = [{"Module index": "reference/haskell.md"}] + groups = {area: [] for area in AREAS} + catalogs = {area: [] for area in AREAS} + indexed = {} index = [ "\n## All public modules\n", "Select a module for its types, constructors, method signatures and function contracts.\n", @@ -171,6 +299,8 @@ def generate_reference(reference, docs, data, html_pages): ), None, ) + declarations = declaration_index(interface) + indexed[name] = {symbol for symbol, _, _ in declarations} # Give each declaration a real heading for page navigation and search. for node in list(interface.walk()): if "top" not in node.attrs.get("class", "").split(): @@ -201,14 +331,77 @@ def generate_reference(reference, docs, data, html_pages): 0, Node("h2", {"id": anchor}, [definition.text()]) ) content = f"# {name}\n\n" - content += '
\n' + content += "[Module index](../haskell.md) · [Numerical conventions](../../explanation/numerical-conventions.md)\n\n" + guides = { + "HMath.Matrix.QR": "[Detailed QR contracts](../qr.md) · [Least-squares guide](../../guides/least-squares.md)", + "HMath.Calculus": "[Differentiation and integration guide](../../guides/calculus.md)", + "HMath.Statistics": "[Descriptive statistics guide](../../guides/statistics.md)", + } + if name in guides: + content += guides[name] + "\n\n" if description: - content += render(description, pages) + "\n" + content += ( + '
\n' + + render(description, pages) + + "\n
\n\n" + ) + if declarations: + content += "## On this page\n\n| API | Purpose |\n| --- | --- |\n" + for symbol, target, summary in declarations: + label = escape(symbol).replace("|", "|") + content += f"| [{label}](#{target}) | {summary} |\n" + content += "\n" + content += '
\n' content += render(interface, pages) + "\n
\n" (destination / f"{basename}.md").write_text(content) path = f"reference/haskell/{basename}.md" - navigation.append({name: path}) - index.append(f"- [{name}](haskell/{basename}.md)\n") + area = area_for(name) + groups[area].append({name: path}) + purpose = ( + plain_summary(description) + if description + else "Public types and operations." + ) + catalogs[area].append((name, basename, purpose, declarations)) + for area, entries in groups.items(): + if not entries: + continue + navigation.append({area: entries}) + index.append(f"\n### {area}\n\n| Module | Purpose |\n| --- | --- |\n") + for name, basename, purpose, declarations in catalogs[area]: + index.append(f"| [{name}](haskell/{basename}.md) | {purpose} |\n") + slug = area.lower().replace(" ", "-") + catalog_path = destination / f"area-{slug}.md" + catalog = f"# {area}: API index\n\nFind an operation by its purpose. Entries link to the complete signature and contract.\n\n" + for name, basename, _, declarations in catalogs[area]: + catalog += f"## {name}\n\n| API | Purpose |\n| --- | --- |\n" + for symbol, target, summary in declarations: + label = escape(symbol).replace("|", "|") + catalog += f"| [{label}]({basename}.md#{target}) | {summary} |\n" + catalog += "\n" + catalog_path.write_text(catalog) + entries.insert(0, {"API by purpose": f"reference/haskell/{catalog_path.name}"}) + index.append( + f"\n[Browse functions in this area](haskell/{catalog_path.name}).\n" + ) with (docs / "reference/haskell.md").open("a") as output: output.write("\n".join(index)) + exports = { + symbol + for module in modules + for symbol in module["visible_exports"] + if symbol.startswith("$hmath-") + } + missing = [ + symbol + for symbol in exports + if symbol.split("$")[-1] not in indexed.get(symbol.split("$")[-2], set()) + ] + if missing: + raise ValueError( + f"Public symbols absent from generated API indexes: {sorted(missing)}" + ) + print( + f"Haskell navigation: {len(modules)} modules, {len(exports)} exported symbols indexed" + ) return navigation From 857ee1b4899a1fa8054941d793e21deae708981e Mon Sep 17 00:00:00 2001 From: LunaStev Date: Thu, 29 Oct 2026 00:42:11 +0900 Subject: [PATCH 2/3] docs: clarify that last-error query reports the preceding operation --- docs/reference/c.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/reference/c.md b/docs/reference/c.md index 79c494b..d258564 100644 --- a/docs/reference/c.md +++ b/docs/reference/c.md @@ -193,9 +193,7 @@ None. #### Returns -Returns `HMATH_OK` on success or a nonzero `hmath_status` on failure. - -On success: the current thread's last checked status. +The current thread's last checked status. This query does not execute a checked operation. Queries, retain/release, and cancellation do not clear it. Successful checked calls replace it with `HMATH_OK`. It does not return another thread's failure. From 677d3998fc61c6cae01cc1e261f15584e1905c17 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Thu, 29 Oct 2026 00:46:56 +0900 Subject: [PATCH 3/3] docs: render module index rows as tables and validate their structure --- tools/haskell_reference.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/haskell_reference.py b/tools/haskell_reference.py index 5539056..ba19b26 100644 --- a/tools/haskell_reference.py +++ b/tools/haskell_reference.py @@ -9,6 +9,8 @@ from pathlib import Path from urllib.parse import urlsplit +import markdown + # Match the reader's numerical task before exposing the module hierarchy. AREAS = { "Foundations": ( @@ -384,8 +386,13 @@ def generate_reference(reference, docs, data, html_pages): index.append( f"\n[Browse functions in this area](haskell/{catalog_path.name}).\n" ) + index_content = "".join(index) + preview = Document(markdown.markdown(index_content, extensions=["tables"])) + table_cells = {node.text() for node in preview.root.walk() if node.tag == "td"} + if indexed.keys() - table_cells: + raise ValueError("Haskell module index rows must render inside tables") with (docs / "reference/haskell.md").open("a") as output: - output.write("\n".join(index)) + output.write(index_content) exports = { symbol for module in modules