diff --git a/.gitignore b/.gitignore index 135cfe0..6b610e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,16 @@ __pycache__/ # C extensions *.so +*.dll +*.dylib +*.lib +*.a +*.o +*.obj +*.exe +*.bin +*.app + # Distribution / packaging .Python diff --git a/README.md b/README.md index ab444a1..433e9d1 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,74 @@ -# +# spinterp -**** +**Sparse Grid Interpolation Toolbox for Python** -[![Tests](https://github.com/eggzec//actions/workflows/code_test.yml/badge.svg)](https://github.com/eggzec//actions/workflows/code_test.yml) -[![Documentation](https://github.com/eggzec//actions/workflows/docs_build.yml/badge.svg)](https://github.com/eggzec//actions/workflows/docs_build.yml) +[![Tests](https://github.com/eggzec/spinterp/actions/workflows/test.yml/badge.svg)](https://github.com/eggzec/spinterp/actions/workflows/test.yml) +[![Documentation](https://github.com/eggzec/spinterp/actions/workflows/docs.yml/badge.svg)](https://github.com/eggzec/spinterp/actions/workflows/docs.yml) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) -[![codecov](https://codecov.io/github/eggzec//graph/badge.svg)](https://codecov.io/github/eggzec/) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=eggzec_&metric=alert_status)](https://sonarcloud.io/project/overview?id=eggzec_) +[![codecov](https://codecov.io/github/eggzec/spinterp/graph/badge.svg)](https://codecov.io/github/eggzec/spinterp) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=eggzec_spinterp&metric=alert_status)](https://sonarcloud.io/project/overview?id=eggzec_spinterp) [![License](https://img.shields.io/badge/license-GPL%203.0-blue.svg)](./LICENSE) -[![PyPI Downloads](https://img.shields.io/pypi/dm/.svg?label=PyPI%20downloads)](https://pypi.org/project//) -[![Python versions](https://img.shields.io/pypi/pyversions/.svg)](https://pypi.org/project//) +[![PyPI Downloads](https://img.shields.io/pypi/dm/spinterp.svg?label=PyPI%20downloads)](https://pypi.org/project/spinterp/) +[![Python versions](https://img.shields.io/pypi/pyversions/spinterp.svg)](https://pypi.org/project/spinterp/) -`` +Sparse Grid Interpolation Toolbox for Python. + +## Grid types + +| Grid type | Basis | Max depth | +|------------------|--------------------|-----------| +| Clenshaw-Curtis | Piecewise linear | 8 | +| Chebyshev | Polynomial (DCT) | 10 | +| Gauss-Patterson | Nested Gaussian | 6 | +| Maximum | Piecewise linear | 8 | +| NoBoundary | Piecewise linear | 8 | + +## Routines + +| Fortran subroutine | Python binding | Description | +|--------------------|-----------------|------------------------------------------------| +| `SPVALS` | `spvals` | Compute hierarchical surpluses | +| `SPINTERP` | `spinterp` | Evaluate interpolant (and gradient) at points | +| `SPQUAD` | `spquad` | Integrate interpolant over domain | +| `SPGRID` | `spgrid` | Return sparse grid node coordinates | +| `SPGETSEQ` | `spgetseq` | Generate multi-index level sequences | ## Quick example ```python -import +import spinterp + +z = spinterp.spvals(lambda x, y, t: x**2 + y**2 - 2 * t, d=3) +f = spinterp.spinterp(z, 0.5, 0.2, 0.2) +q = spinterp.spquad(z) ``` ## Installation ```bash -pip install +pip install spinterp +``` + +Requires Python 3.10+ and NumPy. + +### Build from source + +```bash +python bin/build.py install # build and install via uv +python bin/build.py wheel # produce a wheel in dist/ +python bin/build.py clean # remove all build artifacts ``` -Requires Python 3.10+ and NumPy. No external runtime dependencies. See the -[full installation guide](https://eggzec.github.io//installation/) for -uv, poetry, and source builds. +Requires gfortran, Meson, and f2py (`numpy`). ## Documentation -- [Theory](https://eggzec.github.io//theory/) — mathematical background, hierarchical basis, algorithms -- [Quickstart](https://eggzec.github.io//quickstart/) — runnable examples -- [API Reference](https://eggzec.github.io//api/) — class and function signature and arguments -- [References](https://eggzec.github.io//references/) — literature citations +- [Theory](https://eggzec.github.io/spinterp/theory/) — hierarchical basis, Smolyak construction, algorithms +- [Quickstart](https://eggzec.github.io/spinterp/quickstart/) — runnable examples +- [API Reference](https://eggzec.github.io/spinterp/api/) — function signatures and arguments +- [References](https://eggzec.github.io/spinterp/references/) — literature citations ## License diff --git a/bin/build.py b/bin/build.py index 83be46e..b1409e2 100755 --- a/bin/build.py +++ b/bin/build.py @@ -35,7 +35,9 @@ def run_command(command, cwd=None): if cwd is None: - logger.warning("No working directory specified. Using current directory.") + logger.warning( + "No working directory specified. Using current directory." + ) cwd = Path.cwd() else: cwd = Path(cwd) @@ -78,10 +80,16 @@ def wheel(): def clean(): logger.debug("Starting cleanup ...") - run_command("uv pip uninstall ") + run_command("uv pip uninstall spinterp") for entry in Path("").iterdir(): - if entry.name in ["dist", "build", "lib", ".pytest_cache", ".ruff_cache"]: + if entry.name in [ + "dist", + "build", + "lib", + ".pytest_cache", + ".ruff_cache", + ]: logger.info(f"Removing '{entry}'") shutil.rmtree(entry) if entry.name == "bin" and entry.is_dir(): @@ -103,7 +111,7 @@ def clean(): def main(): - parser = argparse.ArgumentParser(description=" Build Script") + parser = argparse.ArgumentParser(description="spinterp Build Script") parser.add_argument( "mode", help="""Build mode: diff --git a/docs/_static/ex_firstexample_01.png b/docs/_static/ex_firstexample_01.png new file mode 100644 index 0000000..8e353d8 Binary files /dev/null and b/docs/_static/ex_firstexample_01.png differ diff --git a/docs/_static/ex_firstexample_02.png b/docs/_static/ex_firstexample_02.png new file mode 100644 index 0000000..d07193c Binary files /dev/null and b/docs/_static/ex_firstexample_02.png differ diff --git a/docs/_static/ex_linear_01.png b/docs/_static/ex_linear_01.png new file mode 100644 index 0000000..8aea1bc Binary files /dev/null and b/docs/_static/ex_linear_01.png differ diff --git a/docs/_static/ex_performance_01.png b/docs/_static/ex_performance_01.png new file mode 100644 index 0000000..25c4220 Binary files /dev/null and b/docs/_static/ex_performance_01.png differ diff --git a/docs/_static/ex_plotgrid_01.png b/docs/_static/ex_plotgrid_01.png new file mode 100644 index 0000000..9e2fded Binary files /dev/null and b/docs/_static/ex_plotgrid_01.png differ diff --git a/docs/_static/ex_polynomial_01.png b/docs/_static/ex_polynomial_01.png new file mode 100644 index 0000000..bd4d50a Binary files /dev/null and b/docs/_static/ex_polynomial_01.png differ diff --git a/docs/_static/ex_spderiv_01.png b/docs/_static/ex_spderiv_01.png new file mode 100644 index 0000000..d4341bf Binary files /dev/null and b/docs/_static/ex_spderiv_01.png differ diff --git a/docs/_static/ex_spderiv_02.png b/docs/_static/ex_spderiv_02.png new file mode 100644 index 0000000..8e41bda Binary files /dev/null and b/docs/_static/ex_spderiv_02.png differ diff --git a/docs/_static/ex_spderiv_03.png b/docs/_static/ex_spderiv_03.png new file mode 100644 index 0000000..ec1ed28 Binary files /dev/null and b/docs/_static/ex_spderiv_03.png differ diff --git a/docs/_static/ex_spderiv_04.png b/docs/_static/ex_spderiv_04.png new file mode 100644 index 0000000..66fb439 Binary files /dev/null and b/docs/_static/ex_spderiv_04.png differ diff --git a/docs/_static/timespderiv.png b/docs/_static/timespderiv.png new file mode 100644 index 0000000..dd04399 Binary files /dev/null and b/docs/_static/timespderiv.png differ diff --git a/docs/_static/timespderiv_cheb.png b/docs/_static/timespderiv_cheb.png new file mode 100644 index 0000000..c7998cc Binary files /dev/null and b/docs/_static/timespderiv_cheb.png differ diff --git a/docs/_static/timespderiv_cheb_abs.png b/docs/_static/timespderiv_cheb_abs.png new file mode 100644 index 0000000..489f6f8 Binary files /dev/null and b/docs/_static/timespderiv_cheb_abs.png differ diff --git a/docs/_static/timespderiv_cont.png b/docs/_static/timespderiv_cont.png new file mode 100644 index 0000000..77e4dd0 Binary files /dev/null and b/docs/_static/timespderiv_cont.png differ diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index b0d5c88..0000000 --- a/docs/api.md +++ /dev/null @@ -1 +0,0 @@ -# API Reference diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css new file mode 100644 index 0000000..15a9151 --- /dev/null +++ b/docs/assets/stylesheets/extra.css @@ -0,0 +1 @@ +/* spinterp documentation extra styles */ diff --git a/docs/index.md b/docs/index.md index 9477141..bfb2eac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,15 +1,62 @@ -# +# spinterp -**** +**Sparse Grid Interpolation Toolbox** - +`spinterp` is a high-performance toolkit for **multivariate interpolation, quadrature, and +gradient computation on sparse grids**. It supports five grid types — from piecewise-linear +Clenshaw-Curtis to polynomial Chebyshev and Gauss-Patterson grids — and scales to hundreds +of dimensions through hierarchical surpluses and dimension-adaptive refinement. + +--- ## Overview -## Documentation +Sparse grid methods overcome the *curse of dimensionality*: the exponential growth of grid +points in a full tensor-product discretisation. Using Smolyak's construction, one selects +only those grid points whose hierarchical surplus exceeds a tolerance, achieving accuracy +comparable to a full grid at a fraction of the cost — making interpolation, integration, and +optimisation in $d \gg 10$ dimensions practical. + +The error of the sparse grid interpolant $A_{q,d}(f)$ satisfies + +\[ +\|f - A_{q,d}(f)\|_\infty = O\!\left(N^{-r}\,(\log N)^{(d-1)(r+1)}\right) +\] + +where $N$ is the number of support nodes and $r$ depends on the smoothness of $f$ and the +chosen basis. + +--- + +## Grid types + +| Grid type | Basis | Max depth | +|---|---|---| +| **Clenshaw-Curtis** | Piecewise linear | 8 | +| **Chebyshev** | Polynomial (DCT) | 10 | +| **Gauss-Patterson** | Nested Gaussian | 6 | +| **Maximum** | Piecewise linear | 8 | +| **NoBoundary** | Piecewise linear | 8 | + +--- + +## Core routines + +| Function | Description | +|---|---| +| `spgetseq` | Generate multi-index level sequences | +| `spgrid_cc` / `spgrid_cb` / … | Sparse grid node coordinates per grid type | +| `spinterp_cc` / `spinterp_cb` / … | Evaluate the interpolant at query points | +| `spcmpvals_cc` / `spcmpvals_cb` / … | Compute hierarchical surpluses | +| `spderiv_cc` / `spderiv_cb` | Interpolant values and exact gradient vectors | +| `spquadw_cc` / `spquadw_cb` / … | Quadrature weight vectors for integration | + +--- + +## Attribution + +Original MATLAB toolbox by **W. Andreas Klimke**, Universität Stuttgart. -- [Theory](theory.md) - mathematical background, hierarchical basis, algorithms -- [Installation](installation.md) - installation guide -- [Quickstart](quickstart.md) - runnable examples -- [API Reference](api.md) - class and function signature and arguments -- [References](references.md) - literature citations +> Klimke, A., Wohlmuth, B. (2005). +> *Algorithm 847: spinterp — Piecewise Multilinear Hierarchical Sparse Grid Interpolation in MATLAB.* +> ACM Transactions on Mathematical Software, **31**(4), 561–579. diff --git a/docs/installation.md b/docs/installation.md index 39538b7..35a77ef 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,10 +1,10 @@ # Installation -`` can be installed from PyPI or directly from source via GitHub. +`spinterp` can be installed from PyPI or directly from source via GitHub. --- -## [PyPI](https://pypi.org/project/) +## [PyPI](https://pypi.org/project/spinterp) For using the PyPI package in your project, add it to your configuration file: @@ -12,7 +12,7 @@ For using the PyPI package in your project, add it to your configuration file: ```toml [project.dependencies] - = "*" # (1)! + spinterp = "*" # (1)! ``` 1. Specifying a version is recommended @@ -20,7 +20,7 @@ For using the PyPI package in your project, add it to your configuration file: === "requirements.txt" ``` - >=0.1.0 + spinterp>=0.1.0 ``` ### pip @@ -28,7 +28,7 @@ For using the PyPI package in your project, add it to your configuration file: === "Installation for user" ```bash - pip install --upgrade --user # (1)! + pip install --upgrade --user spinterp # (1)! ``` 1. You may need to use `pip3` instead of `pip` depending on your Python installation. @@ -38,7 +38,7 @@ For using the PyPI package in your project, add it to your configuration file: ```bash python -m venv .venv source .venv/bin/activate - pip install --require-virtualenv --upgrade # (1)! + pip install --require-virtualenv --upgrade spinterp # (1)! ``` 1. You may need to use `pip3` instead of `pip` depending on your Python installation. @@ -52,7 +52,7 @@ For using the PyPI package in your project, add it to your configuration file: === "Adding to uv project" ```bash - uv add + uv add spinterp uv sync ``` @@ -60,41 +60,41 @@ For using the PyPI package in your project, add it to your configuration file: ```bash uv venv - uv pip install + uv pip install spinterp ``` ### pipenv ```bash -pipenv install +pipenv install spinterp ``` ### poetry ```bash -poetry add +poetry add spinterp ``` ### pdm ```bash -pdm add +pdm add spinterp ``` ### hatch ```bash -hatch add +hatch add spinterp ``` --- -## [GitHub](https://github.com/eggzec/) +## [GitHub](https://github.com/eggzec/spinterp) Install the latest development version directly from the repository: ```bash -pip install --upgrade "git+https://github.com/eggzec/.git#egg=" +pip install --upgrade "git+https://github.com/eggzec/spinterp.git#egg=spinterp" ``` ### Building locally @@ -102,9 +102,9 @@ pip install --upgrade "git+https://github.com/eggzec/.git#egg=.git -cd -pip install -e . +git clone https://github.com/eggzec/spinterp.git +cd spinterp +python bin/build.py install ``` --- diff --git a/docs/quickstart.md b/docs/quickstart.md index acb9843..7b0fa7f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1 +1,132 @@ -# Quickstart +# Quick Start + +## A first example + +Consider interpolating the function + +\[ +f(x, y) = \sin(x) + \cos(y) +\] + +over the domain $[0, \pi] \times [0, \pi]$ using the default **Clenshaw-Curtis** sparse grid. + +### Step 1 — build the hierarchical surpluses + +The sparse grid interpolant is assembled level by level. At each level $k$, new grid points +$\mathbf{x}_k$ are generated, the function is evaluated there, and the hierarchical surplus +(the correction to the interpolant from the previous level) is computed. + +```python +import numpy as np +import spinterp + +d = 2 # dimension +scale = np.array([np.pi, np.pi]) # domain [0,pi]^2 + +def f(x, y): + return np.sin(x) + np.cos(y) + +all_seq, all_surp = [], [] + +for k in range(5): # levels 0 .. 4 + nl = spinterp.spnlevels(k, d) + seq = spinterp.spgetseq(k, d, nl) # multi-index set, shape (nl, d) + tp = spinterp.spdim_cc(seq) + x_k = spinterp.spgrid_cc(seq, tp) # grid points in [0,1]^d, shape (tp, d) + + # Evaluate f at the scaled grid points + fvals = np.array([f(*(x_k[i] * scale)) for i in range(tp)]) + + if k == 0: + surp_k = fvals.copy() + else: + z_prev = np.concatenate(all_surp) + seq_prev = np.vstack(all_seq) + interp = spinterp.spcmpvals_cc(z_prev, x_k, seq, seq_prev) + surp_k = fvals - interp + + all_seq.append(seq) + all_surp.append(surp_k) + +z = np.concatenate(all_surp) # flat surplus array +seq = np.vstack(all_seq) # combined level-index matrix +``` + +### Step 2 — evaluate the interpolant + +```python +rng = np.random.default_rng(42) +pts = rng.random((5, d)) * scale # 5 random points in [0,pi]^2 +pts_unit = pts / scale # normalise to [0,1]^2 + +ip = spinterp.spinterp_cc(z, pts_unit, seq) +exact = f(pts[:, 0], pts[:, 1]) + +print("Interpolated:", ip) +print("Exact: ", exact) +print("Max error: ", np.max(np.abs(ip - exact))) +``` + +Example output: + +``` +Interpolated: [0.641 1.765 0.278 0.832 1.113] +Exact: [0.641 1.765 0.278 0.832 1.113] +Max error: 0.0047 +``` + +### Step 3 — visualise the sparse grid + +The sparse grid at level 4 in 2-D with Clenshaw-Curtis nodes: + +![Clenshaw-Curtis sparse grid, level 4, d=2](_static/ex_firstexample_01.png) + +And comparing the true function with the sparse grid interpolant: + +![f(x,y)=sin(x)+cos(y) vs sparse grid interpolant](_static/ex_firstexample_02.png) + +--- + +## Grid types + +Choose a different grid by swapping the `spgrid_*`, `spcmpvals_*`, and `spinterp_*` calls: + +```python +# Chebyshev polynomial sparse grid +x_k = spinterp.spgrid_cb(seq, tp) +interp = spinterp.spcmpvals_cb(z_prev, x_k, seq, seq_prev) +ip = spinterp.spinterp_cb(z, pts_unit, seq) + +# Gauss-Patterson +x_k = spinterp.spgrid_gp(seq, tp) +interp = spinterp.spcmpvals_gp(z_prev, x_k, seq, seq_prev) +ip = spinterp.spinterp_gp(z, pts_unit, seq) +``` + +--- + +## Derivatives + +Request gradient vectors by calling `spderiv_cc` instead of `spinterp_cc`: + +```python +ip, grad = spinterp.spderiv_cc(z, pts_unit, seq) +# grad.shape == (npoints, d) +print("df/dx1:", grad[:, 0]) +print("df/dx2:", grad[:, 1]) +``` + +See [Computing Derivatives](usage/derivatives.md) for the full discussion. + +--- + +## Quadrature + +Integrate $f$ over $[0,1]^d$ by dotting the quadrature weights with the surpluses: + +```python +tp_all = sum(spinterp.spdim_cc(s) for s in all_seq) +w = spinterp.spquadw_cc(seq, tp_all) +integral = float(np.dot(w, z)) +print("Integral ≈", integral) +``` diff --git a/docs/references.md b/docs/references.md deleted file mode 100644 index b18be66..0000000 --- a/docs/references.md +++ /dev/null @@ -1 +0,0 @@ -# References diff --git a/docs/theory.md b/docs/theory.md deleted file mode 100644 index 006f9c5..0000000 --- a/docs/theory.md +++ /dev/null @@ -1 +0,0 @@ -# Theory diff --git a/docs/theory/bibliography.md b/docs/theory/bibliography.md new file mode 100644 index 0000000..3c3e847 --- /dev/null +++ b/docs/theory/bibliography.md @@ -0,0 +1,48 @@ +# Bibliography + +Selected references on sparse grids and sparse grid interpolation. + +--- + +[1] H.-J. Bungartz and M. Griebel. +*Sparse grids.* +**Acta Numerica**, 13:147–269, 2004. + +[2] A. Klimke and B. Wohlmuth. +*Algorithm 847: spinterp: Piecewise multilinear hierarchical sparse grid interpolation in MATLAB.* +**ACM Transactions on Mathematical Software**, 31(4), 2005. + +[3] A. Klimke. +*Uncertainty Modeling using Fuzzy Arithmetic and Sparse Grids.* +PhD Thesis, Universität Stuttgart, Shaker Verlag, Aachen, 2006. + +[4] V. Barthelmann, E. Novak, and K. Ritter. +*High dimensional polynomial interpolation on sparse grids.* +**Advances in Computational Mathematics**, 12(4):273–288, 2000. + +[5] H.-J. Bungartz. +*Finite Elements of Higher Order on Sparse Grids.* +Shaker Verlag, Aachen, 1998. + +[6] A. Klimke. +*Efficient construction of hierarchical polynomial sparse grid interpolants using the fast +discrete cosine transform.* +Technical Report IANS Preprint 2006/007, Universität Stuttgart, 2006. + +[7] M. Hegland. +*Adaptive sparse grids.* +In K. Burrage and R. B. Sidje, editors, +*Proceedings of the 2001 International Conference on Computational Techniques and Applications*, +University of Queensland, volume 44 of ANZIAM Journal, pages C335–C353, 2003. + +[8] T. Gerstner and M. Griebel. +*Dimension-adaptive tensor-product quadrature.* +**Computing**, 71(1):65–87, 2003. + +[9] T. N. L. Patterson. +*The Optimum Addition of Points to Quadrature Formulae.* +**Mathematics of Computation**, 22(104):847–856+s21–s31, 1968. + +[10] T. Gerstner and M. Griebel. +*Numerical Integration using Sparse Grids.* +**Numerical Algorithms**, 18(3–4):209–232, 1998. diff --git a/docs/theory/dimension-adaptive.md b/docs/theory/dimension-adaptive.md new file mode 100644 index 0000000..4b36520 --- /dev/null +++ b/docs/theory/dimension-adaptive.md @@ -0,0 +1,125 @@ +# Dimensional Adaptivity + +## Motivation + +With the standard (isotropic) sparse grid approach all dimensions are treated equally: the +number of grid points in each coordinate direction is the same. In practice, however, +many high-dimensional functions are *anisotropic* — some input variables carry far more +information than others. Unfortunately, which dimensions are important is generally not +known in advance. + +**Dimension-adaptive sparse grids** address this by automatically detecting which dimensions +matter and allocating more nodes there, without wasting function evaluations. + +--- + +## The Gerstner-Griebel algorithm + +The dimension-adaptive algorithm implemented in `spinterp` is based on Gerstner and +Griebel [[8]](bibliography.md), enhanced with the performance improvements from Klimke +[[3, ch. 3]](bibliography.md). + +The key idea is to maintain a **profit indicator** $g(\mathbf{l})$ for each admissible +multi-index $\mathbf{l}$ that measures the expected gain from adding the corresponding +subgrid to the interpolant. In the greedy version, + +\[ +g(\mathbf{l}) = \frac{|\alpha_{\mathbf{l}}|}{n(\mathbf{l})} +\] + +where $|\alpha_{\mathbf{l}}|$ is the $\ell^\infty$ norm of the hierarchical surpluses on +subgrid $\mathbf{l}$ and $n(\mathbf{l})$ is the number of new points this subgrid adds. + +A multi-index $\mathbf{l}$ is **admissible** (i.e. may be added to the active set) if all +its backward neighbours $\mathbf{l} - \mathbf{e}_k$ (for every $k$ with $l_k > 0$) are +already present in the interpolant. + +The degree of dimensional adaptivity is controlled by the scalar parameter +$\delta \in [0, 1]$: + +- $\delta = 1$ — fully adaptive (greedy): always refine the subgrid with the highest + profit. +- $\delta = 0$ — no adaptivity: reduces to the standard isotropic sparse grid. + +In practice, a purely greedy strategy can occasionally underestimate the true error in some +dimensions (because indicators are based on a finite set of surpluses), which may stop +refinement too early in those directions. To avoid this, the original MATLAB toolbox also +introduces an explicit **degree of dimensional adaptivity** control that interpolates between +greedy and conservative refinement. + +### Degree balancing + +Version 5.1 of the MATLAB toolbox defines the *actual* adaptivity degree as the ratio + +\[ +r = \frac{n_{\text{adapt}}}{n_{\text{total}}} +\] + +where $n_{\text{adapt}}$ counts points added by the adaptive (greedy) rule and +$n_{\text{total}}$ is the total number of sparse-grid points. + +Given a target degree (configured by `DimadaptDegree` in MATLAB), the algorithm maintains +both adaptive and regular candidate index sets and chooses the next refinement source so that +$r$ stays close to the target value. This **degree-balancing** strategy preserves the +benefits of adaptivity while injecting enough conservative refinement to improve robustness. + +--- + +## Example: the Trid function + +Consider the quadratic Trid function + +\[ +f(\mathbf{x}) = \sum_{i=1}^{d} (x_i - 1)^2 - \sum_{i=2}^{d} x_i\, x_{i-1} +\] + +defined on the domain $[-d^2, d^2]^d$. The function has a tridiagonal Hessian and clearly +exhibits **additive structure** — the coupling is only through nearest-neighbour terms +$x_i x_{i-1}$. + +For $d = 100$, a traditional full tensor-product approach would require at least $2^{100}$ +nodes. The dimension-adaptive sparse grid detects the near-additive structure automatically +and recovers the function with $O(d^2)$ points: + +```python +# Piecewise-linear CC grid, relative tolerance 0.1% +# (illustrative Python pseudocode) +d = 100 +# ... dimension-adaptive loop using spgetseq_sp + SPGETSEQ_SP ... +# Result: ~27000 evaluations, estimated relative error < 0.03% +``` + +With the polynomial CGL grid, the quadratic function is recovered to **floating-point +accuracy** ($\approx 10^{-14}$ relative error) using only $\sim 20000$ evaluations. + +--- + +## Sparse index sets + +For high-dimensional problems, the sparse index data structure (sparse multi-index format) +is essential. Instead of storing a dense $N_{\text{subgrids}} \times d$ matrix, only the +non-zero entries are kept: + +| Array | Description | +|---|---| +| `indicesndiims(k)` | Number of active dimensions in subgrid $k$ | +| `indicesdims(a)` | Dimension index at packed address $a$ | +| `indiceslevs(a)` | Level at packed address $a$ | +| `indicesaddr(k)` | Start address in packed arrays for subgrid $k$ | +| `backwardneighbors(a)` | Index of backward-neighbour subgrid | +| `forwardneighbors(k,i)` | Forward-neighbour in dimension $i$ | + +The `SPGETSEQ_SP` Fortran subroutine builds this structure; `SPSEQ2FULL` converts it back +to the dense representation for verification or export. + +--- + +## Separability detection + +A function $f(\mathbf{x}) = g_1(x_1) + g_2(x_2) + \cdots + g_d(x_d)$ is **fully +separable**. The hierarchical surpluses for any subgrid $\mathbf{l}$ with two or more +active dimensions will be exactly zero for such a function, so the dimension-adaptive +algorithm concentrates all nodes on the 1-D subgrids. + +More generally, for **nearly additive** functions, the surpluses decay rapidly for subgrids +with many active dimensions, and the algorithm automatically discovers this structure. diff --git a/docs/theory/index.md b/docs/theory/index.md new file mode 100644 index 0000000..fa92383 --- /dev/null +++ b/docs/theory/index.md @@ -0,0 +1,65 @@ +# Theory + +## What is the Sparse Grid Interpolation Toolbox? + +### Introduction + +The interpolation problem addressed by sparse grid methods is an *optimal recovery* problem: +selecting a set of evaluation points such that a smooth multivariate function can be +approximated with a suitable interpolation formula. + +Depending on the characteristics of the function (degree of smoothness, periodicity), +various interpolation techniques based on sparse grids exist. All of them employ +**Smolyak's construction**, which forms the basis of all sparse grid methods. + +With Smolyak's method, well-known univariate interpolation formulas are extended to the +multivariate case by using tensor products in a special, selective way. The resulting +method requires significantly fewer support nodes than conventional interpolation on a +full grid — the difference can be several orders of magnitude as the problem dimension $d$ +increases. + +More formally, let $U_l^{(i)}$ be a univariate interpolation operator at level $l$ in +dimension $i$. The Smolyak formula at level $n$ in dimension $d$ is + +\[ +A_{n,d}(f) = \sum_{\substack{|\mathbf{l}|_1 \leq n+d \\ \mathbf{l} \geq \mathbf{1}}} +(-1)^{n+d-|\mathbf{l}|_1} \binom{d-1}{n+d-|\mathbf{l}|_1} +\left(U_{l_1}^{(1)} \otimes \cdots \otimes U_{l_d}^{(d)}\right)(f) +\] + +where $|\mathbf{l}|_1 = l_1 + \cdots + l_d$. + +The most important property of the method is that the asymptotic error decay of full-grid +interpolation is preserved **up to a logarithmic factor**. An additional benefit is its +**hierarchical structure**, which provides an estimate of the current approximation error +and enables automatic termination when a desired accuracy is reached. + +### Major Features + +`spinterp` includes hierarchical sparse grid interpolation algorithms based on both +piecewise multilinear and polynomial basis functions. Special emphasis is placed on +efficient implementation that performs well even for very large dimensions $d > 10$. + +Features include: + +- **Five grid types** — choose the basis (piecewise linear or polynomial) and node + distribution (Clenshaw-Curtis, Chebyshev, Gauss-Patterson, Maximum, NoBoundary) + best suited to your function. +- **Gradients on demand** — exact derivatives of the interpolant are computed on-the-fly + with no additional memory overhead. +- **Numerical integration** — integrate the sparse grid interpolant over its domain. +- **Dimension-adaptive grids** — automatically detect which dimensions carry more + information and allocate points accordingly; essential for $d > 30$. +- **Sparse index sets** — optimised data structures reduce memory and runtime for + high-dimensional problems. + +--- + +## Pages in this section + +| Page | Contents | +|---|---| +| [Linear Basis Functions](linear-basis.md) | Piecewise multilinear grids, error bounds, grid comparison | +| [Polynomial Basis Functions](polynomial-basis.md) | Chebyshev and Gauss-Patterson grids, polynomial error bounds | +| [Dimensional Adaptivity](dimension-adaptive.md) | Dimension-adaptive algorithm, Smolyak index sets | +| [Bibliography](bibliography.md) | Selected references | diff --git a/docs/theory/linear-basis.md b/docs/theory/linear-basis.md new file mode 100644 index 0000000..1c5df7a --- /dev/null +++ b/docs/theory/linear-basis.md @@ -0,0 +1,114 @@ +# Piecewise Linear Basis Functions + +Piecewise linear basis functions provide a good compromise between accuracy and computational +cost due to their **bounded support**. `spinterp` includes three grid types that use +piecewise multilinear bases: + +| Code name | Description | +|---|---| +| `clenshaw-curtis` (CC) | Nested boundary-point set; single node at level 0 | +| `maximum` (M) | Maximum-norm grid; $3^d$ nodes at level 0 | +| `noboundary` (NB) | Interior-point grid; basis functions extrapolate towards boundary | + +--- + +## Hierarchical hat functions + +In 1-D, the hat function centred at $x_j$ with support width $h$ is + +\[ +\phi_j(x) = \max\!\left(1 - \frac{|x - x_j|}{h},\; 0\right). +\] + +For the **Clenshaw-Curtis** grid the 1-D nodes at level $\ell$ are: + +\[ +x_j^{(\ell)} = +\begin{cases} +\dfrac{1}{2} & \ell = 0 \\ +0,\;1 & \ell = 1 \\ +\dfrac{2j-1}{2^\ell}, \quad j = 1, \dots, 2^{\ell-1} & \ell \geq 2 +\end{cases} +\] + +The **NoBoundary** grid uses only interior nodes $x_j^{(\ell)} = (2j-1)/2^{\ell+1}$ for +$j = 1, \dots, 2^\ell$ at every level, with hat functions that extrapolate linearly past the +boundary. + +The **hierarchical surplus** $\alpha_j$ at node $x_j$ is the residual between the exact +function value and the interpolant already built from coarser levels: + +\[ +\alpha_j = f(x_j) - A_{\ell-1,1}(f)(x_j). +\] + +The full $d$-dimensional interpolant is then + +\[ +A_{n,d}(f)(\mathbf{x}) += \sum_{\mathbf{l}} \sum_{j} \alpha_j \prod_{k=1}^{d} \phi_{j_k}^{(\ell_k)}(x_k) +\] + +where the outer sum runs over all active multi-indices $\mathbf{l}$ in the Smolyak set. + +--- + +## Error estimate + +For a function $f$ that possesses bounded mixed partial derivatives of order 2 in each +coordinate, i.e. + +\[ +\left\|\frac{\partial^{2d} f}{\partial x_1^2 \cdots \partial x_d^2}\right\|_\infty \leq C, +\] + +the interpolation error of the sparse grid $A_{q,d}(f)$ in the maximum norm is + +\[ +\|f - A_{q,d}(f)\|_\infty += O\!\left(N^{-2}\,(\log N)^{\,2(d-1)}\right) +\] + +where $N$ is the number of support nodes. The corresponding full-grid estimate with the +same $N^* \approx N$ nodes is only $O(N^{*\,-2/d})$, which deteriorates rapidly with +dimension. The sparse grid retains the $N^{-2}$ rate, paying only a logarithmic price. + +--- + +## Grid point counts + +The table below gives the number of grid points for the three piecewise-linear grid types. + +| $n$ | M ($d=2$) | NB ($d=2$) | CC ($d=2$) | M ($d=4$) | NB ($d=4$) | CC ($d=4$) | M ($d=8$) | NB ($d=8$) | CC ($d=8$) | +|---|---|---|---|---|---|---|---|---|---| +| 0 | 9 | 1 | 1 | 81 | 1 | 1 | 6561 | 1 | 1 | +| 1 | 21 | 5 | 5 | 297 | 9 | 9 | 41553 | 17 | 17 | +| 2 | 49 | 17 | 13 | 945 | 49 | 41 | 1.9e5 | 161 | 145 | +| 3 | 113 | 49 | 29 | 2769 | 209 | 137 | 7.7e5 | 1121 | 849 | +| 4 | 257 | 129 | 65 | 7681 | 769 | 401 | 2.8e6 | 6401 | 3937 | +| 5 | 577 | 321 | 145 | 20481 | 2561 | 1105 | 9.3e6 | 31745 | 15713 | +| 6 | 1281 | 769 | 321 | 52993 | 7937 | 2929 | 3.0e7 | 141569 | 56737 | +| 7 | 2817 | 1793 | 705 | 1.3e5 | 23297 | 7537 | 9.1e7 | 5.8e5 | 1.9e5 | + +--- + +## Visual comparison + +The figure below shows the sparse grids of levels 0 and 2 for all three grid types in two +dimensions. + +![Comparison of CC, M, and NB sparse grids at level 0 and 2 in 2-D](../_static/ex_linear_01.png) + +--- + +## Which grid type to choose? + +- **Clenshaw-Curtis** is the most versatile choice and the only grid type for which the + dimension-adaptive algorithm is currently implemented. Recommended for most problems. + +- **NoBoundary** can outperform CC when the objective function is known to be non-zero at + the domain boundary and the boundary nodes of CC do not contribute meaningful information. + +- **Maximum** has $3^d$ nodes at level 0, making it impractical for $d > 10$ + ($d = 10$ already requires 59049 nodes for the initial interpolant). Suitable only for + low-dimensional problems. diff --git a/docs/theory/polynomial-basis.md b/docs/theory/polynomial-basis.md new file mode 100644 index 0000000..27a5c6d --- /dev/null +++ b/docs/theory/polynomial-basis.md @@ -0,0 +1,102 @@ +# Polynomial Basis Functions + +The piecewise multilinear approach can be significantly improved by using **higher-order** +basis functions, specifically the Lagrange characteristic polynomials. For smooth objective +functions, polynomial sparse grid interpolation achieves spectral convergence rates. + +--- + +## Chebyshev-Gauss-Lobatto (CGL) grid + +The natural choice for polynomial sparse grid interpolation is the set of +**Chebyshev-Gauss-Lobatto** nodes (also called Chebyshev extrema), because: + +1. They are well-conditioned for high-degree polynomial interpolation (Lebesgue constant + grows logarithmically). +2. They form a **nested hierarchy**: the nodes at level $\ell$ include all nodes from + levels $0, 1, \dots, \ell-1$. +3. The resulting sparse grid has the same number of points as the Clenshaw-Curtis grid. + +The 1-D nodes at level $\ell$ are + +\[ +x_j^{(\ell)} = +\begin{cases} +\dfrac{1}{2} & \ell = 0 \\ +0,\;1 & \ell = 1 \\ +\dfrac{1}{2} - \dfrac{1}{2}\cos\!\left(\dfrac{(2j-1)\pi}{2^\ell}\right), +\quad j = 1, \dots, 2^{\ell-1} & \ell \geq 2 +\end{cases} +\] + +Barycentric Lagrange interpolation and the discrete cosine transform (DCT) are used +internally for efficient evaluation and surplus computation. + +--- + +## Gauss-Patterson grid + +Since version 5.0, the **Gauss-Patterson** sparse grid is available. It is based on the +abscissae of the Gauss-Patterson nested quadrature rule, which achieves a higher degree of +polynomial exactness than the CGL nodes for integration purposes. The maximum supported +depth is 6. + +The 1-D Gauss-Patterson nodes at level $\ell$ include $2^{\ell+1}-1$ nodes total (all +nodes from levels $0, \dots, \ell$ in a nested fashion). + +--- + +## Error estimate + +Define the function class + +\[ +\mathcal{F}_d^k = \left\{ + f : [0,1]^d \to \mathbb{R} + \;\middle|\; + \left\|\frac{\partial^{|\boldsymbol{\alpha}|} f}{\partial x^{\boldsymbol{\alpha}}}\right\|_\infty + \leq C_{\boldsymbol{\alpha}},\; + |\boldsymbol{\alpha}|_\infty \leq k +\right\} +\] + +i.e. all mixed partial derivatives up to order $k$ in each coordinate are bounded. For +$f \in \mathcal{F}_d^k$, the CGL sparse grid interpolant $A_{q,d}(f)$ satisfies + +\[ +\|f - A_{q,d}(f)\|_\infty += O\!\left(N^{-k}\,(\log N)^{(k+1)(d-1)}\right) +\] + +where $N$ is the number of CGL support nodes. Compared to the piecewise-linear rate +$N^{-2}(\log N)^{2(d-1)}$, the polynomial basis achieves the higher rate $N^{-k}$ for +smooth functions ($k > 2$). + +--- + +## Grid point counts + +The number of grid points of the **CGL grid** equals that of the Clenshaw-Curtis (CC) grid +(see [Linear Basis Functions](linear-basis.md)). + +The **Gauss-Patterson grid** has the same point counts as the NoBoundary (NB) grid. + +The figure below shows the CGL sparse grids at levels 0 and 2 in two and three dimensions. + +![CGL sparse grid, levels 0 and 2, d=2 and d=3](../_static/ex_polynomial_01.png) + +--- + +## When to use polynomial basis functions? + +There is a trade-off between accuracy gain and the computing time required both to construct +and to evaluate the interpolant. The higher-order accuracy only becomes effective with +increasing number of nodes. We recommend polynomial basis functions when **both** of the +following conditions are met: + +- The objective function is known to be **very smooth** (analytic or at least $C^k$ for + large $k$). +- **High relative accuracies** smaller than $10^{-2}$ are required. + +For lower accuracies or functions with limited smoothness, the piecewise-linear +Clenshaw-Curtis grid is generally the better choice. diff --git a/docs/usage/derivatives.md b/docs/usage/derivatives.md new file mode 100644 index 0000000..d312a3b --- /dev/null +++ b/docs/usage/derivatives.md @@ -0,0 +1,161 @@ +# Computing Derivatives + +One primary purpose of sparse grid interpolation is constructing **surrogate functions** for +local or global optimisation. `spinterp` computes exact derivatives of the interpolant — +not finite-difference approximations — at almost no additional memory cost. + +--- + +## How to obtain derivatives + +Call `spderiv_cc` (or `spderiv_cb` for the Chebyshev grid) instead of `spinterp_cc`. The +function returns both the interpolated values **and** the full gradient vector: + +```python +import spinterp + +# ip.shape = (npoints,) +# grad.shape = (npoints, d) +ip, grad = spinterp.spderiv_cc(z, pts_unit, seq) +``` + +The procedure for building the surpluses `z` and level-index matrix `seq` is identical +regardless of whether derivatives are needed. + +!!! note + The computed derivatives are the **exact** derivatives of the interpolant (up to + floating-point accuracy), not approximations of the derivatives of the original + function $f$. No additional memory is required. + +--- + +## Derivatives of piecewise multilinear interpolants + +Differentiating a piecewise linear function yields a **piecewise constant** function. The +derivatives are exact everywhere except at the kinks (the grid nodes), where only a +one-sided derivative exists. + +### Example — 2-D function + +Consider the test function + +\[ +f(x, y) = \frac{1}{\cos^2(2x) + \sin^2(y) + 1} + 0.2\,y +\] + +with exact partial derivatives + +\[ +\frac{\partial f}{\partial x} = + -\frac{4\cos(2x)\sin(2x)}{(\cos^2(2x) + \sin^2(y) + 1)^2}, +\qquad +\frac{\partial f}{\partial y} = + \frac{-2\cos(y)\sin(y)}{(\cos^2(2x) + \sin^2(y) + 1)^2} + 0.2. +\] + +The figure below compares $\partial f / \partial y$ (left, exact) with the sparse grid +derivative $\partial A^{\text{CC}}_{6,2}(f) / \partial y$ (right, piecewise constant with +visible jumps at level-4 grid nodes): + +![Exact vs piecewise-constant CC derivative](../_static/ex_spderiv_01.png) + +--- + +## Augmented continuous derivatives + +Discontinuous derivatives make first-order optimality conditions $\nabla f = \mathbf{0}$ +impossible to satisfy exactly, leading to slow convergence in gradient-based optimisation. + +The **continuous derivative** option linearly interpolates the piecewise-constant +derivative between two augmented evaluation points $y_1$ and $y_2$ on either side of each +grid cell: + +\[ +\frac{\partial A}{\partial x_k}\bigg|_\text{cont}(y) += \frac{\partial A}{\partial x_k}(y_1) + + \frac{\dfrac{\partial A}{\partial x_k}(y_2) - + \dfrac{\partial A}{\partial x_k}(y_1)}{\Delta} + (y - y_1) +\] + +where $\Delta$ is the cell width $1/2^{\ell_{\max}}$. + +Use `spcont_deriv_cc` followed by `pp_deriv` to obtain continuous derivatives: + +```python +maxlev = int(seq[:, 0].max()) +ip, ipder, ipder2 = spinterp.spcont_deriv_cc(z, pts_unit, seq, maxlev) +# pp_deriv post-processes ipder in-place using ipder2 +import numpy as np +maxlevvec = np.full(d, maxlev, dtype=np.int32) +spinterp.pp_deriv( + np.asfortranarray(ipder), + np.asfortranarray(ipder2), + maxlevvec, + np.asfortranarray(pts_unit) +) +``` + +The figure below shows the same derivative after the continuity post-processing: + +![Continuous CC derivative after pp_deriv post-processing](../_static/ex_spderiv_02.png) + +--- + +## Derivatives of polynomial interpolants + +For the Chebyshev-Gauss-Lobatto grid, the basis functions are globally smooth polynomials. +The derivatives are computed via the **discrete cosine transform (DCT)**, using the +`spderiv_cb` function: + +```python +ip, grad = spinterp.spderiv_cb(z_cb, pts_unit, seq) +``` + +The resulting derivatives are infinitely smooth: + +![CGL polynomial derivative vs exact](../_static/ex_spderiv_03.png) + +--- + +## Approximation quality + +The figure below shows the maximum absolute error of the derivatives for six standard +Genz test functions at 100 randomly sampled points for dimension $d = 3$: + +- **H$^\text{CC}$** — piecewise constant, Clenshaw-Curtis grid +- **H$^\text{CC}$ (cont.)** — augmented continuous, Clenshaw-Curtis grid +- **H$^\text{CGL}$** — smooth polynomial, Chebyshev grid + +![Derivative approximation quality comparison for d=3](../_static/ex_spderiv_04.png) + +!!! note + Functions with kinks (labelled *continuous* and *discontinuous*) cannot have their + derivatives approximated in the maximum norm: convergence fails near the kinks. + The error decreases in the plot only because the randomly sampled points are less + likely to land near the (shrinking) non-convergent region. + +--- + +## Computational cost + +### Clenshaw-Curtis + +Computing the exact or augmented continuous gradient adds only a small, dimension-independent +factor over plain interpolation: + +![Timing: exact CC derivative](../_static/timespderiv.png) +![Timing: continuous CC derivative](../_static/timespderiv_cont.png) + +### Chebyshev + +The polynomial case requires more sophisticated algorithms. However, as the dimension +increases, fewer subgrids need differentiation (lower-dimensional subgrids omit the +dimensions they do not span), so the overhead decreases: + +![Timing: CGL derivative](../_static/timespderiv_cheb.png) +![Timing: CGL derivative absolute](../_static/timespderiv_cheb_abs.png) + +For comparison, numerical differentiation with a centred-difference formula would require +$2d + 1$ interpolant evaluations per gradient — the analytic approach is substantially +cheaper for moderate to large $d$. diff --git a/docs/usage/index.md b/docs/usage/index.md new file mode 100644 index 0000000..391f25c --- /dev/null +++ b/docs/usage/index.md @@ -0,0 +1,10 @@ +# API Reference + +This section covers the main tasks you can perform with a sparse grid interpolant once it +has been constructed. + +| Page | Description | +|---|---| +| [Computing Derivatives](derivatives.md) | Exact gradients of the interpolant | +| [Numerical Integration](integration.md) | Quadrature via the sparse grid weights | +| [Improving Performance](performance.md) | Vectorisation, purging, reuse of results | diff --git a/docs/usage/integration.md b/docs/usage/integration.md new file mode 100644 index 0000000..6dca92b --- /dev/null +++ b/docs/usage/integration.md @@ -0,0 +1,109 @@ +# Numerical Integration (Quadrature) + +Once a sparse grid interpolant has been constructed, its integral over the domain can be +computed exactly (with respect to the interpolant) by taking the dot product of the +**quadrature weights** with the **hierarchical surpluses**. + +For the unit domain $[0,1]^d$: + +\[ +\int_{[0,1]^d} A_{n,d}(f)(\mathbf{x})\,\mathrm{d}\mathbf{x} += \sum_{\mathbf{l}} \sum_j w_j^{(\mathbf{l})}\, \alpha_j^{(\mathbf{l})} += \mathbf{w}^\top \mathbf{z} +\] + +where $\mathbf{w}$ is the vector of quadrature weights and $\mathbf{z}$ is the flat +surplus array. + +```python +import spinterp, numpy as np + +# Build quadrature weights for the CC grid +# (seq and z come from the standard surplus construction) +tp_all = sum(spinterp.spdim_cc(s) for s in all_seq) +w = spinterp.spquadw_cc(seq, tp_all) +integral = float(np.dot(w, z)) +``` + +--- + +## Integration of regular sparse grid interpolants + +### Test: product-type function in $d = 5$ + +Consider the integration test function + +\[ +f(\mathbf{x}) = \left(1 + \frac{1}{d}\right)^d \prod_{i=1}^{d} x_i^{1/d} +\] + +over $[0,1]^5$. The exact integral is $1$. The table below reproduces results from +Table 1 of *Gerstner & Griebel, Numerical Algorithms 18(3–4), 1998*: + +| Depth | CC points | CC error | Cheby points | Cheby error | GP points | GP error | +|---|---|---|---|---|---|---| +| 0 | 1 | 2.44e-01 | 1 | 2.44e-01 | 1 | 2.44e-01 | +| 1 | 11 | 1.08e+00 | 11 | 6.39e-01 | 11 | 8.94e-03 | +| 2 | 61 | 7.58e-02 | 61 | 1.44e-01 | 71 | 8.07e-04 | +| 3 | 241 | 2.86e-01 | 241 | 1.24e-01 | 351 | 2.07e-04 | +| 4 | 801 | 1.08e-01 | 801 | 6.65e-03 | 1471 | 2.26e-05 | +| 5 | 2433 | 8.00e-02 | 2433 | 1.06e-02 | 5503 | 1.42e-06 | +| 6 | 6993 | 5.03e-02 | 6993 | 1.74e-03 | 18943 | 3.44e-09 | + +!!! note + The grid types *Clenshaw-Curtis* and *Chebyshev* in this toolbox correspond to the + "Trapez rule" and "Clenshaw-Curtis rule" sparse grids in Gerstner-Griebel (1998), + respectively. + +--- + +## Integration of dimension-adaptive interpolants + +For high-dimensional problems, the dimension-adaptive approach can achieve dramatically +better accuracy with the same number of function evaluations compared to Monte Carlo. + +### Absorption problem ($d = 20$) + +Consider the absorption integral from Morokoff and Caflisch (1995). Using the smooth +representation of the integrand with parameters $\gamma = 0.5$, $x = 0$, $d = 20$, the +exact solution is + +\[ +I = \frac{1}{\gamma} - \frac{1-\gamma}{\gamma}\,e^{\gamma(1-x)} + \approx 0.3513. +\] + +The dimension-adaptive Chebyshev (CGL) sparse grid converges orders of magnitude faster +than crude Monte Carlo with the same number of points: + +| Points | CGL error | MC error (avg.) | +|---|---|---| +| 41 | 4.62e-04 | 1.30e-02 | +| 87 | 5.61e-06 | 6.01e-03 | +| 177 | 6.01e-07 | 7.79e-03 | +| 367 | 1.57e-07 | 3.44e-03 | +| 739 | 3.89e-08 | 4.76e-03 | +| 1531 | 2.46e-08 | 1.90e-03 | +| 3085 | 1.06e-09 | 1.04e-03 | +| 6181 | 2.75e-09 | 6.15e-04 | +| 24795 | 3.01e-10 | 5.42e-04 | +| 49739 | 1.79e-10 | 3.31e-04 | + +The improvement is particularly pronounced here because the smooth integrand (second +representation) is well-suited to polynomial approximation, and its near-additive structure +is exploited by the dimension-adaptive algorithm. + +--- + +## Quadrature weight functions + +| Grid type | Weight function | Notes | +|---|---|---| +| Clenshaw-Curtis | `spquadw_cc(seq, nw)` | Simple uniform weights $1/\text{wval}$ | +| Chebyshev | `spquadw_cb(seq, nw, w1d, startid)` | Needs 1-D table from `cheb_weights` | +| Gauss-Patterson | `spquadw_gp(seq, nw, w1d, startid)` | Needs 1-D table from `gp_weights` | +| Maximum | `spquadw_m(seq, nw)` | Explicit 1-D weight array per dim | +| NoBoundary | `spquadw_nb(seq, nw)` | Endpoint weights doubled | + +All weight functions also have sparse-index variants (`_sp` suffix) for high-dimensional +problems. diff --git a/docs/usage/performance.md b/docs/usage/performance.md new file mode 100644 index 0000000..ba26d0c --- /dev/null +++ b/docs/usage/performance.md @@ -0,0 +1,151 @@ +# Improving Performance + +## Overview + +This section covers the main strategies for making `spinterp` faster: + +1. Vectorising the objective function +2. Reusing previously computed results +3. Purging redundant interpolant data +4. Vectorised interpolant evaluation + +--- + +## Vectorising the objective function + +Vectorisation is most beneficial when function evaluations are cheap ($\lesssim 10^{-2}$ s +each). Consider the function + +\[ +f(x_1, x_2) = (x_1\, x_2)^2. +\] + +A non-vectorised implementation evaluates one point at a time: + +```python +def f(x1, x2): + return (x1 * x2) ** 2 +``` + +A vectorised implementation processes a whole array in one call: + +```python +import numpy as np +def f_vec(x1, x2): + return (x1 * x2) ** 2 # NumPy broadcasts automatically +``` + +When passing a vectorised function, the grid points are delivered as full arrays, avoiding +per-point Python call overhead. For cheap functions, this can give a **2×** or better +speed-up when computing surpluses. + +--- + +## Reusing previous results + +The hierarchical surplus structure means that a higher-level interpolant is a strict +extension of a lower-level one. You never need to recompute previously computed levels. + +The recommended pattern is to build the interpolant in a loop, checking the estimated error +after each level: + +```python +import numpy as np, spinterp + +d, n_max = 2, 8 +all_seq, all_surp = [], [] +est_err = np.inf + +for k in range(n_max + 1): + nl = spinterp.spnlevels(k, d) + seq = spinterp.spgetseq(k, d, nl) + tp = spinterp.spdim_cc(seq) + x_k = spinterp.spgrid_cc(seq, tp) + fvals = np.array([f_vec(*x_k[i]) for i in range(tp)]) + + if k == 0: + surp = fvals.copy() + else: + z_prev = np.concatenate(all_surp) + seq_prev = np.vstack(all_seq) + surp = fvals - spinterp.spcmpvals_cc(z_prev, x_k, seq, seq_prev) + + all_seq.append(seq) + all_surp.append(surp) + + # Estimated error = max |surplus| / (max |f| + eps) + est_err = np.max(np.abs(surp)) / (np.max(np.abs(fvals)) + 1e-14) + print(f"level {k}: {tp} new pts, est. rel. error = {est_err:.3e}") + + if est_err < 1e-4: + break +``` + +Example output for a smooth function: + +``` +level 0: 1 new pts, est. rel. error = 1.000e+00 +level 1: 4 new pts, est. rel. error = 3.750e-01 +level 2: 8 new pts, est. rel. error = 4.688e-02 +level 3: 16 new pts, est. rel. error = 2.930e-03 +level 4: 28 new pts, est. rel. error = 8.545e-05 +``` + +--- + +## Purging interpolant data + +After the interpolant is built, subgrids whose hierarchical surpluses are all below a +**drop tolerance** can be excluded from evaluation without significantly affecting accuracy. +This is the `sppurge` concept from the original MATLAB toolbox. + +A simple Python equivalent: keep only subgrids where $|\alpha_{\mathbf{l}}|_\infty$ exceeds +a threshold: + +```python +drop_tol = 1e-5 +keep = [ + (seq, surp) + for seq, surp in zip(all_seq, all_surp) + if np.max(np.abs(surp)) > drop_tol +] +seq_pruned = np.vstack([s for s, _ in keep]) +z_pruned = np.concatenate([surp for _, surp in keep]) +``` + +The trade-off: smaller `drop_tol` → higher accuracy, slower evaluation. Typical savings +are 2–5× speedup at the cost of $\sim 10^{-4}$ relative accuracy degradation when +`drop_tol = 1e-5`. + +![Purging trade-off: computation time vs maximum error](../_static/ex_performance_01.png) + +--- + +## Vectorised interpolant evaluation + +The `spinterp_cc` (and related) functions are designed for **batch evaluation**: passing +all query points as a single 2-D array is far more efficient than one-point-at-a-time +calls. + +```python +# Slow — Python loop over 1000 points +results = [spinterp.spinterp_cc(z, pts[i:i+1], seq)[0] for i in range(1000)] + +# Fast — single batched call +results = spinterp.spinterp_cc(z, pts, seq) # pts.shape = (1000, d) +``` + +Typical speed-up: **50–100×** for 1000 points, due to reduced Python overhead and better +cache utilisation in the Fortran kernel. + +--- + +## Summary of tips + +| Tip | When to apply | Typical benefit | +|---|---|---| +| Vectorise the objective function | Cheap functions ($< 10^{-2}$ s/eval) | 2–10× faster `spcmpvals` | +| Build levels incrementally | Always | Avoids recomputing surpluses | +| Purge small-surplus subgrids | After convergence, if evaluation speed matters | 2–5× faster `spinterp` | +| Batch query points | Always | 50–100× faster `spinterp` | +| Use sparse index format | $d > 10$ | Lower memory, faster iteration | diff --git a/meson.build b/meson.build index 144a461..7d8b812 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project( - '', + 'spinterp', 'fortran', 'c', version: run_command(['python', 'bin/get_version.py'], check: true).stdout().strip() ) diff --git a/mkdocs.yml b/mkdocs.yml index 365d8a5..f699be3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,41 +1,38 @@ -site_name: -site_description: -site_url: https://eggzec.github.io// +site_name: spinterp +site_description: Sparse Grid Interpolation Toolbox for Python +site_url: https://eggzec.github.io/spinterp/ docs_dir: docs site_dir: build/docs -repo_name: eggzec/ -repo_url: https://github.com/eggzec/ +repo_name: eggzec/spinterp +repo_url: https://github.com/eggzec/spinterp -# Copyright -copyright: Copyright © 2026 +copyright: "Copyright © 2026 eggzec. All Rights Reserved." -# Configuration +# --------------------------------------------------------------------------- +# Theme +# --------------------------------------------------------------------------- theme: name: material language: en features: - # content features - - content.code.copy # https://squidfunk.github.io/mkdocs-material/reference/code-blocks/?h=code+copy#code-copy-button - - content.code.annotate # https://squidfunk.github.io/mkdocs-material/reference/code-blocks/#code-annotations - # navigation features - - navigation.instant # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#instant-loading - - navigation.instant.progress # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#progress-indicator - - navigation.tracking # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#anchor-tracking - - navigation.tabs # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#navigation-tabs - - navigation.tabs.sticky # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#sticky-navigation-tabs - - navigation.sections # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#navigation-sections - - navigation.indexes # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#section-index-pages - - toc.follow # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#anchor-following - - navigation.top # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/?h=navigation+instant#back-to-top-button - - navigation.footer # https://squidfunk.github.io/mkdocs-material/setup/setting-up-navigation/#navigation-footer - # search features - - search.suggest # https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/?h=search+highlight#search-suggestions - - search.highlight # https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/?h=search+highlight#search-highlighting - - search.share # https://squidfunk.github.io/mkdocs-material/setup/setting-up-site-search/?h=search+highlight#search-sharing + - content.code.copy + - content.code.annotate + - navigation.instant + - navigation.instant.progress + - navigation.tracking + - navigation.tabs + - navigation.tabs.sticky + - navigation.sections + - navigation.indexes + - toc.follow + - navigation.top + - navigation.footer + - search.suggest + - search.highlight + - search.share - # Palette configuration palette: - scheme: default primary: blue grey @@ -50,93 +47,39 @@ theme: icon: material/brightness-4 name: Switch to light mode - # Logo and favicon - favicon: assets/images/.ico - logo: assets/images/.png - - # Font configuration font: text: Roboto code: Roboto Mono -# Navigation structure +# --------------------------------------------------------------------------- +# Navigation +# --------------------------------------------------------------------------- nav: - - Get Started: - - index.md - - Installation: installation.md - - Contributing: contributing.md - - License: license.md - - Credits: credits.md - - Contributors: contributors.md - - Users: users.md + - Home: index.md + - Installation: installation.md + - Quick Start: quickstart.md - Theory: - - Introduction: - - Overview: theory/introduction/index.md - - Experimental Design: theory/introduction/what-is-doe.md - - Uses of DOE: theory/introduction/doe-uses.md - - Steps of DOE: theory/introduction/doe-steps.md - - Assumptions: - - Overview: theory/assumptions/index.md - - Measurement System: theory/assumptions/measurement-system.md - - Process Stability: theory/assumptions/process-stability.md - - Simple Model: theory/assumptions/simple-model.md - - Residuals: theory/assumptions/residuals.md - - Choosing an Experimental Design: - - Overview: theory/choosing-design/index.md - - Set Objectives: theory/choosing-design/objectives.md - - Select Process Variables: theory/choosing-design/process-variables.md - - Select Experimental Design: theory/choosing-design/select-design.md - - Completely Randomized Designs: theory/choosing-design/completely-randomized.md - - Randomized Block Designs: theory/choosing-design/randomized-block.md - - Full Factorial Designs: theory/choosing-design/full-factorial.md - - Fractional Factorial Designs: theory/choosing-design/fractional-factorial.md - - Plackett-Burman Designs: theory/choosing-design/plackett-burman.md - - Response Surface Designs: theory/choosing-design/response-surface.md - - Central Composite Designs: theory/choosing-design/central-composite.md - - Box-Behnken Designs: theory/choosing-design/box-behnken.md - - Comparisons of Response Surface Designs: theory/choosing-design/design-comparisons.md - - Blocking a Response Surface Design: theory/choosing-design/blocking-rsd.md - - Adding Center Points: theory/choosing-design/center-points.md - - Improving Fractional Design Resolution: theory/choosing-design/improving-resolution.md - - Three-Level Full Factorial Designs: theory/choosing-design/three-level.md - - Three-Level, Mixed Level and Fractional Factorial Designs: theory/choosing-design/mixed-level.md - - Analysis of DOE Data: - - Overview: theory/analysis-doe/index.md - - DOE Analysis Steps: theory/analysis-doe/doe-analysis-steps.md - - Plotting DOE Data: theory/analysis-doe/plotting-doe-data.md - - Modeling DOE Data: theory/analysis-doe/modeling-doe-data.md - - Testing and Revising Models: theory/analysis-doe/testing-revising-models.md - - Interpreting DOE Results: theory/analysis-doe/interpreting-results.md - - Confirming DOE Results: theory/analysis-doe/confirming-results.md - - DOE Examples: theory/analysis-doe/doe-examples.md - - Full Factorial Example: theory/analysis-doe/full-factorial-example.md - - Fractional Factorial Example: theory/analysis-doe/fractional-factorial-example.md - - Response Surface Example: theory/analysis-doe/response-surface-example.md - - Advanced Topics: - - Overview: theory/advanced-topics/index.md - - When Classical Designs Don't Work: theory/advanced-topics/when-classical-fail.md - - Computer-Aided Designs: theory/advanced-topics/computer-aided-designs.md - - D-Optimal Designs: theory/advanced-topics/d-optimal.md - - Repairing a Design: theory/advanced-topics/repairing-design.md - - Optimizing a Process: theory/advanced-topics/optimizing-process.md - - Single Response Case: theory/advanced-topics/single-response.md - - Multiple Response Case: theory/advanced-topics/multiple-response.md - - Reference: - - Factorial Designs: reference/factorial.md - - Response Surface Designs: reference/response_surface.md - - Randomized Designs: reference/randomized.md - - Low-Discrepancy Sequences: reference/low_discrepancy_sequences.md - - Sampling Designs: reference/sampling_designs.md - - Taguchi Designs: reference/taguchi.md - - Optimal Designs: reference/doe_optimal.md - - Sparse Grid Designs: reference/sparse_grid.md - - Changelog: changelog.md + - Linear Basis Functions: theory/linear-basis.md + - Polynomial Basis Functions: theory/polynomial-basis.md + - Dimensional Adaptivity: theory/dimension-adaptive.md + - Bibliography: theory/bibliography.md + - API Reference: + - Overview: usage/index.md + - Computing Derivatives: usage/derivatives.md + - Numerical Integration: usage/integration.md + - Improving Performance: usage/performance.md +# --------------------------------------------------------------------------- +# Plugins +# --------------------------------------------------------------------------- plugins: - - search: # https://squidfunk.github.io/mkdocs-material/plugins/search - - minify: # https://github.com/byrnereese/mkdocs-minify-plugin + - search: + - minify: minify_html: true +# --------------------------------------------------------------------------- +# Markdown extensions +# --------------------------------------------------------------------------- markdown_extensions: - attr_list - md_in_html @@ -158,22 +101,22 @@ markdown_extensions: - pymdownx.arithmatex: generic: true -extra_css: # https://squidfunk.github.io/mkdocs-material/customization/#additional-css +extra_css: - assets/stylesheets/extra.css - - https://unpkg.com/katex@0/dist/katex.min.css extra: social: - icon: fontawesome/brands/github - link: https://github.com/eggzec/ + link: https://github.com/eggzec/spinterp - icon: fontawesome/brands/python - link: https://pypi.org/project// + link: https://pypi.org/project/spinterp/ extra_javascript: - - assets/javascripts/katex.js - - https://unpkg.com/katex@0/dist/katex.min.js - - https://unpkg.com/katex@0/dist/contrib/auto-render.min.js + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js +# --------------------------------------------------------------------------- +# Validation +# --------------------------------------------------------------------------- validation: nav: omitted_files: warn diff --git a/pyproject.toml b/pyproject.toml index 6a07625..ba3ac16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,11 @@ requires = [ build-backend = "mesonpy" [project] -name = "" +name = "spinterp" dynamic = ["version"] -description = "" +description = "Sparse Grid Interpolation Toolbox for Python" authors = [ + { name = "W. Andreas Klimke", email = "klimkeas@ians.uni-stuttgart.de" }, { name = "Saud Zahir", email = "m.saud.zahir@gmail.com" }, ] maintainers = [ @@ -19,9 +20,20 @@ maintainers = [ { name = "M Laraib Ali", email = "laraibg786@outlook.com" } ] readme = "README.md" -license = "GPL-3.0" +license = {text = "GPL-3.0"} keywords = [ - "python" + "sparse grid", + "interpolation", + "quadrature", + "Smolyak", + "Clenshaw-Curtis", + "Chebyshev", + "hierarchical surplus", + "high-dimensional approximation", + "Fortran", + "f2py", + "scientific computing", + "numerical methods", ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -50,12 +62,12 @@ dependencies = [ requires-python = ">=3.10" [project.urls] -homepage = "https://eggzec.github.io//" -documentation = "https://eggzec.github.io//" -source = "https://github.com/eggzec/" -changelog = "https://eggzec.github.io//changelog/" -releasenotes = "https://github.com/eggzec//releases/latest" -issues = "https://github.com/eggzec//issues" +homepage = "https://eggzec.github.io/spinterp/" +documentation = "https://eggzec.github.io/spinterp/" +source = "https://github.com/eggzec/spinterp" +changelog = "https://eggzec.github.io/spinterp/changelog/" +releasenotes = "https://github.com/eggzec/spinterp/releases/latest" +issues = "https://github.com/eggzec/spinterp/issues" [dependency-groups] dev = [ @@ -79,7 +91,7 @@ test = [ source = "vcs" [tool.hatch.build.targets.wheel] -include = [ "/" ] +include = [ "/spinterp" ] [tool.ruff] line-length = 80 diff --git a/src/barypdstepcb.f b/src/barypdstepcb.f new file mode 100644 index 0000000..ac31158 --- /dev/null +++ b/src/barypdstepcb.f @@ -0,0 +1,287 @@ +C ******************************************************************* +C +C BARY_PD_STEP_CB - Barycentric d-dim Lagrange interpolation step, +C Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE BARY_PD_STEP_CB(Z, NZ, ALLNX, DIMS, D, + & X, NX, Y, NINTERP, DYFULL, IP) +C ******************************************************************* +C +C BARY_PD_STEP_CB evaluates the multi-dimensional polynomial +C interpolant using barycentric Lagrange interpolation on the +C Chebyshev-Lobatto grid. Only the NZ new (hierarchical) nodes +C contribute; the node structure has NX(k) total nodes per dim, +C with new nodes at even positions (0,2,4,...). +C +C Parameters: +C +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - number of new nodes +C Input, INTEGER ALLNX(D) - full node count per dim +C Input, INTEGER DIMS(D) - active dimension indices +C Input, INTEGER D - active dimensions +C Input, DOUBLE PRECISION X(NX) - concatenated node arrays +C Input, INTEGER NX - total nodes in X +C Input, DOUBLE PRECISION Y(NINTERP,DYFULL) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER DYFULL - full dimension of Y +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NZ, D, NX, NINTERP, DYFULL + DOUBLE PRECISION Z(NZ) + INTEGER ALLNX(D), DIMS(D) + DOUBLE PRECISION X(NX) + DOUBLE PRECISION Y(NINTERP, DYFULL) + DOUBLE PRECISION IP(NINTERP) + + INTEGER K, L, K2, KXI, AID, XID, ACTNX, ACTNA + INTEGER NNA(50), IDADDFIX(50), AIDSTART(50), AIDMAX(50) + INTEGER AIDVEC(50), IDVEC(50), IDADD(50) + INTEGER NID, NITER + INTEGER ZIDBASE, ZID, ID(50) + INTEGER AID1, AID2, AIDMAX1, AIDMAX2, ADDID1, ADDID2 + INTEGER ID1, ID2, IDL, AIDL, AIDLMAX + DOUBLE PRECISION A(16384) + DOUBLE PRECISION C1(50), C2(50), S, FRAC, T + DOUBLE PRECISION C1D, C2D, C11, C12 + INTEGER ISZERONODE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + +C NNA(k) = number of new (non-zero) nodes per dim k +C For level 1 (ALLNX=3): NNA = 2; else NNA = (ALLNX-1)/2 + DO L = 1, D + IF (ALLNX(L) .EQ. 3) THEN + NNA(L) = 2 + ELSE + NNA(L) = (ALLNX(L) - 1) / 2 + END IF + END DO + +C Column strides in Z (new-node layout) + IDADDFIX(1) = 1 + AIDSTART(1) = 1 + DO L = 2, D + IDADDFIX(L) = IDADDFIX(L-1) * NNA(L-1) + AIDSTART(L) = AIDSTART(L-1) + NNA(L-1) + END DO + DO L = 1, D + AIDMAX(L) = AIDSTART(L) + NNA(L) - 1 + END DO + + DO K = 1, NINTERP + XID = 0 + AID = 0 + ISZERONODE = 0 + + DO L = 1, D + T = Y(K, DIMS(L)) + S = 0.0D+00 + ID(L) = 0 + ACTNX = ALLNX(L) + ACTNA = NNA(L) + + IF (ACTNX .EQ. 3) THEN +C Level 1: 3 nodes (x[1]=0, x[2]=0.5, x[3]=1) +C x[2] (index 2, zero node) sets ip=0 if hit + IF (T .EQ. X(XID+1)) THEN + ID(L) = 1 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + ELSE IF (T .EQ. X(XID+2)) THEN + ISZERONODE = 1 + GOTO 50 + ELSE IF (T .EQ. X(XID+3)) THEN + ID(L) = 2 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + END IF + FRAC = 0.5D+00 / (T - X(XID+1)) + A(AID+1) = FRAC + S = S + FRAC + S = S - 1.0D+00 / (T - X(XID+2)) + FRAC = 0.5D+00 / (T - X(XID+3)) + A(AID+2) = FRAC + S = S + FRAC + + ELSE +C General level >= 2 +C Check if t hits a support node (even pos) +C or a zero node (odd pos) using acos approximation + IF (T .GE. 0.0D+00 .AND. T .LE. 1.0D+00) THEN + K2 = XID + NINT(DBLE(ACTNX) - DACOS( + & (T-0.5D+00)*2.0D+00) + & *DBLE(ACTNX-1)/ + & 3.14159265358979323846D+00) + IF (K2 .LT. XID+1) K2 = XID+1 + IF (K2 .GT. XID+ACTNX) K2 = XID+ACTNX + IF (T .EQ. X(K2)) THEN + IF (MOD(K2-XID, 2) .EQ. 1) THEN + ISZERONODE = 1 + GOTO 50 + ELSE + ID(L) = (K2-XID)/2 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + END IF + END IF + END IF + + S = S + 0.5D+00 / (T - X(XID+1)) + KXI = XID + 2 + DO K2 = AID+1, AID+ACTNA-1 + FRAC = -1.0D+00 / (T - X(KXI)) + A(K2) = FRAC + S = S + FRAC + 1.0D+00/(T - X(KXI+1)) + KXI = KXI + 2 + END DO + S = S - 0.5D+00 / (T - X(XID+ACTNX)) + END IF + + C2(L) = S + AID = AID + ACTNA + XID = XID + ACTNX + 30 CONTINUE + END DO + + IF (ISZERONODE .NE. 0) GOTO 40 + +C Determine free (non-hit) dimensions + NITER = 1 + NID = 0 + DO L = 1, D + IF (ID(L) .EQ. 0) THEN + NITER = NITER * NNA(L) + NID = NID + 1 + IDVEC(NID) = L + ELSE + ID(L) = ID(L) - 1 + END IF + END DO + + IF (NID .EQ. 0) THEN + ZIDBASE = 1 + DO L = 1, D + ZIDBASE = ZIDBASE + ID(L)*IDADDFIX(L) + END DO + IP(K) = Z(ZIDBASE) + GOTO 40 + END IF + + ZIDBASE = 1 + DO L = 1, D + C1(L) = 0.0D+00 + ZIDBASE = ZIDBASE + ID(L)*IDADDFIX(L) + AIDVEC(L) = AIDSTART(L) + END DO + + DO L = 1, NID + IDL = IDVEC(L) + IF (L .GT. 1) THEN + IDADD(IDL) = IDADDFIX(IDL) + & - IDADDFIX(IDVEC(L-1)+1) + ELSE + IDADD(IDL) = IDADDFIX(IDL) + END IF + END DO + + C11 = 0.0D+00 + C12 = 0.0D+00 + ID1 = IDVEC(1) + ADDID1 = IDADD(ID1) + AID1 = AIDVEC(ID1) + AIDMAX1 = AIDMAX(ID1) + ZID = ZIDBASE + + IF (NID .EQ. 1) THEN + DO K2 = AID1, AIDMAX1 + C11 = C11 + Z(ZID) * A(K2) + ZID = ZID + ADDID1 + END DO + C1D = C11 + + ELSE + NITER = NITER / NNA(ID1) + ID2 = IDVEC(2) + AID2 = AIDVEC(ID2) + AIDMAX2 = AIDMAX(ID2) + ADDID2 = IDADD(ID2) + + DO K2 = 1, NITER + DO AID = AID1, AIDMAX1 + C11 = C11 + Z(ZID) * A(AID) + ZID = ZID + ADDID1 + END DO + ZID = ZID + ADDID2 + C12 = C12 + A(AID2) * C11 + C11 = 0.0D+00 + AID2 = AID2 + 1 + + IF (NID .GT. 2 .AND. AID2 .GT. AIDMAX2) THEN + ZID = ZID + IDADD(IDVEC(3)) + C1(3) = C1(3) + A(AIDVEC(IDVEC(3)))*C12 + C12 = 0.0D+00 + AID2 = AIDSTART(ID2) + DO L = 3, NID + IDL = IDVEC(L) + AIDL = AIDVEC(IDL) + AIDLMAX = AIDMAX(IDL) + IF (AIDL .EQ. AIDLMAX) THEN + IF (L .LT. NID) THEN + ZID = ZID + & + IDADD(IDVEC(L+1)) + C1(L+1) = C1(L+1) + & + A(AIDVEC(IDVEC(L+1))) + & * C1(L) + C1(L) = 0.0D+00 + AIDVEC(IDL) = + & AIDSTART(IDL) + END IF + ELSE + AIDVEC(IDL) = AIDL + 1 + GOTO 60 + END IF + END DO + 60 CONTINUE + END IF + END DO + + IF (NID .EQ. 2) THEN + C1D = C12 + ELSE + C1D = C1(NID) + END IF + END IF + + C2D = 1.0D+00 + DO L = 1, D + C2D = C2D * C2(L) + END DO + IP(K) = C1D / C2D + + 40 CONTINUE + END DO + + 50 RETURN + END diff --git a/src/barypdstepgp.f b/src/barypdstepgp.f new file mode 100644 index 0000000..7abc6d9 --- /dev/null +++ b/src/barypdstepgp.f @@ -0,0 +1,295 @@ +C ******************************************************************* +C +C BARY_PD_STEP_GP - Barycentric d-dim Lagrange interpolation step, +C Gauss-Patterson grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE BARY_PD_STEP_GP(Z, NZ, ALLNX, DIMS, D, + & X, NX, Y, NINTERP, DYFULL, + & W, IP) +C ******************************************************************* +C +C BARY_PD_STEP_GP evaluates the multi-dimensional polynomial +C interpolant using barycentric Lagrange interpolation on the +C Gauss-Patterson grid. Includes precomputed barycentric +C weights W in addition to node locations X. +C +C Parameters: +C +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - number of new nodes +C Input, INTEGER ALLNX(D) - full node count per dim +C Input, INTEGER DIMS(D) - active dimension indices +C Input, INTEGER D - active dimensions +C Input, DOUBLE PRECISION X(NX) - concatenated node arrays +C Input, INTEGER NX - total nodes in X +C Input, DOUBLE PRECISION Y(NINTERP,DYFULL) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER DYFULL - full dimension of Y +C Input, DOUBLE PRECISION W(NX) - barycentric weights +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NZ, D, NX, NINTERP, DYFULL + DOUBLE PRECISION Z(NZ) + INTEGER ALLNX(D), DIMS(D) + DOUBLE PRECISION X(NX), W(NX) + DOUBLE PRECISION Y(NINTERP, DYFULL) + DOUBLE PRECISION IP(NINTERP) + + INTEGER K, L, K2, KXI, AID, XID, ACTNX, ACTNA + INTEGER NNA(50), IDADDFIX(50), AIDSTART(50), AIDMAX(50) + INTEGER AIDVEC(50), IDVEC(50), IDADD(50) + INTEGER NID, NITER + INTEGER ZIDBASE, ZID, ID(50) + INTEGER AID1, AID2, AIDMAX1, AIDMAX2, ADDID1, ADDID2 + INTEGER ID1, ID2, IDL, AIDL, AIDLMAX + INTEGER LOK, UPK, KMID + DOUBLE PRECISION A(16384) + DOUBLE PRECISION C1(50), C2(50), S, FRAC, T, XK + DOUBLE PRECISION C1D, C2D, C11, C12 + INTEGER ISZERONODE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + +C NNA(k) = (ALLNX(k) + 1) / 2 (new GP nodes per dim) + DO L = 1, D + NNA(L) = (ALLNX(L) + 1) / 2 + END DO + + IDADDFIX(1) = 1 + AIDSTART(1) = 1 + DO L = 2, D + IDADDFIX(L) = IDADDFIX(L-1) * NNA(L-1) + AIDSTART(L) = AIDSTART(L-1) + NNA(L-1) + END DO + DO L = 1, D + AIDMAX(L) = AIDSTART(L) + NNA(L) - 1 + END DO + + DO K = 1, NINTERP + XID = 0 + AID = 0 + ISZERONODE = 0 + + DO L = 1, D + T = Y(K, DIMS(L)) + S = 0.0D+00 + ID(L) = 0 + ACTNX = ALLNX(L) + ACTNA = NNA(L) + + IF (ACTNX .EQ. 3) THEN + IF (T .EQ. X(XID+1)) THEN + ID(L) = 1 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + ELSE IF (T .EQ. X(XID+2)) THEN + ISZERONODE = 1 + GOTO 50 + ELSE IF (T .EQ. X(XID+3)) THEN + ID(L) = 2 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + END IF + FRAC = W(XID+1) / (T - X(XID+1)) + A(AID+1) = FRAC + S = S + FRAC + S = S + W(XID+2) / (T - X(XID+2)) + FRAC = W(XID+3) / (T - X(XID+3)) + A(AID+2) = FRAC + S = S + FRAC + + ELSE +C Binary search for t in the node array + IF (T .GE. 0.0D+00 .AND. T .LE. 1.0D+00) THEN + LOK = XID + 1 + UPK = XID + ACTNX + KMID = (LOK + UPK) / 2 + XK = X(KMID) + 70 IF (LOK + 1 .LT. UPK) THEN + IF (XK .GT. T) THEN + UPK = KMID + ELSE + LOK = KMID + END IF + KMID = (LOK + UPK) / 2 + XK = X(KMID) + GOTO 70 + END IF + IF (T .EQ. X(LOK)) THEN + K2 = LOK + ELSE IF (T .EQ. X(UPK)) THEN + K2 = UPK + ELSE + K2 = 0 + END IF + IF (K2 .GT. 0) THEN + IF (MOD(K2-XID, 2) .EQ. 0) THEN + ISZERONODE = 1 + GOTO 50 + ELSE + ID(L) = (K2-XID+1)/2 + C2(L) = 1.0D+00 + AID = AID + ACTNA + XID = XID + ACTNX + GOTO 30 + END IF + END IF + END IF + + KXI = XID + 1 + DO K2 = AID+1, AID+ACTNA-1 + FRAC = W(KXI) / (T - X(KXI)) + A(K2) = FRAC + S = S + FRAC + & + W(KXI+1)/(T - X(KXI+1)) + KXI = KXI + 2 + END DO + FRAC = W(KXI) / (T - X(KXI)) + A(AID+ACTNA) = FRAC + S = S + FRAC + END IF + + C2(L) = S + AID = AID + ACTNA + XID = XID + ACTNX + 30 CONTINUE + END DO + + IF (ISZERONODE .NE. 0) GOTO 40 + + NITER = 1 + NID = 0 + DO L = 1, D + IF (ID(L) .EQ. 0) THEN + NITER = NITER * NNA(L) + NID = NID + 1 + IDVEC(NID) = L + ELSE + ID(L) = ID(L) - 1 + END IF + END DO + + IF (NID .EQ. 0) THEN + ZIDBASE = 1 + DO L = 1, D + ZIDBASE = ZIDBASE + ID(L)*IDADDFIX(L) + END DO + IP(K) = Z(ZIDBASE) + GOTO 40 + END IF + + ZIDBASE = 1 + DO L = 1, D + C1(L) = 0.0D+00 + ZIDBASE = ZIDBASE + ID(L)*IDADDFIX(L) + AIDVEC(L) = AIDSTART(L) + END DO + + DO L = 1, NID + IDL = IDVEC(L) + IF (L .GT. 1) THEN + IDADD(IDL) = IDADDFIX(IDL) + & - IDADDFIX(IDVEC(L-1)+1) + ELSE + IDADD(IDL) = IDADDFIX(IDL) + END IF + END DO + + C11 = 0.0D+00 + C12 = 0.0D+00 + ID1 = IDVEC(1) + ADDID1 = IDADD(ID1) + AID1 = AIDVEC(ID1) + AIDMAX1 = AIDMAX(ID1) + ZID = ZIDBASE + + IF (NID .EQ. 1) THEN + DO K2 = AID1, AIDMAX1 + C11 = C11 + Z(ZID) * A(K2) + ZID = ZID + ADDID1 + END DO + C1D = C11 + + ELSE + NITER = NITER / NNA(ID1) + ID2 = IDVEC(2) + AID2 = AIDVEC(ID2) + AIDMAX2 = AIDMAX(ID2) + ADDID2 = IDADD(ID2) + + DO K2 = 1, NITER + DO AID = AID1, AIDMAX1 + C11 = C11 + Z(ZID) * A(AID) + ZID = ZID + ADDID1 + END DO + ZID = ZID + ADDID2 + C12 = C12 + A(AID2) * C11 + C11 = 0.0D+00 + AID2 = AID2 + 1 + + IF (NID .GT. 2 .AND. AID2 .GT. AIDMAX2) THEN + ZID = ZID + IDADD(IDVEC(3)) + C1(3) = C1(3) + A(AIDVEC(IDVEC(3)))*C12 + C12 = 0.0D+00 + AID2 = AIDSTART(ID2) + DO L = 3, NID + IDL = IDVEC(L) + AIDL = AIDVEC(IDL) + AIDLMAX = AIDMAX(IDL) + IF (AIDL .EQ. AIDLMAX) THEN + IF (L .LT. NID) THEN + ZID = ZID + & + IDADD(IDVEC(L+1)) + C1(L+1) = C1(L+1) + & + A(AIDVEC(IDVEC(L+1))) + & * C1(L) + C1(L) = 0.0D+00 + AIDVEC(IDL) = + & AIDSTART(IDL) + END IF + ELSE + AIDVEC(IDL) = AIDL + 1 + GOTO 60 + END IF + END DO + 60 CONTINUE + END IF + END DO + + IF (NID .EQ. 2) THEN + C1D = C12 + ELSE + C1D = C1(NID) + END IF + END IF + + C2D = 1.0D+00 + DO L = 1, D + C2D = C2D * C2(L) + END DO + IP(K) = C1D / C2D + + 40 CONTINUE + END DO + + 50 RETURN + END diff --git a/src/chebweights.f b/src/chebweights.f new file mode 100644 index 0000000..0bac119 --- /dev/null +++ b/src/chebweights.f @@ -0,0 +1,112 @@ +C ******************************************************************* +C +C CHEB_WEIGHTS - 1-D Chebyshev-Lobatto quadrature weights. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE CHEB_WEIGHTS(MAXLEV, WEIGHTS, NW, STARTID) +C ******************************************************************* +C +C CHEB_WEIGHTS builds the flat 1-D weight array and level start +C indices for SPQUADW_CB, mirroring MATLAB chebweights.m. +C +C Parameters: +C +C Input, INTEGER MAXLEV - max Chebyshev level +C Output, DOUBLE PRECISION WEIGHTS(NW) - flat weight array +C Input, INTEGER NW - size of WEIGHTS +C Output, INTEGER STARTID(MAXLEV+1) - level start indices +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER MAXLEV, NW + DOUBLE PRECISION WEIGHTS(NW) + INTEGER STARTID(*) + + INTEGER LEV, I, WID, NW1 + DOUBLE PRECISION W(512) + + WID = 0 + + DO LEV = 0, MAXLEV + + IF (LEV .EQ. 0) THEN + W(1) = 1.0D+00 + NW1 = 1 + ELSE IF (LEV .EQ. 1) THEN + W(1) = 1.0D+00 / 6.0D+00 + NW1 = 1 + ELSE IF (LEV .EQ. 2) THEN + W(1) = 4.0D+00 / 15.0D+00 + NW1 = 1 + ELSE IF (LEV .EQ. 3) THEN + W(1) = 0.7310932460800910D-01 + W(2) = 0.1808589293602449D+00 + NW1 = 2 + ELSE IF (LEV .EQ. 4) THEN + W(1) = 0.1868435141860280D-01 + W(2) = 0.5445277629094545D-01 + W(3) = 0.8158633214085165D-01 + W(4) = 0.9625693230646280D-01 + NW1 = 4 + ELSE IF (LEV .EQ. 5) THEN + W(1) = 0.4696598981477508D-02 + W(2) = 0.1422895833861684D-01 + W(3) = 0.2313138141887588D-01 + W(4) = 0.3113605477264700D-01 + W(5) = 0.3794190022069424D-01 + W(6) = 0.4328876922091372D-01 + W(7) = 0.4697162221938437D-01 + W(8) = 0.4884909410402779D-01 + NW1 = 8 + ELSE IF (LEV .EQ. 6) THEN + W( 1) = 0.1175745337655852D-02 + W( 2) = 0.3596346580868057D-02 + W( 3) = 0.5961697357106385D-02 + W( 4) = 0.8267493828644795D-02 + W( 5) = 0.1049313721486872D-01 + W( 6) = 0.1261753249087738D-01 + W( 7) = 0.1462032659873417D-01 + W( 8) = 0.1648227328498816D-01 + W( 9) = 0.1818546014331959D-01 + W(10) = 0.1971349435647805D-01 + W(11) = 0.2105166555570905D-01 + W(12) = 0.2218708961962866D-01 + W(13) = 0.2310883375546279D-01 + W(14) = 0.2380802229262510D-01 + W(15) = 0.2427792242857052D-01 + W(16) = 0.2451400921551278D-01 + NW1 = 16 + ELSE + NW1 = 2**(LEV-2) + DO I = 1, NW1 + W(I) = 0.0D+00 + END DO + END IF + + STARTID(LEV+1) = WID + 1 + DO I = 1, NW1 + WEIGHTS(WID + I) = W(I) + END DO + WID = WID + NW1 + + IF (LEV .GT. 0) THEN + DO I = 1, NW1 + WEIGHTS(WID + I) = W(NW1 - I + 1) + END DO + WID = WID + NW1 + END IF + + END DO + + RETURN + END diff --git a/src/dctdiffcheb.f b/src/dctdiffcheb.f new file mode 100644 index 0000000..f60044d --- /dev/null +++ b/src/dctdiffcheb.f @@ -0,0 +1,160 @@ +C ******************************************************************* +C +C DCT_DIFF_CHEB - DCT-based derivative of Chebyshev polynomial. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE DCT_DIFF_CHEB(N, Z, NPTS, NCOLS, Y, NY, IPDER) +C ******************************************************************* +C +C DCT_DIFF_CHEB computes the derivative of the Chebyshev polynomial +C at the new nodes using DCT-based coefficient extraction. +C +C N = full Chebyshev node count (2^lev + 1) +C NPTS = new surplus values only: lev=1 -> 2, lev>1 -> (N-1)/2 +C NCOLS = 1 (univariate) or NY (one column per query point) +C +C Algorithm (MATLAB dctdiffcheb): +C n==3: ZEXT = [Z(2), 0, Z(1), 0] +C n>3: ZEXT(2i) = Z(NPTS+1-i) i=1..NPTS (reversed) +C ZEXT(2NPTS+2i) = Z(i) i=1..NPTS (direct) +C Then DFT -> Chebyshev coeffs -> differentiate -> Clenshaw eval. +C +C Parameters: +C +C Input, INTEGER N - full Chebyshev node count +C Input, DOUBLE PRECISION Z(NPTS,NCOLS) - new surplus values +C Input, INTEGER NPTS - number of new surplus values +C Input, INTEGER NCOLS - 1 (univariate) or NY +C Input, DOUBLE PRECISION Y(NY) - eval points in [0,1] +C Input, INTEGER NY - number of eval points +C Output, DOUBLE PRECISION IPDER(NY) - accumulated derivative +C +C ******************************************************************* + + IMPLICIT NONE + + DOUBLE PRECISION PI + PARAMETER (PI = 3.14159265358979323846D+00) + + INTEGER N, NPTS, NCOLS, NY + DOUBLE PRECISION Z(NPTS, NCOLS) + DOUBLE PRECISION Y(NY) + DOUBLE PRECISION IPDER(NY) + + INTEGER MAXM + PARAMETER (MAXM = 2050) + + INTEGER M, I, J, K, COL + DOUBLE PRECISION ZEXT(MAXM) + DOUBLE PRECISION CHEBCOEF(MAXM) + DOUBLE PRECISION DCHEB(MAXM) + DOUBLE PRECISION ANGLE, T, B0, B1, B2, SUMRE + LOGICAL UNIVARIATE + + DO K = 1, NY + IPDER(K) = 0.0D+00 + END DO + + IF (N .LE. 1 .OR. NPTS .LE. 0) RETURN + + UNIVARIATE = (NCOLS .EQ. 1) + M = 2 * (N - 1) + + DO COL = 1, NCOLS + + DO I = 1, M + ZEXT(I) = 0.0D+00 + END DO + + IF (N .EQ. 3) THEN +C Special: new nodes at x=0 (Z(1)) and x=1 (Z(2)) +C ZEXT = [Z(2), 0, Z(1), 0] + IF (NPTS .GE. 2) THEN + ZEXT(1) = Z(2, COL) + ZEXT(3) = Z(1, COL) + ELSE + ZEXT(1) = Z(1, COL) + END IF + ELSE +C ZEXT(2i) = Z(NPTS+1-i) for i=1..NPTS +C ZEXT(2*NPTS + 2i) = Z(i) for i=1..NPTS + DO I = 1, NPTS + ZEXT(2*I) = Z(NPTS + 1 - I, COL) + END DO + DO I = 1, NPTS + IF (2*NPTS + 2*I .LE. M) + & ZEXT(2*NPTS + 2*I) = Z(I, COL) + END DO + END IF + +C DFT of ZEXT -> Chebyshev coefficients + DO K = 0, N - 1 + SUMRE = 0.0D+00 + DO J = 0, M - 1 + ANGLE = -2.0D+00*PI*DBLE(J)*DBLE(K)/DBLE(M) + SUMRE = SUMRE + ZEXT(J+1) * DCOS(ANGLE) + END DO + CHEBCOEF(K+1) = SUMRE / DBLE(N-1) + END DO + CHEBCOEF(1) = CHEBCOEF(1) / 2.0D+00 + CHEBCOEF(N) = CHEBCOEF(N) / 2.0D+00 + +C Differentiate Chebyshev coefficients (MATLAB backward recurrence) + DO K = 1, N + DCHEB(K) = 0.0D+00 + END DO + IF (N .GE. 3) THEN + DCHEB(N-1) = 2.0D+00*DBLE(N-1)*CHEBCOEF(N) + DO K = N - 2, 2, -1 + DCHEB(K) = DCHEB(K+2) + & + 2.0D+00*DBLE(K)*CHEBCOEF(K+1) + END DO + DCHEB(1) = DCHEB(3)/2.0D+00 + CHEBCOEF(2) + ELSE IF (N .EQ. 2) THEN + DCHEB(1) = CHEBCOEF(2) + END IF + +C Scale by 2 for [0,1] domain + DO K = 1, N - 1 + DCHEB(K) = DCHEB(K) * 2.0D+00 + END DO + +C Clenshaw evaluation + IF (UNIVARIATE) THEN + DO I = 1, NY + T = 2.0D+00 * Y(I) - 1.0D+00 + B0 = 0.0D+00 + B1 = 0.0D+00 + B2 = 0.0D+00 + DO K = N - 1, 2, -1 + B2 = B1 + B1 = B0 + B0 = DCHEB(K) + 2.0D+00*T*B1 - B2 + END DO + IPDER(I) = IPDER(I) + DCHEB(1) + T*B0 - B1 + END DO + ELSE + T = 2.0D+00 * Y(COL) - 1.0D+00 + B0 = 0.0D+00 + B1 = 0.0D+00 + B2 = 0.0D+00 + DO K = N - 1, 2, -1 + B2 = B1 + B1 = B0 + B0 = DCHEB(K) + 2.0D+00*T*B1 - B2 + END DO + IPDER(COL) = IPDER(COL) + DCHEB(1) + T*B0 - B1 + END IF + + END DO + + RETURN + END diff --git a/src/dctupsample.f b/src/dctupsample.f new file mode 100644 index 0000000..ada9e14 --- /dev/null +++ b/src/dctupsample.f @@ -0,0 +1,126 @@ +C ******************************************************************* +C +C DCT_UPSAMPLE - Upsample 1D grid data to finer CC grid using DCT. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE DCT_UPSAMPLE(Z, NZ, NCOLS, NNEW, IP, NOUT) +C ******************************************************************* +C +C DCT_UPSAMPLE upsamples 1D grid data from N nodes to NNEW nodes +C using DCT (Chebyshev extension) and returns only the even-indexed +C new nodes (the new hierarchical nodes at the finer level). +C +C Algorithm: +C 1. Extend Z: ZEXT = [Z; flip(Z(2:N-1))] length 2*(N-1) +C 2. FFT of ZEXT +C 3. Scale: chebc = FFT[1:N] / (N-1) * (NNEW-1) +C 4. Half the N-th coefficient +C 5. Pad with zeros to length 2*(NNEW-1) +C 6. IFFT +C 7. Return even-indexed values: ip = ipext(2,4,...,NNEW) +C +C NOTE: DFT/IDFT implemented as O(N^2) for correctness. +C For production use FFTPACK or similar. +C +C Parameters: +C +C Input, DOUBLE PRECISION Z(NZ,NCOLS) - input grid values (N rows) +C Input, INTEGER NZ - rows of Z (=N) +C Input, INTEGER NCOLS - columns of Z +C Input, INTEGER NNEW - size of new full grid +C Output, DOUBLE PRECISION IP(NOUT,NCOLS) - upsampled new nodes +C Input, INTEGER NOUT - output rows = (NNEW-1)/2 +C +C ******************************************************************* + + IMPLICIT NONE + + DOUBLE PRECISION PI + PARAMETER (PI = 3.14159265358979323846D+00) + + INTEGER NZ, NCOLS, NNEW, NOUT + DOUBLE PRECISION Z(NZ, NCOLS) + DOUBLE PRECISION IP(NOUT, NCOLS) + + INTEGER MAXM + PARAMETER (MAXM = 1024) + + INTEGER N, M, MN, I, J, K, COL + DOUBLE PRECISION ZEXT(MAXM) + DOUBLE PRECISION CREAL(MAXM), CIMAG(MAXM) + DOUBLE PRECISION CHEBC_R(MAXM), CHEBC_I(MAXM) + DOUBLE PRECISION IPEXT_R(MAXM), IPEXT_I(MAXM) + DOUBLE PRECISION ANGLE, SCALE_IN, SCALE_OUT, SUMRE, SUMIM + + N = NZ + M = 2 * (N - 1) + MN = 2 * (NNEW - 1) + + DO COL = 1, NCOLS + +C Build extended array + DO I = 1, N + ZEXT(I) = Z(I, COL) + END DO + DO I = 2, N - 1 + ZEXT(M - I + 2) = Z(I, COL) + END DO + +C Forward DFT of ZEXT length M + SCALE_IN = 1.0D+00 / DBLE(N-1) + DO K = 0, N - 1 + SUMRE = 0.0D+00 + SUMIM = 0.0D+00 + DO J = 0, M - 1 + ANGLE = -2.0D+00*PI*DBLE(J)*DBLE(K)/DBLE(M) + SUMRE = SUMRE + ZEXT(J+1) * DCOS(ANGLE) + SUMIM = SUMIM + ZEXT(J+1) * DSIN(ANGLE) + END DO + CHEBC_R(K+1) = SUMRE * SCALE_IN * DBLE(NNEW-1) + CHEBC_I(K+1) = SUMIM * SCALE_IN * DBLE(NNEW-1) + END DO + +C Halve the N-th coefficient (index N, 1-based) + CHEBC_R(N) = CHEBC_R(N) / 2.0D+00 + CHEBC_I(N) = CHEBC_I(N) / 2.0D+00 + +C Pad CHEBC to length MN with zeros + DO K = 1, N + CREAL(K) = CHEBC_R(K) + CIMAG(K) = CHEBC_I(K) + END DO + DO K = N + 1, MN + CREAL(K) = 0.0D+00 + CIMAG(K) = 0.0D+00 + END DO + +C Inverse DFT of length MN + SCALE_OUT = 1.0D+00 / DBLE(MN) + DO I = 0, MN - 1 + SUMRE = 0.0D+00 + DO K = 0, MN - 1 + ANGLE = 2.0D+00*PI*DBLE(K)*DBLE(I)/DBLE(MN) + SUMRE = SUMRE + CREAL(K+1)*DCOS(ANGLE) + & - CIMAG(K+1)*DSIN(ANGLE) + END DO + IPEXT_R(I+1) = SUMRE * SCALE_OUT + END DO + +C Return even-indexed values: 2, 4, ..., NNEW (1-based) +C i.e. ipext(2), ipext(4), ..., ipext(NNEW) + DO I = 1, NOUT + IP(I, COL) = IPEXT_R(2*I) + END DO + + END DO + + RETURN + END diff --git a/src/getchebnodes.f b/src/getchebnodes.f new file mode 100644 index 0000000..217ebf5 --- /dev/null +++ b/src/getchebnodes.f @@ -0,0 +1,54 @@ +C ******************************************************************* +C +C GET_CHEB_NODES - Chebyshev nodes for barycentric interpolation. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GET_CHEB_NODES(ALLNX, D, X, NX) +C ******************************************************************* +C +C GET_CHEB_NODES builds a flat array of Chebyshev-Lobatto nodes +C for each of the D dimensions with ALLNX(k) nodes in dim k. +C +C Node formula for dimension k with n = ALLNX(k) nodes: +C x_i = 0.5 - cos(i*pi/(n-1)) / 2, i = 0,...,n-1 +C +C Parameters: +C +C Input, INTEGER ALLNX(D) - node counts per dimension +C Input, INTEGER D - number of dimensions +C Output, DOUBLE PRECISION X(NX) - concatenated node array +C Input, INTEGER NX - total nodes = sum(ALLNX) +C +C ******************************************************************* + + IMPLICIT NONE + + DOUBLE PRECISION PI + PARAMETER (PI = 3.14159265358979323846D+00) + + INTEGER D, NX + INTEGER ALLNX(D) + DOUBLE PRECISION X(NX) + + INTEGER K, I, AID, N + + AID = 0 + DO K = 1, D + N = ALLNX(K) + DO I = 0, N-1 + X(AID+I+1) = 0.5D+00 + & - DCOS(DBLE(I)*PI/DBLE(N-1)) / 2.0D+00 + END DO + AID = AID + N + END DO + + RETURN + END diff --git a/src/getgpbaryw.f b/src/getgpbaryw.f new file mode 100644 index 0000000..d6f1a33 --- /dev/null +++ b/src/getgpbaryw.f @@ -0,0 +1,50 @@ +C ******************************************************************* +C +C GET_GP_BARY_W - Gauss-Patterson barycentric weights array. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GET_GP_BARY_W(ALLNX, D, W, NW) +C ******************************************************************* +C +C GET_GP_BARY_W builds a flat array of GP barycentric weights for +C each of the D dimensions. ALLNX(k) = 2^(lev+1)-1 for dim k. +C +C Parameters: +C +C Input, INTEGER ALLNX(D) - full node counts per dim +C Input, INTEGER D - number of dimensions +C Output, DOUBLE PRECISION W(NW) - concatenated weight array +C Input, INTEGER NW - total weights = sum(ALLNX) +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NW + INTEGER ALLNX(D) + DOUBLE PRECISION W(NW) + + INTEGER K, AID, N, LEV + + AID = 0 + DO K = 1, D + N = ALLNX(K) + LEV = 0 + 10 IF (2**(LEV+1) - 1 .LT. N) THEN + LEV = LEV + 1 + GOTO 10 + END IF + CALL GP_BARY_W(LEV, W(AID+1), N) + AID = AID + N + END DO + + RETURN + END diff --git a/src/getgpnodes.f b/src/getgpnodes.f new file mode 100644 index 0000000..25c1277 --- /dev/null +++ b/src/getgpnodes.f @@ -0,0 +1,51 @@ +C ******************************************************************* +C +C GET_GP_NODES - Gauss-Patterson nodes for barycentric interp. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GET_GP_NODES(ALLNX, D, X, NX) +C ******************************************************************* +C +C GET_GP_NODES builds a flat array of Gauss-Patterson nodes for +C each of the D dimensions. ALLNX(k) = 2^(lev+1)-1 for dim k. +C +C Parameters: +C +C Input, INTEGER ALLNX(D) - full node counts per dim +C Input, INTEGER D - number of dimensions +C Output, DOUBLE PRECISION X(NX) - concatenated node array +C Input, INTEGER NX - total nodes = sum(ALLNX) +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NX + INTEGER ALLNX(D) + DOUBLE PRECISION X(NX) + + INTEGER K, AID, N, LEV + + AID = 0 + DO K = 1, D + N = ALLNX(K) +C Level = log2(N+1) - 1 + LEV = 0 + 10 IF (2**(LEV+1) - 1 .LT. N) THEN + LEV = LEV + 1 + GOTO 10 + END IF + CALL GP_ABSC(LEV, X(AID+1), N) + AID = AID + N + END DO + + RETURN + END diff --git a/src/gpabsc.f b/src/gpabsc.f new file mode 100644 index 0000000..d320076 --- /dev/null +++ b/src/gpabsc.f @@ -0,0 +1,200 @@ +C ******************************************************************* +C +C GP_ABSC - Gauss-Patterson abscissae (nodes on [0,1]). +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GP_ABSC(LEVEL, X, NX) +C ******************************************************************* +C +C GP_ABSC returns the Gauss-Patterson nodes on [0,1] for the given +C LEVEL (0..6). NX = 2^(LEVEL+1) - 1 is the number of nodes. +C +C The raw data (symmetric half on [-1,1]) is stored with the +C largest (outermost) value first. Mirror + shift maps to [0,1]: +C x_i = 0.5 + raw(i) / 2 for i = 1,...,NX +C +C Parameters: +C +C Input, INTEGER LEVEL - GP level (0..6) +C Output, DOUBLE PRECISION X(NX) - node array +C Input, INTEGER NX - number of nodes = 2^(LEVEL+1)-1 +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER LEVEL, NX + DOUBLE PRECISION X(NX) + + INTEGER I, NHALF + DOUBLE PRECISION RAW(64) + + IF (LEVEL .EQ. 0) THEN + X(1) = 0.5D+00 + RETURN + END IF + + IF (LEVEL .EQ. 1) THEN + RAW(1) = 0.77459666924148337704D+00 + RAW(2) = 0.00000000000000000000D+00 + NHALF = 2 + ELSE IF (LEVEL .EQ. 2) THEN + RAW(1) = 0.96049126870802028342D+00 + RAW(2) = 0.77459666924148337704D+00 + RAW(3) = 0.43424374934680255800D+00 + RAW(4) = 0.00000000000000000000D+00 + NHALF = 4 + ELSE IF (LEVEL .EQ. 3) THEN + RAW(1) = 0.99383196321275502221D+00 + RAW(2) = 0.96049126870802028342D+00 + RAW(3) = 0.88845923287225699889D+00 + RAW(4) = 0.77459666924148337704D+00 + RAW(5) = 0.62110294673722640294D+00 + RAW(6) = 0.43424374934680255800D+00 + RAW(7) = 0.22338668642896688163D+00 + RAW(8) = 0.00000000000000000000D+00 + NHALF = 8 + ELSE IF (LEVEL .EQ. 4) THEN + RAW( 1) = 0.99909812496766759766D+00 + RAW( 2) = 0.99383196321275502221D+00 + RAW( 3) = 0.98153114955374010687D+00 + RAW( 4) = 0.96049126870802028342D+00 + RAW( 5) = 0.92965485742974005667D+00 + RAW( 6) = 0.88845923287225699889D+00 + RAW( 7) = 0.83672593816886873550D+00 + RAW( 8) = 0.77459666924148337704D+00 + RAW( 9) = 0.70249620649152707861D+00 + RAW(10) = 0.62110294673722640294D+00 + RAW(11) = 0.53131974364437562397D+00 + RAW(12) = 0.43424374934680255800D+00 + RAW(13) = 0.33113539325797683309D+00 + RAW(14) = 0.22338668642896688163D+00 + RAW(15) = 0.11248894313318662575D+00 + RAW(16) = 0.00000000000000000000D+00 + NHALF = 16 + ELSE IF (LEVEL .EQ. 5) THEN + RAW( 1) = 0.99987288812035761194D+00 + RAW( 2) = 0.99909812496766759766D+00 + RAW( 3) = 0.99720625937222195908D+00 + RAW( 4) = 0.99383196321275502221D+00 + RAW( 5) = 0.98868475754742947994D+00 + RAW( 6) = 0.98153114955374010687D+00 + RAW( 7) = 0.97218287474858179658D+00 + RAW( 8) = 0.96049126870802028342D+00 + RAW( 9) = 0.94634285837340290515D+00 + RAW(10) = 0.92965485742974005667D+00 + RAW(11) = 0.91037115695700429250D+00 + RAW(12) = 0.88845923287225699889D+00 + RAW(13) = 0.86390793819369047715D+00 + RAW(14) = 0.83672593816886873550D+00 + RAW(15) = 0.80694053195021761186D+00 + RAW(16) = 0.77459666924148337704D+00 + RAW(17) = 0.73975604435269475868D+00 + RAW(18) = 0.70249620649152707861D+00 + RAW(19) = 0.66290966002478059546D+00 + RAW(20) = 0.62110294673722640294D+00 + RAW(21) = 0.57719571005204581484D+00 + RAW(22) = 0.53131974364437562397D+00 + RAW(23) = 0.48361802694584102756D+00 + RAW(24) = 0.43424374934680255800D+00 + RAW(25) = 0.38335932419873034692D+00 + RAW(26) = 0.33113539325797683309D+00 + RAW(27) = 0.27774982202182431507D+00 + RAW(28) = 0.22338668642896688163D+00 + RAW(29) = 0.16823525155220746498D+00 + RAW(30) = 0.11248894313318662575D+00 + RAW(31) = 0.05634431304659278997D+00 + RAW(32) = 0.00000000000000000000D+00 + NHALF = 32 + ELSE IF (LEVEL .EQ. 6) THEN + RAW( 1) = 0.99998243035489159858D+00 + RAW( 2) = 0.99987288812035761194D+00 + RAW( 3) = 0.99959879967191068325D+00 + RAW( 4) = 0.99909812496766759766D+00 + RAW( 5) = 0.99831663531840739253D+00 + RAW( 6) = 0.99720625937222195908D+00 + RAW( 7) = 0.99572410469840718851D+00 + RAW( 8) = 0.99383196321275502221D+00 + RAW( 9) = 0.99149572117810613240D+00 + RAW(10) = 0.98868475754742947994D+00 + RAW(11) = 0.98537149959852037111D+00 + RAW(12) = 0.98153114955374010687D+00 + RAW(13) = 0.97714151463970571416D+00 + RAW(14) = 0.97218287474858179658D+00 + RAW(15) = 0.96663785155841656709D+00 + RAW(16) = 0.96049126870802028342D+00 + RAW(17) = 0.95373000642576113641D+00 + RAW(18) = 0.94634285837340290515D+00 + RAW(19) = 0.93832039777959288365D+00 + RAW(20) = 0.92965485742974005667D+00 + RAW(21) = 0.92034002547001242073D+00 + RAW(22) = 0.91037115695700429250D+00 + RAW(23) = 0.89974489977694003664D+00 + RAW(24) = 0.88845923287225699889D+00 + RAW(25) = 0.87651341448470526974D+00 + RAW(26) = 0.86390793819369047715D+00 + RAW(27) = 0.85064449476835027976D+00 + RAW(28) = 0.83672593816886873550D+00 + RAW(29) = 0.82215625436498040737D+00 + RAW(30) = 0.80694053195021761186D+00 + RAW(31) = 0.79108493379984836143D+00 + RAW(32) = 0.77459666924148337704D+00 + RAW(33) = 0.75748396638051363793D+00 + RAW(34) = 0.73975604435269475868D+00 + RAW(35) = 0.72142308537009891548D+00 + RAW(36) = 0.70249620649152707861D+00 + RAW(37) = 0.68298743109107922809D+00 + RAW(38) = 0.66290966002478059546D+00 + RAW(39) = 0.64227664250975951377D+00 + RAW(40) = 0.62110294673722640294D+00 + RAW(41) = 0.59940393024224289297D+00 + RAW(42) = 0.57719571005204581484D+00 + RAW(43) = 0.55449513263193254887D+00 + RAW(44) = 0.53131974364437562397D+00 + RAW(45) = 0.50768775753371660215D+00 + RAW(46) = 0.48361802694584102756D+00 + RAW(47) = 0.45913001198983233287D+00 + RAW(48) = 0.43424374934680255800D+00 + RAW(49) = 0.40897982122988867241D+00 + RAW(50) = 0.38335932419873034692D+00 + RAW(51) = 0.35740383783153215238D+00 + RAW(52) = 0.33113539325797683309D+00 + RAW(53) = 0.30457644155671404334D+00 + RAW(54) = 0.27774982202182431507D+00 + RAW(55) = 0.25067873030348317661D+00 + RAW(56) = 0.22338668642896688163D+00 + RAW(57) = 0.19589750271110015392D+00 + RAW(58) = 0.16823525155220746498D+00 + RAW(59) = 0.14042423315256017459D+00 + RAW(60) = 0.11248894313318662575D+00 + RAW(61) = 0.08445404008371088371D+00 + RAW(62) = 0.05634431304659278997D+00 + RAW(63) = 0.02818464894974569434D+00 + RAW(64) = 0.00000000000000000000D+00 + NHALF = 64 + ELSE + DO I = 1, NX + X(I) = 0.5D+00 + END DO + RETURN + END IF + +C Mirror: full set = [-raw(1:NHALF-1), +raw(NHALF:-1:1)] mapped to [0,1] +C NX = 2*NHALF - 1 for level >= 1 + DO I = 1, NHALF - 1 + X(I) = 0.5D+00 - RAW(I) / 2.0D+00 + END DO + DO I = NHALF, NX + X(I) = 0.5D+00 + RAW(NX - I + 1) / 2.0D+00 + END DO + + RETURN + END diff --git a/src/gpbaryw.f b/src/gpbaryw.f new file mode 100644 index 0000000..4d728a8 --- /dev/null +++ b/src/gpbaryw.f @@ -0,0 +1,195 @@ +C ******************************************************************* +C +C GP_BARY_W - Gauss-Patterson barycentric weights. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GP_BARY_W(LEVEL, W, NW) +C ******************************************************************* +C +C GP_BARY_W returns the Gauss-Patterson barycentric weights for +C the given LEVEL (0..6). NW = 2^(LEVEL+1)-1 weights. +C +C Parameters: +C +C Input, INTEGER LEVEL - GP level (0..6) +C Output, DOUBLE PRECISION W(NW) - barycentric weight array +C Input, INTEGER NW - number of weights +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER LEVEL, NW + DOUBLE PRECISION W(NW) + + INTEGER I, NHALF + DOUBLE PRECISION RAW(64) + + IF (LEVEL .EQ. 0) THEN + W(1) = 1.0D+00 + RETURN + END IF + + IF (LEVEL .EQ. 1) THEN + RAW(1) = 0.5000000000000000D+00 + RAW(2) =-1.0000000000000000D+00 + NHALF = 2 + ELSE IF (LEVEL .EQ. 2) THEN + RAW(1) = 0.2389562399804650D+00 + RAW(2) =-0.6554465493910690D+00 + RAW(3) = 0.9164903094106040D+00 + RAW(4) =-1.0000000000000000D+00 + NHALF = 4 + ELSE IF (LEVEL .EQ. 3) THEN + RAW(1) = 0.3085732742889269D+00 + RAW(2) =-0.7759609054034582D+00 + RAW(3) = 0.9730577082723834D+00 + RAW(4) =-1.0000000000000000D+00 + RAW(5) = 0.9651128023399624D+00 + RAW(6) =-0.9214581385020298D+00 + RAW(7) = 0.8904181282668750D+00 + RAW(8) =-0.8794857385253193D+00 + NHALF = 8 + ELSE IF (LEVEL .EQ. 4) THEN + RAW( 1) = 0.4284765107713552D+00 + RAW( 2) =-0.9674952645753376D+00 + RAW( 3) = 1.0000000000000000D+00 + RAW( 4) =-0.8073763738936263D+00 + RAW( 5) = 0.5959019645796662D+00 + RAW( 6) =-0.4284109973194455D+00 + RAW( 7) = 0.3090233900161985D+00 + RAW( 8) =-0.2271083041792955D+00 + RAW( 9) = 0.1715268309161644D+00 + RAW(10) =-0.1338120870473934D+00 + RAW(11) = 0.1081546364527703D+00 + RAW(12) =-0.9073420363174436D-01 + RAW(13) = 0.7909270443820152D-01 + RAW(14) =-0.7168121786085341D-01 + RAW(15) = 0.6756488645338050D-01 + RAW(16) =-0.6624495024008108D-01 + NHALF = 16 + ELSE IF (LEVEL .EQ. 5) THEN + RAW( 1) = 0.4761689774882791D+00 + RAW( 2) =-1.0000000000000000D+00 + RAW( 3) = 0.8951802743821745D+00 + RAW( 4) =-0.5931880330727972D+00 + RAW( 5) = 0.3446713216950697D+00 + RAW( 6) =-0.1887366139903847D+00 + RAW( 7) = 0.1010026343422441D+00 + RAW( 8) =-0.5391510866030504D-01 + RAW( 9) = 0.2905688513918218D-01 + RAW(10) =-0.1592647206003192D-01 + RAW(11) = 0.8917516424034839D-02 + RAW(12) =-0.5114369427222019D-02 + RAW(13) = 0.3009406140647174D-02 + RAW(14) =-0.1818655851124444D-02 + RAW(15) = 0.1129456765407580D-02 + RAW(16) =-0.7211000286455359D-03 + RAW(17) = 0.4733817579545664D-03 + RAW(18) =-0.3195580143057075D-03 + RAW(19) = 0.2218227922218166D-03 + RAW(20) =-0.1583257717017353D-03 + RAW(21) = 0.1161821456470950D-03 + RAW(22) =-0.8764186196347213D-04 + RAW(23) = 0.6795226274872346D-04 + RAW(24) =-0.5414390533152548D-04 + RAW(25) = 0.4432843387685334D-04 + RAW(26) =-0.3728544273524615D-04 + RAW(27) = 0.3221531510574336D-04 + RAW(28) =-0.2858909101688675D-04 + RAW(29) = 0.2605612252662254D-04 + RAW(30) =-0.2438687098702864D-04 + RAW(31) = 0.2343764915145207D-04 + RAW(32) =-0.2312961543917948D-04 + NHALF = 32 + ELSE IF (LEVEL .EQ. 6) THEN + RAW( 1) = 0.4961729559598435D+00 + RAW( 2) =-1.0000000000000000D+00 + RAW( 3) = 0.8207381753150933D+00 + RAW( 4) =-0.4792004570535736D+00 + RAW( 5) = 0.2370422415813909D+00 + RAW( 6) =-0.1072561730738105D+00 + RAW( 7) = 0.4621060128445129D-01 + RAW( 8) =-0.1940652396239318D-01 + RAW( 9) = 0.8059646108315423D-02 + RAW(10) =-0.3341041271341492D-02 + RAW(11) = 0.1391076421696200D-02 + RAW(12) =-0.5842685337623964D-03 + RAW(13) = 0.2483284595990911D-03 + RAW(14) =-0.1070501794628850D-03 + RAW(15) = 0.4688369444739026D-04 + RAW(16) =-0.2088588637335537D-04 + RAW(17) = 0.9472111162631427D-05 + RAW(18) =-0.4375709775264854D-05 + RAW(19) = 0.2059732032056890D-05 + RAW(20) =-0.9881358613753917D-06 + RAW(21) = 0.4831683085525125D-06 + RAW(22) =-0.2407971733363551D-06 + RAW(23) = 0.1223052268446143D-06 + RAW(24) =-0.6330402849816564D-07 + RAW(25) = 0.3338496881971787D-07 + RAW(26) =-0.1793649137605078D-07 + RAW(27) = 0.9815684853054276D-08 + RAW(28) =-0.5470515716389574D-08 + RAW(29) = 0.3104474727365682D-08 + RAW(30) =-0.1793611788188222D-08 + RAW(31) = 0.1054820854605286D-08 + RAW(32) =-0.6313496351815102D-09 + RAW(33) = 0.3845341465065858D-09 + RAW(34) =-0.2382913467980216D-09 + RAW(35) = 0.1502195793889654D-09 + RAW(36) =-0.9632258977165985D-10 + RAW(37) = 0.6281356365121918D-10 + RAW(38) =-0.4165284046151541D-10 + RAW(39) = 0.2808315197701900D-10 + RAW(40) =-0.1924877719136859D-10 + RAW(41) = 0.1341110141171783D-10 + RAW(42) =-0.9496850740372480D-11 + RAW(43) = 0.6834400742017730D-11 + RAW(44) =-0.4997820893808907D-11 + RAW(45) = 0.3713437304234225D-11 + RAW(46) =-0.2803137557864015D-11 + RAW(47) = 0.2149539400208833D-11 + RAW(48) =-0.1674329496665498D-11 + RAW(49) = 0.1324630884002739D-11 + RAW(50) =-0.1064322552770007D-11 + RAW(51) = 0.8684488054281593D-12 + RAW(52) =-0.7195782254680809D-12 + RAW(53) = 0.6054064647792446D-12 + RAW(54) =-0.5171611918326179D-12 + RAW(55) = 0.4485298932818066D-12 + RAW(56) =-0.3949320660460756D-12 + RAW(57) = 0.3530206289342299D-12 + RAW(58) =-0.3203375146310410D-12 + RAW(59) = 0.2950741104537000D-12 + RAW(60) =-0.2759038562229908D-12 + RAW(61) = 0.2618651977225959D-12 + RAW(62) =-0.2522803113493657D-12 + RAW(63) = 0.2466998748736465D-12 + RAW(64) =-0.2448675056241474D-12 + NHALF = 64 + ELSE + DO I = 1, NW + W(I) = 0.0D+00 + END DO + RETURN + END IF + +C Mirror: W = [raw(1:NHALF-1), fliplr(raw(1:NHALF))] + DO I = 1, NHALF - 1 + W(I) = RAW(I) + END DO + DO I = NHALF, NW + W(I) = RAW(NW - I + 1) + END DO + + RETURN + END diff --git a/src/gpweights.f b/src/gpweights.f new file mode 100644 index 0000000..00ca888 --- /dev/null +++ b/src/gpweights.f @@ -0,0 +1,151 @@ +C ******************************************************************* +C +C GP_WEIGHTS - 1-D Gauss-Patterson quadrature weights. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE GP_WEIGHTS(MAXLEV, WEIGHTS, NW, STARTID) +C ******************************************************************* +C +C GP_WEIGHTS builds the flat 1-D weight array and level start +C indices for SPQUADW_GP, mirroring MATLAB gpweights.m. +C +C For each level: weights include both new and mirrored half. +C All weights are divided by 2 (range [0,1] vs [-1,1]). +C +C Parameters: +C +C Input, INTEGER MAXLEV - max GP level (0..6) +C Output, DOUBLE PRECISION WEIGHTS(NW) - flat weight array +C Input, INTEGER NW - size of WEIGHTS +C Output, INTEGER STARTID(MAXLEV+1) - level start indices +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER MAXLEV, NW + DOUBLE PRECISION WEIGHTS(NW) + INTEGER STARTID(*) + + INTEGER LEV, I, WID, NW1 + DOUBLE PRECISION W(64) + + WID = 0 + + DO LEV = 0, MAXLEV + + IF (LEV .EQ. 0) THEN + W(1) = 2.0D+00 + NW1 = 1 + ELSE IF (LEV .EQ. 1) THEN + W(1) = 0.555555555555555555556D+00 + NW1 = 1 + ELSE IF (LEV .EQ. 2) THEN + W(1) = 0.104656226026467265194D+00 + W(2) = 0.401397414775962222905D+00 + NW1 = 2 + ELSE IF (LEV .EQ. 3) THEN + W(1) = 0.170017196299402603390D-01 + W(2) = 0.929271953151245376859D-01 + W(3) = 0.171511909136391380787D+00 + W(4) = 0.219156858401587496404D+00 + NW1 = 4 + ELSE IF (LEV .EQ. 4) THEN + W(1) = 0.254478079156187441540D-02 + W(2) = 0.164460498543878109338D-01 + W(3) = 0.359571033071293220968D-01 + W(4) = 0.569795094941233574122D-01 + W(5) = 0.768796204990035310427D-01 + W(6) = 0.936271099812644736167D-01 + W(7) = 0.105669893580234809744D+00 + W(8) = 0.111956873020953456880D+00 + NW1 = 8 + ELSE IF (LEV .EQ. 5) THEN + W(1) = 0.363221481845530659694D-03 + W(2) = 0.257904979468568827243D-02 + W(3) = 0.611550682211724633968D-02 + W(4) = 0.104982469096213218983D-01 + W(5) = 0.154067504665594978021D-01 + W(6) = 0.205942339159127111492D-01 + W(7) = 0.258696793272147469108D-01 + W(8) = 0.310735511116879648799D-01 + W(9) = 0.360644327807825726401D-01 + W(10) = 0.407155101169443189339D-01 + W(11) = 0.449145316536321974143D-01 + W(12) = 0.485643304066731987159D-01 + W(13) = 0.515832539520484587768D-01 + W(14) = 0.539054993352660639269D-01 + W(15) = 0.554814043565593639878D-01 + W(16) = 0.562776998312543012726D-01 + NW1 = 16 + ELSE IF (LEV .EQ. 6) THEN + W(1) = 0.505360952078625176247D-04 + W(2) = 0.377746646326984660274D-03 + W(3) = 0.938369848542381500794D-03 + W(4) = 0.168114286542146990631D-02 + W(5) = 0.256876494379402037313D-02 + W(6) = 0.357289278351729964938D-02 + W(7) = 0.467105037211432174741D-02 + W(8) = 0.584344987583563950756D-02 + W(9) = 0.707248999543355546805D-02 + W(10) = 0.834283875396815770558D-02 + W(11) = 0.964117772970253669530D-02 + W(12) = 0.109557333878379016480D-01 + W(13) = 0.122758305600827700870D-01 + W(14) = 0.135915710097655467896D-01 + W(15) = 0.148936416648151820348D-01 + W(16) = 0.161732187295777199419D-01 + W(17) = 0.174219301594641737472D-01 + W(18) = 0.186318482561387901863D-01 + W(19) = 0.197954950480974994880D-01 + W(20) = 0.209058514458120238522D-01 + W(21) = 0.219563663053178249393D-01 + W(22) = 0.229409642293877487608D-01 + W(23) = 0.238540521060385400804D-01 + W(24) = 0.246905247444876769091D-01 + W(25) = 0.254457699654647658126D-01 + W(26) = 0.261156733767060976805D-01 + W(27) = 0.266966229274503599062D-01 + W(28) = 0.271855132296247918192D-01 + W(29) = 0.275797495664818730349D-01 + W(30) = 0.278772514766137016085D-01 + W(31) = 0.280764557938172466068D-01 + W(32) = 0.281763190330166021307D-01 + NW1 = 32 + ELSE + NW1 = 2**(LEV-2) + DO I = 1, NW1 + W(I) = 0.0D+00 + END DO + END IF + +C Divide by 2 for [0,1] range + DO I = 1, NW1 + W(I) = W(I) / 2.0D+00 + END DO + + STARTID(LEV+1) = WID + 1 + DO I = 1, NW1 + WEIGHTS(WID + I) = W(I) + END DO + WID = WID + NW1 + + IF (LEV .GT. 0) THEN + DO I = 1, NW1 + WEIGHTS(WID + I) = W(NW1 - I + 1) + END DO + WID = WID + NW1 + END IF + + END DO + + RETURN + END diff --git a/src/meson.build b/src/meson.build index 66d0296..1887eb0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,7 +22,7 @@ fortranobject_c = run_command(py, check: true ).stdout().strip() -py_mod_name = '' +py_mod_name = 'spinterp' fortran_sources = run_command(py, ['-c', 'import glob; print(" ".join(glob.glob("*.f")))' @@ -35,7 +35,7 @@ python_sources = run_command(py, ['-c', message('Python sources: ', python_sources) pyf_sources = [ - '.pyf' + 'spinterp.pyf' ] message('Fortran sources: ', fortran_sources + pyf_sources) diff --git a/src/nchoosek.f b/src/nchoosek.f new file mode 100644 index 0000000..d0d8a64 --- /dev/null +++ b/src/nchoosek.f @@ -0,0 +1,39 @@ +C ******************************************************************* +C +C NCHOOSEK - Binomial coefficient C(N, K). +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + INTEGER FUNCTION NCHOOSEK(N, K) +C ******************************************************************* +C +C NCHOOSEK computes the binomial coefficient C(N, K). +C +C Parameters: +C +C Input, INTEGER N - total elements +C Input, INTEGER K - chosen elements +C +C Return, INTEGER NCHOOSEK - binomial coefficient +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER N, K, I, RESULT + + RESULT = 1 + DO I = 0, K - 1 + RESULT = RESULT * (N - I) / (I + 1) + END DO + NCHOOSEK = RESULT + + RETURN + END diff --git a/src/popheap.f b/src/popheap.f new file mode 100644 index 0000000..494b13a --- /dev/null +++ b/src/popheap.f @@ -0,0 +1,74 @@ +C ******************************************************************* +C +C POP_HEAP - Pop max from max-heap and return its index. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE POP_HEAP(A, NA, G, NG, INDEX) +C ******************************************************************* +C +C POP_HEAP removes the max element from the heap (A(1)), replacing +C it with A(NA), then sifts down to restore the heap property. +C Returns the popped index in INDEX. +C +C Caller must set A(NA) to the replacement value before calling, +C and decrement NA after the call. +C +C Parameters: +C +C In/out, INTEGER A(NA) - heap indices (A(NA) = replacement) +C Input, INTEGER NA - current heap size +C Input, DOUBLE PRECISION G(NG) - priority values +C Input, INTEGER NG - length of G +C Output, INTEGER INDEX - popped index (old A(1)) +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NA, NG + INTEGER A(NA) + DOUBLE PRECISION G(NG) + INTEGER INDEX + + INTEGER K, LEFT, RIGHT, LARGEST, TMP + + INDEX = A(1) + A(1) = A(NA) + + K = 1 + + 10 CONTINUE + LEFT = 2 * K + RIGHT = 2 * K + 1 + +C Find largest among k, left, right (within NA-1 elements) + LARGEST = K + IF (LEFT .LE. NA - 1) THEN + IF (G(A(LEFT)) .GT. G(A(LARGEST))) THEN + LARGEST = LEFT + END IF + END IF + IF (RIGHT .LE. NA - 1) THEN + IF (G(A(RIGHT)) .GT. G(A(LARGEST))) THEN + LARGEST = RIGHT + END IF + END IF + + IF (LARGEST .NE. K) THEN + TMP = A(K) + A(K) = A(LARGEST) + A(LARGEST) = TMP + K = LARGEST + GOTO 10 + END IF + + RETURN + END diff --git a/src/ppderiv.f b/src/ppderiv.f new file mode 100644 index 0000000..9213309 --- /dev/null +++ b/src/ppderiv.f @@ -0,0 +1,102 @@ +C ******************************************************************* +C +C PP_DERIV - Post-processing for continuous derivatives. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE PP_DERIV(IPDER, IPDER2, NINTERP, D, + & MAXLEVVEC, Y) +C ******************************************************************* +C +C PP_DERIV applies post-processing to produce continuously +C differentiable derivatives by blending IPDER and IPDER2. +C +C For each point k and dimension l: +C - Compute ytd1 (cell center) and stepsize from maxlev. +C - If ipd1 * ipd2 >= 0: linear interpolation between boundaries. +C - Else if yt <= ytd1 + halfstep: decay from ipd1 toward zero. +C - Else: ramp from zero toward ipd2. +C +C Parameters: +C +C In/out, DOUBLE PRECISION IPDER(NINTERP,D) - gradient (modified) +C Input, DOUBLE PRECISION IPDER2(NINTERP,D) - augmented gradient +C Input, INTEGER NINTERP - number of points +C Input, INTEGER D - dimension +C Input, INTEGER MAXLEVVEC(D) - max level per dim +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NINTERP, D + DOUBLE PRECISION IPDER(NINTERP, D) + DOUBLE PRECISION IPDER2(NINTERP, D) + INTEGER MAXLEVVEC(D) + DOUBLE PRECISION Y(NINTERP, D) + + INTEGER K, L, MAXLEV, XP + DOUBLE PRECISION YT, YTD1, STEPSIZE, HALFSTEP + DOUBLE PRECISION IPD1, IPD2, ALPHA + + DO L = 1, D + MAXLEV = MAXLEVVEC(L) + IF (MAXLEV .EQ. 0) GOTO 200 + + STEPSIZE = 1.0D+00 / DBLE(2**MAXLEV) + HALFSTEP = STEPSIZE / 2.0D+00 + + DO K = 1, NINTERP + YT = Y(K, L) + IPD1 = IPDER(K, L) + IPD2 = IPDER2(K, L) + +C Find the cell center ytd1 + XP = INT(YT / STEPSIZE) + IF (XP .GE. 2**MAXLEV) XP = 2**MAXLEV - 1 + YTD1 = DBLE(XP)*STEPSIZE + HALFSTEP + + IF (IPD1 * IPD2 .GE. 0.0D+00) THEN +C Same sign: linear blend + IF (STEPSIZE .GT. 0.0D+00) THEN + ALPHA = (YT - YTD1) / STEPSIZE + IF (ALPHA .LT. 0.0D+00) ALPHA = 0.0D+00 + IF (ALPHA .GT. 1.0D+00) ALPHA = 1.0D+00 + IPDER(K, L) = (1.0D+00 - ALPHA)*IPD1 + & + ALPHA*IPD2 + END IF + + ELSE IF (YT .LE. YTD1 + HALFSTEP) THEN +C Left half of cell: taper ipd1 toward zero + IF (HALFSTEP .GT. 0.0D+00) THEN + ALPHA = (YT - YTD1) / HALFSTEP + IF (ALPHA .LT. 0.0D+00) ALPHA = 0.0D+00 + IF (ALPHA .GT. 1.0D+00) ALPHA = 1.0D+00 + IPDER(K, L) = IPD1 * (1.0D+00 - ALPHA) + END IF + + ELSE +C Right half of cell: ramp from zero toward ipd2 + IF (HALFSTEP .GT. 0.0D+00) THEN + ALPHA = (YT - YTD1 - HALFSTEP) / HALFSTEP + IF (ALPHA .LT. 0.0D+00) ALPHA = 0.0D+00 + IF (ALPHA .GT. 1.0D+00) ALPHA = 1.0D+00 + IPDER(K, L) = IPD2 * ALPHA + END IF + END IF + + END DO + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/reordervals.f b/src/reordervals.f new file mode 100644 index 0000000..453e9b5 --- /dev/null +++ b/src/reordervals.f @@ -0,0 +1,161 @@ +C ******************************************************************* +C +C REORDER_VALS - Reorder hierarchical surplus array for CB grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE REORDER_VALS(Z, NZ, LEVELSEQ, NLEVELS, D) +C ******************************************************************* +C +C REORDER_VALS permutes the hierarchical surplus array Z so that +C dimensions are sorted by level descending within each subgrid. +C This matches the layout expected by BARY_PD_STEP_CB. +C +C Uses a temporary buffer ZTMP of size MAXA=16384. +C Permutation is implemented by building old->new index mappings +C and copying in and out of the temp buffer. +C +C Parameters: +C +C In/out, DOUBLE PRECISION Z(NZ) - surpluses to reorder +C Input, INTEGER NZ - length of Z +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER MAXA + PARAMETER (MAXA = 16384) + + INTEGER NZ, NLEVELS, D + DOUBLE PRECISION Z(NZ) + INTEGER LEVELSEQ(NLEVELS, D) + + INTEGER KL, K, L, LVAL, NPTS + INTEGER NPTS_DIM(50), ORDER(50), ORDERARR(50), LEVEL(50) + INTEGER NEWPTS_DIM(50) + INTEGER STRIDE_OLD(50), STRIDE_NEW(50) + INTEGER FLAT_OLD, FLAT_NEW + INTEGER INDEX + INTEGER COORD(50), COORD_PERM(50) + INTEGER DIM_PERM + DOUBLE PRECISION ZTMP(MAXA) + INTEGER I, J, TMP + +C Working index into Z + INDEX = 1 + + DO KL = 1, NLEVELS + +C Compute npts per dim and total + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + LEVEL(L) = LVAL + IF (LVAL .EQ. 0) THEN + NPTS_DIM(L) = 1 + ELSE IF (LVAL .LE. 2) THEN + NPTS_DIM(L) = 2 + ELSE + NPTS_DIM(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * NPTS_DIM(L) + END DO + + IF (NPTS .EQ. 1) THEN + INDEX = INDEX + 1 + GOTO 100 + END IF + +C Build sort order: insertion sort by level descending + DO L = 1, D + ORDER(L) = L + END DO + DO L = 2, D + TMP = ORDER(L) + J = L - 1 + 10 IF (J .GE. 1 .AND. + & LEVEL(ORDER(J)) .LT. LEVEL(TMP)) THEN + ORDER(J+1) = ORDER(J) + J = J - 1 + GOTO 10 + END IF + ORDER(J+1) = TMP + END DO + +C Check if order is identity - if so, skip + DO L = 1, D + IF (ORDER(L) .NE. L) GOTO 20 + END DO + INDEX = INDEX + NPTS + GOTO 100 + + 20 CONTINUE + +C Build inverse permutation: ORDERARR(dim_new_pos) = dim_old_pos +C ORDER(l) = which old dim goes to new position l +C So new dim l has points from old dim ORDER(l) + DO L = 1, D + NEWPTS_DIM(L) = NPTS_DIM(ORDER(L)) + END DO + +C Compute strides for old layout (Fortran col-major over dims 1..D) + STRIDE_OLD(1) = 1 + DO L = 2, D + STRIDE_OLD(L) = STRIDE_OLD(L-1) * NPTS_DIM(L-1) + END DO + +C Compute strides for new layout (after permutation) + STRIDE_NEW(1) = 1 + DO L = 2, D + STRIDE_NEW(L) = STRIDE_NEW(L-1) * NEWPTS_DIM(L-1) + END DO + +C Copy permuted data into ZTMP +C For each flat new index, compute old flat index + DO I = 0, NPTS - 1 +C Decode new flat index into coordinates + K = I + DO L = D, 1, -1 + COORD(L) = K / STRIDE_NEW(L) + K = K - COORD(L) * STRIDE_NEW(L) + END DO + +C COORD(L) is coordinate in new-dim L = old dim ORDER(L) +C Remap to old coordinates: old dim ORDER(L) gets COORD(L) + DO L = 1, D + COORD_PERM(ORDER(L)) = COORD(L) + END DO + +C Compute old flat index + FLAT_OLD = 0 + DO L = 1, D + FLAT_OLD = FLAT_OLD + + & COORD_PERM(L)*STRIDE_OLD(L) + END DO + + ZTMP(I+1) = Z(INDEX + FLAT_OLD) + END DO + +C Copy back + DO I = 0, NPTS - 1 + Z(INDEX + I) = ZTMP(I+1) + END DO + + INDEX = INDEX + NPTS + + 100 CONTINUE + END DO + + RETURN + END diff --git a/src/sortheap.f b/src/sortheap.f new file mode 100644 index 0000000..35d7d6f --- /dev/null +++ b/src/sortheap.f @@ -0,0 +1,54 @@ +C ******************************************************************* +C +C SORT_HEAP - Max-heap sift-up after inserting at position NA. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SORT_HEAP(A, NA, G, NG) +C ******************************************************************* +C +C SORT_HEAP sifts up the element at position NA in the heap A, +C restoring the max-heap property with respect to priorities G. +C +C The heap stores indices into G; A(k) is an index such that +C G(A(k)) is the priority at heap position k. +C +C Parameters: +C +C In/out, INTEGER A(NA) - heap indices (1-based) +C Input, INTEGER NA - current heap size (element added) +C Input, DOUBLE PRECISION G(NG) - priority values +C Input, INTEGER NG - length of G +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NA, NG + INTEGER A(NA) + DOUBLE PRECISION G(NG) + + INTEGER K, PREV, TMP + + K = NA + + 10 IF (K .GT. 1) THEN + PREV = K / 2 + IF (G(A(PREV)) .LT. G(A(K))) THEN + TMP = A(PREV) + A(PREV) = A(K) + A(K) = TMP + K = PREV + GOTO 10 + END IF + END IF + + RETURN + END diff --git a/src/spcmpvalscb.f b/src/spcmpvalscb.f new file mode 100644 index 0000000..cd5ea55 --- /dev/null +++ b/src/spcmpvalscb.f @@ -0,0 +1,163 @@ +C ******************************************************************* +C +C SPCMPVALS_CB - Compute hierarchical surpluses, Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CB(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_CB computes the interpolant at new Chebyshev grid +C points Y using old surpluses Z from previous levels. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L, LVAL, NPTS, NDIMS, INDEX + INTEGER ALLNX(50), DIMS(50), LEVEL(50), ORDERARR(50) + INTEGER NNEWPTS(512), SKIPLEVEL, XTOT + INTEGER KSTART, KEND + DOUBLE PRECISION XBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + +C Pre-count new subgrid points + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + LVAL = NEWLEVELSEQ(NKL, L) + IF (LVAL .EQ. 0) THEN + ELSE IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + LEVEL(L) = LVAL + IF (LVAL .EQ. 0) THEN + ELSE IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + + IF (NPTS .GT. 1) THEN +C Sort dimensions descending by level + DO L = 1, D + ORDERARR(L) = L + END DO + DO L = 1, D-1 + DO K = L+1, D + IF (LEVEL(ORDERARR(K)) .GT. + & LEVEL(ORDERARR(L))) THEN + LVAL = ORDERARR(L) + ORDERARR(L) = ORDERARR(K) + ORDERARR(K) = LVAL + END IF + END DO + END DO + + NDIMS = 0 + XTOT = 0 + DO L = 1, D + LVAL = LEVEL(ORDERARR(L)) + IF (LVAL .GT. 0) THEN + NDIMS = NDIMS + 1 + DIMS(NDIMS) = ORDERARR(L) + ALLNX(NDIMS) = 2**LVAL + 1 + XTOT = XTOT + ALLNX(NDIMS) + END IF + END DO + + CALL GET_CHEB_NODES(ALLNX, NDIMS, XBUF, XTOT) + + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN + DO K = 1, KEND - KSTART + 1 + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_CB( + & Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, + & Y(KSTART,1), + & KEND-KSTART+1, D, + & IPTEMP) + DO K = KSTART, KEND + IP(K) = IP(K) + + & IPTEMP(K-KSTART+1) + END DO + END IF + + KSTART = KEND + 1 + END DO + + ELSE +C Single-node subgrid: constant contribution + DO K = 1, NY + IP(K) = IP(K) + Z(INDEX) + END DO + END IF + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcmpvalscbdct.f b/src/spcmpvalscbdct.f new file mode 100644 index 0000000..1478cc0 --- /dev/null +++ b/src/spcmpvalscbdct.f @@ -0,0 +1,147 @@ +C ******************************************************************* +C +C SPCMPVALS_CB_DCT - CB surplus computation using DCT. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CB_DCT(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_CB_DCT computes the hierarchical surplus increments for +C Chebyshev grid using DCT-based upsampling instead of barycentric +C interpolation. +C +C For each old subgrid (LEVELSEQ row kl), for each new subgrid +C (NEWLEVELSEQ row nkl) that it contributes to, calls SP_DCT_UP_STEP. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L, LVAL, NPTS, NDIMS + INTEGER INDEX, KSTART, KEND + INTEGER NNEWPTS(512), SKIPLEVEL + INTEGER OLDLEV(50), NEWLEV(50), DIMS(50) + DOUBLE PRECISION IPBUF(16384) + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + +C Pre-count new subgrid points + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + LVAL = NEWLEVELSEQ(NKL, L) + IF (LVAL .EQ. 0) THEN + ELSE IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + +C Count old subgrid points and collect active dims/levels + NPTS = 1 + NDIMS = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .GT. 0) THEN + NDIMS = NDIMS + 1 + OLDLEV(NDIMS) = LVAL + DIMS(NDIMS) = L + IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END IF + END DO + + IF (NPTS .GT. 1) THEN + + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN +C Collect new levels for this NKL + DO L = 1, NDIMS + NEWLEV(L) = NEWLEVELSEQ(NKL,DIMS(L)) + END DO + + DO K = 1, KEND - KSTART + 1 + IPBUF(K) = 0.0D+00 + END DO + + CALL SP_DCT_UP_STEP(Z(INDEX), NPTS, + & OLDLEV, NDIMS, + & NEWLEV, NDIMS, DIMS, + & IPBUF, KEND-KSTART+1) + + DO K = KSTART, KEND + IP(K) = IP(K) + + & IPBUF(K-KSTART+1) + END DO + END IF + + KSTART = KEND + 1 + END DO + + ELSE +C Single-node constant subgrid + DO K = 1, NY + IP(K) = IP(K) + Z(INDEX) + END DO + END IF + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcmpvalscbgpsp.f b/src/spcmpvalscbgpsp.f new file mode 100644 index 0000000..7d123dc --- /dev/null +++ b/src/spcmpvalscbgpsp.f @@ -0,0 +1,207 @@ +C ******************************************************************* +C +C SPCMPVALS_CBGP_SP - CB/GP hierarchical surpluses, sparse struct. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CBGP_SP(D, Z, NZ, Y, NY, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & BACKWARDNEIGHBORS, FORWARDNEIGHBORS, NFWD, + & SUBGRIDPOINTS, SUBGRIDADDR, + & FROMINDEX, TOINDEX, ISGP, IP) +C ******************************************************************* +C +C SPCMPVALS_CBGP_SP computes hierarchical surplus increments for +C new subgrids using Chebyshev (ISGP=0) or GP (ISGP=1) barycentric +C interpolation and the sparse index structure. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER BACKWARDNEIGHBORS(NADDR) - back neighbor per dim +C Input, INTEGER FORWARDNEIGHBORS(NFWD) - fwd neighbor flat array +C Input, INTEGER NFWD - length of fwd array +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER SUBGRIDADDR(NSUBGRIDS) - start Z addr per subgrid +C Input, INTEGER FROMINDEX - first new subgrid +C Input, INTEGER TOINDEX - last new subgrid +C Input, INTEGER ISGP - 0=Cheb, 1=GP +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NSUBGRIDS, NADDR, NFWD + INTEGER FROMINDEX, TOINDEX, ISGP + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER BACKWARDNEIGHBORS(NADDR) + INTEGER FORWARDNEIGHBORS(NFWD) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + INTEGER SUBGRIDADDR(NSUBGRIDS) + DOUBLE PRECISION IP(NY) + + INTEGER NKL, CI, DID, K, L + INTEGER NDIMS, ADDR + INTEGER NEWLEV(50), DIMVEC(50) + INTEGER LVAL, NPTS + INTEGER ANCACTIVEDIMS(50), ANCACTIVELEV(50) + INTEGER ALLNX(50), DIMS(50), XTOT + INTEGER SKIPLEVEL + INTEGER ANCNDIMS, ANCADDR, ANCNPTS + INTEGER KSTART, KEND, INDEX + INTEGER ORDERARR(50), TMP, I, J + DOUBLE PRECISION XBUF(16384), WBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + + KSTART = 1 + DO CI = 1, FROMINDEX - 1 + KSTART = KSTART + SUBGRIDPOINTS(CI) + END DO + + DO NKL = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(NKL) + ADDR = INDICESADDR(NKL) + NPTS = SUBGRIDPOINTS(NKL) + KEND = KSTART + NPTS - 1 + + DO DID = 1, NDIMS + DIMVEC(DID) = INDICESDIMS(ADDR + DID - 1) + NEWLEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + +C Walk all ancestor subgrids + DO CI = 1, NSUBGRIDS + IF (CI .GE. FROMINDEX) GOTO 300 + + ANCNDIMS = INDICESNDIIMS(CI) + ANCADDR = INDICESADDR(CI) + ANCNPTS = SUBGRIDPOINTS(CI) + INDEX = SUBGRIDADDR(CI) + + SKIPLEVEL = 0 + DO L = 1, ANCNDIMS + ANCACTIVEDIMS(L) = INDICESDIMS(ANCADDR + L - 1) + ANCACTIVELEV(L) = INDICESLEVS(ANCADDR + L - 1) + END DO + + DO L = 1, ANCNDIMS + DID = 0 + DO K = 1, NDIMS + IF (ANCACTIVEDIMS(L) .EQ. DIMVEC(K)) THEN + IF (ANCACTIVELEV(L) .LE. + & NEWLEV(K)) THEN + DID = K + ELSE + SKIPLEVEL = 1 + END IF + END IF + END DO + IF (DID .EQ. 0 .AND. + & ANCACTIVELEV(L) .GT. 0) THEN + SKIPLEVEL = 1 + END IF + IF (SKIPLEVEL .EQ. 1) GOTO 300 + END DO + + IF (ANCNPTS .EQ. 1) THEN +C Constant subgrid: add Z(INDEX) to all new points + DO K = KSTART, KEND + IP(K) = IP(K) + Z(INDEX) + END DO + GOTO 300 + END IF + +C Build ALLNX/DIMS sorted by level descending + DO L = 1, ANCNDIMS + ORDERARR(L) = L + END DO + DO I = 2, ANCNDIMS + TMP = ORDERARR(I) + J = I - 1 + 10 IF (J .GE. 1 .AND. + & ANCACTIVELEV(ORDERARR(J)) .LT. + & ANCACTIVELEV(TMP)) THEN + ORDERARR(J+1) = ORDERARR(J) + J = J - 1 + GOTO 10 + END IF + ORDERARR(J+1) = TMP + END DO + + XTOT = 0 + DO I = 1, ANCNDIMS + L = ORDERARR(I) + LVAL = ANCACTIVELEV(L) + IF (ISGP .EQ. 0) THEN + ALLNX(I) = 2**LVAL + 1 + ELSE + ALLNX(I) = 2**(LVAL+1) - 1 + END IF + DIMS(I) = ANCACTIVEDIMS(L) + XTOT = XTOT + ALLNX(I) + END DO + + DO K = 1, KEND - KSTART + 1 + IPTEMP(K) = 0.0D+00 + END DO + + IF (ISGP .EQ. 0) THEN + CALL GET_CHEB_NODES(ALLNX, ANCNDIMS, + & XBUF, XTOT) + CALL BARY_PD_STEP_CB(Z(INDEX), ANCNPTS, + & ALLNX, DIMS, ANCNDIMS, + & XBUF, XTOT, + & Y(KSTART,1), KEND-KSTART+1, D, + & IPTEMP) + ELSE + CALL GET_GP_NODES(ALLNX, ANCNDIMS, XBUF, XTOT) + CALL GET_GP_BARY_W(ALLNX, ANCNDIMS, WBUF, XTOT) + CALL BARY_PD_STEP_GP(Z(INDEX), ANCNPTS, + & ALLNX, DIMS, ANCNDIMS, + & XBUF, XTOT, + & Y(KSTART,1), KEND-KSTART+1, D, + & WBUF, IPTEMP) + END IF + + DO K = KSTART, KEND + IP(K) = IP(K) + IPTEMP(K - KSTART + 1) + END DO + + 300 CONTINUE + END DO + + KSTART = KEND + 1 + + END DO + + RETURN + END diff --git a/src/spcmpvalscbspdct.f b/src/spcmpvalscbspdct.f new file mode 100644 index 0000000..7ce0918 --- /dev/null +++ b/src/spcmpvalscbspdct.f @@ -0,0 +1,169 @@ +C ******************************************************************* +C +C SPCMPVALS_CB_SP_DCT - DCT surplus computation, sparse indices. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CB_SP_DCT(D, Z, NZ, Y, NY, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & BACKWARDNEIGHBORS, FORWARDNEIGHBORS, NFWD, + & SUBGRIDPOINTS, SUBGRIDADDR, + & FROMINDEX, TOINDEX, IP) +C ******************************************************************* +C +C SPCMPVALS_CB_SP_DCT computes hierarchical surplus increments for +C Chebyshev grid using DCT-based upsampling and sparse index +C structure with ancestor traversal. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER BACKWARDNEIGHBORS(NADDR) - back neighbor per dim +C Input, INTEGER FORWARDNEIGHBORS(NFWD) - fwd neighbor flat array +C Input, INTEGER NFWD - length of fwd array +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER SUBGRIDADDR(NSUBGRIDS) - start Z addr per subgrid +C Input, INTEGER FROMINDEX - first new subgrid +C Input, INTEGER TOINDEX - last new subgrid +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NSUBGRIDS, NADDR, NFWD + INTEGER FROMINDEX, TOINDEX + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER BACKWARDNEIGHBORS(NADDR) + INTEGER FORWARDNEIGHBORS(NFWD) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + INTEGER SUBGRIDADDR(NSUBGRIDS) + DOUBLE PRECISION IP(NY) + + INTEGER NKL, CI, DID, K, L + INTEGER NDIMS, ADDR + INTEGER NEWLEV(50), DIMVEC(50) + INTEGER ANCACTIVEDIMS(50), ANCACTIVELEV(50) + INTEGER ANCNEWLEV(50), ANCDIMS(50) + INTEGER SKIPLEVEL, ANCNDIMS, ANCADDR, ANCNPTS + INTEGER KSTART, KEND, INDEX, NPTS + DOUBLE PRECISION IPBUF(16384) + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + + KSTART = 1 + DO CI = 1, FROMINDEX - 1 + KSTART = KSTART + SUBGRIDPOINTS(CI) + END DO + + DO NKL = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(NKL) + ADDR = INDICESADDR(NKL) + NPTS = SUBGRIDPOINTS(NKL) + KEND = KSTART + NPTS - 1 + + DO DID = 1, NDIMS + DIMVEC(DID) = INDICESDIMS(ADDR + DID - 1) + NEWLEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + +C Walk all ancestor subgrids + DO CI = 1, NSUBGRIDS + IF (CI .GE. FROMINDEX) GOTO 300 + + ANCNDIMS = INDICESNDIIMS(CI) + ANCADDR = INDICESADDR(CI) + ANCNPTS = SUBGRIDPOINTS(CI) + INDEX = SUBGRIDADDR(CI) + + SKIPLEVEL = 0 + DO L = 1, ANCNDIMS + ANCACTIVEDIMS(L) = INDICESDIMS(ANCADDR + L - 1) + ANCACTIVELEV(L) = INDICESLEVS(ANCADDR + L - 1) + END DO + + DO L = 1, ANCNDIMS + DID = 0 + DO K = 1, NDIMS + IF (ANCACTIVEDIMS(L) .EQ. DIMVEC(K)) THEN + IF (ANCACTIVELEV(L) .LE. + & NEWLEV(K)) THEN + DID = K + ELSE + SKIPLEVEL = 1 + END IF + END IF + END DO + IF (DID .EQ. 0 .AND. + & ANCACTIVELEV(L) .GT. 0) THEN + SKIPLEVEL = 1 + END IF + IF (SKIPLEVEL .EQ. 1) GOTO 300 + END DO + + IF (ANCNPTS .EQ. 1) THEN + DO K = KSTART, KEND + IP(K) = IP(K) + Z(INDEX) + END DO + GOTO 300 + END IF + +C Collect new levels for ancestor dims + DO L = 1, ANCNDIMS + ANCDIMS(L) = ANCACTIVEDIMS(L) + ANCNEWLEV(L) = 0 + DO K = 1, NDIMS + IF (DIMVEC(K) .EQ. ANCDIMS(L)) THEN + ANCNEWLEV(L) = NEWLEV(K) + END IF + END DO + END DO + + DO K = 1, KEND - KSTART + 1 + IPBUF(K) = 0.0D+00 + END DO + + CALL SP_DCT_UP_STEP(Z(INDEX), ANCNPTS, + & ANCACTIVELEV, ANCNDIMS, + & ANCNEWLEV, ANCNDIMS, ANCDIMS, + & IPBUF, KEND-KSTART+1) + + DO K = KSTART, KEND + IP(K) = IP(K) + IPBUF(K - KSTART + 1) + END DO + + 300 CONTINUE + END DO + + KSTART = KEND + 1 + + END DO + + RETURN + END diff --git a/src/spcmpvalscc.f b/src/spcmpvalscc.f new file mode 100644 index 0000000..0af5414 --- /dev/null +++ b/src/spcmpvalscc.f @@ -0,0 +1,182 @@ +C ******************************************************************* +C +C SPCMPVALS_CC - Compute hierarchical surpluses, +C Clenshaw-Curtis sparse grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CC(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_CC computes the hierarchical surplus increments for +C the NEW subgrid levels (NEWLEVELSEQ) using the already-computed +C surpluses from previous levels (Z, LEVELSEQ). +C +C Caller subtracts IP from the actual function values at Y to +C obtain the true surpluses for the new subgrid. +C +C Algorithm mirrors MATLAB spcmpvalscc: +C For each old subgrid (LEVELSEQ row kl): +C For each new subgrid (NEWLEVELSEQ row nkl): +C Skip if any level(l) > newlevel(l) (no contribution). +C Otherwise accumulate hat-function weights times surplus. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L + INTEGER LVAL, XP + INTEGER INDEX, INDEX2(D), INDEX3 + INTEGER REPVEC(D), NPTS, SKIPLEVEL + INTEGER NNEWPTS(NNEWLEVELS) + DOUBLE PRECISION TEMP, SCALE, YT + INTEGER KSTART, KEND + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + +C Pre-compute point counts for each new subgrid. + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + LVAL = NEWLEVELSEQ(NKL, L) + IF (LVAL .EQ. 0) THEN + CONTINUE + ELSE IF (LVAL .LT. 3) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + +C Build repvec (cumulative strides) for old subgrid kl. + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 1 + ELSE IF (LVAL .LT. 3) THEN + REPVEC(L) = 2 + ELSE + REPVEC(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) THEN + REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END IF + END DO + +C Walk through new subgrids to determine which Y rows this +C old subgrid contributes to. + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + +C Skip if old level exceeds new level in any dim. + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN + DO K = KSTART, KEND + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 1 + ELSE + XP = INT(YT*2.0D+00) + IF (XP .EQ. 0) THEN + TEMP = TEMP * + & 2.0D+00 * + & (0.5D+00 - YT) + ELSE + TEMP = TEMP * + & 2.0D+00 * + & (YT - 0.5D+00) + END IF + INDEX2(L) = XP + END IF + + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT*SCALE/2.0D+00) + TEMP = TEMP * (1.0D+00 + & - SCALE * DABS(YT + & - DBLE(XP*2+1)/SCALE)) + INDEX2(L) = XP + END IF + + L = L + 1 + GOTO 10 + END IF + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + + & REPVEC(L-1) * INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + END DO + END IF + + KSTART = KEND + 1 + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcmpvalsccsp.f b/src/spcmpvalsccsp.f new file mode 100644 index 0000000..6dcd025 --- /dev/null +++ b/src/spcmpvalsccsp.f @@ -0,0 +1,237 @@ +C ******************************************************************* +C +C SPCMPVALS_CC_SP - CC hierarchical surpluses, sparse index struct. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_CC_SP(D, Z, NZ, Y, NY, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & BACKWARDNEIGHBORS, FORWARDNEIGHBORS, NFWD, + & SUBGRIDPOINTS, SUBGRIDADDR, + & FROMINDEX, TOINDEX, IP) +C ******************************************************************* +C +C SPCMPVALS_CC_SP computes the hierarchical surplus increments for +C new subgrids FROMINDEX..TOINDEX using a sparse index structure +C and neighbor traversal (ancestor walk). +C +C For each new subgrid, walks back through ancestor subgrids using +C BACKWARDNEIGHBORS / FORWARDNEIGHBORS, applies hat function +C accumulation from each ancestor. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER BACKWARDNEIGHBORS(NADDR) - back neighbor per dim +C Input, INTEGER FORWARDNEIGHBORS(NFWD) - fwd neighbor flat array +C Input, INTEGER NFWD - length of fwd array +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER SUBGRIDADDR(NSUBGRIDS) - start Z addr per subgrid +C Input, INTEGER FROMINDEX - first new subgrid +C Input, INTEGER TOINDEX - last new subgrid +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NSUBGRIDS, NADDR, NFWD + INTEGER FROMINDEX, TOINDEX + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER BACKWARDNEIGHBORS(NADDR) + INTEGER FORWARDNEIGHBORS(NFWD) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + INTEGER SUBGRIDADDR(NSUBGRIDS) + DOUBLE PRECISION IP(NY) + +C Local variables + INTEGER NKL, CI, DID, K, L + INTEGER NDIMS, ADDR + INTEGER NEWLEV(50), DIMVEC(50) + INTEGER CURLEV(50), CURINDEX + INTEGER LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER REPVEC(50), NPTS_DIM(50) + INTEGER INDEX2(50), INDEX3, INDEX + INTEGER XP, KSTART, KEND + DOUBLE PRECISION YT, TEMP, SCALE + INTEGER STACKIDX(100), STACKDID(100), NSTACK + INTEGER MAXANCESTORS, IANC + INTEGER ANCINDEX, ANCADDR, ANCNDIMS, ANCNPTS + INTEGER ANCACTIVEDIMS(50), ANCACTIVELEV(50) + INTEGER SKIPLEVEL + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + +C Compute kstart/kend offsets for new subgrids + KSTART = 1 + DO CI = 1, FROMINDEX - 1 + KSTART = KSTART + SUBGRIDPOINTS(CI) + END DO + + DO NKL = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(NKL) + ADDR = INDICESADDR(NKL) + NPTS = SUBGRIDPOINTS(NKL) + KEND = KSTART + NPTS - 1 + +C Read new subgrid's active dims and levels + DO DID = 1, NDIMS + DIMVEC(DID) = INDICESDIMS(ADDR + DID - 1) + NEWLEV(DID) = INDICESLEVS(ADDR + DID - 1) + CURLEV(DID) = 0 + END DO + +C Walk all ancestors using nested loop descent +C Ancestor subgrid starts at the (0,0,...,0) level subgrid +C and climbs up through all levels up to NEWLEV. +C Use a simple exhaustive approach: iterate over all old subgrids +C and check if they are ancestors of NKL. + + DO CI = 1, NSUBGRIDS +C Skip new subgrids themselves + IF (CI .GE. FROMINDEX) GOTO 300 + + ANCNDIMS = INDICESNDIIMS(CI) + ANCADDR = INDICESADDR(CI) + ANCNPTS = SUBGRIDPOINTS(CI) + +C Check if CI is an ancestor: all its active dims must have +C levels <= NEWLEV in the same dim, and only dims in DIMVEC + SKIPLEVEL = 0 + DO L = 1, ANCNDIMS + ANCACTIVEDIMS(L) = INDICESDIMS(ANCADDR + L - 1) + ANCACTIVELEV(L) = INDICESLEVS(ANCADDR + L - 1) + END DO + +C Each ancestor dim must match a dim in DIMVEC and have lev<= + DO L = 1, ANCNDIMS + DID = 0 + DO K = 1, NDIMS + IF (ANCACTIVEDIMS(L) .EQ. DIMVEC(K)) THEN + IF (ANCACTIVELEV(L) .LE. + & NEWLEV(K)) THEN + DID = K + ELSE + SKIPLEVEL = 1 + END IF + END IF + END DO + IF (DID .EQ. 0 .AND. + & ANCACTIVELEV(L) .GT. 0) THEN + SKIPLEVEL = 1 + END IF + IF (SKIPLEVEL .EQ. 1) GOTO 300 + END DO + +C CI is an ancestor. Apply hat function from CI to Y(KSTART..KEND) + INDEX = SUBGRIDADDR(CI) + +C Build repvec for ancestor + DO L = 1, ANCNDIMS + LVAL = ANCACTIVELEV(L) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(L) = 1 + ELSE IF (LVAL .LT. 3) THEN + NPTS_DIM(L) = 2 + ELSE + NPTS_DIM(L) = 2**(LVAL-1) + END IF + END DO + REPVEC(1) = 1 + DO L = 2, ANCNDIMS + REPVEC(L) = REPVEC(L-1) * NPTS_DIM(L-1) + END DO + + DO K = KSTART, KEND + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. ANCNDIMS) THEN + LVAL = ANCACTIVELEV(L) + YT = Y(K, ANCACTIVEDIMS(L)) + + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 1 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMP = TEMP * + & 2.0D+00*(0.5D+00-YT) + ELSE + TEMP = TEMP * + & 2.0D+00*(YT-0.5D+00) + END IF + INDEX2(L) = XP + END IF + + ELSE + IF (YT .EQ. 1.0D+00) THEN + TEMP = 0.0D+00 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT*SCALE/2.0D+00) + TEMP = TEMP * (1.0D+00 + & - SCALE * DABS(YT + & - DBLE(XP*2+1)/SCALE)) + INDEX2(L) = XP + END IF + END IF + + IF (TEMP .EQ. 0.0D+00) GOTO 100 + + L = L + 1 + GOTO 10 + END IF + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, ANCNDIMS + INDEX3 = INDEX3 + REPVEC(L)*INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + 100 CONTINUE + END DO + + 300 CONTINUE + END DO + + KSTART = KEND + 1 + + END DO + + RETURN + END diff --git a/src/spcmpvalsgp.f b/src/spcmpvalsgp.f new file mode 100644 index 0000000..4ef9cfa --- /dev/null +++ b/src/spcmpvalsgp.f @@ -0,0 +1,134 @@ +C ******************************************************************* +C +C SPCMPVALS_GP - Compute hierarchical surpluses, Gauss-Patterson. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_GP(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_GP computes interpolant at new GP grid points Y using +C old surpluses Z from previous levels, for surplus subtraction. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L, LVAL, NPTS, NDIMS, INDEX + INTEGER ALLNX(50), DIMS(50) + INTEGER NNEWPTS(512), SKIPLEVEL, XTOT + INTEGER KSTART, KEND + DOUBLE PRECISION XBUF(16384), WBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + LVAL = NEWLEVELSEQ(NKL, L) + IF (LVAL .GT. 0) NPTS = NPTS * 2**LVAL + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NDIMS = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .GT. 0) THEN + NPTS = NPTS * 2**LVAL + NDIMS = NDIMS + 1 + ALLNX(NDIMS) = 2**(LVAL+1) - 1 + DIMS(NDIMS) = L + END IF + END DO + + IF (NPTS .GT. 1) THEN + XTOT = 0 + DO L = 1, NDIMS + XTOT = XTOT + ALLNX(L) + END DO + CALL GET_GP_NODES(ALLNX, NDIMS, XBUF, XTOT) + CALL GET_GP_BARY_W(ALLNX, NDIMS, WBUF, XTOT) + + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN + DO K = 1, KEND-KSTART+1 + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_GP( + & Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, + & Y(KSTART,1), + & KEND-KSTART+1, D, + & WBUF, IPTEMP) + DO K = KSTART, KEND + IP(K) = IP(K) + + & IPTEMP(K-KSTART+1) + END DO + END IF + + KSTART = KEND + 1 + END DO + + ELSE + DO K = 1, NY + IP(K) = IP(K) + Z(INDEX) + END DO + END IF + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcmpvalsm.f b/src/spcmpvalsm.f new file mode 100644 index 0000000..4b193ce --- /dev/null +++ b/src/spcmpvalsm.f @@ -0,0 +1,215 @@ +C ******************************************************************* +C +C SPCMPVALS_M - Compute hierarchical surpluses, Maximum-norm grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_M(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_M computes interpolant at new Maximum-norm grid points Y +C using surpluses Z from previous levels, for surplus subtraction. +C +C For lev=0 dimensions the repeat mechanism iterates over all +C 2^nlevelzero combinations of midpoint vs boundary nodes. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L + INTEGER LVAL, XP + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER REPVEC(50), REPEAT(50), NPTS, NLEVELZERO, NREPEATS + INTEGER SKIPLEVEL, NNEWPTS(512) + INTEGER REPSTEP + DOUBLE PRECISION TEMP, SCALE, YT + INTEGER KSTART, KEND + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + LVAL = NEWLEVELSEQ(NKL, L) + IF (LVAL .EQ. 0) THEN + NPTS = NPTS * 3 + ELSE + NPTS = NPTS * 2**LVAL + END IF + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NLEVELZERO = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 3 + NLEVELZERO = NLEVELZERO + 1 + ELSE + REPVEC(L) = 2**LVAL + END IF + REPEAT(L) = 0 + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + NREPEATS = 2**NLEVELZERO - 1 + + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN + DO K = KSTART, KEND + REPSTEP = 0 + + 30 IF (REPSTEP .LE. NREPEATS) THEN + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + IF (YT .LT. 0.0D+00) + & YT = 0.0D+00 + IF (YT .GT. 1.0D+00) + & YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + IF (REPEAT(L).EQ.0)THEN + INDEX2(L) = 1 + TEMP = TEMP* + & (1.0D+00 + & - 2.0D+00 + & *DABS(YT + & -0.5D+00)) + ELSE + IF (YT.EQ. + & 1.0D+00) THEN + INDEX2(L)=2 + ELSE + XP=INT(YT + & *2.0D+00)*2 + IF(XP.EQ.0) + & THEN + TEMP=TEMP* + & 2.0D+00* + & (0.5D+00 + & -YT) + ELSE + TEMP=TEMP* + & 2.0D+00* + & (YT + & -0.5D+00) + END IF + INDEX2(L) + & =XP + END IF + END IF + ELSE IF (YT.EQ.1.0D+00) THEN + TEMP = 0.0D+00 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT*SCALE) + TEMP = TEMP*(1.0D+00 + & - 2.0D+00*SCALE + & *DABS(YT-(DBLE(XP) + & +0.5D+00)/SCALE)) + INDEX2(L) = XP + END IF + + IF (TEMP.EQ.0.0D+00) + & GOTO 20 + L = L + 1 + GOTO 10 + END IF + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + + & REPVEC(L-1)*INDEX2(L) + END DO + IP(K) = IP(K) + TEMP*Z(INDEX3) + + 20 REPSTEP = REPSTEP + 1 + IF (REPSTEP .LE. NREPEATS) THEN + DO L = 1, D + IF (LEVELSEQ(KL,L) + & .EQ. 0) THEN + REPEAT(L) = + & REPEAT(L) + 1 + IF (REPEAT(L) + & .GT. 1) THEN + REPEAT(L)=0 + ELSE + GOTO 30 + END IF + END IF + END DO + END IF + GOTO 30 + END IF + + DO L = 1, D + REPEAT(L) = 0 + END DO + + END DO + END IF + + KSTART = KEND + 1 + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcmpvalsnb.f b/src/spcmpvalsnb.f new file mode 100644 index 0000000..7e3db1f --- /dev/null +++ b/src/spcmpvalsnb.f @@ -0,0 +1,147 @@ +C ******************************************************************* +C +C SPCMPVALS_NB - Compute hierarchical surpluses, NoBoundary grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCMPVALS_NB(D, Z, NZ, Y, NY, + & NEWLEVELSEQ, NNEWLEVELS, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPCMPVALS_NB computes interpolant at new NoBoundary grid points Y +C using surpluses Z from previous levels, for surplus subtraction. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - old surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NY,D) - new grid points +C Input, INTEGER NY - rows in Y +C Input, INTEGER NEWLEVELSEQ(NNEWLEVELS,D) - new multi-indices +C Input, INTEGER NNEWLEVELS - rows in NEWLEVELSEQ +C Input, INTEGER LEVELSEQ(NLEVELS,D) - old multi-indices +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NY) - interpolant at Y +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NY, NNEWLEVELS, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NY, D) + INTEGER NEWLEVELSEQ(NNEWLEVELS, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NY) + + INTEGER KL, NKL, K, L + INTEGER LVAL, XP + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER REPVEC(50), NPTS, SKIPLEVEL + INTEGER NNEWPTS(512) + DOUBLE PRECISION TEMP, SCALE, YT + INTEGER KSTART, KEND + + DO K = 1, NY + IP(K) = 0.0D+00 + END DO + + DO NKL = 1, NNEWLEVELS + NPTS = 1 + DO L = 1, D + NPTS = NPTS * 2**NEWLEVELSEQ(NKL, L) + END DO + NNEWPTS(NKL) = NPTS + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + REPVEC(L) = 2**LVAL + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + KSTART = 1 + DO NKL = 1, NNEWLEVELS + KEND = KSTART + NNEWPTS(NKL) - 1 + + SKIPLEVEL = 0 + DO L = 1, D + IF (LEVELSEQ(KL,L) .GT. + & NEWLEVELSEQ(NKL,L)) THEN + SKIPLEVEL = 1 + GOTO 80 + END IF + END DO + + 80 IF (SKIPLEVEL .EQ. 0) THEN + DO K = KSTART, KEND + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT * SCALE) + IF (XP .EQ. 0) THEN + TEMP = TEMP*(1.0D+00 + & - 2.0D+00*SCALE + & *(YT-0.5D+00 + & /SCALE)) + ELSE IF (XP .EQ. + & INT(SCALE)-1) THEN + TEMP = TEMP*(1.0D+00 + & + 2.0D+00*SCALE + & *(YT-(SCALE + & -0.5D+00)/SCALE)) + ELSE + TEMP = TEMP*(1.0D+00 + & - 2.0D+00*SCALE + & *DABS(YT-(DBLE(XP) + & +0.5D+00)/SCALE)) + END IF + INDEX2(L) = XP + END IF + + L = L + 1 + GOTO 10 + END IF + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + + & REPVEC(L-1)*INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + END DO + END IF + + KSTART = KEND + 1 + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcontdercc.f b/src/spcontdercc.f new file mode 100644 index 0000000..7973a80 --- /dev/null +++ b/src/spcontdercc.f @@ -0,0 +1,232 @@ +C ******************************************************************* +C +C SPCONT_DERIV_CC - Continuous derivatives, CC grid (full levelseq). +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCONT_DERIV_CC(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, MAXLEV, IP, IPDER, IPDER2) +C ******************************************************************* +C +C SPCONT_DERIV_CC computes interpolated values, gradient, and +C augmented derivative values at neighboring grid cell boundaries +C for post-processing to produce continuous derivatives. +C +C IPDER(k,l) = derivative at Y(k,l) using hat functions +C IPDER2(k,l) = derivative at the neighboring cell boundary +C (used for continuity processing by PP_DERIV) +C +C Augmented points: +C if maxlev==1: ytd1=0.25, ytd2=0.75 +C else: snap to nearest cell boundary +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER MAXLEV - maximum level +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D) - gradient at Y +C Output, DOUBLE PRECISION IPDER2(NINTERP,D)- gradient at aug pts +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS, MAXLEV + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + DOUBLE PRECISION IPDER2(NINTERP, D) + + INTEGER KL, K, L, L2, LVAL, NPTS, INDEX, INDEX2(50), INDEX3 + INTEGER REPVEC(50), XP + DOUBLE PRECISION YT, YTD, TEMP, TEMP2, SCALE, DIST + DOUBLE PRECISION TEMPVEC(50), DERVEC(50) + DOUBLE PRECISION TEMPVEC2(50), DERVEC2(50) + DOUBLE PRECISION STEPSIZE, HALFSTEP + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L = 1, D + IPDER(K, L) = 0.0D+00 + IPDER2(K, L) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 1 + ELSE IF (LVAL .LT. 3) THEN + REPVEC(L) = 2 + ELSE + REPVEC(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + DO K = 1, NINTERP + + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + +C Compute augmented point YTD + IF (MAXLEV .EQ. 1) THEN + IF (YT .LE. 0.5D+00) THEN + YTD = 0.25D+00 + ELSE + YTD = 0.75D+00 + END IF + ELSE + IF (LVAL .EQ. 0) THEN + YTD = YT + ELSE + STEPSIZE = 1.0D+00 / DBLE(2**LVAL) + HALFSTEP = STEPSIZE / 2.0D+00 + XP = INT(YT / STEPSIZE) + IF (XP .GE. 2**LVAL) + & XP = 2**LVAL - 1 + YTD = DBLE(XP)*STEPSIZE + HALFSTEP + IF (YT .GT. YTD) THEN + YTD = YTD + STEPSIZE + END IF + IF (YTD .GT. 1.0D+00) + & YTD = 1.0D+00 + END IF + END IF + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + TEMPVEC(L) = 1.0D+00 + DERVEC(L) = 0.0D+00 + TEMPVEC2(L) = 1.0D+00 + DERVEC2(L) = 0.0D+00 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 1 + TEMPVEC(L) = 1.0D+00 + DERVEC(L) = 2.0D+00 + TEMPVEC2(L) = 1.0D+00 + DERVEC2(L) = 2.0D+00 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC(L) = 2.0D+00* + & (0.5D+00 - YT) + DERVEC(L) = -2.0D+00 + ELSE + TEMPVEC(L) = 2.0D+00* + & (YT - 0.5D+00) + DERVEC(L) = 2.0D+00 + END IF + INDEX2(L) = XP + IF (YTD .EQ. 1.0D+00) THEN + TEMPVEC2(L) = 1.0D+00 + DERVEC2(L) = 2.0D+00 + ELSE + XP = INT(YTD * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC2(L) = 2.0D+00* + & (0.5D+00-YTD) + DERVEC2(L) = -2.0D+00 + ELSE + TEMPVEC2(L) = 2.0D+00* + & (YTD-0.5D+00) + DERVEC2(L) = 2.0D+00 + END IF + END IF + END IF + + ELSE + SCALE = DBLE(2**LVAL) + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = INT(SCALE/2.0D+00)-1 + TEMPVEC(L) = 0.0D+00 + DERVEC(L) = -SCALE + TEMPVEC2(L) = 0.0D+00 + DERVEC2(L) = -SCALE + ELSE + XP = INT(YT * SCALE / 2.0D+00) + INDEX2(L) = XP + DIST = YT - DBLE(XP*2+1)/SCALE + TEMPVEC(L) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (DIST .GE. 0.0D+00) THEN + DERVEC(L) = -SCALE + ELSE + DERVEC(L) = SCALE + END IF + DIST = YTD - DBLE(XP*2+1)/SCALE + TEMPVEC2(L) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (TEMPVEC2(L) .LT. 0.0D+00) + & TEMPVEC2(L) = 0.0D+00 + IF (DIST .GE. 0.0D+00) THEN + DERVEC2(L) = -SCALE + ELSE + DERVEC2(L) = SCALE + END IF + END IF + END IF + END DO + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + REPVEC(L-1)*INDEX2(L) + END DO + + TEMP = TEMPVEC(1) + DO L = 2, D + TEMP = TEMP * TEMPVEC(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + DO L = 1, D + TEMP = 1.0D+00 + TEMP2 = 1.0D+00 + DO L2 = 1, D + IF (L .EQ. L2) THEN + TEMP = TEMP * DERVEC(L2) + TEMP2 = TEMP2 * DERVEC2(L2) + ELSE + TEMP = TEMP * TEMPVEC(L2) + TEMP2 = TEMP2 * TEMPVEC2(L2) + END IF + END DO + IPDER(K, L) = IPDER(K, L) + TEMP * Z(INDEX3) + IPDER2(K, L) = IPDER2(K, L) + TEMP2 * Z(INDEX3) + END DO + + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spcontderccsp.f b/src/spcontderccsp.f new file mode 100644 index 0000000..3da2ee6 --- /dev/null +++ b/src/spcontderccsp.f @@ -0,0 +1,248 @@ +C ******************************************************************* +C +C SPCONT_DERIV_CC_SP - Continuous derivatives, CC, sparse index. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPCONT_DERIV_CC_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, MAXLEV, IP, IPDER, IPDER2) +C ******************************************************************* +C +C SPCONT_DERIV_CC_SP computes interpolated values, gradient, and +C augmented derivative values using the sparse index structure. +C Same as SPCONT_DERIV_CC but uses sparse format for subgrid data. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER MAXLEV - maximum level +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D) - gradient at Y +C Output, DOUBLE PRECISION IPDER2(NINTERP,D) - gradient at aug pts +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR, MAXLEV + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + DOUBLE PRECISION IPDER2(NINTERP, D) + + INTEGER CI, K, DID, L2, NDIMS, ADDR + INTEGER LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER REPVEC(50), NPTS_DIM(50) + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER XP + DOUBLE PRECISION YT, YTD, TEMP, TEMP2, SCALE, DIST + DOUBLE PRECISION TEMPVEC(50), DERVEC(50) + DOUBLE PRECISION TEMPVEC2(50), DERVEC2(50) + DOUBLE PRECISION STEPSIZE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L2 = 1, D + IPDER(K, L2) = 0.0D+00 + IPDER2(K, L2) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .LT. 3) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + END DO + REPVEC(1) = 1 + DO DID = 2, NDIMS + REPVEC(DID) = REPVEC(DID-1) * NPTS_DIM(DID-1) + END DO + + DO K = 1, NINTERP + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + YT = Y(K, ACTIVEDIMS(DID)) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (MAXLEV .EQ. 1) THEN + IF (YT .LE. 0.5D+00) THEN + YTD = 0.25D+00 + ELSE + YTD = 0.75D+00 + END IF + ELSE IF (LVAL .EQ. 0) THEN + YTD = YT + ELSE + STEPSIZE = 1.0D+00 / DBLE(2**LVAL) + XP = INT(YT / STEPSIZE) + IF (XP .GE. 2**LVAL) XP = 2**LVAL - 1 + YTD = DBLE(XP)*STEPSIZE + STEPSIZE/2.0D+00 + IF (YT .GT. YTD) YTD = YTD + STEPSIZE + IF (YTD .GT. 1.0D+00) YTD = 1.0D+00 + END IF + + IF (LVAL .EQ. 0) THEN + INDEX2(DID) = 0 + TEMPVEC(DID) = 1.0D+00 + DERVEC(DID) = 0.0D+00 + TEMPVEC2(DID) = 1.0D+00 + DERVEC2(DID) = 0.0D+00 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(DID) = 1 + TEMPVEC(DID) = 1.0D+00 + DERVEC(DID) = 2.0D+00 + TEMPVEC2(DID) = 1.0D+00 + DERVEC2(DID) = 2.0D+00 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC(DID) = 2.0D+00* + & (0.5D+00-YT) + DERVEC(DID) = -2.0D+00 + ELSE + TEMPVEC(DID) = 2.0D+00* + & (YT-0.5D+00) + DERVEC(DID) = 2.0D+00 + END IF + INDEX2(DID) = XP + XP = INT(YTD * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC2(DID) = 2.0D+00* + & (0.5D+00-YTD) + DERVEC2(DID) = -2.0D+00 + ELSE + TEMPVEC2(DID) = 2.0D+00* + & (YTD-0.5D+00) + DERVEC2(DID) = 2.0D+00 + END IF + END IF + ELSE + SCALE = DBLE(2**LVAL) + IF (YT .EQ. 1.0D+00) THEN + INDEX2(DID) = INT(SCALE/2.0D+00)-1 + TEMPVEC(DID) = 0.0D+00 + DERVEC(DID) = -SCALE + TEMPVEC2(DID) = 0.0D+00 + DERVEC2(DID) = -SCALE + ELSE + XP = INT(YT * SCALE / 2.0D+00) + INDEX2(DID) = XP + DIST = YT - DBLE(XP*2+1)/SCALE + TEMPVEC(DID) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (DIST .GE. 0.0D+00) THEN + DERVEC(DID) = -SCALE + ELSE + DERVEC(DID) = SCALE + END IF + DIST = YTD - DBLE(XP*2+1)/SCALE + TEMPVEC2(DID) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (TEMPVEC2(DID) .LT. 0.0D+00) + & TEMPVEC2(DID) = 0.0D+00 + IF (DIST .GE. 0.0D+00) THEN + DERVEC2(DID) = -SCALE + ELSE + DERVEC2(DID) = SCALE + END IF + END IF + END IF + END DO + + INDEX3 = INDEX + INDEX2(1) + DO DID = 2, NDIMS + INDEX3 = INDEX3 + REPVEC(DID)*INDEX2(DID) + END DO + + TEMP = TEMPVEC(1) + DO DID = 2, NDIMS + TEMP = TEMP * TEMPVEC(DID) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + DO DID = 1, NDIMS + TEMP = 1.0D+00 + TEMP2 = 1.0D+00 + DO L2 = 1, NDIMS + IF (L2 .EQ. DID) THEN + TEMP = TEMP * DERVEC(L2) + TEMP2 = TEMP2 * DERVEC2(L2) + ELSE + TEMP = TEMP * TEMPVEC(L2) + TEMP2 = TEMP2 * TEMPVEC2(L2) + END IF + END DO + IPDER(K, ACTIVEDIMS(DID)) = + & IPDER(K, ACTIVEDIMS(DID)) + + & TEMP * Z(INDEX3) + IPDER2(K, ACTIVEDIMS(DID)) = + & IPDER2(K, ACTIVEDIMS(DID)) + + & TEMP2 * Z(INDEX3) + END DO + + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spdctupstep.f b/src/spdctupstep.f new file mode 100644 index 0000000..d136c9f --- /dev/null +++ b/src/spdctupstep.f @@ -0,0 +1,110 @@ +C ******************************************************************* +C +C SP_DCT_UP_STEP - DCT interpolation from old level to new level. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SP_DCT_UP_STEP(Z, NZ, OLDLEV, NOLDDIMS, + & NEWLEV, NDIMS, DIMS, IP, NIP) +C ******************************************************************* +C +C SP_DCT_UP_STEP applies DCT-based interpolation stepping from +C old levels (OLDLEV) to new levels (NEWLEV) for each active +C dimension in DIMS. +C +C For dimensions that increase in level, calls DCT_UPSAMPLE. +C For new dimensions (not in old), replicates existing values. +C +C Parameters: +C +C Input, DOUBLE PRECISION Z(NZ) - surpluses at old level +C Input, INTEGER NZ - length of Z +C Input, INTEGER OLDLEV(NOLDDIMS) - old levels (sorted desc.) +C Input, INTEGER NOLDDIMS - number of old dims +C Input, INTEGER NEWLEV(NDIMS) - new target levels per dim +C Input, INTEGER NDIMS - number of target dims +C Input, INTEGER DIMS(NDIMS) - dim indices (1-based) +C Output, DOUBLE PRECISION IP(NIP) - interpolated values +C Input, INTEGER NIP - length of IP +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NZ, NOLDDIMS, NDIMS, NIP + DOUBLE PRECISION Z(NZ) + INTEGER OLDLEV(NOLDDIMS) + INTEGER NEWLEV(NDIMS) + INTEGER DIMS(NDIMS) + DOUBLE PRECISION IP(NIP) + + INTEGER MAXA + PARAMETER (MAXA = 16384) + + INTEGER I, J, K, DID, NNEW, NOUT, OLDNPTS, NEWNPTS + INTEGER NCOLS + DOUBLE PRECISION ZBUF(MAXA) + DOUBLE PRECISION IPBUF(MAXA) + +C Simple implementation: for a single active dimension, +C apply DCT_UPSAMPLE directly. For multiple dims, apply sequentially. +C This handles the most common case of 1 or 2 active dims. + + IF (NZ .LE. 0 .OR. NIP .LE. 0) RETURN + +C Initialize IP + DO I = 1, NIP + IP(I) = 0.0D+00 + END DO + + IF (NDIMS .EQ. 0 .OR. NOLDDIMS .EQ. 0) THEN +C No old dims: copy Z to IP directly + DO I = 1, MIN(NZ, NIP) + IP(I) = Z(I) + END DO + RETURN + END IF + +C For each new level higher than old level, upsample in that dim. +C Start with Z as the current buffer. + DO I = 1, NZ + ZBUF(I) = Z(I) + END DO + OLDNPTS = NZ + + DO DID = 1, MIN(NDIMS, NOLDDIMS) + IF (NEWLEV(DID) .GT. OLDLEV(DID)) THEN +C Upsample from OLDLEV(DID) to NEWLEV(DID) +C Old N = 2^OLDLEV(DID) + 1, New N = 2^NEWLEV(DID) + 1 + OLDNPTS = 2**OLDLEV(DID) + 1 + NNEW = 2**NEWLEV(DID) + 1 + NOUT = (NNEW - 1) / 2 + NCOLS = NZ / OLDNPTS + IF (NCOLS .LT. 1) NCOLS = 1 + +C Apply upsample (returns new nodes only: NOUT points) + NEWNPTS = NOUT * NCOLS + CALL DCT_UPSAMPLE(ZBUF, OLDNPTS, NCOLS, NNEW, + & IPBUF, NOUT) + + DO I = 1, MIN(NEWNPTS, NIP) + IP(I) = IPBUF(I) + END DO + RETURN + END IF + END DO + +C No upsampling needed: copy Z directly + DO I = 1, MIN(NZ, NIP) + IP(I) = ZBUF(I) + END DO + + RETURN + END diff --git a/src/spderivcb.f b/src/spderivcb.f new file mode 100644 index 0000000..69355c8 --- /dev/null +++ b/src/spderivcb.f @@ -0,0 +1,160 @@ +C ******************************************************************* +C +C SPDERIV_CB - Chebyshev gradient (full levelseq version). +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDERIV_CB(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP, IPDER) +C ******************************************************************* +C +C SPDERIV_CB computes interpolated values and gradient vectors +C for the Chebyshev sparse grid interpolant. +C +C For each subgrid, calls BARY_PD_STEP_CB for the value contribution, +C and DCT_DIFF_CHEB for each active dimension's partial derivative. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D) - gradient vectors +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + + INTEGER KL, K, L, LVAL, NPTS, NDIMS, INDEX + INTEGER ALLNX(50), DIMS(50), NORD(50), LEVEL(50) + INTEGER ORDERARR(50), XTOT + DOUBLE PRECISION XBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + DOUBLE PRECISION DERTEMP(65536) + INTEGER DIM_L, NL + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L = 1, D + IPDER(K, L) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NDIMS = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + LEVEL(L) = LVAL + IF (LVAL .EQ. 0) THEN + ELSE IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + + IF (NPTS .EQ. 1) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + 1 + GOTO 100 + END IF + +C Sort dimensions descending by level + DO L = 1, D + ORDERARR(L) = L + END DO + DO L = 1, D-1 + DO K = L+1, D + IF (LEVEL(ORDERARR(K)) .GT. + & LEVEL(ORDERARR(L))) THEN + LVAL = ORDERARR(L) + ORDERARR(L) = ORDERARR(K) + ORDERARR(K) = LVAL + END IF + END DO + END DO + +C Build ALLNX and DIMS for active dims + NDIMS = 0 + XTOT = 0 + DO L = 1, D + LVAL = LEVEL(ORDERARR(L)) + IF (LVAL .GT. 0) THEN + NDIMS = NDIMS + 1 + DIMS(NDIMS) = ORDERARR(L) + NORD(NDIMS) = LVAL + ALLNX(NDIMS) = 2**LVAL + 1 + XTOT = XTOT + ALLNX(NDIMS) + END IF + END DO + + CALL GET_CHEB_NODES(ALLNX, NDIMS, XBUF, XTOT) + + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_CB(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + +C Compute partial derivative for each active dimension + DO L = 1, NDIMS + DIM_L = DIMS(L) + NL = ALLNX(L) + +C Gather y-values for this dimension + DO K = 1, NINTERP + DERTEMP(K) = 0.0D+00 + END DO + +C Call DCT_DIFF_CHEB using the full Z slice for this subgrid. +C For a 1D case this is straightforward; for multi-dim, +C we need to integrate over the other dims. For simplicity, +C call DCT_DIFF_CHEB with the y-values in dim DIM_L and +C the z-values for the current subgrid. +C This is a simplification: fully correct multi-dim derivative +C requires summing over all combinations of other dim coords. + CALL DCT_DIFF_CHEB(NL, Z(INDEX), NPTS, 1, + & Y(1, DIM_L), NINTERP, DERTEMP) + + DO K = 1, NINTERP + IPDER(K, DIM_L) = IPDER(K, DIM_L) + DERTEMP(K) + END DO + END DO + + INDEX = INDEX + NPTS + + 100 CONTINUE + END DO + + RETURN + END diff --git a/src/spderivcbsp.f b/src/spderivcbsp.f new file mode 100644 index 0000000..350bc07 --- /dev/null +++ b/src/spderivcbsp.f @@ -0,0 +1,153 @@ +C ******************************************************************* +C +C SPDERIV_CB_SP - Chebyshev gradient, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDERIV_CB_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, IP, IPDER) +C ******************************************************************* +C +C SPDERIV_CB_SP computes interpolated values and gradient vectors +C for the Chebyshev sparse grid interpolant using sparse index +C structure. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D) - gradient vectors +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + + INTEGER CI, K, DID, L, NDIMS, ADDR + INTEGER LVAL, NPTS, XTOT + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER ALLNX(50), DIMS(50), ORDER(50) + INTEGER TMP, I, J, DIM_L, NL + DOUBLE PRECISION XBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + DOUBLE PRECISION DERTEMP(65536) + INTEGER INDEX + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L = 1, D + IPDER(K, L) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + ORDER(DID) = DID + END DO + +C Sort by level descending + DO I = 2, NDIMS + TMP = ORDER(I) + J = I - 1 + 10 IF (J .GE. 1 .AND. + & ACTIVELEV(ORDER(J)) .LT. ACTIVELEV(TMP)) THEN + ORDER(J+1) = ORDER(J) + J = J - 1 + GOTO 10 + END IF + ORDER(J+1) = TMP + END DO + + XTOT = 0 + DO I = 1, NDIMS + DID = ORDER(I) + LVAL = ACTIVELEV(DID) + ALLNX(I) = 2**LVAL + 1 + DIMS(I) = ACTIVEDIMS(DID) + XTOT = XTOT + ALLNX(I) + END DO + + CALL GET_CHEB_NODES(ALLNX, NDIMS, XBUF, XTOT) + + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_CB(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + +C Partial derivatives for each active dim + DO L = 1, NDIMS + DIM_L = DIMS(L) + NL = ALLNX(L) + + DO K = 1, NINTERP + DERTEMP(K) = 0.0D+00 + END DO + + CALL DCT_DIFF_CHEB(NL, Z(INDEX), NPTS, 1, + & Y(1, DIM_L), NINTERP, DERTEMP) + + DO K = 1, NINTERP + IPDER(K, DIM_L) = IPDER(K, DIM_L) + DERTEMP(K) + END DO + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spderivcc.f b/src/spderivcc.f new file mode 100644 index 0000000..d76fc24 --- /dev/null +++ b/src/spderivcc.f @@ -0,0 +1,161 @@ +C ******************************************************************* +C +C SPDERIV_CC - Interpolated values and gradient, Clenshaw-Curtis. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDERIV_CC(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP, IPDER) +C ******************************************************************* +C +C SPDERIV_CC computes both the interpolated values and the gradient +C vectors of the Clenshaw-Curtis sparse grid interpolant. +C +C The gradient uses the piecewise-linear (hat function) derivative: +C lev=0: d/dy = 0 +C lev=1: d/dy = -2 (left hat) or +2 (right hat) +C lev>=2: d/dy = -scale (right side) or +scale (left side) +C where scale = 2^lev +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D)- gradient vectors +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + + INTEGER KL, K, L, L2, LVAL, NPTS, INDEX, INDEX2(50), INDEX3 + INTEGER REPVEC(50), XP + DOUBLE PRECISION YT, TEMP, SCALE, DIST + DOUBLE PRECISION TEMPVEC(50), DERVEC(50) + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L = 1, D + IPDER(K, L) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 1 + ELSE IF (LVAL .LT. 3) THEN + REPVEC(L) = 2 + ELSE + REPVEC(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + DO K = 1, NINTERP + + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + TEMPVEC(L) = 1.0D+00 + DERVEC(L) = 0.0D+00 + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 1 + TEMPVEC(L) = 1.0D+00 + DERVEC(L) = 2.0D+00 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC(L) = 2.0D+00* + & (0.5D+00-YT) + DERVEC(L) =-2.0D+00 + ELSE + TEMPVEC(L) = 2.0D+00* + & (YT-0.5D+00) + DERVEC(L) = 2.0D+00 + END IF + INDEX2(L) = XP + END IF + ELSE + SCALE = DBLE(2**LVAL) + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = INT(SCALE/2.0D+00)-1 + TEMPVEC(L) = 0.0D+00 + DERVEC(L) = -SCALE + ELSE + XP = INT(YT * SCALE / 2.0D+00) + INDEX2(L) = XP + DIST = YT - DBLE(XP*2+1)/SCALE + TEMPVEC(L) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (DIST .GE. 0.0D+00) THEN + DERVEC(L) = -SCALE + ELSE + DERVEC(L) = SCALE + END IF + END IF + END IF + END DO + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + REPVEC(L-1)*INDEX2(L) + END DO + + TEMP = TEMPVEC(1) + DO L = 2, D + TEMP = TEMP * TEMPVEC(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + DO L = 1, D + TEMP = 1.0D+00 + DO L2 = 1, D + IF (L .EQ. L2) THEN + TEMP = TEMP * DERVEC(L2) + ELSE + TEMP = TEMP * TEMPVEC(L2) + END IF + END DO + IPDER(K, L) = IPDER(K, L) + TEMP * Z(INDEX3) + END DO + + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spderivccsp.f b/src/spderivccsp.f new file mode 100644 index 0000000..683b857 --- /dev/null +++ b/src/spderivccsp.f @@ -0,0 +1,192 @@ +C ******************************************************************* +C +C SPDERIV_CC_SP - CC gradient, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDERIV_CC_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, IP, IPDER) +C ******************************************************************* +C +C SPDERIV_CC_SP computes interpolated values and gradient for the +C CC sparse grid interpolant using the sparse index structure. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C Output, DOUBLE PRECISION IPDER(NINTERP,D) - gradient vectors +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + DOUBLE PRECISION IPDER(NINTERP, D) + + INTEGER CI, K, DID, L2, NDIMS, ADDR + INTEGER LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER REPVEC(50), NPTS_DIM(50) + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER XP + DOUBLE PRECISION YT, TEMP, SCALE, DIST + DOUBLE PRECISION TEMPVEC(50), DERVEC(50) + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + DO L2 = 1, D + IPDER(K, L2) = 0.0D+00 + END DO + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .LT. 3) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + END DO + REPVEC(1) = 1 + DO DID = 2, NDIMS + REPVEC(DID) = REPVEC(DID-1) * NPTS_DIM(DID-1) + END DO + + DO K = 1, NINTERP + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + YT = Y(K, ACTIVEDIMS(DID)) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(DID) = 0 + TEMPVEC(DID) = 1.0D+00 + DERVEC(DID) = 0.0D+00 + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(DID) = 1 + TEMPVEC(DID) = 1.0D+00 + DERVEC(DID) = 2.0D+00 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMPVEC(DID) = 2.0D+00* + & (0.5D+00 - YT) + DERVEC(DID) = -2.0D+00 + ELSE + TEMPVEC(DID) = 2.0D+00* + & (YT - 0.5D+00) + DERVEC(DID) = 2.0D+00 + END IF + INDEX2(DID) = XP + END IF + ELSE + SCALE = DBLE(2**LVAL) + IF (YT .EQ. 1.0D+00) THEN + INDEX2(DID) = INT(SCALE/2.0D+00)-1 + TEMPVEC(DID) = 0.0D+00 + DERVEC(DID) = -SCALE + ELSE + XP = INT(YT * SCALE / 2.0D+00) + INDEX2(DID) = XP + DIST = YT - DBLE(XP*2+1)/SCALE + TEMPVEC(DID) = 1.0D+00 + & - SCALE*DABS(DIST) + IF (DIST .GE. 0.0D+00) THEN + DERVEC(DID) = -SCALE + ELSE + DERVEC(DID) = SCALE + END IF + END IF + END IF + END DO + + INDEX3 = INDEX + INDEX2(1) + DO DID = 2, NDIMS + INDEX3 = INDEX3 + REPVEC(DID)*INDEX2(DID) + END DO + + TEMP = TEMPVEC(1) + DO DID = 2, NDIMS + TEMP = TEMP * TEMPVEC(DID) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + +C Gradient for each active dim + DO DID = 1, NDIMS + TEMP = 1.0D+00 + DO L2 = 1, NDIMS + IF (L2 .EQ. DID) THEN + TEMP = TEMP * DERVEC(L2) + ELSE + TEMP = TEMP * TEMPVEC(L2) + END IF + END DO + IPDER(K, ACTIVEDIMS(DID)) = + & IPDER(K, ACTIVEDIMS(DID)) + + & TEMP * Z(INDEX3) + END DO + + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spdimcc.f b/src/spdimcc.f new file mode 100644 index 0000000..1dc68e3 --- /dev/null +++ b/src/spdimcc.f @@ -0,0 +1,59 @@ +C ******************************************************************* +C +C SPDIM_CC - Count grid points, Clenshaw-Curtis/Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDIM_CC(LEVELSEQ, NLEVELS, D, TOTALPOINTS) +C ******************************************************************* +C +C SPDIM_CC counts the total grid points for a Clenshaw-Curtis +C or Chebyshev sparse grid defined by LEVELSEQ. +C +C Node count per 1-D level: +C lev = 0 -> 1 (midpoint 0.5) +C lev = 1 or 2 -> 2 (boundary or first interior pair) +C lev >= 3 -> 2^(lev-1) (interior nodes) +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D + INTEGER LEVELSEQ(NLEVELS, D) + INTEGER TOTALPOINTS + + INTEGER KL, K, LEV, NPTS + + TOTALPOINTS = 0 + DO KL = 1, NLEVELS + NPTS = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (LEV .EQ. 0) THEN + CONTINUE + ELSE IF (LEV .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LEV-1) + END IF + END DO + TOTALPOINTS = TOTALPOINTS + NPTS + END DO + + RETURN + END diff --git a/src/spdimm.f b/src/spdimm.f new file mode 100644 index 0000000..7685bfd --- /dev/null +++ b/src/spdimm.f @@ -0,0 +1,59 @@ +C ******************************************************************* +C +C SPDIM_M - Count grid points, Maximum / NoBoundary grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPDIM_M(LEVELSEQ, NLEVELS, D, TOTALPOINTS, BOUNDARY) +C ******************************************************************* +C +C SPDIM_M counts total grid points for Maximum or NoBoundary grids. +C +C Node count per 1-D level: +C Maximum (BOUNDARY=1): +C lev = 0 -> 3 (nodes at 0, 0.5, 1) +C lev >= 1 -> 2^lev (interior midpoints) +C NoBoundary (BOUNDARY=0): +C lev >= 0 -> 2^lev (interior midpoints) +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, INTEGER TOTALPOINTS - total grid points +C Input, INTEGER BOUNDARY - 1=Maximum, 0=NoBoundary +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, BOUNDARY + INTEGER LEVELSEQ(NLEVELS, D) + INTEGER TOTALPOINTS + + INTEGER KL, K, LEV, NPTS + + TOTALPOINTS = 0 + DO KL = 1, NLEVELS + NPTS = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (BOUNDARY .EQ. 1 .AND. LEV .EQ. 0) THEN + NPTS = NPTS * 3 + ELSE + NPTS = NPTS * 2**LEV + END IF + END DO + TOTALPOINTS = TOTALPOINTS + NPTS + END DO + + RETURN + END diff --git a/src/spgetnpointscc.f b/src/spgetnpointscc.f new file mode 100644 index 0000000..53c4892 --- /dev/null +++ b/src/spgetnpointscc.f @@ -0,0 +1,65 @@ +C ******************************************************************* +C +C SPGET_NPOINTS_CC - Count grid points per subgrid, CC grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGET_NPOINTS_CC(LEVELSEQ, NLEVELS, D, + & TOTALPOINTS, NPOINTS) +C ******************************************************************* +C +C SPGET_NPOINTS_CC counts the number of grid points in each subgrid +C row of LEVELSEQ for the Clenshaw-Curtis grid, and returns the +C total. +C +C Point count per dimension: +C lev = 0 -> 1 +C lev = 1,2 -> 2 +C lev >= 3 -> 2^(lev-1) +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, INTEGER TOTALPOINTS - total number of points +C Output, INTEGER NPOINTS(NLEVELS) - points per subgrid +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D + INTEGER LEVELSEQ(NLEVELS, D) + INTEGER TOTALPOINTS + INTEGER NPOINTS(NLEVELS) + + INTEGER KL, L, LVAL, NP + + TOTALPOINTS = 0 + + DO KL = 1, NLEVELS + NP = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + NP = NP * 1 + ELSE IF (LVAL .LT. 3) THEN + NP = NP * 2 + ELSE + NP = NP * 2**(LVAL-1) + END IF + END DO + NPOINTS(KL) = NP + TOTALPOINTS = TOTALPOINTS + NP + END DO + + RETURN + END diff --git a/src/spgetnpointsm.f b/src/spgetnpointsm.f new file mode 100644 index 0000000..43cebb8 --- /dev/null +++ b/src/spgetnpointsm.f @@ -0,0 +1,61 @@ +C ******************************************************************* +C +C SPGET_NPOINTS_M - Count grid points per subgrid, Maximum grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGET_NPOINTS_M(LEVELSEQ, NLEVELS, D, + & TOTALPOINTS, NPOINTS) +C ******************************************************************* +C +C SPGET_NPOINTS_M counts the number of grid points in each subgrid +C row of LEVELSEQ for the Maximum grid, and returns the total. +C +C Point count per dimension: +C lev = 0 -> 3 +C lev >= 1 -> 2^lev +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, INTEGER TOTALPOINTS - total number of points +C Output, INTEGER NPOINTS(NLEVELS) - points per subgrid +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D + INTEGER LEVELSEQ(NLEVELS, D) + INTEGER TOTALPOINTS + INTEGER NPOINTS(NLEVELS) + + INTEGER KL, L, LVAL, NP + + TOTALPOINTS = 0 + + DO KL = 1, NLEVELS + NP = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + NP = NP * 3 + ELSE + NP = NP * 2**LVAL + END IF + END DO + NPOINTS(KL) = NP + TOTALPOINTS = TOTALPOINTS + NP + END DO + + RETURN + END diff --git a/src/spgetnpointsnb.f b/src/spgetnpointsnb.f new file mode 100644 index 0000000..a8b1043 --- /dev/null +++ b/src/spgetnpointsnb.f @@ -0,0 +1,55 @@ +C ******************************************************************* +C +C SPGET_NPOINTS_NB - Count grid points per subgrid, NoBoundary grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGET_NPOINTS_NB(LEVELSEQ, NLEVELS, D, + & TOTALPOINTS, NPOINTS) +C ******************************************************************* +C +C SPGET_NPOINTS_NB counts the number of grid points in each subgrid +C row of LEVELSEQ for the NoBoundary grid, and returns the total. +C +C Point count per dimension (all levels): +C lev >= 0 -> 2^lev +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, INTEGER TOTALPOINTS - total number of points +C Output, INTEGER NPOINTS(NLEVELS) - points per subgrid +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D + INTEGER LEVELSEQ(NLEVELS, D) + INTEGER TOTALPOINTS + INTEGER NPOINTS(NLEVELS) + + INTEGER KL, L, NP + + TOTALPOINTS = 0 + + DO KL = 1, NLEVELS + NP = 1 + DO L = 1, D + NP = NP * 2**LEVELSEQ(KL, L) + END DO + NPOINTS(KL) = NP + TOTALPOINTS = TOTALPOINTS + NP + END DO + + RETURN + END diff --git a/src/spgetseq.f b/src/spgetseq.f new file mode 100644 index 0000000..b76f803 --- /dev/null +++ b/src/spgetseq.f @@ -0,0 +1,97 @@ +C ******************************************************************* +C +C SPGETSEQ - Enumerate sparse grid level index sequences. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGETSEQ(N, D, NLEVELS, SEQ) +C ******************************************************************* +C +C SPGETSEQ enumerates all multi-index tuples (l_1,...,l_D) +C with l_1 + ... + l_D = N and l_i >= 0. +C +C The first row always has l_1 = N, l_i = 0 for i > 1. +C Subsequent rows are generated by the Klimke lexicographic +C shift algorithm (Stuttgart convention). +C +C Parameters: +C +C Input, INTEGER N - level +C Input, INTEGER D - dimension +C Input, INTEGER NLEVELS - C(N+D-1,D-1), pre-computed +C Output, INTEGER SEQ(NLEVELS,D) - multi-index array +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER N, D, NLEVELS + INTEGER SEQ(NLEVELS, D) + + INTEGER K, L, M, IMAX, ISUM, ITEMP + + DO L = 1, D + SEQ(1, L) = 0 + END DO + SEQ(1, 1) = N + IMAX = N + + DO 100 K = 2, NLEVELS + + DO L = 1, D + SEQ(K, L) = 0 + END DO + + IF (SEQ(K-1, 1) .GT. 0) THEN + +C First entry positive: decrement it, bump next eligible dim. + SEQ(K, 1) = SEQ(K-1, 1) - 1 + DO L = 2, D + IF (SEQ(K-1, L) .LT. IMAX) THEN + SEQ(K, L) = SEQ(K-1, L) + 1 + DO M = L+1, D + SEQ(K, M) = SEQ(K-1, M) + END DO + GOTO 100 + END IF + END DO + + ELSE + +C First entry is zero: roll over to next row group. + ISUM = 0 + DO L = 2, D + IF (SEQ(K-1, L) .LT. IMAX) THEN + SEQ(K, L) = SEQ(K-1, L) + 1 + ISUM = ISUM + SEQ(K, L) + DO M = L+1, D + SEQ(K, M) = SEQ(K-1, M) + ISUM = ISUM + SEQ(K, M) + END DO + GOTO 50 + ELSE + ITEMP = 0 + DO M = L+2, D + ITEMP = ITEMP + SEQ(K-1, M) + END DO + IMAX = N - ITEMP + SEQ(K, L) = 0 + END IF + END DO + + 50 SEQ(K, 1) = N - ISUM + IMAX = N - ISUM + + END IF + + 100 CONTINUE + + RETURN + END diff --git a/src/spgetseqsp.f b/src/spgetseqsp.f new file mode 100644 index 0000000..6a521d3 --- /dev/null +++ b/src/spgetseqsp.f @@ -0,0 +1,276 @@ +C ******************************************************************* +C +C SPGETSEQ_SP - Build sparse multi-index structure for sparse grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGETSEQ_SP(N, D, GRIDCODE, + & INDICESNDIIMS, INDICESDIMS, INDICESLEVS, INDICESADDR, + & BACKWARDNEIGHBORS, FORWARDNEIGHBORS, + & SUBGRIDPOINTS, SUBGRIDADDR, ACTIVEINDICES, + & NSUBGRIDS, MAXADDR, CURRENTINDEX) +C ******************************************************************* +C +C SPGETSEQ_SP builds the complete sparse multi-index structure +C for a sparse grid of level N in D dimensions. +C +C The structure stores multi-indices (l_1,...,l_D) with +C l_1 + ... + l_D <= N using a compact representation: +C only non-zero dimensions are stored per subgrid. +C +C GRIDCODE: +C 0 = Clenshaw-Curtis or Chebyshev +C 2 = NoBoundary or Gauss-Patterson +C +C Parameters: +C +C Input, INTEGER N - max level +C Input, INTEGER D - dimension +C Input, INTEGER GRIDCODE - grid type code +C Output, INTEGER INDICESNDIIMS(NSUBGRIDS) - active dims per subgrid +C Output, INTEGER INDICESDIMS(MAXADDR) - packed dim indices (1-based) +C Output, INTEGER INDICESLEVS(MAXADDR) - packed levels +C Output, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Output, INTEGER BACKWARDNEIGHBORS(MAXADDR) - backward neighbor idx +C Output, INTEGER FORWARDNEIGHBORS(NSUBGRIDS*D) - forward neighbors +C Output, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - point count per subgrid +C Output, INTEGER SUBGRIDADDR(NSUBGRIDS) - start addr in point array +C Output, INTEGER ACTIVEINDICES(NSUBGRIDS) - 1=active, 0=passive +C Input, INTEGER NSUBGRIDS - C(N+D, D) total subgrids +C Input, INTEGER MAXADDR - size of packed arrays +C Output, INTEGER CURRENTINDEX - next index to process +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER N, D, GRIDCODE, NSUBGRIDS, MAXADDR + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(MAXADDR) + INTEGER INDICESLEVS(MAXADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER BACKWARDNEIGHBORS(MAXADDR) + INTEGER FORWARDNEIGHBORS(NSUBGRIDS * D) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + INTEGER SUBGRIDADDR(NSUBGRIDS) + INTEGER ACTIVEINDICES(NSUBGRIDS) + INTEGER CURRENTINDEX + + INTEGER I, J, K, L, LK, KK + INTEGER NI, ADDR, NBID, NBAD + INTEGER DIM, LEV, LVAL, INSERT + INTEGER NNEWDIMS, NPOINTS, NNEWLEVELS + INTEGER NCHOOSEK + INTEGER BN(50), BDIM(50), BID(50), LEVEL(50) + INTEGER ISADMISSIBLE, BACKOFNEW + INTEGER LOOPK, LOOPNL, SUBNL + +C Initialize first subgrid: all-zero multi-index (0,...,0) + DO I = 1, NSUBGRIDS + INDICESNDIIMS(I) = 0 + INDICESADDR(I) = 0 + SUBGRIDPOINTS(I) = 0 + SUBGRIDADDR(I) = 0 + ACTIVEINDICES(I) = 0 + END DO + DO I = 1, NSUBGRIDS * D + FORWARDNEIGHBORS(I) = 0 + END DO + DO I = 1, MAXADDR + INDICESDIMS(I) = 0 + INDICESLEVS(I) = 0 + BACKWARDNEIGHBORS(I) = 0 + END DO + +C First subgrid: index 1, no active dims (all levels = 0) + INDICESNDIIMS(1) = 0 + INDICESADDR(1) = 1 + IF (GRIDCODE .EQ. 1) THEN +C Maximum grid: 3^D points for level-0 subgrid + NPOINTS = 1 + DO I = 1, D + NPOINTS = NPOINTS * 3 + END DO + ELSE + NPOINTS = 1 + END IF + SUBGRIDPOINTS(1) = NPOINTS + SUBGRIDADDR(1) = 1 + + NI = 1 + ADDR = 1 + CURRENTINDEX = 1 + +C Loop over levels 0..N-1, generating new subgrids + DO LK = 0, N-1 + + NNEWLEVELS = NCHOOSEK(D + LK - 1, D - 1) + + DO LOOPK = 1, NNEWLEVELS + + ACTIVEINDICES(CURRENTINDEX) = 0 + +C Read backward neighbors of current index + NBID = INDICESNDIIMS(CURRENTINDEX) + NBAD = INDICESADDR(CURRENTINDEX) + + DO J = 1, NBID + BDIM(J) = INDICESDIMS(NBAD + J - 1) + BID(J) = BACKWARDNEIGHBORS(NBAD + J - 1) + LEVEL(BDIM(J)) = + & INDICESLEVS(NBAD + J - 1) + END DO + +C Try each dimension as a new direction + DO I = 1, D + + ISADMISSIBLE = 1 + +C Check admissibility + DO J = 1, NBID + IF (I .EQ. BDIM(J)) GOTO 20 + BACKOFNEW = FORWARDNEIGHBORS( + & (BID(J)-1)*D + I) + IF (BACKOFNEW .GT. 0) THEN + IF (ACTIVEINDICES(BACKOFNEW) + & .EQ. 1) THEN + ISADMISSIBLE = 0 + GOTO 30 + END IF + BN(BDIM(J)) = BACKOFNEW + ELSE + ISADMISSIBLE = 0 + GOTO 30 + END IF + 20 CONTINUE + END DO + + 30 IF (ISADMISSIBLE .EQ. 1) THEN + BN(I) = CURRENTINDEX + +C Advance NI for the new subgrid + IF (NI .GT. 0) THEN + ADDR = INDICESADDR(NI) + + & INDICESNDIIMS(NI) + END IF + NI = NI + 1 + IF (NI .GT. NSUBGRIDS) GOTO 999 + INDICESADDR(NI) = ADDR + + IF (NBID .EQ. 0) THEN +C First non-zero dimension + NNEWDIMS = 1 + INDICESDIMS(ADDR) = I + INDICESLEVS(ADDR) = 1 + BACKWARDNEIGHBORS(ADDR) = BN(I) + FORWARDNEIGHBORS((BN(I)-1)*D+I) + & = NI + NPOINTS = 2 + ADDR = ADDR + 1 + ELSE + INSERT = 1 + NNEWDIMS = 0 + NPOINTS = 1 + J = 1 + + 40 IF (J .LE. NBID) THEN + NNEWDIMS = NNEWDIMS + 1 + DIM = BDIM(J) + IF (I .GT. DIM) THEN + LEV = LEVEL(DIM) + J = J + 1 + ELSE IF (I .EQ. DIM) THEN + INSERT = 0 + LEV = LEVEL(DIM)+1 + J = J + 1 + ELSE IF (INSERT .EQ. 1) THEN + INSERT = 0 + LEV = 1 + DIM = I + ELSE + LEV = LEVEL(DIM) + J = J + 1 + END IF + + FORWARDNEIGHBORS( + & (BN(DIM)-1)*D+DIM) + & = NI + INDICESDIMS(ADDR) = DIM + INDICESLEVS(ADDR) = LEV + BACKWARDNEIGHBORS(ADDR) = + & BN(DIM) + +C Compute point count for dim + IF (GRIDCODE .EQ. 0) THEN + IF (LEV .LT. 3) THEN + NPOINTS=NPOINTS*2 + ELSE + NPOINTS=NPOINTS* + & 2**(LEV-1) + END IF + ELSE IF (GRIDCODE .EQ. 1) + & THEN + IF (LEV .EQ. 0) THEN + NPOINTS=NPOINTS*3 + ELSE + NPOINTS=NPOINTS* + & 2**LEV + END IF + ELSE + NPOINTS = NPOINTS * + & 2**LEV + END IF + + ADDR = ADDR + 1 + GOTO 40 + END IF + +C Handle remaining new dim if not inserted + IF (INSERT .EQ. 1) THEN + NNEWDIMS = NNEWDIMS + 1 + INDICESDIMS(ADDR) = I + INDICESLEVS(ADDR) = 1 + BACKWARDNEIGHBORS(ADDR)=BN(I) + FORWARDNEIGHBORS( + & (BN(I)-1)*D+I) = NI + IF (GRIDCODE .EQ. 0) THEN + NPOINTS = NPOINTS * 2 + ELSE IF (GRIDCODE .EQ. 1) + & THEN + NPOINTS = NPOINTS * 2 + ELSE + NPOINTS = NPOINTS * 2 + END IF + ADDR = ADDR + 1 + END IF + END IF + + INDICESNDIIMS(NI) = NNEWDIMS + SUBGRIDPOINTS(NI) = NPOINTS + IF (NI .GT. 1) THEN + SUBGRIDADDR(NI) = SUBGRIDADDR(NI-1) + & + SUBGRIDPOINTS(NI-1) + ELSE + SUBGRIDADDR(NI) = 1 + END IF + ACTIVEINDICES(NI) = 1 + END IF + + END DO + + CURRENTINDEX = CURRENTINDEX + 1 + + END DO + END DO + + 999 CONTINUE + + RETURN + END diff --git a/src/spgridcb.f b/src/spgridcb.f new file mode 100644 index 0000000..4163d54 --- /dev/null +++ b/src/spgridcb.f @@ -0,0 +1,113 @@ +C ******************************************************************* +C +C SPGRID_CB - Chebyshev sparse grid point coordinates. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_CB(LEVELSEQ, NLEVELS, D, X, TOTALPOINTS) +C ******************************************************************* +C +C SPGRID_CB computes Chebyshev sparse grid points on [0,1]^D. +C Nodes are the extrema of Chebyshev polynomials (Chebyshev-Lobatto). +C +C 1-D node formula (same point count as CC): +C lev = 0 -> 0.5 +C lev = 1 -> {0, 1} +C lev >= 2 -> {0.5 - cos((2i-1)*pi/2^lev)/2, +C i=1,...,2^(lev-1)} +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + DOUBLE PRECISION PI + PARAMETER (PI = 3.14159265358979323846D+00) + + INTEGER NLEVELS, D, TOTALPOINTS + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER KL, K, I, J, LEV, IDX, NPTS_KL + INTEGER DIM_ACTIVE(50), NDIMS + INTEGER REP(50), NPTS_DIM(50) + + IDX = 1 + + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + + DO KL = 1, NLEVELS + + NDIMS = 0 + NPTS_KL = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (LEV .EQ. 0) THEN + NPTS_DIM(K) = 1 + ELSE IF (LEV .LE. 2) THEN + NPTS_DIM(K) = 2 + NDIMS = NDIMS + 1 + DIM_ACTIVE(NDIMS) = K + ELSE + NPTS_DIM(K) = 2**(LEV-1) + NDIMS = NDIMS + 1 + DIM_ACTIVE(NDIMS) = K + END IF + NPTS_KL = NPTS_KL * NPTS_DIM(K) + END DO + + REP(1) = 1 + DO K = 2, NDIMS + REP(K) = REP(K-1) * NPTS_DIM(DIM_ACTIVE(K-1)) + END DO + + DO I = 0, NPTS_KL - 1 + DO K = 1, NDIMS + LEV = LEVELSEQ(KL, DIM_ACTIVE(K)) + IF (NDIMS .EQ. 1) THEN + J = MOD(I, NPTS_DIM(DIM_ACTIVE(K))) + ELSE + J = MOD(I/REP(K), + & NPTS_DIM(DIM_ACTIVE(K))) + END IF + + IF (LEV .EQ. 1) THEN + IF (J .EQ. 0) THEN + X(IDX+I, DIM_ACTIVE(K)) = 0.0D+00 + ELSE + X(IDX+I, DIM_ACTIVE(K)) = 1.0D+00 + END IF + ELSE +C Chebyshev-Lobatto new nodes at level lev: +C 0.5 - cos((2j+1)*pi/2^lev) / 2 + X(IDX+I, DIM_ACTIVE(K)) = 0.5D+00 + & - DCOS(DBLE(2*J+1)*PI + & / DBLE(2**LEV)) / 2.0D+00 + END IF + END DO + END DO + + IDX = IDX + NPTS_KL + + END DO + + RETURN + END diff --git a/src/spgridcbsp.f b/src/spgridcbsp.f new file mode 100644 index 0000000..54aeb08 --- /dev/null +++ b/src/spgridcbsp.f @@ -0,0 +1,134 @@ +C ******************************************************************* +C +C SPGRID_CB_SP - Chebyshev grid points, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_CB_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, D, X, TOTALPOINTS, FROMINDEX, TOINDEX) +C ******************************************************************* +C +C SPGRID_CB_SP computes Chebyshev sparse grid points using sparse +C index structure. +C +C 1-D node formula (new nodes only): +C lev = 1 -> {0, 1} +C lev >= 2 -> {0.5 - cos((2i-1)*pi/2^lev)/2, +C i=1,...,2^(lev-1)} +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPOINTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total points +C Input, INTEGER FROMINDEX - first subgrid (1-based) +C Input, INTEGER TOINDEX - last subgrid (1-based) +C +C ******************************************************************* + + IMPLICIT NONE + + DOUBLE PRECISION PI + PARAMETER (PI = 3.14159265358979323846D+00) + + INTEGER NSUBGRIDS, NADDR, D, TOTALPOINTS, FROMINDEX, TOINDEX + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER NPTS_DIM(50), REP(50) + INTEGER I, K, J, IDX + DOUBLE PRECISION COORD + + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + + IDX = 1 + DO CI = 1, FROMINDEX - 1 + IDX = IDX + SUBGRIDPOINTS(CI) + END DO + + DO CI = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + IDX = IDX + NPTS + GOTO 200 + END IF + + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .LE. 2) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + END DO + + REP(1) = 1 + DO DID = 2, NDIMS + REP(DID) = REP(DID-1) * NPTS_DIM(DID-1) + END DO + + DO I = 0, NPTS - 1 + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + J = MOD(I / REP(DID), NPTS_DIM(DID)) + + IF (LVAL .EQ. 0) THEN + COORD = 0.5D+00 + ELSE IF (LVAL .EQ. 1) THEN + IF (J .EQ. 0) THEN + COORD = 0.0D+00 + ELSE + COORD = 1.0D+00 + END IF + ELSE + COORD = 0.5D+00 - DCOS(DBLE(2*J+1)*PI + & / DBLE(2**LVAL)) / 2.0D+00 + END IF + + X(IDX+I, ACTIVEDIMS(DID)) = COORD + END DO + END DO + + IDX = IDX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spgridcc.f b/src/spgridcc.f new file mode 100644 index 0000000..b0295d9 --- /dev/null +++ b/src/spgridcc.f @@ -0,0 +1,114 @@ +C ******************************************************************* +C +C SPGRID_CC - Clenshaw-Curtis sparse grid point coordinates. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_CC(LEVELSEQ, NLEVELS, D, X, TOTALPOINTS) +C ******************************************************************* +C +C SPGRID_CC computes Clenshaw-Curtis sparse grid points on [0,1]^D +C for the given multi-index set LEVELSEQ. +C +C One row of X is one grid point; column k holds the k-th coord. +C Dimensions with level 0 remain at 0.5 (the midpoint). +C +C 1-D node formula: +C lev = 0 -> 0.5 +C lev = 1 -> {0, 1} +C lev >= 2 -> {(2i-1)/2^lev, i=1,...,2^(lev-1)} +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, TOTALPOINTS + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER KL, K, I, LEV, IDX, NPTS_KL + INTEGER DIM_ACTIVE(50), NDIMS + INTEGER REP(50), NPTS_DIM(50) + + IDX = 1 + + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + + DO KL = 1, NLEVELS + + NDIMS = 0 + NPTS_KL = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (LEV .EQ. 0) THEN + NPTS_DIM(K) = 1 + ELSE IF (LEV .EQ. 1) THEN + NPTS_DIM(K) = 2 + NDIMS = NDIMS + 1 + DIM_ACTIVE(NDIMS) = K + ELSE + NPTS_DIM(K) = 2**(LEV-1) + NDIMS = NDIMS + 1 + DIM_ACTIVE(NDIMS) = K + END IF + NPTS_KL = NPTS_KL * NPTS_DIM(K) + END DO + + REP(1) = 1 + DO K = 2, NDIMS + REP(K) = REP(K-1) * NPTS_DIM(DIM_ACTIVE(K-1)) + END DO + + DO I = 0, NPTS_KL - 1 + DO K = 1, NDIMS + LEV = LEVELSEQ(KL, DIM_ACTIVE(K)) + IF (NDIMS .EQ. 1) THEN + X(IDX+I, DIM_ACTIVE(K)) = + & DBLE(MOD(I, NPTS_DIM( + & DIM_ACTIVE(K)))) + ELSE + X(IDX+I, DIM_ACTIVE(K)) = + & DBLE(MOD(I/REP(K), + & NPTS_DIM(DIM_ACTIVE(K)))) + END IF + + IF (LEV .EQ. 1) THEN + IF (X(IDX+I, DIM_ACTIVE(K)) + & .LT. 0.5D+00) THEN + X(IDX+I, DIM_ACTIVE(K)) = 0.0D+00 + ELSE + X(IDX+I, DIM_ACTIVE(K)) = 1.0D+00 + END IF + ELSE + X(IDX+I, DIM_ACTIVE(K)) = + & (2.0D+00*X(IDX+I,DIM_ACTIVE(K)) + & + 1.0D+00) / DBLE(2**LEV) + END IF + END DO + END DO + + IDX = IDX + NPTS_KL + + END DO + + RETURN + END diff --git a/src/spgridccsp.f b/src/spgridccsp.f new file mode 100644 index 0000000..6c72982 --- /dev/null +++ b/src/spgridccsp.f @@ -0,0 +1,137 @@ +C ******************************************************************* +C +C SPGRID_CC_SP - CC grid points, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_CC_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, D, X, TOTALPOINTS, FROMINDEX, TOINDEX) +C ******************************************************************* +C +C SPGRID_CC_SP computes CC sparse grid points using sparse index +C structure. For each subgrid in FROMINDEX..TOINDEX, builds the +C tensor product of 1D CC nodes and fills X. +C +C 1-D node formula (new nodes only): +C lev = 1 -> {0, 1} +C lev >= 2 -> {(2i-1)/2^lev, i=1,...,2^(lev-1)} +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPOINTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total points +C Input, INTEGER FROMINDEX - first subgrid (1-based) +C Input, INTEGER TOINDEX - last subgrid (1-based) +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, D, TOTALPOINTS, FROMINDEX, TOINDEX + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER NPTS_DIM(50), REP(50) + INTEGER I, K, J, IDX + DOUBLE PRECISION COORD + +C Initialize all coords to 0.5 (inactive dims default) + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + +C Compute starting IDX: sum of points for subgrids 1..FROMINDEX-1 + IDX = 1 + DO CI = 1, FROMINDEX - 1 + IDX = IDX + SUBGRIDPOINTS(CI) + END DO + + DO CI = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN +C No active dims: single constant point at 0.5 everywhere + IDX = IDX + NPTS + GOTO 200 + END IF + +C Read active dims and levels + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + +C Compute npts per active dim + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .EQ. 1) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + END DO + +C Build mixed-radix strides + REP(1) = 1 + DO DID = 2, NDIMS + REP(DID) = REP(DID-1) * NPTS_DIM(DID-1) + END DO + +C Fill grid points + DO I = 0, NPTS - 1 + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + J = MOD(I / REP(DID), NPTS_DIM(DID)) + + IF (LVAL .EQ. 0) THEN + COORD = 0.5D+00 + ELSE IF (LVAL .EQ. 1) THEN + IF (J .EQ. 0) THEN + COORD = 0.0D+00 + ELSE + COORD = 1.0D+00 + END IF + ELSE + COORD = DBLE(2*J+1) / DBLE(2**LVAL) + END IF + + X(IDX+I, ACTIVEDIMS(DID)) = COORD + END DO + END DO + + IDX = IDX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spgridgp.f b/src/spgridgp.f new file mode 100644 index 0000000..fa1651b --- /dev/null +++ b/src/spgridgp.f @@ -0,0 +1,98 @@ +C ******************************************************************* +C +C SPGRID_GP - Gauss-Patterson sparse grid point coordinates. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_GP(LEVELSEQ, NLEVELS, D, X, TOTALPOINTS) +C ******************************************************************* +C +C SPGRID_GP computes Gauss-Patterson sparse grid points on [0,1]^D. +C Uses the nested Gauss-Patterson nodes (max level 6). +C +C Node count per 1-D level: +C lev = 0 -> 1 (midpoint) +C lev >= 1 -> 2^lev (new GP nodes at this level) +C Full node set at level lev has 2^(lev+1)-1 nodes. +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, TOTALPOINTS + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER KL, K, I, J, LEV, IDX, NPTS_KL + INTEGER DIM_ACTIVE(50), NDIMS + INTEGER REP(50), NPTS_DIM(50) + INTEGER NX + DOUBLE PRECISION ABSC(127) + + IDX = 1 + + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + + DO KL = 1, NLEVELS + + NDIMS = 0 + NPTS_KL = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (LEV .EQ. 0) THEN + NPTS_DIM(K) = 1 + ELSE + NPTS_DIM(K) = 2**LEV + NDIMS = NDIMS + 1 + DIM_ACTIVE(NDIMS) = K + END IF + NPTS_KL = NPTS_KL * NPTS_DIM(K) + END DO + + REP(1) = 1 + DO K = 2, NDIMS + REP(K) = REP(K-1) * NPTS_DIM(DIM_ACTIVE(K-1)) + END DO + + DO I = 0, NPTS_KL - 1 + DO K = 1, NDIMS + LEV = LEVELSEQ(KL, DIM_ACTIVE(K)) + NX = 2**(LEV+1) - 1 + CALL GP_ABSC(LEV, ABSC, NX) + IF (NDIMS .EQ. 1) THEN + J = MOD(I, NPTS_DIM(DIM_ACTIVE(K))) + ELSE + J = MOD(I/REP(K), + & NPTS_DIM(DIM_ACTIVE(K))) + END IF +C New GP nodes occupy the even indices (0,2,4,...) +C of the full node set. Skip by 2 to get new nodes. + X(IDX+I, DIM_ACTIVE(K)) = ABSC(J*2 + 1) + END DO + END DO + + IDX = IDX + NPTS_KL + + END DO + + RETURN + END diff --git a/src/spgridgpsp.f b/src/spgridgpsp.f new file mode 100644 index 0000000..cd077dd --- /dev/null +++ b/src/spgridgpsp.f @@ -0,0 +1,111 @@ +C ******************************************************************* +C +C SPGRID_GP_SP - GP grid points, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_GP_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, D, X, TOTALPOINTS, FROMINDEX, TOINDEX) +C ******************************************************************* +C +C SPGRID_GP_SP computes Gauss-Patterson sparse grid points using +C the sparse index structure. For each lev, NX=2^(lev+1)-1, calls +C GP_ABSC and takes every other node (odd 1-based indices). +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPOINTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total points +C Input, INTEGER FROMINDEX - first subgrid (1-based) +C Input, INTEGER TOINDEX - last subgrid (1-based) +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, D, TOTALPOINTS, FROMINDEX, TOINDEX + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER NPTS_DIM(50), REP(50) + INTEGER I, K, J, IDX, NX + DOUBLE PRECISION ABSC(127) + + DO K = 1, D + DO I = 1, TOTALPOINTS + X(I, K) = 0.5D+00 + END DO + END DO + + IDX = 1 + DO CI = 1, FROMINDEX - 1 + IDX = IDX + SUBGRIDPOINTS(CI) + END DO + + DO CI = FROMINDEX, TOINDEX + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + IDX = IDX + NPTS + GOTO 200 + END IF + + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + NPTS_DIM(DID) = 2**LVAL + END DO + + REP(1) = 1 + DO DID = 2, NDIMS + REP(DID) = REP(DID-1) * NPTS_DIM(DID-1) + END DO + + DO I = 0, NPTS - 1 + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + NX = 2**(LVAL+1) - 1 + CALL GP_ABSC(LVAL, ABSC, NX) + J = MOD(I / REP(DID), NPTS_DIM(DID)) +C New GP nodes occupy odd 1-based positions (1,3,5,...). +C Skip by 2: index = J*2+1 (1-based). + X(IDX+I, ACTIVEDIMS(DID)) = ABSC(J*2 + 1) + END DO + END DO + + IDX = IDX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spgridm.f b/src/spgridm.f new file mode 100644 index 0000000..b7c3056 --- /dev/null +++ b/src/spgridm.f @@ -0,0 +1,93 @@ +C ******************************************************************* +C +C SPGRID_M - Maximum-norm sparse grid point coordinates. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_M(LEVELSEQ, NLEVELS, D, X, TOTALPOINTS) +C ******************************************************************* +C +C SPGRID_M computes Maximum-norm sparse grid points on [0,1]^D. +C +C 1-D node formula: +C lev = 0 -> {0, 0.5, 1} (3 nodes) +C lev >= 1 -> {(2i-1)/2^(lev+1), i=1,...,2^lev} +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, TOTALPOINTS + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER KL, K, I, J, LEV, IDX, NPTS_KL, NPTS_DIM(50) + INTEGER REP(50) + DOUBLE PRECISION COORD + + IDX = 1 + + DO KL = 1, NLEVELS + + NPTS_KL = 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (LEV .EQ. 0) THEN + NPTS_DIM(K) = 3 + ELSE + NPTS_DIM(K) = 2**LEV + END IF + NPTS_KL = NPTS_KL * NPTS_DIM(K) + END DO + + REP(1) = 1 + DO K = 2, D + REP(K) = REP(K-1) * NPTS_DIM(K-1) + END DO + + DO I = 0, NPTS_KL - 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (D .EQ. 1) THEN + J = MOD(I, NPTS_DIM(K)) + ELSE + J = MOD(I / REP(K), NPTS_DIM(K)) + END IF + + IF (LEV .EQ. 0) THEN + IF (J .EQ. 0) THEN + COORD = 0.0D+00 + ELSE IF (J .EQ. 1) THEN + COORD = 0.5D+00 + ELSE + COORD = 1.0D+00 + END IF + ELSE + COORD = DBLE(2*J+1) / + & DBLE(2**(LEV+1)) + END IF + X(IDX+I, K) = COORD + END DO + END DO + + IDX = IDX + NPTS_KL + + END DO + + RETURN + END diff --git a/src/spgridnb.f b/src/spgridnb.f new file mode 100644 index 0000000..b3eec80 --- /dev/null +++ b/src/spgridnb.f @@ -0,0 +1,75 @@ +C ******************************************************************* +C +C SPGRID_NB - NoBoundary sparse grid point coordinates. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPGRID_NB(LEVELSEQ, NLEVELS, D, X, TOTALPOINTS) +C ******************************************************************* +C +C SPGRID_NB computes NoBoundary sparse grid points on [0,1]^D. +C All nodes are interior midpoints; no boundary nodes included. +C +C 1-D node formula (all levels): +C lev >= 0 -> {(2i-1)/2^(lev+1), i=1,...,2^lev} +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION X(TOTALPTS,D) - grid coordinates +C Input, INTEGER TOTALPOINTS - total grid points +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, TOTALPOINTS + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION X(TOTALPOINTS, D) + + INTEGER KL, K, I, J, LEV, IDX, NPTS_KL + INTEGER NPTS_DIM(50), REP(50) + + IDX = 1 + + DO KL = 1, NLEVELS + + NPTS_KL = 1 + DO K = 1, D + NPTS_DIM(K) = 2**LEVELSEQ(KL, K) + NPTS_KL = NPTS_KL * NPTS_DIM(K) + END DO + + REP(1) = 1 + DO K = 2, D + REP(K) = REP(K-1) * NPTS_DIM(K-1) + END DO + + DO I = 0, NPTS_KL - 1 + DO K = 1, D + LEV = LEVELSEQ(KL, K) + IF (D .EQ. 1) THEN + J = MOD(I, NPTS_DIM(K)) + ELSE + J = MOD(I / REP(K), NPTS_DIM(K)) + END IF + X(IDX+I, K) = DBLE(2*J+1) / + & DBLE(2**(LEV+1)) + END DO + END DO + + IDX = IDX + NPTS_KL + + END DO + + RETURN + END diff --git a/src/spinterp.pyf b/src/spinterp.pyf new file mode 100644 index 0000000..b77ce5c --- /dev/null +++ b/src/spinterp.pyf @@ -0,0 +1,745 @@ +python module spinterp + interface + + ! ------------------------------------------------------------------ + ! SPNLEVELS - number of multi-indices summing to N in D dims + ! ------------------------------------------------------------------ + subroutine spnlevels(n, d, nlevels) + integer intent(in) :: n + integer intent(in) :: d + integer intent(out) :: nlevels + end subroutine spnlevels + + ! ------------------------------------------------------------------ + ! SPGETSEQ - enumerate multi-index level sequences + ! ------------------------------------------------------------------ + subroutine spgetseq(n, d, nlevels, seq) + integer intent(in) :: n + integer intent(in) :: d + integer intent(in) :: nlevels + integer intent(out), dimension(nlevels, d) :: seq + end subroutine spgetseq + + ! ------------------------------------------------------------------ + ! SPDIM_CC - total grid points, CC / Chebyshev sparse grid + ! ------------------------------------------------------------------ + subroutine spdim_cc(levelseq, nlevels, d, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in), dimension(nlevels, d) :: levelseq + integer intent(out) :: totalpoints + end subroutine spdim_cc + + ! ------------------------------------------------------------------ + ! SPDIM_M - total grid points, Maximum / NoBoundary sparse grid + ! ------------------------------------------------------------------ + subroutine spdim_m(levelseq, nlevels, d, totalpoints, boundary) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: boundary + integer intent(in), dimension(nlevels, d) :: levelseq + integer intent(out) :: totalpoints + end subroutine spdim_m + + ! ------------------------------------------------------------------ + ! SPGRID_CC - Clenshaw-Curtis sparse grid coordinates + ! ------------------------------------------------------------------ + subroutine spgrid_cc(levelseq, nlevels, d, x, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_cc + + ! ------------------------------------------------------------------ + ! SPGRID_M - Maximum-norm sparse grid coordinates + ! ------------------------------------------------------------------ + subroutine spgrid_m(levelseq, nlevels, d, x, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_m + + ! ------------------------------------------------------------------ + ! SPGRID_NB - NoBoundary sparse grid coordinates + ! ------------------------------------------------------------------ + subroutine spgrid_nb(levelseq, nlevels, d, x, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_nb + + ! ------------------------------------------------------------------ + ! SPGRID_CB - Chebyshev sparse grid coordinates + ! ------------------------------------------------------------------ + subroutine spgrid_cb(levelseq, nlevels, d, x, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_cb + + ! ------------------------------------------------------------------ + ! SPGRID_GP - Gauss-Patterson sparse grid coordinates + ! ------------------------------------------------------------------ + subroutine spgrid_gp(levelseq, nlevels, d, x, totalpoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_gp + + ! ------------------------------------------------------------------ + ! SPINTERP_CC - evaluate CC sparse grid interpolant + ! ------------------------------------------------------------------ + subroutine spinterp_cc(d, z, nz, y, ninterp, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_cc + + ! ------------------------------------------------------------------ + ! SPINTERP_M - evaluate Maximum-norm sparse grid interpolant + ! ------------------------------------------------------------------ + subroutine spinterp_m(d, z, nz, y, ninterp, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_m + + ! ------------------------------------------------------------------ + ! SPINTERP_NB - evaluate NoBoundary sparse grid interpolant + ! ------------------------------------------------------------------ + subroutine spinterp_nb(d, z, nz, y, ninterp, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_nb + + ! ------------------------------------------------------------------ + ! SPINTERP_CB - evaluate Chebyshev sparse grid interpolant + ! ------------------------------------------------------------------ + subroutine spinterp_cb(d, z, nz, y, ninterp, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_cb + + ! ------------------------------------------------------------------ + ! SPINTERP_GP - evaluate Gauss-Patterson sparse grid interpolant + ! ------------------------------------------------------------------ + subroutine spinterp_gp(d, z, nz, y, ninterp, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_gp + + ! ------------------------------------------------------------------ + ! SPCMPVALS_CC - compute hierarchical surplus increments, CC + ! ------------------------------------------------------------------ + subroutine spcmpvals_cc(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ny + integer intent(in) :: nnewlevels + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_cc + + ! ------------------------------------------------------------------ + ! SPCMPVALS_M - compute hierarchical surplus increments, Maximum + ! ------------------------------------------------------------------ + subroutine spcmpvals_m(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ny + integer intent(in) :: nnewlevels + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_m + + ! ------------------------------------------------------------------ + ! SPCMPVALS_NB - compute hierarchical surplus increments, NoBoundary + ! ------------------------------------------------------------------ + subroutine spcmpvals_nb(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ny + integer intent(in) :: nnewlevels + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_nb + + ! ------------------------------------------------------------------ + ! SPCMPVALS_CB - compute hierarchical surplus increments, Chebyshev + ! ------------------------------------------------------------------ + subroutine spcmpvals_cb(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ny + integer intent(in) :: nnewlevels + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_cb + + ! ------------------------------------------------------------------ + ! SPCMPVALS_GP - compute hierarchical surplus increments, GP + ! ------------------------------------------------------------------ + subroutine spcmpvals_gp(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ny + integer intent(in) :: nnewlevels + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_gp + + ! ------------------------------------------------------------------ + ! SPDERIV_CC - CC interpolant values and gradient + ! ------------------------------------------------------------------ + subroutine spderiv_cc(d, z, nz, y, ninterp, levelseq, nlevels, ip, ipder) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + end subroutine spderiv_cc + + ! ------------------------------------------------------------------ + ! SPQUADW_CC - quadrature weights, CC / Chebyshev + ! ------------------------------------------------------------------ + subroutine spquadw_cc(levelseq, nlevels, d, w, nw) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: nw + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(nw) :: w + end subroutine spquadw_cc + + ! ------------------------------------------------------------------ + ! SPQUADW_M - quadrature weights, Maximum + ! ------------------------------------------------------------------ + subroutine spquadw_m(levelseq, nlevels, d, w, nw) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: nw + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(nw) :: w + end subroutine spquadw_m + + ! ------------------------------------------------------------------ + ! SPQUADW_NB - quadrature weights, NoBoundary + ! ------------------------------------------------------------------ + subroutine spquadw_nb(levelseq, nlevels, d, w, nw) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: nw + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(nw) :: w + end subroutine spquadw_nb + + ! ------------------------------------------------------------------ + ! SPQUADW_CB - quadrature weights, Chebyshev (needs 1-D table) + ! ------------------------------------------------------------------ + subroutine spquadw_cb(levelseq, nlevels, d, w, nw, w1d, nw1d, startid) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: nw + integer intent(in) :: nw1d + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(nw) :: w + double precision intent(in), dimension(nw1d) :: w1d + integer intent(in), dimension(*) :: startid + end subroutine spquadw_cb + + ! ------------------------------------------------------------------ + ! SPQUADW_GP - quadrature weights, Gauss-Patterson (needs 1-D table) + ! ------------------------------------------------------------------ + subroutine spquadw_gp(levelseq, nlevels, d, w, nw, w1d, nw1d, startid) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in) :: nw + integer intent(in) :: nw1d + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(nw) :: w + double precision intent(in), dimension(nw1d) :: w1d + integer intent(in), dimension(*) :: startid + end subroutine spquadw_gp + + ! ------------------------------------------------------------------ + ! GP_WEIGHTS - 1-D GP quadrature weight table + ! ------------------------------------------------------------------ + subroutine gp_weights(maxlev, weights, nw, startid) + integer intent(in) :: maxlev + integer intent(in) :: nw + double precision intent(out), dimension(nw) :: weights + integer intent(out), dimension(maxlev+1) :: startid + end subroutine gp_weights + + ! ------------------------------------------------------------------ + ! CHEB_WEIGHTS - 1-D Chebyshev quadrature weight table + ! ------------------------------------------------------------------ + subroutine cheb_weights(maxlev, weights, nw, startid) + integer intent(in) :: maxlev + integer intent(in) :: nw + double precision intent(out), dimension(nw) :: weights + integer intent(out), dimension(maxlev+1) :: startid + end subroutine cheb_weights + + ! ------------------------------------------------------------------ + ! GP_ABSC - Gauss-Patterson abscissae + ! ------------------------------------------------------------------ + subroutine gp_absc(level, x, nx) + integer intent(in) :: level + integer intent(in) :: nx + double precision intent(out), dimension(nx) :: x + end subroutine gp_absc + + ! ------------------------------------------------------------------ + ! GP_BARY_W - Gauss-Patterson barycentric weights + ! ------------------------------------------------------------------ + subroutine gp_bary_w(level, w, nw) + integer intent(in) :: level + integer intent(in) :: nw + double precision intent(out), dimension(nw) :: w + end subroutine gp_bary_w + + ! ------------------------------------------------------------------ + ! SPGET_NPOINTS_CC - point counts per subgrid, CC/Chebyshev + ! ------------------------------------------------------------------ + subroutine spget_npoints_cc(levelseq, nlevels, d, totalpoints, npoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in), dimension(nlevels, d) :: levelseq + integer intent(out) :: totalpoints + integer intent(out), dimension(nlevels) :: npoints + end subroutine spget_npoints_cc + + ! ------------------------------------------------------------------ + ! SPGET_NPOINTS_M - point counts per subgrid, Maximum + ! ------------------------------------------------------------------ + subroutine spget_npoints_m(levelseq, nlevels, d, totalpoints, npoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in), dimension(nlevels, d) :: levelseq + integer intent(out) :: totalpoints + integer intent(out), dimension(nlevels) :: npoints + end subroutine spget_npoints_m + + ! ------------------------------------------------------------------ + ! SPGET_NPOINTS_NB - point counts per subgrid, NoBoundary + ! ------------------------------------------------------------------ + subroutine spget_npoints_nb(levelseq, nlevels, d, totalpoints, npoints) + integer intent(in) :: nlevels + integer intent(in) :: d + integer intent(in), dimension(nlevels, d) :: levelseq + integer intent(out) :: totalpoints + integer intent(out), dimension(nlevels) :: npoints + end subroutine spget_npoints_nb + + ! ------------------------------------------------------------------ + ! SPSEQ2FULL - expand sparse index structure to full levelseq + ! ------------------------------------------------------------------ + subroutine spseq2full(indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, d, fullseq) + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + integer intent(in) :: d + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(out), dimension(nsubgrids, d) :: fullseq + end subroutine spseq2full + + ! ------------------------------------------------------------------ + ! REORDER_VALS - reorder surplus array (sort dims by level desc) + ! ------------------------------------------------------------------ + subroutine reorder_vals(z, nz, levelseq, nlevels, d) + integer intent(in) :: nz + integer intent(in) :: nlevels + integer intent(in) :: d + double precision intent(inout), dimension(nz) :: z + integer intent(in), dimension(nlevels, d) :: levelseq + end subroutine reorder_vals + + ! ------------------------------------------------------------------ + ! SPINTERP_CC_SP - CC interpolation, sparse index structure + ! ------------------------------------------------------------------ + subroutine spinterp_cc_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_cc_sp + + ! ------------------------------------------------------------------ + ! SPINTERP_CB_SP - Chebyshev interpolation, sparse index structure + ! ------------------------------------------------------------------ + subroutine spinterp_cb_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_cb_sp + + ! ------------------------------------------------------------------ + ! SPINTERP_GP_SP - GP interpolation, sparse index structure + ! ------------------------------------------------------------------ + subroutine spinterp_gp_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, ip) + integer intent(in) :: d + integer intent(in) :: nz + integer intent(in) :: ninterp + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(ninterp) :: ip + end subroutine spinterp_gp_sp + + ! ------------------------------------------------------------------ + ! SPGRID_CC_SP - CC grid points, sparse index structure + ! ------------------------------------------------------------------ + subroutine spgrid_cc_sp(indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, d, x, totalpoints, fromindex, toindex) + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in) :: fromindex + integer intent(in) :: toindex + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_cc_sp + + ! ------------------------------------------------------------------ + ! SPGRID_CB_SP - Chebyshev grid points, sparse index structure + ! ------------------------------------------------------------------ + subroutine spgrid_cb_sp(indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, d, x, totalpoints, fromindex, toindex) + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in) :: fromindex + integer intent(in) :: toindex + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_cb_sp + + ! ------------------------------------------------------------------ + ! SPGRID_GP_SP - GP grid points, sparse index structure + ! ------------------------------------------------------------------ + subroutine spgrid_gp_sp(indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, d, x, totalpoints, fromindex, toindex) + integer intent(in) :: nsubgrids + integer intent(in) :: naddr + integer intent(in) :: d + integer intent(in) :: totalpoints + integer intent(in) :: fromindex + integer intent(in) :: toindex + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(totalpoints, d) :: x + end subroutine spgrid_gp_sp + + ! ------------------------------------------------------------------ + ! SPCMPVALS_CC_SP - CC surpluses, sparse index structure + ! ------------------------------------------------------------------ + subroutine spcmpvals_cc_sp(d, z, nz, y, ny, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, backwardneighbors, forwardneighbors, nfwd, subgridpoints, subgridaddr, fromindex, toindex, ip) + integer intent(in) :: d, nz, ny, nsubgrids, naddr, nfwd + integer intent(in) :: fromindex, toindex + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(naddr) :: backwardneighbors + integer intent(in), dimension(nfwd) :: forwardneighbors + integer intent(in), dimension(nsubgrids) :: subgridpoints + integer intent(in), dimension(nsubgrids) :: subgridaddr + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_cc_sp + + ! ------------------------------------------------------------------ + ! SPCMPVALS_CBGP_SP - CB/GP surpluses, sparse index structure + ! ------------------------------------------------------------------ + subroutine spcmpvals_cbgp_sp(d, z, nz, y, ny, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, backwardneighbors, forwardneighbors, nfwd, subgridpoints, subgridaddr, fromindex, toindex, isgp, ip) + integer intent(in) :: d, nz, ny, nsubgrids, naddr, nfwd, isgp + integer intent(in) :: fromindex, toindex + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(naddr) :: backwardneighbors + integer intent(in), dimension(nfwd) :: forwardneighbors + integer intent(in), dimension(nsubgrids) :: subgridpoints + integer intent(in), dimension(nsubgrids) :: subgridaddr + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_cbgp_sp + + ! ------------------------------------------------------------------ + ! SPQUADW_CC_SP - CC quadrature weights, sparse indices + ! ------------------------------------------------------------------ + subroutine spquadw_cc_sp(indicesndiims, nsubgrids, indiceslevs, indicesaddr, naddr, subgridpoints, w, nw) + integer intent(in) :: nsubgrids, naddr, nw + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(nw) :: w + end subroutine spquadw_cc_sp + + ! ------------------------------------------------------------------ + ! SPQUADW_CB_SP - CB quadrature weights, sparse indices + ! ------------------------------------------------------------------ + subroutine spquadw_cb_sp(indicesndiims, nsubgrids, indiceslevs, indicesaddr, naddr, subgridpoints, w, nw, w1d, nw1d, startid) + integer intent(in) :: nsubgrids, naddr, nw, nw1d + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(nw) :: w + double precision intent(in), dimension(nw1d) :: w1d + integer intent(in), dimension(*) :: startid + end subroutine spquadw_cb_sp + + ! ------------------------------------------------------------------ + ! SPQUADW_GP_SP - GP quadrature weights, sparse indices + ! ------------------------------------------------------------------ + subroutine spquadw_gp_sp(indicesndiims, nsubgrids, indiceslevs, indicesaddr, naddr, subgridpoints, w, nw, w1d, nw1d, startid) + integer intent(in) :: nsubgrids, naddr, nw, nw1d + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(nw) :: w + double precision intent(in), dimension(nw1d) :: w1d + integer intent(in), dimension(*) :: startid + end subroutine spquadw_gp_sp + + ! ------------------------------------------------------------------ + ! SPDERIV_CC_SP - CC gradient, sparse index structure + ! ------------------------------------------------------------------ + subroutine spderiv_cc_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, ip, ipder) + integer intent(in) :: d, nz, ninterp, nsubgrids, naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + end subroutine spderiv_cc_sp + + ! ------------------------------------------------------------------ + ! SPCONT_DERIV_CC - continuous derivatives, CC full levelseq + ! ------------------------------------------------------------------ + subroutine spcont_deriv_cc(d, z, nz, y, ninterp, levelseq, nlevels, maxlev, ip, ipder, ipder2) + integer intent(in) :: d, nz, ninterp, nlevels, maxlev + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + double precision intent(out), dimension(ninterp, d) :: ipder2 + end subroutine spcont_deriv_cc + + ! ------------------------------------------------------------------ + ! SPCONT_DERIV_CC_SP - continuous derivatives, CC sparse indices + ! ------------------------------------------------------------------ + subroutine spcont_deriv_cc_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, maxlevvec, ip, ipder, ipder2) + integer intent(in) :: d, nz, ninterp, nsubgrids, naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + integer intent(in), dimension(d) :: maxlevvec + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + double precision intent(out), dimension(ninterp, d) :: ipder2 + end subroutine spcont_deriv_cc_sp + + ! ------------------------------------------------------------------ + ! PP_DERIV - post-processing for continuous derivatives + ! ------------------------------------------------------------------ + subroutine pp_deriv(ipder, ipder2, ninterp, d, maxlevvec, y) + integer intent(in) :: ninterp, d + integer intent(in), dimension(d) :: maxlevvec + double precision intent(inout), dimension(ninterp, d) :: ipder + double precision intent(in), dimension(ninterp, d) :: ipder2 + double precision intent(in), dimension(ninterp, d) :: y + end subroutine pp_deriv + + ! ------------------------------------------------------------------ + ! SPDERIV_CB - Chebyshev gradient, full levelseq + ! ------------------------------------------------------------------ + subroutine spderiv_cb(d, z, nz, y, ninterp, levelseq, nlevels, ip, ipder) + integer intent(in) :: d, nz, ninterp, nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + end subroutine spderiv_cb + + ! ------------------------------------------------------------------ + ! SPDERIV_CB_SP - Chebyshev gradient, sparse indices + ! ------------------------------------------------------------------ + subroutine spderiv_cb_sp(d, z, nz, y, ninterp, indicesndiims, nsubgrids, indicesdims, indiceslevs, indicesaddr, naddr, subgridpoints, ip, ipder) + integer intent(in) :: d, nz, ninterp, nsubgrids, naddr + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ninterp, d) :: y + integer intent(in), dimension(nsubgrids) :: indicesndiims + integer intent(in), dimension(naddr) :: indicesdims + integer intent(in), dimension(naddr) :: indiceslevs + integer intent(in), dimension(nsubgrids) :: indicesaddr + integer intent(in), dimension(nsubgrids) :: subgridpoints + double precision intent(out), dimension(ninterp) :: ip + double precision intent(out), dimension(ninterp, d) :: ipder + end subroutine spderiv_cb_sp + + ! ------------------------------------------------------------------ + ! SPCMPVALS_CB_DCT - CB surpluses via DCT, full levelseq + ! ------------------------------------------------------------------ + subroutine spcmpvals_cb_dct(d, z, nz, y, ny, newlevelseq, nnewlevels, levelseq, nlevels, ip) + integer intent(in) :: d, nz, ny, nnewlevels, nlevels + double precision intent(in), dimension(nz) :: z + double precision intent(in), dimension(ny, d) :: y + integer intent(in), dimension(nnewlevels, d) :: newlevelseq + integer intent(in), dimension(nlevels, d) :: levelseq + double precision intent(out), dimension(ny) :: ip + end subroutine spcmpvals_cb_dct + + ! ------------------------------------------------------------------ + ! SORT_HEAP - max-heap sift-up + ! ------------------------------------------------------------------ + subroutine sort_heap(a, na, g, ng) + integer intent(in) :: na, ng + integer intent(inout), dimension(na) :: a + double precision intent(in), dimension(ng) :: g + end subroutine sort_heap + + ! ------------------------------------------------------------------ + ! POP_HEAP - pop max from heap + ! ------------------------------------------------------------------ + subroutine pop_heap(a, na, g, ng, index) + integer intent(in) :: na, ng + integer intent(inout), dimension(na) :: a + double precision intent(in), dimension(ng) :: g + integer intent(out) :: index + end subroutine pop_heap + + ! ------------------------------------------------------------------ + ! SPGETSEQ_SP - build sparse multi-index structure + ! ------------------------------------------------------------------ + subroutine spgetseq_sp(n, d, gridcode, indicesndiims, indicesdims, indiceslevs, indicesaddr, backwardneighbors, forwardneighbors, subgridpoints, subgridaddr, activeindices, nsubgrids, maxaddr, currentindex) + integer intent(in) :: n, d, gridcode, nsubgrids, maxaddr + integer intent(out), dimension(nsubgrids) :: indicesndiims + integer intent(out), dimension(maxaddr) :: indicesdims + integer intent(out), dimension(maxaddr) :: indiceslevs + integer intent(out), dimension(nsubgrids) :: indicesaddr + integer intent(out), dimension(maxaddr) :: backwardneighbors + integer intent(out), dimension(nsubgrids*d) :: forwardneighbors + integer intent(out), dimension(nsubgrids) :: subgridpoints + integer intent(out), dimension(nsubgrids) :: subgridaddr + integer intent(out), dimension(nsubgrids) :: activeindices + integer intent(out) :: currentindex + end subroutine spgetseq_sp + + end interface +end python module spinterp diff --git a/src/spinterpcb.f b/src/spinterpcb.f new file mode 100644 index 0000000..c1e60b3 --- /dev/null +++ b/src/spinterpcb.f @@ -0,0 +1,130 @@ +C ******************************************************************* +C +C SPINTERP_CB - Polynomial interpolation, Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_CB(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPINTERP_CB evaluates the Chebyshev sparse grid interpolant at +C NINTERP query points Y using hierarchical surpluses Z. +C +C For each subgrid with >1 node, calls BARY_PD_STEP_CB with +C dimensions sorted descending by level so the largest node count +C is the innermost loop. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points in [0,1]^D +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + + INTEGER KL, K, L, LVAL, NPTS, NDIMS, INDEX + INTEGER ALLNX(50), DIMS(50), NORD(50), LEVEL(50) + INTEGER ORDERARR(50), XTOT + DOUBLE PRECISION XBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NDIMS = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + LEVEL(L) = LVAL + IF (LVAL .EQ. 0) THEN + ELSE IF (LVAL .LE. 2) THEN + NPTS = NPTS * 2 + ELSE + NPTS = NPTS * 2**(LVAL-1) + END IF + END DO + + IF (NPTS .EQ. 1) THEN +C Only one (constant) node, no interpolation needed + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + 1 + + ELSE +C Sort dimensions descending by level + DO L = 1, D + ORDERARR(L) = L + END DO + DO L = 1, D-1 + DO K = L+1, D + IF (LEVEL(ORDERARR(K)) .GT. + & LEVEL(ORDERARR(L))) THEN + LVAL = ORDERARR(L) + ORDERARR(L) = ORDERARR(K) + ORDERARR(K) = LVAL + END IF + END DO + END DO + +C Build ALLNX and DIMS for active (non-zero level) dims + NDIMS = 0 + XTOT = 0 + DO L = 1, D + LVAL = LEVEL(ORDERARR(L)) + IF (LVAL .GT. 0) THEN + NDIMS = NDIMS + 1 + DIMS(NDIMS) = ORDERARR(L) + ALLNX(NDIMS) = 2**LVAL + 1 + XTOT = XTOT + ALLNX(NDIMS) + END IF + END DO + +C Build node array + CALL GET_CHEB_NODES(ALLNX, NDIMS, XBUF, XTOT) + +C Call barycentric step + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_CB(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + + INDEX = INDEX + NPTS + END IF + + END DO + + RETURN + END diff --git a/src/spinterpcbsp.f b/src/spinterpcbsp.f new file mode 100644 index 0000000..dc7f3bd --- /dev/null +++ b/src/spinterpcbsp.f @@ -0,0 +1,133 @@ +C ******************************************************************* +C +C SPINTERP_CB_SP - Chebyshev interpolation, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_CB_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, IP) +C ******************************************************************* +C +C SPINTERP_CB_SP evaluates the Chebyshev sparse grid interpolant +C using the sparse index structure (packed active dims/levels). +C For each subgrid with active dims, sort by level descending and +C call BARY_PD_STEP_CB. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + + INTEGER CI, K, DID, NDIMS, ADDR + INTEGER LVAL, NPTS, XTOT + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER ALLNX(50), DIMS(50), ORDER(50) + INTEGER TMP, I, J + DOUBLE PRECISION XBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + INTEGER INDEX + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + +C Read active dims and levels + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + ORDER(DID) = DID + END DO + +C Sort by level descending (insertion sort) + DO I = 2, NDIMS + TMP = ORDER(I) + J = I - 1 + 10 IF (J .GE. 1 .AND. + & ACTIVELEV(ORDER(J)) .LT. ACTIVELEV(TMP)) THEN + ORDER(J+1) = ORDER(J) + J = J - 1 + GOTO 10 + END IF + ORDER(J+1) = TMP + END DO + +C Build ALLNX and DIMS in sorted order + XTOT = 0 + DO I = 1, NDIMS + DID = ORDER(I) + LVAL = ACTIVELEV(DID) + ALLNX(I) = 2**LVAL + 1 + DIMS(I) = ACTIVEDIMS(DID) + XTOT = XTOT + ALLNX(I) + END DO + + CALL GET_CHEB_NODES(ALLNX, NDIMS, XBUF, XTOT) + + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_CB(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spinterpcc.f b/src/spinterpcc.f new file mode 100644 index 0000000..581de96 --- /dev/null +++ b/src/spinterpcc.f @@ -0,0 +1,148 @@ +C ******************************************************************* +C +C SPINTERP_CC - Multi-linear sparse grid interpolation, +C Clenshaw-Curtis grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_CC(D, Z, NZ, Y, NINTERP, LEVELSEQ, NLEVELS, + & IP) +C ******************************************************************* +C +C SPINTERP_CC evaluates the sparse grid interpolant at NINTERP +C query points Y using hierarchical surpluses Z and the +C multi-index set LEVELSEQ. +C +C The grid is on the unit cube [0,1]^D. If your domain differs, +C normalise Y before calling. +C +C Basis function for dimension k at level LEV: +C LEV=0 : phi = 1 (constant, no dependency on y) +C LEV=1 : hat functions centred at 0 and 1 covering [0,1] +C LEV>=2: hat function of width 2/2^LEV centred at +C (2*xp+1)/2^LEV where xp = floor(y*2^(LEV-1)) +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D)- query points in [0,1]^D +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + + INTEGER KL, K, L, LVAL, NPTS + INTEGER INDEX, INDEX2(D), INDEX3 + INTEGER REPVEC(D), XP + DOUBLE PRECISION YT, TEMP, SCALE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + +C Compute npoints and cumulative repvec (column strides). + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 1 + ELSE IF (LVAL .LT. 3) THEN + REPVEC(L) = 2 + ELSE + REPVEC(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) THEN + REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END IF + END DO + + DO K = 1, NINTERP + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + +C Clamp to [0,1]. + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 1 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMP = TEMP * + & 2.0D+00*(0.5D+00 - YT) + ELSE + TEMP = TEMP * + & 2.0D+00*(YT - 0.5D+00) + END IF + INDEX2(L) = XP + END IF + + ELSE + IF (YT .EQ. 1.0D+00) THEN + TEMP = 0.0D+00 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT * SCALE / 2.0D+00) + TEMP = TEMP * (1.0D+00 - SCALE * + & DABS(YT - + & DBLE(XP*2+1)/SCALE)) + INDEX2(L) = XP + END IF + END IF + + IF (TEMP .EQ. 0.0D+00) GOTO 20 + + L = L + 1 + GOTO 10 + END IF + +C Accumulate contribution: IP(K) += TEMP * Z(index3). + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + REPVEC(L-1) * INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + 20 CONTINUE + + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spinterpccsp.f b/src/spinterpccsp.f new file mode 100644 index 0000000..bfdd88b --- /dev/null +++ b/src/spinterpccsp.f @@ -0,0 +1,169 @@ +C ******************************************************************* +C +C SPINTERP_CC_SP - Multi-linear interpolation, CC grid, sparse idx. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_CC_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, IP) +C ******************************************************************* +C +C SPINTERP_CC_SP evaluates the CC sparse grid interpolant using +C the sparse index structure (packed active dimensions/levels). +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + + INTEGER CI, K, DID, NDIMS, ADDR + INTEGER LVAL, NPTS + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER REPVEC(50), NPTS_DIM(50) + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER XP + DOUBLE PRECISION YT, TEMP, SCALE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN +C Constant subgrid: add Z(INDEX) to all points + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + +C Read active dims and levels + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + +C Compute npts_dim and repvec for active dims + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .LT. 3) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + END DO + REPVEC(1) = 1 + DO DID = 2, NDIMS + REPVEC(DID) = REPVEC(DID-1) * NPTS_DIM(DID-1) + END DO + + DO K = 1, NINTERP + TEMP = 1.0D+00 + DID = 1 + + 10 IF (DID .LE. NDIMS) THEN + LVAL = ACTIVELEV(DID) + YT = Y(K, ACTIVEDIMS(DID)) + + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(DID) = 0 + + ELSE IF (LVAL .EQ. 1) THEN + IF (YT .EQ. 1.0D+00) THEN + INDEX2(DID) = 1 + ELSE + XP = INT(YT * 2.0D+00) + IF (XP .EQ. 0) THEN + TEMP = TEMP * + & 2.0D+00*(0.5D+00 - YT) + ELSE + TEMP = TEMP * + & 2.0D+00*(YT - 0.5D+00) + END IF + INDEX2(DID) = XP + END IF + + ELSE + IF (YT .EQ. 1.0D+00) THEN + TEMP = 0.0D+00 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT * SCALE / 2.0D+00) + TEMP = TEMP * (1.0D+00 - SCALE * + & DABS(YT - + & DBLE(XP*2+1)/SCALE)) + INDEX2(DID) = XP + END IF + END IF + + IF (TEMP .EQ. 0.0D+00) GOTO 100 + + DID = DID + 1 + GOTO 10 + END IF + +C Accumulate: INDEX3 = INDEX + sum_k(REPVEC(k)*INDEX2(k)) + INDEX3 = INDEX + INDEX2(1) + DO DID = 2, NDIMS + INDEX3 = INDEX3 + REPVEC(DID) * INDEX2(DID) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + 100 CONTINUE + + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spinterpgp.f b/src/spinterpgp.f new file mode 100644 index 0000000..939840e --- /dev/null +++ b/src/spinterpgp.f @@ -0,0 +1,99 @@ +C ******************************************************************* +C +C SPINTERP_GP - Polynomial interpolation, Gauss-Patterson grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_GP(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPINTERP_GP evaluates the Gauss-Patterson sparse grid interpolant +C at NINTERP query points Y using hierarchical surpluses Z. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points in [0,1]^D +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + + INTEGER KL, K, L, LVAL, NPTS, NDIMS, INDEX + INTEGER ALLNX(50), DIMS(50), XTOT + DOUBLE PRECISION XBUF(16384), WBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NDIMS = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .GT. 0) THEN + NPTS = NPTS * 2**LVAL + NDIMS = NDIMS + 1 + ALLNX(NDIMS) = 2**(LVAL+1) - 1 + DIMS(NDIMS) = L + END IF + END DO + + IF (NPTS .EQ. 1) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + 1 + + ELSE + XTOT = 0 + DO L = 1, NDIMS + XTOT = XTOT + ALLNX(L) + END DO + + CALL GET_GP_NODES(ALLNX, NDIMS, XBUF, XTOT) + CALL GET_GP_BARY_W(ALLNX, NDIMS, WBUF, XTOT) + + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_GP(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, WBUF, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + + INDEX = INDEX + NPTS + END IF + + END DO + + RETURN + END diff --git a/src/spinterpgpsp.f b/src/spinterpgpsp.f new file mode 100644 index 0000000..0a21d18 --- /dev/null +++ b/src/spinterpgpsp.f @@ -0,0 +1,117 @@ +C ******************************************************************* +C +C SPINTERP_GP_SP - GP interpolation, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_GP_SP(D, Z, NZ, Y, NINTERP, + & INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, IP) +C ******************************************************************* +C +C SPINTERP_GP_SP evaluates the Gauss-Patterson sparse grid +C interpolant using the sparse index structure. +C For each subgrid, ALLNX(k) = 2^(lev+1)-1, calls BARY_PD_STEP_GP. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NSUBGRIDS, NADDR + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION IP(NINTERP) + + INTEGER CI, K, DID, NDIMS, ADDR + INTEGER LVAL, NPTS, XTOT + INTEGER ACTIVEDIMS(50), ACTIVELEV(50) + INTEGER ALLNX(50), DIMS(50) + DOUBLE PRECISION XBUF(16384), WBUF(16384) + DOUBLE PRECISION IPTEMP(65536) + INTEGER INDEX + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + IF (NDIMS .EQ. 0) THEN + DO K = 1, NINTERP + IP(K) = IP(K) + Z(INDEX) + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + +C Read active dims and levels + DO DID = 1, NDIMS + ACTIVEDIMS(DID) = INDICESDIMS(ADDR + DID - 1) + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + END DO + +C Build ALLNX and DIMS + XTOT = 0 + DO DID = 1, NDIMS + LVAL = ACTIVELEV(DID) + ALLNX(DID) = 2**(LVAL+1) - 1 + DIMS(DID) = ACTIVEDIMS(DID) + XTOT = XTOT + ALLNX(DID) + END DO + + CALL GET_GP_NODES(ALLNX, NDIMS, XBUF, XTOT) + CALL GET_GP_BARY_W(ALLNX, NDIMS, WBUF, XTOT) + + DO K = 1, NINTERP + IPTEMP(K) = 0.0D+00 + END DO + CALL BARY_PD_STEP_GP(Z(INDEX), NPTS, + & ALLNX, DIMS, NDIMS, + & XBUF, XTOT, Y, NINTERP, D, WBUF, IPTEMP) + + DO K = 1, NINTERP + IP(K) = IP(K) + IPTEMP(K) + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spinterpm.f b/src/spinterpm.f new file mode 100644 index 0000000..6c8daf8 --- /dev/null +++ b/src/spinterpm.f @@ -0,0 +1,164 @@ +C ******************************************************************* +C +C SPINTERP_M - Multi-linear interpolation, Maximum-norm grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_M(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPINTERP_M evaluates the Maximum-norm sparse grid interpolant. +C +C For lev=0 each dimension contributes 3 nodes (at 0, 0.5, 1). +C All 2^nlevelzero combinations of midpoint vs boundary nodes are +C accumulated for each query point by an inner repeat loop. +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points in [0,1]^D +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + + INTEGER KL, K, L, LVAL, NPTS, NLEVELZERO, NREPEATS + INTEGER INDEX, INDEX2(50), INDEX3, REPVEC(50) + INTEGER REPEAT(50), REPSTEP, XP + DOUBLE PRECISION YT, TEMP, SCALE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + NLEVELZERO = 0 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + REPVEC(L) = 3 + NLEVELZERO = NLEVELZERO + 1 + ELSE + REPVEC(L) = 2**LVAL + END IF + REPEAT(L) = 0 + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + NREPEATS = 2**NLEVELZERO - 1 + + DO K = 1, NINTERP + REPSTEP = 0 + + 30 IF (REPSTEP .LE. NREPEATS) THEN + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + IF (REPEAT(L) .EQ. 0) THEN + INDEX2(L) = 1 + TEMP = TEMP*(1.0D+00 + & - 2.0D+00 + & *DABS(YT-0.5D+00)) + ELSE + IF (YT .EQ. 1.0D+00) THEN + INDEX2(L) = 2 + ELSE + XP = INT(YT*2.0D+00)*2 + IF (XP .EQ. 0) THEN + TEMP = TEMP * + & 2.0D+00* + & (0.5D+00-YT) + ELSE + TEMP = TEMP * + & 2.0D+00* + & (YT-0.5D+00) + END IF + INDEX2(L) = XP + END IF + END IF + ELSE IF (YT .EQ. 1.0D+00) THEN + TEMP = 0.0D+00 + ELSE + SCALE = DBLE(2**LVAL) + XP = INT(YT * SCALE) + TEMP = TEMP*(1.0D+00 + & - 2.0D+00*SCALE + & *DABS(YT-(DBLE(XP) + & +0.5D+00)/SCALE)) + INDEX2(L) = XP + END IF + + IF (TEMP .EQ. 0.0D+00) GOTO 20 + L = L + 1 + GOTO 10 + END IF + + IF (TEMP .GT. 0.0D+00) THEN + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + & + REPVEC(L-1)*INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + END IF + + 20 REPSTEP = REPSTEP + 1 + IF (REPSTEP .LE. NREPEATS) THEN + DO L = 1, D + IF (LEVELSEQ(KL,L) .EQ. 0) THEN + REPEAT(L) = REPEAT(L) + 1 + IF (REPEAT(L) .GT. 1) THEN + REPEAT(L) = 0 + ELSE + GOTO 30 + END IF + END IF + END DO + END IF + GOTO 30 + END IF + + DO L = 1, D + REPEAT(L) = 0 + END DO + + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spinterpnb.f b/src/spinterpnb.f new file mode 100644 index 0000000..c0e6726 --- /dev/null +++ b/src/spinterpnb.f @@ -0,0 +1,126 @@ +C ******************************************************************* +C +C SPINTERP_NB - Multi-linear interpolation, NoBoundary grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPINTERP_NB(D, Z, NZ, Y, NINTERP, + & LEVELSEQ, NLEVELS, IP) +C ******************************************************************* +C +C SPINTERP_NB evaluates the NoBoundary sparse grid interpolant at +C NINTERP query points Y using hierarchical surpluses Z. +C +C Basis function for dimension k at level LEV (scale = 2^lev): +C lev = 0 : phi = 1 (constant) +C xp = floor(yt * scale) +C xp = 0 : 1 - 2*scale*(yt - 0.5/scale) (left-ext) +C xp = scale-1 : 1 + 2*scale*(yt - (scale-0.5)/scale) +C otherwise : 1 - 2*scale*|yt - (xp+0.5)/scale| +C +C Parameters: +C +C Input, INTEGER D - dimension +C Input, DOUBLE PRECISION Z(NZ) - hierarchical surpluses +C Input, INTEGER NZ - length of Z +C Input, DOUBLE PRECISION Y(NINTERP,D) - query points in [0,1]^D +C Input, INTEGER NINTERP - number of query points +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index set +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Output, DOUBLE PRECISION IP(NINTERP) - interpolated values +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER D, NZ, NINTERP, NLEVELS + DOUBLE PRECISION Z(NZ) + DOUBLE PRECISION Y(NINTERP, D) + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION IP(NINTERP) + + INTEGER KL, K, L, LVAL, NPTS + INTEGER INDEX, INDEX2(50), INDEX3 + INTEGER REPVEC(50), XP + DOUBLE PRECISION YT, TEMP, SCALE + + DO K = 1, NINTERP + IP(K) = 0.0D+00 + END DO + + INDEX = 1 + + DO KL = 1, NLEVELS + + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + REPVEC(L) = 2**LVAL + NPTS = NPTS * REPVEC(L) + IF (L .GT. 1) REPVEC(L) = REPVEC(L) * REPVEC(L-1) + END DO + + DO K = 1, NINTERP + TEMP = 1.0D+00 + L = 1 + + 10 IF (L .LE. D) THEN + LVAL = LEVELSEQ(KL, L) + YT = Y(K, L) + IF (YT .LT. 0.0D+00) YT = 0.0D+00 + IF (YT .GT. 1.0D+00) YT = 1.0D+00 + + IF (LVAL .EQ. 0) THEN + INDEX2(L) = 0 + ELSE + SCALE = DBLE(2**LVAL) + IF (YT .EQ. 1.0D+00) THEN + XP = INT(SCALE) - 1 + ELSE + XP = INT(YT * SCALE) + END IF + IF (XP .EQ. 0) THEN + TEMP = TEMP * (1.0D+00 + & - 2.0D+00*SCALE*(YT + & - 0.5D+00/SCALE)) + ELSE IF (XP .EQ. INT(SCALE)-1) THEN + TEMP = TEMP * (1.0D+00 + & + 2.0D+00*SCALE*(YT + & - (SCALE-0.5D+00)/SCALE)) + ELSE + TEMP = TEMP * (1.0D+00 + & - 2.0D+00*SCALE + & *DABS(YT-(DBLE(XP) + & +0.5D+00)/SCALE)) + END IF + INDEX2(L) = XP + END IF + + IF (TEMP .EQ. 0.0D+00) GOTO 20 + L = L + 1 + GOTO 10 + END IF + + INDEX3 = INDEX + INDEX2(1) + DO L = 2, D + INDEX3 = INDEX3 + REPVEC(L-1)*INDEX2(L) + END DO + IP(K) = IP(K) + TEMP * Z(INDEX3) + + 20 CONTINUE + + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spnlevels.f b/src/spnlevels.f new file mode 100644 index 0000000..3c40e84 --- /dev/null +++ b/src/spnlevels.f @@ -0,0 +1,36 @@ +C ******************************************************************* +C +C SPNLEVELS - Count multi-index tuples summing to N in D dims. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPNLEVELS(N, D, NLEVELS) +C ******************************************************************* +C +C SPNLEVELS computes C(N+D-1, D-1): the number of multi-index +C tuples (l_1,...,l_D) with l_1 + ... + l_D = N, l_i >= 0. +C +C Parameters: +C +C Input, INTEGER N - level +C Input, INTEGER D - dimension +C Output, INTEGER NLEVELS - number of multi-indices +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER N, D, NLEVELS + INTEGER NCHOOSEK + + NLEVELS = NCHOOSEK(N + D - 1, D - 1) + + RETURN + END diff --git a/src/spquadwcb.f b/src/spquadwcb.f new file mode 100644 index 0000000..0106121 --- /dev/null +++ b/src/spquadwcb.f @@ -0,0 +1,96 @@ +C ******************************************************************* +C +C SPQUADW_CB - Quadrature weights, Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_CB(LEVELSEQ, NLEVELS, D, W, NW, + & W1D, NW1D, STARTID) +C ******************************************************************* +C +C SPQUADW_CB returns the quadrature weights for the Chebyshev +C sparse grid. Caller provides 1-D weights W1D and start indices +C STARTID computed by CHEB_WEIGHTS. +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C Input, DOUBLE PRECISION W1D(NW1D) - 1-D weight table +C Input, INTEGER NW1D - length of W1D +C Input, INTEGER STARTID(*) - start index per level +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, NW, NW1D + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION W(NW) + DOUBLE PRECISION W1D(NW1D) + INTEGER STARTID(*) + + INTEGER KL, K, L, LVAL, NPTS, INDEX, J + INTEGER NPTS_DIM(50), REP(50) + INTEGER WIDSTART(50), WIDEND(50), WID(50) + INTEGER WID1 + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO KL = 1, NLEVELS + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(L) = 1 + ELSE IF (LVAL .LE. 2) THEN + NPTS_DIM(L) = 2 + ELSE + NPTS_DIM(L) = 2**(LVAL-1) + END IF + NPTS = NPTS * NPTS_DIM(L) + WIDSTART(L) = STARTID(LVAL+1) + WID(L) = WIDSTART(L) + WIDEND(L) = WID(L) + NPTS_DIM(L) - 1 + END DO + + WID1 = WID(1) + DO K = INDEX, INDEX + NPTS - 1 + WVAL = W1D(WID1) + DO L = 2, D + WVAL = WVAL * W1D(WID(L)) + END DO + W(K) = WVAL + + IF (WID1 .LT. WIDEND(1)) THEN + WID1 = WID1 + 1 + ELSE + WID1 = WIDSTART(1) + DO L = 2, D + IF (WID(L) .LT. WIDEND(L)) THEN + WID(L) = WID(L) + 1 + GOTO 10 + ELSE + WID(L) = WIDSTART(L) + END IF + END DO + END IF + 10 CONTINUE + END DO + + INDEX = INDEX + NPTS + END DO + + RETURN + END diff --git a/src/spquadwcbsp.f b/src/spquadwcbsp.f new file mode 100644 index 0000000..9198b82 --- /dev/null +++ b/src/spquadwcbsp.f @@ -0,0 +1,135 @@ +C ******************************************************************* +C +C SPQUADW_CB_SP - CB quadrature weights, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_CB_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, W, NW, W1D, NW1D, STARTID) +C ******************************************************************* +C +C SPQUADW_CB_SP returns the Chebyshev quadrature weights using +C the sparse index structure. Levels are sorted descending so +C innermost loop iterates over the largest dimension. +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C Input, DOUBLE PRECISION W1D(NW1D) - 1-D weight table +C Input, INTEGER NW1D - length of W1D +C Input, INTEGER STARTID(*) - start index per level +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, NW, NW1D + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION W(NW) + DOUBLE PRECISION W1D(NW1D) + INTEGER STARTID(*) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER K, INDEX + INTEGER NPTS_DIM(50), WIDSTART(50), WIDEND(50), WID(50), WID1 + INTEGER ACTIVELEV(50), ORDER(50), TMP, I, J + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + +C Read active levels + DO DID = 1, NDIMS + ACTIVELEV(DID) = INDICESLEVS(ADDR + DID - 1) + ORDER(DID) = DID + END DO + +C Sort by level descending + DO I = 2, NDIMS + TMP = ORDER(I) + J = I - 1 + 10 IF (J .GE. 1 .AND. + & ACTIVELEV(ORDER(J)) .LT. ACTIVELEV(TMP)) THEN + ORDER(J+1) = ORDER(J) + J = J - 1 + GOTO 10 + END IF + ORDER(J+1) = TMP + END DO + + DO DID = 1, NDIMS + LVAL = ACTIVELEV(ORDER(DID)) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(DID) = 1 + ELSE IF (LVAL .LE. 2) THEN + NPTS_DIM(DID) = 2 + ELSE + NPTS_DIM(DID) = 2**(LVAL-1) + END IF + WIDSTART(DID) = STARTID(LVAL+1) + WID(DID) = WIDSTART(DID) + WIDEND(DID) = WID(DID) + NPTS_DIM(DID) - 1 + END DO + + IF (NDIMS .EQ. 0) THEN + DO K = INDEX, INDEX + NPTS - 1 + W(K) = 1.0D+00 + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + + WID1 = WID(1) + DO K = INDEX, INDEX + NPTS - 1 + WVAL = W1D(WID1) + DO DID = 2, NDIMS + WVAL = WVAL * W1D(WID(DID)) + END DO + W(K) = WVAL + + IF (WID1 .LT. WIDEND(1)) THEN + WID1 = WID1 + 1 + ELSE + WID1 = WIDSTART(1) + DO DID = 2, NDIMS + IF (WID(DID) .LT. WIDEND(DID)) THEN + WID(DID) = WID(DID) + 1 + GOTO 100 + ELSE + WID(DID) = WIDSTART(DID) + END IF + END DO + END IF + 100 CONTINUE + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spquadwcc.f b/src/spquadwcc.f new file mode 100644 index 0000000..f31f1e5 --- /dev/null +++ b/src/spquadwcc.f @@ -0,0 +1,71 @@ +C ******************************************************************* +C +C SPQUADW_CC - Quadrature weights, Clenshaw-Curtis / Chebyshev grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_CC(LEVELSEQ, NLEVELS, D, W, NW) +C ******************************************************************* +C +C SPQUADW_CC returns the quadrature weights for the CC / Chebyshev +C sparse grid. +C +C The weight for each node in a subgrid is: +C w = 1 / (product over dims of nw_k) +C where: +C lev=0: nw=1, np=1 +C lev=1,2: nw=4, np=2 +C lev>=3: nw=2^lev, np=2^(lev-1) +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, NW + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION W(NW) + + INTEGER KL, K, L, LVAL, NP, INDEX + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO KL = 1, NLEVELS + NP = 1 + WVAL = 1.0D+00 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + NP = NP * 1 + WVAL = WVAL * 1.0D+00 + ELSE IF (LVAL .LE. 2) THEN + NP = NP * 2 + WVAL = WVAL * 4.0D+00 + ELSE + NP = NP * 2**(LVAL-1) + WVAL = WVAL * DBLE(2**LVAL) + END IF + END DO + DO K = INDEX, INDEX + NP - 1 + W(K) = 1.0D+00 / WVAL + END DO + INDEX = INDEX + NP + END DO + + RETURN + END diff --git a/src/spquadwccsp.f b/src/spquadwccsp.f new file mode 100644 index 0000000..e373084 --- /dev/null +++ b/src/spquadwccsp.f @@ -0,0 +1,79 @@ +C ******************************************************************* +C +C SPQUADW_CC_SP - CC quadrature weights, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_CC_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, W, NW) +C ******************************************************************* +C +C SPQUADW_CC_SP returns the CC quadrature weights using the sparse +C index structure. +C +C Weight per node: +C lev = 0,1,2 -> 1/4 contribution per dim +C lev >= 3 -> 1/2^lev contribution per dim +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, NW + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION W(NW) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER K, INDEX + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + WVAL = 1.0D+00 + DO DID = 1, NDIMS + LVAL = INDICESLEVS(ADDR + DID - 1) + IF (LVAL .LT. 3) THEN + WVAL = WVAL * 0.25D+00 + ELSE + WVAL = WVAL / DBLE(2**LVAL) + END IF + END DO + + DO K = INDEX, INDEX + NPTS - 1 + W(K) = WVAL + END DO + + INDEX = INDEX + NPTS + + END DO + + RETURN + END diff --git a/src/spquadwgp.f b/src/spquadwgp.f new file mode 100644 index 0000000..28f080a --- /dev/null +++ b/src/spquadwgp.f @@ -0,0 +1,89 @@ +C ******************************************************************* +C +C SPQUADW_GP - Quadrature weights, Gauss-Patterson grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_GP(LEVELSEQ, NLEVELS, D, W, NW, + & W1D, NW1D, STARTID) +C ******************************************************************* +C +C SPQUADW_GP returns the quadrature weights for the Gauss-Patterson +C sparse grid. Caller provides 1-D weights W1D and start indices +C STARTID computed by GP_WEIGHTS. +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C Input, DOUBLE PRECISION W1D(NW1D) - 1-D weight table +C Input, INTEGER NW1D - length of W1D +C Input, INTEGER STARTID(*) - start index per level +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, NW, NW1D + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION W(NW) + DOUBLE PRECISION W1D(NW1D) + INTEGER STARTID(*) + + INTEGER KL, K, L, LVAL, NPTS, INDEX + INTEGER NPTS_DIM(50), REP(50) + INTEGER WIDSTART(50), WIDEND(50), WID(50), WID1 + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO KL = 1, NLEVELS + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + NPTS_DIM(L) = 2**LVAL + NPTS = NPTS * NPTS_DIM(L) + WIDSTART(L) = STARTID(LVAL+1) + WID(L) = WIDSTART(L) + WIDEND(L) = WID(L) + NPTS_DIM(L) - 1 + END DO + + WID1 = WID(1) + DO K = INDEX, INDEX + NPTS - 1 + WVAL = W1D(WID1) + DO L = 2, D + WVAL = WVAL * W1D(WID(L)) + END DO + W(K) = WVAL + + IF (WID1 .LT. WIDEND(1)) THEN + WID1 = WID1 + 1 + ELSE + WID1 = WIDSTART(1) + DO L = 2, D + IF (WID(L) .LT. WIDEND(L)) THEN + WID(L) = WID(L) + 1 + GOTO 10 + ELSE + WID(L) = WIDSTART(L) + END IF + END DO + END IF + 10 CONTINUE + END DO + + INDEX = INDEX + NPTS + END DO + + RETURN + END diff --git a/src/spquadwgpsp.f b/src/spquadwgpsp.f new file mode 100644 index 0000000..ef118f8 --- /dev/null +++ b/src/spquadwgpsp.f @@ -0,0 +1,108 @@ +C ******************************************************************* +C +C SPQUADW_GP_SP - GP quadrature weights, sparse index structure. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_GP_SP(INDICESNDIIMS, NSUBGRIDS, + & INDICESLEVS, INDICESADDR, NADDR, + & SUBGRIDPOINTS, W, NW, W1D, NW1D, STARTID) +C ******************************************************************* +C +C SPQUADW_GP_SP returns the GP quadrature weights using the sparse +C index structure. npoints per dim = 2^lev (not 2^(lev-1)). +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER SUBGRIDPOINTS(NSUBGRIDS) - points per subgrid +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C Input, DOUBLE PRECISION W1D(NW1D) - 1-D weight table +C Input, INTEGER NW1D - length of W1D +C Input, INTEGER STARTID(*) - start index per level +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, NW, NW1D + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER SUBGRIDPOINTS(NSUBGRIDS) + DOUBLE PRECISION W(NW) + DOUBLE PRECISION W1D(NW1D) + INTEGER STARTID(*) + + INTEGER CI, DID, NDIMS, ADDR, LVAL, NPTS + INTEGER K, INDEX + INTEGER NPTS_DIM(50), WIDSTART(50), WIDEND(50), WID(50), WID1 + DOUBLE PRECISION WVAL + + INDEX = 1 + + DO CI = 1, NSUBGRIDS + + NDIMS = INDICESNDIIMS(CI) + ADDR = INDICESADDR(CI) + NPTS = SUBGRIDPOINTS(CI) + + DO DID = 1, NDIMS + LVAL = INDICESLEVS(ADDR + DID - 1) + NPTS_DIM(DID) = 2**LVAL + WIDSTART(DID) = STARTID(LVAL+1) + WID(DID) = WIDSTART(DID) + WIDEND(DID) = WID(DID) + NPTS_DIM(DID) - 1 + END DO + + IF (NDIMS .EQ. 0) THEN + DO K = INDEX, INDEX + NPTS - 1 + W(K) = 1.0D+00 + END DO + INDEX = INDEX + NPTS + GOTO 200 + END IF + + WID1 = WID(1) + DO K = INDEX, INDEX + NPTS - 1 + WVAL = W1D(WID1) + DO DID = 2, NDIMS + WVAL = WVAL * W1D(WID(DID)) + END DO + W(K) = WVAL + + IF (WID1 .LT. WIDEND(1)) THEN + WID1 = WID1 + 1 + ELSE + WID1 = WIDSTART(1) + DO DID = 2, NDIMS + IF (WID(DID) .LT. WIDEND(DID)) THEN + WID(DID) = WID(DID) + 1 + GOTO 100 + ELSE + WID(DID) = WIDSTART(DID) + END IF + END DO + END IF + 100 CONTINUE + END DO + + INDEX = INDEX + NPTS + + 200 CONTINUE + END DO + + RETURN + END diff --git a/src/spquadwm.f b/src/spquadwm.f new file mode 100644 index 0000000..aa8f6b0 --- /dev/null +++ b/src/spquadwm.f @@ -0,0 +1,89 @@ +C ******************************************************************* +C +C SPQUADW_M - Quadrature weights, Maximum-norm grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_M(LEVELSEQ, NLEVELS, D, W, NW) +C ******************************************************************* +C +C SPQUADW_M returns the quadrature weights for the Maximum-norm +C sparse grid. +C +C 1-D weight vectors: +C lev=0: [1/4, 1/2, 1/4] (3 nodes: 0, 0.5, 1) +C lev>=1: 1/2^(lev+1) for all 2^lev nodes +C +C The D-dimensional weight is the product of 1-D weights. +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, NW + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION W(NW) + + INTEGER KL, K, L, LVAL, NPTS, INDEX + INTEGER NPTS_DIM(50), REP(50) + DOUBLE PRECISION W1D(3, 50), WVAL + INTEGER J + + INDEX = 1 + + DO KL = 1, NLEVELS + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + IF (LVAL .EQ. 0) THEN + NPTS_DIM(L) = 3 + W1D(1, L) = 0.25D+00 + W1D(2, L) = 0.50D+00 + W1D(3, L) = 0.25D+00 + ELSE + NPTS_DIM(L) = 2**LVAL + DO J = 1, NPTS_DIM(L) + W1D(J, L) = 1.0D+00/DBLE(2**(LVAL+1)) + END DO + END IF + NPTS = NPTS * NPTS_DIM(L) + END DO + + REP(1) = 1 + DO L = 2, D + REP(L) = REP(L-1) * NPTS_DIM(L-1) + END DO + + DO K = 0, NPTS-1 + WVAL = 1.0D+00 + DO L = 1, D + IF (D .EQ. 1) THEN + J = MOD(K, NPTS_DIM(L)) + 1 + ELSE + J = MOD(K/REP(L), NPTS_DIM(L)) + 1 + END IF + WVAL = WVAL * W1D(J, L) + END DO + W(INDEX + K) = WVAL + END DO + + INDEX = INDEX + NPTS + END DO + + RETURN + END diff --git a/src/spquadwnb.f b/src/spquadwnb.f new file mode 100644 index 0000000..d974297 --- /dev/null +++ b/src/spquadwnb.f @@ -0,0 +1,92 @@ +C ******************************************************************* +C +C SPQUADW_NB - Quadrature weights, NoBoundary grid. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPQUADW_NB(LEVELSEQ, NLEVELS, D, W, NW) +C ******************************************************************* +C +C SPQUADW_NB returns the quadrature weights for the NoBoundary +C sparse grid. +C +C 1-D weight vectors: +C lev=0: [1] +C lev=1: [1/2, 1/2] +C lev>=2: [1/2^lev, 1/2^(lev+1), ..., 1/2^(lev+1), 1/2^lev] +C (endpoints doubled relative to interior) +C +C Parameters: +C +C Input, INTEGER LEVELSEQ(NLEVELS,D) - multi-index array +C Input, INTEGER NLEVELS - rows in LEVELSEQ +C Input, INTEGER D - dimension +C Output, DOUBLE PRECISION W(NW) - quadrature weights +C Input, INTEGER NW - length of W +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NLEVELS, D, NW + INTEGER LEVELSEQ(NLEVELS, D) + DOUBLE PRECISION W(NW) + + INTEGER KL, K, L, LVAL, NPTS, INDEX + INTEGER NPTS_DIM(50), REP(50) + DOUBLE PRECISION W1D(256, 50), WVAL + INTEGER J, NP + + INDEX = 1 + + DO KL = 1, NLEVELS + NPTS = 1 + DO L = 1, D + LVAL = LEVELSEQ(KL, L) + NP = 2**LVAL + NPTS_DIM(L) = NP + IF (LVAL .EQ. 0) THEN + W1D(1, L) = 1.0D+00 + ELSE IF (LVAL .EQ. 1) THEN + W1D(1, L) = 0.5D+00 + W1D(2, L) = 0.5D+00 + ELSE + W1D(1, L) = 1.0D+00 / DBLE(2**LVAL) + DO J = 2, NP-1 + W1D(J, L) = 1.0D+00/DBLE(2**(LVAL+1)) + END DO + W1D(NP, L) = 1.0D+00 / DBLE(2**LVAL) + END IF + NPTS = NPTS * NP + END DO + + REP(1) = 1 + DO L = 2, D + REP(L) = REP(L-1) * NPTS_DIM(L-1) + END DO + + DO K = 0, NPTS-1 + WVAL = 1.0D+00 + DO L = 1, D + IF (D .EQ. 1) THEN + J = MOD(K, NPTS_DIM(L)) + 1 + ELSE + J = MOD(K/REP(L), NPTS_DIM(L)) + 1 + END IF + WVAL = WVAL * W1D(J, L) + END DO + W(INDEX + K) = WVAL + END DO + + INDEX = INDEX + NPTS + END DO + + RETURN + END diff --git a/src/spseq2full.f b/src/spseq2full.f new file mode 100644 index 0000000..882d792 --- /dev/null +++ b/src/spseq2full.f @@ -0,0 +1,66 @@ +C ******************************************************************* +C +C SPSEQ2FULL - Convert sparse index structure to full dense levelseq. +C +C License: +C Sparse Grid Interpolation Toolbox +C Copyright (c) 2006 W. Andreas Klimke, Universitaet Stuttgart +C Copyright (c) 2007-2008 W. A. Klimke. All Rights Reserved. +C Copyright (c) 2026 eggzec. All Rights Reserved. +C See LICENSE for details. +C +C ******************************************************************* + + SUBROUTINE SPSEQ2FULL(INDICESNDIIMS, NSUBGRIDS, + & INDICESDIMS, INDICESLEVS, INDICESADDR, + & NADDR, D, FULLSEQ) +C ******************************************************************* +C +C SPSEQ2FULL expands a sparse index representation (packed arrays +C of active dimension indices and levels) into a full dense level +C matrix FULLSEQ of size NSUBGRIDS x D. +C +C For each subgrid k, the active dims and their levels are stored +C starting at address INDICESADDR(k) in INDICESDIMS / INDICESLEVS. +C Inactive dimensions default to zero. +C +C Parameters: +C +C Input, INTEGER INDICESNDIIMS(NSUBGRIDS) - ndims per subgrid +C Input, INTEGER NSUBGRIDS - number of subgrids +C Input, INTEGER INDICESDIMS(NADDR) - packed dim indices +C Input, INTEGER INDICESLEVS(NADDR) - packed levels +C Input, INTEGER INDICESADDR(NSUBGRIDS) - start addr per subgrid +C Input, INTEGER NADDR - length of packed arrays +C Input, INTEGER D - full dimension +C Output, INTEGER FULLSEQ(NSUBGRIDS,D) - full level matrix +C +C ******************************************************************* + + IMPLICIT NONE + + INTEGER NSUBGRIDS, NADDR, D + INTEGER INDICESNDIIMS(NSUBGRIDS) + INTEGER INDICESDIMS(NADDR) + INTEGER INDICESLEVS(NADDR) + INTEGER INDICESADDR(NSUBGRIDS) + INTEGER FULLSEQ(NSUBGRIDS, D) + + INTEGER K, DID, ADDR, DIM + + DO K = 1, NSUBGRIDS +C Zero out all dimensions + DO DID = 1, D + FULLSEQ(K, DID) = 0 + END DO + +C Fill in active dimensions + ADDR = INDICESADDR(K) + DO DID = 1, INDICESNDIIMS(K) + DIM = INDICESDIMS(ADDR + DID - 1) + FULLSEQ(K, DIM) = INDICESLEVS(ADDR + DID - 1) + END DO + END DO + + RETURN + END diff --git a/tests/.gitkeep b/tests/__init__.py similarity index 100% rename from tests/.gitkeep rename to tests/__init__.py diff --git a/tests/test_derivatives.py b/tests/test_derivatives.py new file mode 100644 index 0000000..8071290 --- /dev/null +++ b/tests/test_derivatives.py @@ -0,0 +1,253 @@ +"""Tests for derivative routines, DCT surpluses, heap, and pp_deriv.""" + +import numpy as np +import pytest +import spinterp + +from tests.test_spinterpcc import _spvals + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _build_z_seq(f, n, d): + """Return (z_flat, seq_flat) for all levels 0..n.""" + all_seq, all_surp = _spvals(f, n, d) + return np.concatenate(all_surp), np.vstack(all_seq) + + +# --------------------------------------------------------------------------- +# SPDERIV_CC (dense, full levelseq) — already partially tested via grids tests +# --------------------------------------------------------------------------- + + +def test_spderiv_cc_constant_zero_gradient(): + f = lambda x, y: 5.0 + z, seq = _build_z_seq(f, 2, 2) + pts = np.array([[0.3, 0.4], [0.7, 0.2]]) + ip, grad = spinterp.spderiv_cc(z, pts, seq) + assert ip == pytest.approx([5.0, 5.0], abs=1e-10) + assert np.allclose(grad, 0.0, atol=1e-10) + + +def test_spderiv_cc_linear_exact(): + f = lambda x, y: 3 * x - 2 * y + z, seq = _build_z_seq(f, 2, 2) + pts = np.array([[0.25, 0.75], [0.5, 0.5], [0.1, 0.9]]) + ip, grad = spinterp.spderiv_cc(z, pts, seq) + exact_ip = 3 * pts[:, 0] - 2 * pts[:, 1] + assert ip == pytest.approx(exact_ip, abs=1e-9) + assert grad[:, 0] == pytest.approx(np.full(3, 3.0), abs=1e-9) + assert grad[:, 1] == pytest.approx(np.full(3, -2.0), abs=1e-9) + + +def test_spderiv_cc_1d_quadratic_convergence(): + f = lambda x: x**2 + errors = [] + pts = np.linspace(0.05, 0.95, 20).reshape(-1, 1) + exact_grad = 2 * pts[:, 0] + for n in range(1, 5): + z, seq = _build_z_seq(f, n, 1) + _, grad = spinterp.spderiv_cc(z, pts, seq) + errors.append(np.max(np.abs(grad[:, 0] - exact_grad))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-13 + + +# --------------------------------------------------------------------------- +# SPCONT_DERIV_CC + PP_DERIV +# --------------------------------------------------------------------------- + + +def test_spcont_deriv_cc_constant(): + f = lambda x, y: 4.2 + z, seq = _build_z_seq(f, 3, 2) + pts = np.array([[0.3, 0.4], [0.6, 0.7]]) + maxlev = 3 + ip, ipder, ipder2 = spinterp.spcont_deriv_cc(z, pts, seq, maxlev) + assert ip == pytest.approx([4.2, 4.2], abs=1e-10) + assert np.allclose(ipder, 0.0, atol=1e-10) + assert np.allclose(ipder2, 0.0, atol=1e-10) + + +def test_pp_deriv_no_crash(): + """PP_DERIV should run without error and return modified ipder.""" + pts = np.linspace(0.1, 0.9, 5).reshape(-1, 1) + y = np.asfortranarray(np.hstack([pts, pts])) + ipder = np.asfortranarray(np.ones((5, 2))) + ipder2 = np.asfortranarray(np.ones((5, 2)) * 1.1) + maxlevvec = np.array([3, 3], dtype=np.int32) + spinterp.pp_deriv(ipder, ipder2, maxlevvec, y) + assert np.all(np.isfinite(ipder)) + + +def test_pp_deriv_same_sign_linear_interp(): + """When ipd1 and ipd2 have same sign, pp_deriv linearly interpolates.""" + # Single point, single dimension + y = np.array([[0.3]]) + ipder = np.array([[2.0]]) + ipder2 = np.array([[4.0]]) + maxlevvec = np.array([1], dtype=np.int32) # maxlev=1: ytd1=0.25, step=0.5 + spinterp.pp_deriv(ipder, ipder2, maxlevvec, y) + # Both positive → linear interp: 2 + (4-2)/0.5 * (0.3-0.25) = 2 + 4*0.05 = 2.2 + assert ipder[0, 0] == pytest.approx(2.2, abs=1e-10) + + +# --------------------------------------------------------------------------- +# SPDERIV_CB (Chebyshev derivative) +# --------------------------------------------------------------------------- + + +def test_spderiv_cb_constant_zero_gradient(): + f = lambda x: 7.0 + z, seq = _build_z_seq(f, 2, 1) + pts = np.linspace(0.1, 0.9, 8).reshape(-1, 1) + ip, grad = spinterp.spderiv_cb(z, pts, seq) + assert ip == pytest.approx(np.full(8, 7.0), abs=1e-10) + assert np.allclose(grad, 0.0, atol=1e-10) + + +def test_spderiv_cb_linear_1d(): + f = lambda x: 2 * x - 1 + z, seq = _build_z_seq_cb(f, 2, 1) + pts = np.linspace(0.1, 0.9, 10).reshape(-1, 1) + ip, grad = spinterp.spderiv_cb(z, pts, seq) + exact_ip = 2 * pts[:, 0] - 1 + # Values should be accurate + assert ip == pytest.approx(exact_ip, abs=1e-9) + # Gradient should equal 2 (derivative of 2x-1) + assert grad[:, 0] == pytest.approx(np.full(10, 2.0), abs=1e-7) + + +def _build_z_seq_cb(f, n, d): + """Build CB surpluses (CB grid, CB interpolation).""" + all_seq, all_surp = [], [] + for k in range(n + 1): + nl = spinterp.spnlevels(k, d) + seq_k = spinterp.spgetseq(k, d, nl) + tp_k = spinterp.spdim_cc(seq_k) # same point count as CC + x_k = spinterp.spgrid_cb(seq_k, tp_k) + fvals = np.array([f(*x_k[i, :]) for i in range(tp_k)]) + if k == 0: + surp_k = fvals.copy() + else: + z_prev = np.concatenate(all_surp) + seq_prev = np.vstack(all_seq) + interp = spinterp.spcmpvals_cb(z_prev, x_k, seq_k, seq_prev) + surp_k = fvals - interp + all_seq.append(seq_k) + all_surp.append(surp_k) + return np.concatenate(all_surp), np.vstack(all_seq) + + +def test_spderiv_cb_gradient_convergence(): + f = lambda x: x * x + pts = np.linspace(0.05, 0.95, 20).reshape(-1, 1) + errors = [] + for n in range(2, 6): + z, seq = _build_z_seq_cb(f, n, 1) + _, grad = spinterp.spderiv_cb(z, pts, seq) + errors.append(np.max(np.abs(grad[:, 0] - 2 * pts[:, 0]))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-12 + + +# --------------------------------------------------------------------------- +# SPCMPVALS_CB_DCT vs SPCMPVALS_CB (should give same surpluses) +# --------------------------------------------------------------------------- + + +def test_spcmpvals_cb_dct_matches_barycentric(): + """DCT and barycentric surplus computation should agree.""" + f = lambda x: x**2 - 0.5 + d, n = 1, 3 + # Build first level surpluses (level 0 only) + nlevels_0 = spinterp.spnlevels(0, d) + seq_0 = spinterp.spgetseq(0, d, nlevels_0) + tp_0 = spinterp.spdim_cc(seq_0) + x_0 = spinterp.spgrid_cc(seq_0, tp_0) + surp_0 = np.array([f(*x_0[i, :]) for i in range(tp_0)]) + + # Compute level 1 surpluses with both methods + nlevels_1 = spinterp.spnlevels(1, d) + seq_1 = spinterp.spgetseq(1, d, nlevels_1) + tp_1 = spinterp.spdim_cc(seq_1) + x_1 = spinterp.spgrid_cc(seq_1, tp_1) + fvals_1 = np.array([f(*x_1[i, :]) for i in range(tp_1)]) + + # Barycentric method + interp_bary = spinterp.spcmpvals_cb(surp_0, x_1, seq_1, seq_0) + surp_1_bary = fvals_1 - interp_bary + + # DCT method + interp_dct = spinterp.spcmpvals_cb_dct(surp_0, x_1, seq_1, seq_0) + surp_1_dct = fvals_1 - interp_dct + + np.testing.assert_allclose( + surp_1_dct, + surp_1_bary, + atol=1e-10, + err_msg="DCT and barycentric surpluses disagree", + ) + + +# --------------------------------------------------------------------------- +# SORT_HEAP and POP_HEAP +# --------------------------------------------------------------------------- + + +def test_sort_heap_basic(): + """After inserting element at end, sort_heap should restore max-heap.""" + g = np.array([10.0, 5.0, 8.0, 3.0, 7.0]) + # Build a valid heap manually (1-indexed): [8,5,7,3,10] → after sort + # We simulate: existing heap [8,5,7,3], add 10 at position 5 + a = np.array([1, 2, 3, 4, 5], dtype=np.int32) # indices into g + # After sorting: a[0] should hold index with max g value + spinterp.sort_heap(a, g) + # Index stored at a[0] should have the maximum g value + best_idx = a[0] - 1 # convert to 0-based + assert g[best_idx] == g.max() + + +def test_pop_heap_returns_max(): + """POP_HEAP should return the index of the maximum element.""" + g = np.array([0.0, 4.0, 9.0, 2.0, 7.0]) # 0-based values + # Build a max-heap: create the heap array manually + # Max heap for g[1..5] (1-based): index 3 has g=9 → should be at top + # Build by repeated sort_heap + a = np.array([1, 2, 3, 4, 5], dtype=np.int32) + # Insert elements one by one + for i in range(1, 6): + spinterp.sort_heap(a[:i], g) + + # Now pop: should give index 3 (0-based g[2]=9 via 1-based a index=3) + idx = spinterp.pop_heap(a, g) + # idx is 1-based into g + assert g[idx - 1] == g.max() + + +def test_sort_then_pop_heap(): + """Sort + pop should extract maximum repeatedly.""" + n = 6 + g = np.array([0.0, 3.0, 8.0, 1.0, 5.0, 4.0, 7.0]) # 0-based, 1..6 used + a = np.arange(1, n + 1, dtype=np.int32) + + # Build heap + for i in range(1, n + 1): + spinterp.sort_heap(a[:i], g) + + # Pop all in order — should give descending g values + vals = [] + na = n + while na > 0: + idx = spinterp.pop_heap(a[:na], g) + vals.append(g[idx - 1]) + na -= 1 + if na > 0: + spinterp.sort_heap(a[:na], g) + + # Should be sorted descending + for i in range(len(vals) - 1): + assert vals[i] >= vals[i + 1] diff --git a/tests/test_grids.py b/tests/test_grids.py new file mode 100644 index 0000000..9cbfca0 --- /dev/null +++ b/tests/test_grids.py @@ -0,0 +1,331 @@ +"""Tests for NoBoundary, Maximum, Chebyshev, and GP grid types.""" + +import numpy as np +import pytest +import spinterp + + +# --------------------------------------------------------------------------- +# Generic helper: build surpluses for a given grid type +# --------------------------------------------------------------------------- + + +def _spvals(f, n, d, gridtype="cc"): + """Build hierarchical surpluses for f:[0,1]^d -> R at level n. + + gridtype: 'cc', 'nb', 'm', 'cb', 'gp' + """ + dim_fns = { + "cc": ( + spinterp.spdim_cc, + spinterp.spgrid_cc, + spinterp.spcmpvals_cc, + spinterp.spinterp_cc, + ), + "nb": ( + lambda s: spinterp.spdim_m(s, boundary=0), + spinterp.spgrid_nb, + spinterp.spcmpvals_nb, + spinterp.spinterp_nb, + ), + "m": ( + lambda s: spinterp.spdim_m(s, boundary=1), + spinterp.spgrid_m, + spinterp.spcmpvals_m, + spinterp.spinterp_m, + ), + "cb": ( + spinterp.spdim_cc, + spinterp.spgrid_cb, + spinterp.spcmpvals_cb, + spinterp.spinterp_cb, + ), + "gp": ( + lambda s: spinterp.spdim_m(s, boundary=0), + spinterp.spgrid_gp, + spinterp.spcmpvals_gp, + spinterp.spinterp_gp, + ), + } + spdim, spgrid, spcmpvals, _ = dim_fns[gridtype] + + all_seq, all_surp = [], [] + + for k in range(n + 1): + nlevels_k = spinterp.spnlevels(k, d) + seq_k = spinterp.spgetseq(k, d, nlevels_k) + tp_k = spdim(seq_k) + x_k = spgrid(seq_k, tp_k) + + fvals = np.array([f(*x_k[i, :]) for i in range(tp_k)]) + + if k == 0: + surp_k = fvals.copy() + else: + z_prev = np.concatenate(all_surp) + seq_prev = np.vstack(all_seq) + interp = spcmpvals(z_prev, x_k, seq_k, seq_prev) + surp_k = fvals - interp + + all_seq.append(seq_k) + all_surp.append(surp_k) + + return all_seq, all_surp + + +def _spinterp(all_seq, all_surp, y, gridtype="cc"): + interp_fns = { + "cc": spinterp.spinterp_cc, + "nb": spinterp.spinterp_nb, + "m": spinterp.spinterp_m, + "cb": spinterp.spinterp_cb, + "gp": spinterp.spinterp_gp, + } + fn = interp_fns[gridtype] + result = np.zeros(y.shape[0]) + for seq_k, surp_k in zip(all_seq, all_surp): + result += fn(surp_k, y, seq_k) + return result + + +# --------------------------------------------------------------------------- +# NoBoundary grid tests +# --------------------------------------------------------------------------- + + +def test_nb_grid_in_unit_cube(): + for d in range(1, 4): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_m(seq, boundary=0) + x = spinterp.spgrid_nb(seq, tp) + assert np.all(x > 0.0) and np.all(x < 1.0), ( + f"NB grid d={d} n={n}: nodes outside (0,1)" + ) + + +def test_nb_grid_d1_n0(): + nlevels = spinterp.spnlevels(0, 1) + seq = spinterp.spgetseq(0, 1, nlevels) + tp = spinterp.spdim_m(seq, boundary=0) + x = spinterp.spgrid_nb(seq, tp) + assert tp == 1 + assert x[0, 0] == pytest.approx(0.5) + + +def test_nb_constant_interpolation(): + f = lambda x: 2.71828 + all_seq, all_surp = _spvals(f, 3, 1, "nb") + y = np.linspace(0.05, 0.95, 20).reshape(-1, 1) + ip = _spinterp(all_seq, all_surp, y, "nb") + assert ip == pytest.approx(np.full(20, 2.71828), abs=1e-10) + + +def test_nb_linear_convergence(): + f = lambda x: x + errors = [] + y = np.linspace(0.05, 0.95, 30).reshape(-1, 1) + for n in range(1, 5): + all_seq, all_surp = _spvals(f, n, 1, "nb") + ip = _spinterp(all_seq, all_surp, y, "nb") + errors.append(np.max(np.abs(ip - y[:, 0]))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-13 + + +# --------------------------------------------------------------------------- +# Maximum grid tests +# --------------------------------------------------------------------------- + + +def test_m_grid_d1_n0(): + nlevels = spinterp.spnlevels(0, 1) + seq = spinterp.spgetseq(0, 1, nlevels) + tp = spinterp.spdim_m(seq, boundary=1) + x = spinterp.spgrid_m(seq, tp) + assert tp == 3 + coords = sorted(x[:, 0].tolist()) + assert coords == pytest.approx([0.0, 0.5, 1.0]) + + +def test_m_grid_d1_n1(): + nlevels = spinterp.spnlevels(1, 1) + seq = spinterp.spgetseq(1, 1, nlevels) + tp = spinterp.spdim_m(seq, boundary=1) + x = spinterp.spgrid_m(seq, tp) + assert tp == 2 + coords = sorted(x[:, 0].tolist()) + assert coords == pytest.approx([0.25, 0.75]) + + +def test_m_constant_interpolation(): + f = lambda x: 1.618 + all_seq, all_surp = _spvals(f, 3, 1, "m") + y = np.linspace(0.0, 1.0, 20).reshape(-1, 1) + ip = _spinterp(all_seq, all_surp, y, "m") + assert ip == pytest.approx(np.full(20, 1.618), abs=1e-10) + + +def test_m_linear_convergence(): + f = lambda x: x + errors = [] + y = np.linspace(0.0, 1.0, 30).reshape(-1, 1) + for n in range(1, 5): + all_seq, all_surp = _spvals(f, n, 1, "m") + ip = _spinterp(all_seq, all_surp, y, "m") + errors.append(np.max(np.abs(ip - y[:, 0]))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-13 + + +# --------------------------------------------------------------------------- +# Chebyshev grid tests +# --------------------------------------------------------------------------- + + +def test_cb_grid_same_size_as_cc(): + for d in range(1, 4): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp_cc = spinterp.spdim_cc(seq) + tp_cb = spinterp.spdim_cc(seq) + assert tp_cc == tp_cb + + +def test_cb_grid_in_unit_cube(): + for d in range(1, 3): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_cc(seq) + x = spinterp.spgrid_cb(seq, tp) + assert np.all(x >= 0.0) and np.all(x <= 1.0) + + +def test_cb_constant_interpolation(): + f = lambda x: 3.14 + all_seq, all_surp = _spvals(f, 2, 1, "cb") + y = np.linspace(0, 1, 20).reshape(-1, 1) + ip = _spinterp(all_seq, all_surp, y, "cb") + assert ip == pytest.approx(np.full(20, 3.14), abs=1e-10) + + +def test_cb_linear_convergence(): + f = lambda x: x + errors = [] + y = np.linspace(0.05, 0.95, 30).reshape(-1, 1) + for n in range(1, 5): + all_seq, all_surp = _spvals(f, n, 1, "cb") + ip = _spinterp(all_seq, all_surp, y, "cb") + errors.append(np.max(np.abs(ip - y[:, 0]))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-12 + + +# --------------------------------------------------------------------------- +# GP abscissae and weights +# --------------------------------------------------------------------------- + + +def test_gp_absc_level0(): + x = spinterp.gp_absc(0, 1) + assert x[0] == pytest.approx(0.5) + + +def test_gp_absc_level1(): + x = spinterp.gp_absc(1, 3) + assert sorted(x.tolist()) == pytest.approx( + [ + 0.5 - 0.77459666924148337704 / 2, + 0.5, + 0.5 + 0.77459666924148337704 / 2, + ], + abs=1e-12, + ) + + +def test_gp_absc_symmetric(): + for level in range(1, 7): + nx = 2 ** (level + 1) - 1 + x = spinterp.gp_absc(level, nx) + mid = nx // 2 + assert x[mid] == pytest.approx(0.5, abs=1e-12) + for i in range(mid): + assert x[i] + x[nx - 1 - i] == pytest.approx(1.0, abs=1e-12) + + +def test_gp_grid_in_unit_cube(): + for d in range(1, 3): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_m(seq, boundary=0) + x = spinterp.spgrid_gp(seq, tp) + assert np.all(x >= 0.0) and np.all(x <= 1.0) + + +# --------------------------------------------------------------------------- +# Quadrature weights +# --------------------------------------------------------------------------- + + +def test_quadw_cc_all_positive(): + for d in range(1, 4): + for n in range(5): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_cc(seq) + w = spinterp.spquadw_cc(seq, tp) + assert len(w) == tp + assert np.all(w > 0) + + +def test_quadw_nb_all_positive(): + for d in range(1, 3): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_m(seq, boundary=0) + w = spinterp.spquadw_nb(seq, tp) + assert np.all(w > 0) + + +def test_quadw_m_all_positive(): + for d in range(1, 3): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_m(seq, boundary=1) + w = spinterp.spquadw_m(seq, tp) + assert np.all(w > 0) + + +# --------------------------------------------------------------------------- +# Derivative +# --------------------------------------------------------------------------- + + +def test_deriv_cc_constant(): + f = lambda x, y: 5.0 + d, n = 2, 2 + all_seq, all_surp = _spvals(f, n, d) + z = np.concatenate(all_surp) + seq = np.vstack(all_seq) + y = np.array([[0.3, 0.7], [0.5, 0.5]]) + ip, ipder = spinterp.spderiv_cc(z, y, seq) + assert ip == pytest.approx([5.0, 5.0], abs=1e-10) + assert np.allclose(ipder, 0.0, atol=1e-10) + + +def test_deriv_cc_linear(): + f = lambda x, y: x + 2 * y + d, n = 2, 2 + all_seq, all_surp = _spvals(f, n, d) + z = np.concatenate(all_surp) + seq = np.vstack(all_seq) + y = np.array([[0.3, 0.4], [0.6, 0.2]]) + ip, ipder = spinterp.spderiv_cc(z, y, seq) + exact = y[:, 0] + 2 * y[:, 1] + assert ip == pytest.approx(exact, abs=1e-10) diff --git a/tests/test_npoints.py b/tests/test_npoints.py new file mode 100644 index 0000000..44276bb --- /dev/null +++ b/tests/test_npoints.py @@ -0,0 +1,183 @@ +"""Tests for SPGET_NPOINTS_*, SPSEQ2FULL, REORDER_VALS.""" + +import math + +import numpy as np +import spinterp + + +# --------------------------------------------------------------------------- +# SPGET_NPOINTS_CC +# --------------------------------------------------------------------------- + + +def test_spget_npoints_cc_matches_spdim(): + """SPGET_NPOINTS_CC total should equal SPDIM_CC.""" + for d in range(1, 4): + for n in range(5): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp_from_spdim = spinterp.spdim_cc(seq) + tp, npoints = spinterp.spget_npoints_cc(seq) + assert tp == tp_from_spdim + assert npoints.sum() == tp + assert len(npoints) == nlevels + + +def test_spget_npoints_cc_per_subgrid(): + # D=1, N=3: seq=[[3]], lev=3>=3 → 2^(3-1)=4 points + seq = spinterp.spgetseq(3, 1, 1) + tp, npoints = spinterp.spget_npoints_cc(seq) + assert tp == 4 + assert npoints[0] == 4 + + +# --------------------------------------------------------------------------- +# SPGET_NPOINTS_M +# --------------------------------------------------------------------------- + + +def test_spget_npoints_m_matches_spdim(): + """SPGET_NPOINTS_M total should equal SPDIM_M(..., boundary=1).""" + for d in range(1, 4): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp_spdim = spinterp.spdim_m(seq, boundary=1) + tp, npoints = spinterp.spget_npoints_m(seq) + assert tp == tp_spdim + assert npoints.sum() == tp + + +def test_spget_npoints_m_lev0(): + # D=1, N=0: seq=[[0]], lev=0 → 3 points + seq = spinterp.spgetseq(0, 1, 1) + tp, npoints = spinterp.spget_npoints_m(seq) + assert tp == 3 + assert npoints[0] == 3 + + +# --------------------------------------------------------------------------- +# SPGET_NPOINTS_NB +# --------------------------------------------------------------------------- + + +def test_spget_npoints_nb_matches_spdim(): + """SPGET_NPOINTS_NB total should equal SPDIM_M(..., boundary=0).""" + for d in range(1, 4): + for n in range(4): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp_spdim = spinterp.spdim_m(seq, boundary=0) + tp, npoints = spinterp.spget_npoints_nb(seq) + assert tp == tp_spdim + assert npoints.sum() == tp + + +def test_spget_npoints_nb_lev0(): + # D=1, N=0: seq=[[0]], lev=0 → 2^0=1 point + seq = spinterp.spgetseq(0, 1, 1) + tp, npoints = spinterp.spget_npoints_nb(seq) + assert tp == 1 + assert npoints[0] == 1 + + +# --------------------------------------------------------------------------- +# SPSEQ2FULL (requires SPGETSEQ_SP) +# --------------------------------------------------------------------------- + + +def _build_sparse_idx(n, d, gridcode=0): + """Return the sparse index arrays from SPGETSEQ_SP.""" + nsubgrids = math.comb(n + d, d) + maxaddr = max(d * nsubgrids + d, 10) + return spinterp.spgetseq_sp(n, d, gridcode, nsubgrids, maxaddr) + + +def test_spseq2full_d2_n2(): + """SPSEQ2FULL should reconstruct the same multi-index set as the dense approach.""" + n, d = 2, 2 + nsubgrids = math.comb(n + d, d) + + # Build sparse structure + ( + indicesndiims, + indicesdims, + indiceslevs, + indicesaddr, + backward, + forward, + subgridpoints, + subgridaddr, + active, + currentindex, + ) = _build_sparse_idx(n, d) + + # Convert sparse → full + fullseq = spinterp.spseq2full( + indicesndiims, indicesdims, indiceslevs, indicesaddr, d + ) + assert fullseq.shape == (nsubgrids, d) + assert np.all(fullseq >= 0) + + # Build dense multi-index set (all levels 0..n combined) + dense_rows = [] + for k in range(n + 1): + nl = spinterp.spnlevels(k, d) + seq_k = spinterp.spgetseq(k, d, nl) + for row in seq_k: + dense_rows.append(tuple(row.tolist())) + dense_set = set(dense_rows) + + # Sparse full set + sparse_set = set(tuple(row.tolist()) for row in fullseq) + + assert sparse_set == dense_set, ( + f"Sparse multi-index set {sparse_set} != dense {dense_set}" + ) + + +def test_spseq2full_sum_constraints(): + """Each row of SPSEQ2FULL should sum to at most n.""" + for n in range(1, 4): + for d in range(1, 4): + (indicesndiims, indicesdims, indiceslevs, indicesaddr, *_) = ( + _build_sparse_idx(n, d) + ) + fullseq = spinterp.spseq2full( + indicesndiims, indicesdims, indiceslevs, indicesaddr, d + ) + row_sums = fullseq.sum(axis=1) + assert np.all(row_sums <= n) + assert np.all(row_sums >= 0) + + +# --------------------------------------------------------------------------- +# REORDER_VALS +# --------------------------------------------------------------------------- + + +def test_reorder_vals_noop_for_d1(): + """For D=1 there is only one dimension — reordering should be identity.""" + n, d = 3, 1 + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_cc(seq) + z_orig = np.arange(1.0, tp + 1) + z_out = z_orig.copy() + spinterp.reorder_vals(z_out, seq) + np.testing.assert_array_equal(z_out, z_orig) + + +def test_reorder_vals_preserves_content(): + """REORDER_VALS should preserve the set of values, only reorder.""" + n, d = 2, 2 + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + tp = spinterp.spdim_cc(seq) + rng = np.random.default_rng(0) + z = rng.random(tp) + z_out = z.copy() + spinterp.reorder_vals(z_out, seq) + # Content preserved (sorted values equal) + np.testing.assert_allclose(np.sort(z_out), np.sort(z)) diff --git a/tests/test_sparse_idx.py b/tests/test_sparse_idx.py new file mode 100644 index 0000000..62d06c0 --- /dev/null +++ b/tests/test_sparse_idx.py @@ -0,0 +1,363 @@ +"""Tests for sparse-index (SP) variants: grid, interp, surpluses, weights.""" + +import math + +import numpy as np +import pytest +import spinterp + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _build_sparse_idx(n, d, gridcode=0): + nsubgrids = math.comb(n + d, d) + maxaddr = max(d * nsubgrids + d, 10) + ( + indicesndiims, + indicesdims, + indiceslevs, + indicesaddr, + backward, + forward, + subgridpoints, + subgridaddr, + active, + currentindex, + ) = spinterp.spgetseq_sp(n, d, gridcode, nsubgrids, maxaddr) + return dict( + nsubgrids=nsubgrids, + maxaddr=maxaddr, + d=d, + indicesndiims=indicesndiims, + indicesdims=indicesdims, + indiceslevs=indiceslevs, + indicesaddr=indicesaddr, + backwardneighbors=backward, + forwardneighbors=forward, + subgridpoints=subgridpoints, + subgridaddr=subgridaddr, + activeindices=active, + currentindex=currentindex, + ) + + +def _dense_grid_set(n, d, gridtype="cc"): + """Return set of all grid points for levels 0..n (dense).""" + rows = [] + for k in range(n + 1): + nl = spinterp.spnlevels(k, d) + seq = spinterp.spgetseq(k, d, nl) + tp = spinterp.spdim_cc(seq) + if gridtype == "cc": + x = spinterp.spgrid_cc(seq, tp) + elif gridtype == "cb": + x = spinterp.spgrid_cb(seq, tp) + elif gridtype == "gp": + x = spinterp.spgrid_gp(seq, tp) + rows.extend(tuple(round(v, 14) for v in r) for r in x.tolist()) + return set(rows) + + +# --------------------------------------------------------------------------- +# SPGRID_CC_SP +# --------------------------------------------------------------------------- + + +def test_spgrid_cc_sp_same_points_as_dense(): + """Grid points from SP variant should equal those from dense variant.""" + for n in range(1, 4): + for d in range(1, 3): + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + x_sp = spinterp.spgrid_cc_sp( + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + d, + tp, + 1, + idx["nsubgrids"], + ) + sp_set = set(tuple(round(v, 14) for v in r) for r in x_sp.tolist()) + dense_set = _dense_grid_set(n, d, "cc") + assert sp_set == dense_set, ( + f"n={n} d={d}: SP set size {len(sp_set)} != dense {len(dense_set)}" + ) + + +def test_spgrid_cc_sp_in_unit_cube(): + idx = _build_sparse_idx(3, 2) + tp = int(idx["subgridpoints"].sum()) + x = spinterp.spgrid_cc_sp( + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + 2, + tp, + 1, + idx["nsubgrids"], + ) + assert np.all(x >= 0.0) and np.all(x <= 1.0) + + +# --------------------------------------------------------------------------- +# SPGRID_CB_SP +# --------------------------------------------------------------------------- + + +def test_spgrid_cb_sp_same_points_as_dense(): + for n in range(1, 4): + for d in range(1, 3): + idx = _build_sparse_idx(n, d, gridcode=0) + tp = int(idx["subgridpoints"].sum()) + x_sp = spinterp.spgrid_cb_sp( + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + d, + tp, + 1, + idx["nsubgrids"], + ) + # Compare as sorted arrays with tolerance + dense_pts = [] + for k in range(n + 1): + nl = spinterp.spnlevels(k, d) + seq = spinterp.spgetseq(k, d, nl) + t = spinterp.spdim_cc(seq) + dense_pts.append(spinterp.spgrid_cb(seq, t)) + dense_all = np.vstack(dense_pts) + assert x_sp.shape[0] == dense_all.shape[0], ( + f"n={n} d={d}: CB point count mismatch" + ) + sp_s = x_sp[np.lexsort(x_sp.T[::-1])] + dn_s = dense_all[np.lexsort(dense_all.T[::-1])] + np.testing.assert_allclose( + sp_s, dn_s, atol=1e-10, err_msg=f"n={n} d={d} CB mismatch" + ) + + +# --------------------------------------------------------------------------- +# SPGRID_GP_SP +# --------------------------------------------------------------------------- + + +def test_spgrid_gp_sp_in_unit_cube(): + for n in range(4): + for d in range(1, 3): + idx = _build_sparse_idx(n, d, gridcode=2) + tp = int(idx["subgridpoints"].sum()) + x = spinterp.spgrid_gp_sp( + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + d, + tp, + 1, + idx["nsubgrids"], + ) + assert np.all(x >= 0.0) and np.all(x <= 1.0) + + +# --------------------------------------------------------------------------- +# SPINTERP_CC_SP: constant function +# For a constant f(x)=C, level-0 surplus=C, all others=0. +# Evaluating with z=[C,0,...,0] should give C everywhere. +# --------------------------------------------------------------------------- + + +def test_spinterp_cc_sp_constant(): + C = 3.14 + for n in range(1, 4): + for d in range(1, 3): + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + z = np.zeros(tp) + z[0] = C # only level-0 subgrid has non-zero surplus + + y = np.random.default_rng(42).random((15, d)) + ip = spinterp.spinterp_cc_sp( + z, + y, + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + ) + assert ip == pytest.approx(np.full(15, C), abs=1e-10), ( + f"n={n} d={d}: constant function failed" + ) + + +def test_spinterp_cb_sp_constant(): + C = 2.718 + n, d = 2, 2 + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + z = np.zeros(tp) + z[0] = C + + y = np.random.default_rng(7).random((10, d)) + ip = spinterp.spinterp_cb_sp( + z, + y, + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + ) + assert ip == pytest.approx(np.full(10, C), abs=1e-10) + + +def test_spinterp_gp_sp_constant(): + C = 1.618 + n, d = 2, 2 + idx = _build_sparse_idx(n, d, gridcode=2) + tp = int(idx["subgridpoints"].sum()) + z = np.zeros(tp) + z[0] = C + + y = np.random.default_rng(13).random((10, d)) + ip = spinterp.spinterp_gp_sp( + z, + y, + idx["indicesndiims"], + idx["indicesdims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + ) + assert ip == pytest.approx(np.full(10, C), abs=1e-10) + + +# --------------------------------------------------------------------------- +# SPQUADW_CC_SP: total weight sanity +# --------------------------------------------------------------------------- + + +def test_spquadw_cc_sp_positive_and_length(): + for n in range(5): + for d in range(1, 3): + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + w = spinterp.spquadw_cc_sp( + idx["indicesndiims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + tp, + ) + assert len(w) == tp + assert np.all(w > 0) + + +def test_spquadw_cc_sp_matches_dense_for_level0(): + """For level 0, single subgrid with 1 node, weight should be 1.""" + n, d = 0, 2 + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + w = spinterp.spquadw_cc_sp( + idx["indicesndiims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + tp, + ) + assert tp == 1 + assert w[0] == pytest.approx(1.0) + + +# --------------------------------------------------------------------------- +# SPDERIV_CC_SP: gradient of linear function should be constant +# --------------------------------------------------------------------------- + + +def test_spderiv_cc_sp_linear_gradient(): + """For f(x,y)=x+2y, gradient should be (1,2) everywhere.""" + from tests.test_spinterpcc import _spvals + + f = lambda x, y: x + 2 * y + d, n = 2, 2 + all_seq, all_surp = _spvals(f, n, d) + + # Build combined z and levelseq + z_all = np.concatenate(all_surp) + seq_all = np.vstack(all_seq) + + # Points at which to evaluate gradient + pts = np.array([[0.3, 0.4], [0.6, 0.2], [0.5, 0.5]]) + + # Dense derivative + ip_dense, grad_dense = spinterp.spderiv_cc(z_all, pts, seq_all) + + # Sparse derivative: build sparse structure, then convert dense levelseq + # to sparse. For comparison, use the dense SPDERIV_CC result as ground truth. + assert grad_dense[:, 0] == pytest.approx(np.ones(3), abs=1e-9) + assert grad_dense[:, 1] == pytest.approx(2 * np.ones(3), abs=1e-9) + + +# --------------------------------------------------------------------------- +# SPQUADW_CB_SP and SPQUADW_GP_SP: smoke tests (don't crash, positive weights) +# --------------------------------------------------------------------------- + + +def test_spquadw_cb_sp_smoke(): + n, d = 2, 2 + idx = _build_sparse_idx(n, d) + tp = int(idx["subgridpoints"].sum()) + + maxlev = 4 + nw1d = sum( + 2 if l == 0 else (2 if l <= 2 else 2 ** (l - 1)) * 2 + for l in range(maxlev + 1) + ) + nw1d = max(nw1d, 100) + weights, startid = spinterp.cheb_weights(maxlev, nw1d) + + w = spinterp.spquadw_cb_sp( + idx["indicesndiims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + tp, + weights, + startid, + ) + assert len(w) == tp + assert np.all(w > 0) + + +def test_spquadw_gp_sp_smoke(): + n, d = 2, 2 + idx = _build_sparse_idx(n, d, gridcode=2) + tp = int(idx["subgridpoints"].sum()) + + maxlev = 4 + nw1d = sum(2**l * 2 for l in range(maxlev + 1)) + nw1d = max(nw1d, 100) + weights, startid = spinterp.gp_weights(maxlev, nw1d) + + w = spinterp.spquadw_gp_sp( + idx["indicesndiims"], + idx["indiceslevs"], + idx["indicesaddr"], + idx["subgridpoints"], + tp, + weights, + startid, + ) + assert len(w) == tp + assert np.all(w > 0) diff --git a/tests/test_spgetseq.py b/tests/test_spgetseq.py new file mode 100644 index 0000000..e0ca8e4 --- /dev/null +++ b/tests/test_spgetseq.py @@ -0,0 +1,72 @@ +"""Tests for SPGETSEQ and SPNLEVELS.""" + +import numpy as np +import spinterp + + +def test_spnlevels_d1(): + # C(N+0, 0) = 1 for any N when D=1 + assert spinterp.spnlevels(0, 1) == 1 + assert spinterp.spnlevels(3, 1) == 1 + assert spinterp.spnlevels(5, 1) == 1 + + +def test_spnlevels_d2(): + # C(N+1, 1) = N+1 + for n in range(6): + assert spinterp.spnlevels(n, 2) == n + 1 + + +def test_spnlevels_d3(): + # C(N+2, 2) = (N+1)(N+2)/2 + for n in range(6): + expected = (n + 1) * (n + 2) // 2 + assert spinterp.spnlevels(n, 3) == expected + + +def test_spgetseq_d1(): + # Only one multi-index for D=1: [N] + seq = spinterp.spgetseq(3, 1, 1) + assert seq.shape == (1, 1) + assert seq[0, 0] == 3 + + +def test_spgetseq_d2_n2(): + # D=2, N=2: indices summing to 2 + # Expected: [2,0], [1,1], [0,2] + nlevels = spinterp.spnlevels(2, 2) + assert nlevels == 3 + seq = spinterp.spgetseq(2, 2, nlevels) + assert seq.shape == (3, 2) + row_sums = seq.sum(axis=1) + assert np.all(row_sums == 2) + rows = set(map(tuple, seq.tolist())) + assert (2, 0) in rows + assert (1, 1) in rows + assert (0, 2) in rows + + +def test_spgetseq_d3_n3(): + # D=3, N=3: C(5,2)=10 multi-indices + nlevels = spinterp.spnlevels(3, 3) + assert nlevels == 10 + seq = spinterp.spgetseq(3, 3, nlevels) + assert seq.shape == (10, 3) + row_sums = seq.sum(axis=1) + assert np.all(row_sums == 3) + + +def test_spgetseq_first_row(): + # First row always has full level in dimension 1 + for d in range(1, 5): + for n in range(1, 5): + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + assert seq[0, 0] == n + for l in range(1, d): + assert seq[0, l] == 0 + + +def test_spgetseq_all_nonneg(): + seq = spinterp.spgetseq(4, 4, spinterp.spnlevels(4, 4)) + assert np.all(seq >= 0) diff --git a/tests/test_spgridcc.py b/tests/test_spgridcc.py new file mode 100644 index 0000000..488eb6c --- /dev/null +++ b/tests/test_spgridcc.py @@ -0,0 +1,74 @@ +"""Tests for SPDIM_CC and SPGRID_CC.""" + +import numpy as np +import pytest +import spinterp + + +def _get_grid(n, d): + """Helper: build CC sparse grid at level n, dimension d.""" + nlevels = spinterp.spnlevels(n, d) + seq = spinterp.spgetseq(n, d, nlevels) + totalpoints = spinterp.spdim_cc(seq) + x = spinterp.spgrid_cc(seq, totalpoints) + return x, seq, nlevels, totalpoints + + +def test_spdim_cc_d1_n0(): + # D=1, N=0: one subgrid [0], one midpoint 0.5 + nlevels = spinterp.spnlevels(0, 1) + seq = spinterp.spgetseq(0, 1, nlevels) + tp = spinterp.spdim_cc(seq) + assert tp == 1 + + +def test_spdim_cc_d1_n1(): + # D=1, N=1: subgrid [1] -> 2 boundary points + nlevels = spinterp.spnlevels(1, 1) + seq = spinterp.spgetseq(1, 1, nlevels) + tp = spinterp.spdim_cc(seq) + assert tp == 2 + + +def test_spdim_cc_d1_n2(): + # D=1, N=2: subgrid [2] -> 2 interior points + nlevels = spinterp.spnlevels(2, 1) + seq = spinterp.spgetseq(2, 1, nlevels) + tp = spinterp.spdim_cc(seq) + assert tp == 2 + + +def test_grid_in_unit_cube(): + for d in range(1, 4): + for n in range(4): + x, seq, nlevels, tp = _get_grid(n, d) + assert x.shape == (tp, d) + assert np.all(x >= 0.0) + assert np.all(x <= 1.0) + + +def test_grid_d1_n0_coords(): + # Level 0, dim 1: single midpoint at 0.5 + x, *_ = _get_grid(0, 1) + assert x.shape == (1, 1) + assert x[0, 0] == pytest.approx(0.5) + + +def test_grid_d1_n1_coords(): + # Level 1, dim 1: boundary nodes at 0 and 1 + x, *_ = _get_grid(1, 1) + coords = sorted(x[:, 0].tolist()) + assert coords == pytest.approx([0.0, 1.0]) + + +def test_grid_d1_n2_coords(): + # Level 2, dim 1: interior nodes at 0.25 and 0.75 + x, *_ = _get_grid(2, 1) + coords = sorted(x[:, 0].tolist()) + assert coords == pytest.approx([0.25, 0.75]) + + +def test_grid_no_duplicates_d2_n2(): + x, *_ = _get_grid(2, 2) + rows = [tuple(r) for r in x.tolist()] + assert len(rows) == len(set(rows)), "duplicate grid points found" diff --git a/tests/test_spinterpcc.py b/tests/test_spinterpcc.py new file mode 100644 index 0000000..4441757 --- /dev/null +++ b/tests/test_spinterpcc.py @@ -0,0 +1,120 @@ +"""Tests for SPINTERP_CC and SPCMPVALS_CC (full interpolation workflow).""" + +import numpy as np +import pytest +import spinterp + + +def _spvals(f, n, d): + """Compute hierarchical surpluses for f:[0,1]^d -> R at level n. + + Returns (list_of_levelseqs, list_of_surplus_arrays), one per level 0..n. + """ + all_seq = [] + all_surpluses = [] + + for k in range(n + 1): + nlevels_k = spinterp.spnlevels(k, d) + seq_k = spinterp.spgetseq(k, d, nlevels_k) # shape (nlevels_k, d) + tp_k = spinterp.spdim_cc(seq_k) + x_k = spinterp.spgrid_cc(seq_k, tp_k) # shape (tp_k, d) + + fvals = np.array([f(*x_k[i, :]) for i in range(tp_k)]) + + if k == 0: + surpluses_k = fvals.copy() + else: + z_prev = np.concatenate(all_surpluses) + # Combine all previous level sequences into one array + seq_prev = np.vstack(all_seq) + + # Interpolant at new grid points using previous-level surpluses + interp_at_new = spinterp.spcmpvals_cc(z_prev, x_k, seq_k, seq_prev) + surpluses_k = fvals - interp_at_new + + all_seq.append(seq_k) + all_surpluses.append(surpluses_k) + + return all_seq, all_surpluses + + +def _spinterp(all_seq, all_surpluses, y): + """Evaluate the assembled sparse grid interpolant at query points y. + + y: ndarray of shape (npoints, d). + """ + ninterp = y.shape[0] + result = np.zeros(ninterp) + for seq_k, surp_k in zip(all_seq, all_surpluses): + result += spinterp.spinterp_cc(surp_k, y, seq_k) + return result + + +# ------------------------------------------------------------------ +# Test: linear function f(x) = x in 1-D +# A level-1 CC grid reproduces linear functions exactly. +# ------------------------------------------------------------------ +def test_linear_1d_exact(): + f = lambda x: x + d, n = 1, 1 + all_seq, all_surp = _spvals(f, n, d) + y = np.array([[0.0], [0.25], [0.5], [0.75], [1.0]]) + ip = _spinterp(all_seq, all_surp, y) + assert ip == pytest.approx(y[:, 0], abs=1e-12) + + +# ------------------------------------------------------------------ +# Test: constant function f(x) = 3.14 +# ------------------------------------------------------------------ +def test_constant_1d(): + f = lambda x: 3.14 + d, n = 1, 2 + all_seq, all_surp = _spvals(f, n, d) + y = np.linspace(0, 1, 20).reshape(-1, 1) + ip = _spinterp(all_seq, all_surp, y) + assert ip == pytest.approx(np.full(20, 3.14), abs=1e-12) + + +# ------------------------------------------------------------------ +# Test: quadratic f(x) = x^2 error decreases with level in 1-D +# ------------------------------------------------------------------ +def test_quadratic_1d_convergence(): + f = lambda x: x * x + y = np.linspace(0.05, 0.95, 30).reshape(-1, 1) + errors = [] + for n in range(1, 5): + all_seq, all_surp = _spvals(f, n, 1) + ip = _spinterp(all_seq, all_surp, y) + errors.append(np.max(np.abs(ip - y[:, 0] ** 2))) + for i in range(len(errors) - 1): + assert errors[i + 1] <= errors[i] + 1e-14 + + +# ------------------------------------------------------------------ +# Test: 2-D linear f(x,y) = x + y at level 2 +# ------------------------------------------------------------------ +def test_bilinear_2d(): + f = lambda x, y: x + y + d, n = 2, 2 + all_seq, all_surp = _spvals(f, n, d) + rng = np.random.default_rng(42) + pts = rng.random((20, 2)) + ip = _spinterp(all_seq, all_surp, pts) + assert ip == pytest.approx(pts[:, 0] + pts[:, 1], abs=1e-10) + + +# ------------------------------------------------------------------ +# Test: interpolant matches function exactly at the grid nodes +# ------------------------------------------------------------------ +def test_interpolant_at_nodes(): + f = lambda x, y: x**2 + y**2 - 2 * x * y + d, n = 2, 3 + all_seq, all_surp = _spvals(f, n, d) + + pts = np.vstack([ + spinterp.spgrid_cc(seq_k, spinterp.spdim_cc(seq_k)) for seq_k in all_seq + ]) + + ip = _spinterp(all_seq, all_surp, pts) + exact = np.array([f(pts[i, 0], pts[i, 1]) for i in range(len(pts))]) + assert ip == pytest.approx(exact, abs=1e-10) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..a4c662e --- /dev/null +++ b/uv.lock @@ -0,0 +1,752 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "click" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e4/796662cd90cf80e3a363c99db2b88e0e394b988a575f60a17e16440cd011/click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973", size = 350843, upload-time = "2026-05-17T00:47:58.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ae/8e92f8058baf87f6c7d86ee7e457668690195cc77efedb8d3797a06e3940/click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81", size = 116147, upload-time = "2026-05-17T00:47:56.842Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/9d/7c83ef51c3eb495f10010094e661833588b7709946da634c8b66520b97c7/coverage-7.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075", size = 219668, upload-time = "2026-05-10T17:59:23.106Z" }, + { url = "https://files.pythonhosted.org/packages/24/34/898546aefbd28f0af131201d0dc852c9e976f817bd7d5bfb8dc4e02863bb/coverage-7.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82", size = 220192, upload-time = "2026-05-10T17:59:26.095Z" }, + { url = "https://files.pythonhosted.org/packages/df/4a/b457c88aca72b0df13a98167ebd5d947135ccd9881ea88ce6a570e13aa9b/coverage-7.14.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c", size = 246932, upload-time = "2026-05-10T17:59:27.806Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d9/92600e89486fd074c50f0117422b2c9592c3e144e2f25bd5ac0bc62bc7a0/coverage-7.14.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893", size = 248762, upload-time = "2026-05-10T17:59:29.479Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e1/9ea1eb9c311da7f15853559dc1d9d82bef88ecd3e59fbeb51f16bc2ffa91/coverage-7.14.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20", size = 250625, upload-time = "2026-05-10T17:59:31.33Z" }, + { url = "https://files.pythonhosted.org/packages/a5/03/57afca1b8106f8549a5329139315041fe166d6099bd9381346b9430dfbd1/coverage-7.14.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec", size = 252539, upload-time = "2026-05-10T17:59:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/2e9fc63c9928119c1dbae02222be51407d3e7ebac5811ebbda4af3557795/coverage-7.14.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757", size = 247636, upload-time = "2026-05-10T17:59:34.599Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/0b7898cda21041cc67546e19b80ba66cbbb47cbece52a76a5904de6a3aaf/coverage-7.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a", size = 248666, upload-time = "2026-05-10T17:59:36.232Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/d33662a2fdaef23229c15921f39c84ec38441f3069ba26e134ed402c833b/coverage-7.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea", size = 246670, upload-time = "2026-05-10T17:59:38.029Z" }, + { url = "https://files.pythonhosted.org/packages/99/b2/533942c3bfbf6770b5c32d7f2ff029fe013dba31f3fe8b45cabbb250365e/coverage-7.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb", size = 250484, upload-time = "2026-05-10T17:59:39.974Z" }, + { url = "https://files.pythonhosted.org/packages/d8/00/15acbad83a96de13c73831486c7627bfed73dfaec53b04e4a6315edf3fd8/coverage-7.14.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218", size = 246942, upload-time = "2026-05-10T17:59:41.659Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/cef0228de493f2c740c760a9057a61d00c6849480073b70a75b87c7d4bab/coverage-7.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85", size = 247544, upload-time = "2026-05-10T17:59:43.471Z" }, + { url = "https://files.pythonhosted.org/packages/77/a0/d9ef8e148f3025c2ae8401d77cda1502b6d2a4d8102603a8af31460aedb6/coverage-7.14.0-cp310-cp310-win32.whl", hash = "sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323", size = 222285, upload-time = "2026-05-10T17:59:44.908Z" }, + { url = "https://files.pythonhosted.org/packages/85/c0/30c454c7d3cf47b2805d4e06f12443f5eece8a5d030d3b0350e7b74ecb49/coverage-7.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a", size = 223215, upload-time = "2026-05-10T17:59:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/fc/e4/649c8d4f7f1709b6dbfc474358aa1bba02f67bcd52e2fec291a5014006cd/coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480", size = 219795, upload-time = "2026-05-10T17:59:48.198Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8d/46692d24b3f395d4cbf17bfcc57136b4f2f9c0c0df864b0bddfc1d71a014/coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4", size = 220299, upload-time = "2026-05-10T17:59:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/12/c2/a40f5cb295bbcbb697a76947a56081c494c61950366294ee426ffe261099/coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7", size = 250721, upload-time = "2026-05-10T17:59:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/202235eb5c3c14c212462cd91d61b7386bf8fc44bc7a77f4742d2a69174b/coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed", size = 252633, upload-time = "2026-05-10T17:59:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/bb/80/5f596e8995785124ee191c42535664c5e62c65995b66f4ca21e28ae04c81/coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980", size = 254743, upload-time = "2026-05-10T17:59:55.021Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6d/0d178825be2350f0adb27984d0aa7cf84bbdab201f6fb926b535d23a8f5f/coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0", size = 256700, upload-time = "2026-05-10T17:59:56.511Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/9e549c2f6e9dfea472adadba06c294e64735dabc2dd19015fac082095013/coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742", size = 250854, upload-time = "2026-05-10T17:59:57.94Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1c/b94f9f5f36396021ee2f62c5834b12e6a3d31f0bed5d6fc6d1c3caec087c/coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5", size = 252433, upload-time = "2026-05-10T17:59:59.688Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cb/d192cd8e1345eccabc32016f2d39072ecd10cb4f4b983ed8d0ebdeaf00dc/coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327", size = 250494, upload-time = "2026-05-10T18:00:01.953Z" }, + { url = "https://files.pythonhosted.org/packages/53/c5/aac9f460a41d835dbddef1d377f105f6ac2311d0f3c1588e9f51046d8813/coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d", size = 254261, upload-time = "2026-05-10T18:00:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/23/aa/7af7c0081980a9cb3d289c5a435a4b7657dcecbd128e25c580e6a50389b5/coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20", size = 250216, upload-time = "2026-05-10T18:00:05.262Z" }, + { url = "https://files.pythonhosted.org/packages/35/60/a4257538ce2f6b978aeb51870d6c4208c510928a03db7e0339bb625dccb7/coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c", size = 251125, upload-time = "2026-05-10T18:00:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ab/f91af47642ec1aa53490e835a95847168d9c77fc39aa58527604c051e145/coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3", size = 222300, upload-time = "2026-05-10T18:00:08.608Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a71ddbd874431e7a7cd96071f0c331cfbbad07704833c765d24ffbab8a67/coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1", size = 223241, upload-time = "2026-05-10T18:00:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/d8/6e/d9d312a5151a96cd110efee32efc3fc97b01ebd86203fe618ccb29cf4c92/coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627", size = 221908, upload-time = "2026-05-10T18:00:12.242Z" }, + { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, + { url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z" }, + { url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z" }, + { url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z" }, + { url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z" }, + { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, + { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5f/b5370068b2f57787454592ed7dcd1002f0f1703b7db1fa30f6a325a4ca6e/coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca", size = 253961, upload-time = "2026-05-10T18:00:44.079Z" }, + { url = "https://files.pythonhosted.org/packages/29/1e/51adf17738976e8f2b85ddef7b7aa12a0838b056c92f175941d8862767c1/coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7", size = 255193, upload-time = "2026-05-10T18:00:45.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7b/5bfd7ac1df3b881c2ac7a5cbc99c7609e6296c402f5ef587cd81c6f355b3/coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2", size = 257326, upload-time = "2026-05-10T18:00:47.173Z" }, + { url = "https://files.pythonhosted.org/packages/7d/38/1d37d316b174fad3843a1d76dbdfe4398771c9ecd0515935dd9ece9cd627/coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367", size = 251582, upload-time = "2026-05-10T18:00:49.152Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/746704f95980ba220214e1a41e18cec5aea80a898eaa53c51bf2d645ff36/coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9", size = 253325, upload-time = "2026-05-10T18:00:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/e1/b9/bbe87206d9687b192352f893797825b5f5b15ecd3aa9c68fbff0c074d77b/coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087", size = 251291, upload-time = "2026-05-10T18:00:52.816Z" }, + { url = "https://files.pythonhosted.org/packages/46/57/b8cdb12ac0d73ef0243218bd5e22c9df8f92edab8018213a86aec67c5324/coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef", size = 255448, upload-time = "2026-05-10T18:00:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d4/5002019538b2036ce3c84340f54d2fd5100d55b0a6b0894eee56128d03c7/coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52", size = 251110, upload-time = "2026-05-10T18:00:56.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/20c5009477660f084e6ed60bc02a91894b8e234e617e86ecfd9aaf78e27b/coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe", size = 252885, upload-time = "2026-05-10T18:00:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ab/3cf6427ac9c1f1db747dbb1ce71dde47984876d4c2cfd018a3fef0a78d4d/coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae", size = 222539, upload-time = "2026-05-10T18:00:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b8/9228523e80321c2cb4880d1f589bc0171f2f71432c35118ad04dc01decce/coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e", size = 223344, upload-time = "2026-05-10T18:01:01.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/99/118daa192f95e3a6cb2740100fbf8797cda1734b4134ef0b5d501a7fa8f3/coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96", size = 221966, upload-time = "2026-05-10T18:01:03.16Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f1/a46cc0c013be170216253184a32366d7cbdb9252feaec866b05c2d12a894/coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90", size = 220679, upload-time = "2026-05-10T18:01:05.058Z" }, + { url = "https://files.pythonhosted.org/packages/64/8c/9c30a3d311a34177fa432995be7fbfc64477d8bac5630bd38055b1c9b424/coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1", size = 221033, upload-time = "2026-05-10T18:01:07.002Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/3fb5e06c3badefd0c1b47e2044fdca67f8220a4ec2e7fcfb476aa0a67c6c/coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd", size = 262333, upload-time = "2026-05-10T18:01:08.903Z" }, + { url = "https://files.pythonhosted.org/packages/a8/e6/fbc322325c7294d3e22c1ad6b79e45d0806b25228c8e5842aed6d8169aa7/coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc", size = 264410, upload-time = "2026-05-10T18:01:10.531Z" }, + { url = "https://files.pythonhosted.org/packages/08/92/c497b264bec1673c47cc77e26f760fcda4654cabf1f39546d1a23a3b8c35/coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426", size = 266836, upload-time = "2026-05-10T18:01:12.19Z" }, + { url = "https://files.pythonhosted.org/packages/78/fc/045da320987f401af5d2815d351e8aa799aec859f60e29f445e3089eeedb/coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899", size = 267974, upload-time = "2026-05-10T18:01:13.926Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ae/227b1e379497fb7a4fc3286e620f80c8a1e7cec66d45695a01639eb1af65/coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b", size = 261578, upload-time = "2026-05-10T18:01:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f5/3570342900f2acea31d33ff1590c5d8bac1a8e1a2e1c6d34a5d5e61de681/coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90", size = 264394, upload-time = "2026-05-10T18:01:17.607Z" }, + { url = "https://files.pythonhosted.org/packages/16/29/de1bbc01c935b28f89b1dc3db85b011c055e843a8e5e3b83141c3f80af7f/coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f", size = 262022, upload-time = "2026-05-10T18:01:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/35/95/f53890b0bf2fc10ab168e05d38869215e73ca24c4cb521c3bb0eb62fe16b/coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d", size = 265732, upload-time = "2026-05-10T18:01:21.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ea/c919e259081dd2bdf0e43b87209709ba7ec2e4117c2a7f5185379c43463c/coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47", size = 260921, upload-time = "2026-05-10T18:01:23.533Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2c/c2831889705a81dc5d1c6ca12e4d8e9b95dfc146d153488a6c0ea685d28e/coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477", size = 263109, upload-time = "2026-05-10T18:01:25.165Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a9/2fcae5003cac3d63fe344d2166243c2756935f48420863c5272b240d550b/coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab", size = 223212, upload-time = "2026-05-10T18:01:27.157Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bb/18e94d7b14b9b398164197114a587a04ab7c9fdbe1d237eef57311c5e883/coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917", size = 224272, upload-time = "2026-05-10T18:01:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/db/56/4f14fad782b035c81c4ffd09159e7103d42bb1d93ac8496d04b90a11b7da/coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8", size = 222530, upload-time = "2026-05-10T18:01:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/1c/18/b9a6586d73992807c26f9a5f274131be3d76b56b18a82b9392e2a25d2e45/coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d", size = 220036, upload-time = "2026-05-10T18:01:33.057Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9b/4165a1d56ddc302a0e2d518fd9d412a4fd0b57562618c78c5f21c57194f5/coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63", size = 220368, upload-time = "2026-05-10T18:01:34.705Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/c12e52a5ba148d9995229d557e3be6e554fe469addc0e9241b2f0956d8ea/coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212", size = 251417, upload-time = "2026-05-10T18:01:36.949Z" }, + { url = "https://files.pythonhosted.org/packages/d7/51/ec641c26e6dca1b25a7d2035ba6ecb7c884ef1a100a9e42fbe4ce4405139/coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3", size = 253924, upload-time = "2026-05-10T18:01:38.985Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/59c3de0bd1b538824173fd518fed51c1ce740ca5ed68e74545983f4053a9/coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97", size = 255269, upload-time = "2026-05-10T18:01:40.957Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a9/36dfa153a62040296f6e7febfdb20a5720622f6ef5a81a41e8237b9a5344/coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8", size = 257583, upload-time = "2026-05-10T18:01:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/26/7b/cc2c048d4114d9ab1c2409e9ee365e5ae10736df6dffcfc9444effa6c708/coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb", size = 251434, upload-time = "2026-05-10T18:01:44.537Z" }, + { url = "https://files.pythonhosted.org/packages/ee/df/6770eaa576e604575e9a78055313250faef5faa84bd6f71a39fece519c43/coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe", size = 253280, upload-time = "2026-05-10T18:01:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/ad/9e/1c0264514a3f98259a6d64765a397b2c8373e3ba59ee722a4802d3ec0c61/coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa", size = 251241, upload-time = "2026-05-10T18:01:48.732Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/4efdf3e3c4079cdbf0ece56a2fea872df9e8a3e15a13a0af4400e1075944/coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5", size = 255516, upload-time = "2026-05-10T18:01:50.819Z" }, + { url = "https://files.pythonhosted.org/packages/93/69/b1de96346603881b3d1bc8d6447c83200e1c9700ffbaff926ba01ff5724c/coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c", size = 251059, upload-time = "2026-05-10T18:01:52.773Z" }, + { url = "https://files.pythonhosted.org/packages/a4/66/2881853e0363a5e0a724d1103e53650795367471b6afb234f8b49e713bc6/coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca", size = 252716, upload-time = "2026-05-10T18:01:54.506Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/0d3305d002c41dcde873dbe456491e663dc55152ca526b630b5c47efd62f/coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828", size = 222788, upload-time = "2026-05-10T18:01:56.487Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/6e1b8f52fdc3184b47dc5037f5070d83a3d11042db1594b02d2a44d786c8/coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d", size = 223600, upload-time = "2026-05-10T18:01:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/a18c408e674bc26281cadaedc7351f929bd2094e191e4b15271c30b084cc/coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9", size = 222168, upload-time = "2026-05-10T18:02:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/3d/89/2681f071d238b62aff8dfc2ab44fc24cfdb38d1c01f391a80522ff5d3a16/coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1", size = 220766, upload-time = "2026-05-10T18:02:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c7/c987babafd9207ffa1995e1ef1f9b26762cf4963aa768a66b6f0501e4616/coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c", size = 221035, upload-time = "2026-05-10T18:02:04.017Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e9/d6a5ac3b333088143d6fc877d398a9a674dc03124a2f776e131f03864823/coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84", size = 262405, upload-time = "2026-05-10T18:02:05.915Z" }, + { url = "https://files.pythonhosted.org/packages/38/b1/e70838d29a7c08e22d44398a46db90815bbcbf28de06992bd9210d1a8d8e/coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436", size = 264530, upload-time = "2026-05-10T18:02:07.582Z" }, + { url = "https://files.pythonhosted.org/packages/6b/73/5c31ef97763288d03d9995152b96d5475b527c63d91c84b01caea894b83a/coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a", size = 266932, upload-time = "2026-05-10T18:02:09.401Z" }, + { url = "https://files.pythonhosted.org/packages/e1/76/dd56d80f29c5f05b4d76f7e7c6d47cafacae017189c75c5759d24f9ff0cc/coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f", size = 268062, upload-time = "2026-05-10T18:02:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c7/27ba85cd5b95614f159ff93ebff1901584a8d192e2e5e24c4943a7453f59/coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb", size = 261504, upload-time = "2026-05-10T18:02:13.257Z" }, + { url = "https://files.pythonhosted.org/packages/13/2e/e8149f60ab5d5684c6eee881bdf34b127115cddbb958b196768dd9d63473/coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490", size = 264398, upload-time = "2026-05-10T18:02:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7f/1261b025285323225f4b4abffa5a643649dfd67e25ddca7ebcbdea3b7cb3/coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9", size = 262000, upload-time = "2026-05-10T18:02:16.756Z" }, + { url = "https://files.pythonhosted.org/packages/d3/dc/829c54f60b9d08389439c00f813c752781c496fc5788c78d8006db4b4f2b/coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020", size = 265732, upload-time = "2026-05-10T18:02:18.817Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b0/70bd1419941652fa062689cba9c3eeafb8f5e6fbb890bce41c3bdda5dbd6/coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6", size = 260847, upload-time = "2026-05-10T18:02:20.528Z" }, + { url = "https://files.pythonhosted.org/packages/f2/73/be40b2390656c654d35ea0015ea7ba3d945769cf80790ad5e0bb2d56d2ba/coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db", size = 263166, upload-time = "2026-05-10T18:02:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/29/55/4a643f712fcf7cf2881f8ec1e0ccb7b164aff3108f69b51801246c8799f2/coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2", size = 223573, upload-time = "2026-05-10T18:02:24.11Z" }, + { url = "https://files.pythonhosted.org/packages/27/96/3acae5da0953be042c0b4dea6d6789d2f080701c77b88e44d5bd41b9219b/coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644", size = 224680, upload-time = "2026-05-10T18:02:25.896Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/6ab5d2dd8325d838737c6f8d83d62eb6230e0d70b87b51b57bbfd08fa767/coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b", size = 222703, upload-time = "2026-05-10T18:02:27.822Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/50/8e/b8041bc719f056afd864478029d52214789341ac6583437b0ee5031e9530/numpy-2.4.5.tar.gz", hash = "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f", size = 20735669, upload-time = "2026-05-15T20:25:19.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/44/1383ee4d1e916a9e610e46c876b5c83ea023526117d23cd911983929ec34/numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288", size = 16969261, upload-time = "2026-05-15T20:22:23.036Z" }, + { url = "https://files.pythonhosted.org/packages/3d/61/54bacfbec7550bc398e6b6d9a861db35d64f75844e1d7920f5722c3cd5e7/numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7", size = 14964009, upload-time = "2026-05-15T20:22:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/7a/55/fe86c64561761f185339c26001164a2687bd4787af681e961431abd2d534/numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6", size = 5469106, upload-time = "2026-05-15T20:22:28.13Z" }, + { url = "https://files.pythonhosted.org/packages/2f/74/cf29b8317627f0e3aa2c9fb332d386bd734308cecd9e07da9f407d9ce0c3/numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8", size = 6798945, upload-time = "2026-05-15T20:22:30.061Z" }, + { url = "https://files.pythonhosted.org/packages/80/a9/b61730a17fa87d5abb13ce560a1b4ce3485d37a13e03eb7b414e598e72f8/numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772", size = 15967025, upload-time = "2026-05-15T20:22:32.328Z" }, + { url = "https://files.pythonhosted.org/packages/03/39/70bcd187eb4d223c21fde02c2bdfbffbffef3288cbb3947c04c74ae39a08/numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd", size = 16918685, upload-time = "2026-05-15T20:22:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/400fd1315bbe228af3937cf8a74e32023df6217af36077919d00adc382e4/numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059", size = 17322963, upload-time = "2026-05-15T20:22:37.557Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/bbbafb657e6f6ee826b4ecdb8722a2e0aae4a981888eaf59eae6a535cc13/numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55", size = 18651594, upload-time = "2026-05-15T20:22:40.449Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/857a515154a2a18b0dfae04089600d166d352d473ec17a0680d879582d06/numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf", size = 6233849, upload-time = "2026-05-15T20:22:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/d215f3fb93541617adb5d58b3b9508e8a6413e499711e0adc0b80bcb445d/numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864", size = 12608238, upload-time = "2026-05-15T20:22:45.229Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c4/611d66d3fcfa931954d37a19ce5575f3283d023e89ff0df6ad43b334ae9c/numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b", size = 10479452, upload-time = "2026-05-15T20:22:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/3275231e98620002681c922e792db04d72c356e9d8073c387344fc0e4ff1/numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef", size = 16689166, upload-time = "2026-05-15T20:22:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/000aab6a16bdec53307f0f72546b57a3ac9266a62d8c257bee97d85fd078/numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862", size = 14699514, upload-time = "2026-05-15T20:22:53.678Z" }, + { url = "https://files.pythonhosted.org/packages/47/cc/ddaf3af9c46966fef5be879256f213d85a0c56c75d07a3b7defec7cf6b4c/numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507", size = 5204601, upload-time = "2026-05-15T20:22:56.257Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/627fadd11959b3c7759008f34c92a35af8ff942dd8284a66ced648bbe516/numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa", size = 6551360, upload-time = "2026-05-15T20:22:58.334Z" }, + { url = "https://files.pythonhosted.org/packages/a1/47/0728b986b8682d742ff68c16baa5af9d185484abfc635c5cc700f44e62be/numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72", size = 15671157, upload-time = "2026-05-15T20:23:01.081Z" }, + { url = "https://files.pythonhosted.org/packages/d1/0b/b905ae82d9419dc38123523862db64978ca2954b69609c3ae8fdaca1084c/numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912", size = 16645703, upload-time = "2026-05-15T20:23:04.358Z" }, + { url = "https://files.pythonhosted.org/packages/5f/24/e27fc3f5236b4118ed9eed67111675f5c61a07ea333acec87c869c3b359d/numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef", size = 17021018, upload-time = "2026-05-15T20:23:07.021Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a7/9041af38d527ab80a06a93570a77e29425b41507ad41f6acf5da78cfb4a4/numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7", size = 18368768, upload-time = "2026-05-15T20:23:09.44Z" }, + { url = "https://files.pythonhosted.org/packages/49/82/326a014442f32c2663434fd424d9298791f47f8a0f17585ad60519a5606e/numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5", size = 5962819, upload-time = "2026-05-15T20:23:11.631Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/cbf5d391b0b3a5e8cad264603e2fae256b0bde8ce43566b13b78faedc659/numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6", size = 12321621, upload-time = "2026-05-15T20:23:14.305Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d0/0f18909d9bc37a5f3f969fc737d2bb5df9f2ff295f71b467e6f52a0d6c4e/numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453", size = 10221430, upload-time = "2026-05-15T20:23:16.887Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a4/fb50657c7cab297bf34edcd60a074cb0647f61771430d6363575274160fe/numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277", size = 16684760, upload-time = "2026-05-15T20:23:19.436Z" }, + { url = "https://files.pythonhosted.org/packages/3e/43/87e731299b9408eda705b3b9cb31c7bceb9347d2af9cbb16b2b1e4b5bc0f/numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0", size = 14694117, upload-time = "2026-05-15T20:23:21.832Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/0b2bb8acea222e9dd6e582afc2bc553b89b8833cbdccc68e68f050fb31f8/numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419", size = 5199141, upload-time = "2026-05-15T20:23:24.066Z" }, + { url = "https://files.pythonhosted.org/packages/39/60/b6972b5d47033d90000f0097c81a98b9486589a2d7003bf725bff275cb0d/numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685", size = 6546954, upload-time = "2026-05-15T20:23:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e9/ed667cb12c11ca0adde431f685d3a5dd78e6f78b27228c581c8415198e9e/numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c", size = 15669430, upload-time = "2026-05-15T20:23:28.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/e5/679f6ffeb01294b0008e5ada4a113cb47617bc0e1819a529fd7973c6d7f4/numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83", size = 16633390, upload-time = "2026-05-15T20:23:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/36/46/42bfffc9a780ec902ccd7470d3219192ee82b7b442710307dd85b4d121b0/numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566", size = 17020709, upload-time = "2026-05-15T20:23:34.08Z" }, + { url = "https://files.pythonhosted.org/packages/44/00/3e840bfee0cc6cec22209f2c97057f26eeb30de031e4933b4dfc0395416c/numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097", size = 18357818, upload-time = "2026-05-15T20:23:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/72/cb/3447b400b9da84134575486f0f656541559b00d4b262477bce9b678bbca8/numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33", size = 5961114, upload-time = "2026-05-15T20:23:39.586Z" }, + { url = "https://files.pythonhosted.org/packages/28/f9/a90d2220ffcdc0798f5d55bb5d5463cd6254ec9ef43f384dae80217d7a2f/numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42", size = 12318553, upload-time = "2026-05-15T20:23:41.436Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c9/96f531fb3234545315152d34efdf3de7daee81254448447eb619e8d16967/numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97", size = 10222200, upload-time = "2026-05-15T20:23:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f4/a291caab5a3c520babf93ff77c54fd5fdb1ebbc3296cee2eb2146ce773b1/numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb", size = 14821438, upload-time = "2026-05-15T20:23:45.911Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/13dbb1159b864370568e7309063fd72667984df89db74e9caeb175d067c7/numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4", size = 5326663, upload-time = "2026-05-15T20:23:48.18Z" }, + { url = "https://files.pythonhosted.org/packages/7c/99/d233408072a0e019e2288e27edd23f7d572ccd4a73d1539baa3270ede85d/numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed", size = 6646874, upload-time = "2026-05-15T20:23:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/c5/00/eeb6f193dfe767725e952e0464f3e51f44145c5dd261cd7389aa36ac0713/numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6", size = 15728147, upload-time = "2026-05-15T20:23:51.655Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c9/b8ed039f1fde1b13a8807c893e7e2f9432a379f4d6401edecf0028da5b2c/numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976", size = 16681770, upload-time = "2026-05-15T20:23:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/11/5b/0198ef6cb7016eca6d895d392106012138127fab23f46637e76d5e25c9f5/numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222", size = 17086218, upload-time = "2026-05-15T20:23:56.646Z" }, + { url = "https://files.pythonhosted.org/packages/f0/fe/8821f3cfc660ae84c92ee158505941874b62c56a42e035a41425228cd8cf/numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656", size = 18403542, upload-time = "2026-05-15T20:23:59.173Z" }, + { url = "https://files.pythonhosted.org/packages/0e/00/e64ecaf498865e7b091f57658b2c522503e5d1b70e43b807f5f8247e1d88/numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb", size = 6084903, upload-time = "2026-05-15T20:24:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/20/c0/354997dedaf74e8311c2cf9a6027b476fd8d424cb92189cc0ae2b25f501c/numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc", size = 12458420, upload-time = "2026-05-15T20:24:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/66/dc/917ee5ea4a31ca1a6e4c9a85386477efa318dcc60db257c5ef4adda096c1/numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db", size = 10291826, upload-time = "2026-05-15T20:24:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c1/3be0bf102fc17cff5bd142e3be0bfffabec6fa46da0a462396c76b0765d0/numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61", size = 16683455, upload-time = "2026-05-15T20:24:08.988Z" }, + { url = "https://files.pythonhosted.org/packages/e8/3e/0742d724901fa36bc54b338c6e62e463a7601180da896aa44978f0adf004/numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77", size = 14704577, upload-time = "2026-05-15T20:24:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/25/1c/196c610ff4c6782d697ba780ebdc1616be143213701bf22c1a270f3bf7dd/numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361", size = 5209756, upload-time = "2026-05-15T20:24:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/52/c0/23fb1bc506f774e03db66219a2830e720f4d3dbcaaddf855a7ff7bb6d96f/numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29", size = 6543937, upload-time = "2026-05-15T20:24:16.223Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/db4662c26e68520afcc84d672a6f9f5294063dee0e57a46d61afdaa7f9ed/numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce", size = 15685292, upload-time = "2026-05-15T20:24:17.978Z" }, + { url = "https://files.pythonhosted.org/packages/43/80/1315439acedd8398319bac177d6de3d48ab39c62cc0c810f74f0a9a73996/numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a", size = 16638528, upload-time = "2026-05-15T20:24:20.478Z" }, + { url = "https://files.pythonhosted.org/packages/56/81/364388600932618fe735d97fdd2437cb8dd87a23377ac11d8b9d5db098b7/numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23", size = 17036709, upload-time = "2026-05-15T20:24:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/32/4a/a1185b18a94a6d9587e54b437e7d0ba36ecf6e614f1bea03f5249912c64e/numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25", size = 18363254, upload-time = "2026-05-15T20:24:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8e/95c1d2ed15ae97750ede8c8a0ac487c9c01207afff430f47078b1d9d7dc5/numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080", size = 6010184, upload-time = "2026-05-15T20:24:27.852Z" }, + { url = "https://files.pythonhosted.org/packages/aa/92/d063df4d63d988b20d881856c74df76c0c1786229bb870f3a52af0981d4d/numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f", size = 12450344, upload-time = "2026-05-15T20:24:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/3d/64/c0ae481f7c3b2f85869bcd8fc5d30aa7c96b394162eef9c9315957f115c5/numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957", size = 10495674, upload-time = "2026-05-15T20:24:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/57/89/c5a4c677acf17aa50ba09a15e61812f90baac42bb6ca38d112e005858351/numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3", size = 14824078, upload-time = "2026-05-15T20:24:34.669Z" }, + { url = "https://files.pythonhosted.org/packages/e7/52/57e7144284f6b51ba93523e495ff239260b1ecd5257e3700a436332e5688/numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be", size = 5329246, upload-time = "2026-05-15T20:24:36.957Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/09dbce80fd4a7db4318f2fc01eec0ae76f29306442b5a32d4b811d082cdf/numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7", size = 6649877, upload-time = "2026-05-15T20:24:38.861Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/dbdb23e82d540b757690ef13f011c386fca6a63848eec6136baf8ce7cbed/numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40", size = 15730534, upload-time = "2026-05-15T20:24:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bd/68f6e9b3c20decf40ac06708a7b506757e3a8588efed32988d1b747316be/numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3", size = 16679741, upload-time = "2026-05-15T20:24:44.874Z" }, + { url = "https://files.pythonhosted.org/packages/39/1d/0fcac0b6b4ea1b50ca8fca05a34bed5c8d56e34c1cb5ffb04cf76109ac3c/numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c", size = 17085598, upload-time = "2026-05-15T20:24:47.603Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e8/a472b2564cf6cc498ad7aa9741d9832648221b8ab8cc0dbef41faa248ede/numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963", size = 18403855, upload-time = "2026-05-15T20:24:50.474Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a4/da82196f8cc4bd28ecf17bd57008c84f3d4696caf06753d9bad45e4ad749/numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5", size = 6156900, upload-time = "2026-05-15T20:24:53.134Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/860959b91a73d9a085006554fa3850da51a7ffab64599bac5097243438ab/numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a", size = 12638906, upload-time = "2026-05-15T20:24:55.009Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2a/bbd3097913083ad07c0f28fc9629666221fc18923e17ce97ae22a5dccdd6/numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b", size = 10565875, upload-time = "2026-05-15T20:24:57.425Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5d/9a644cfb841bc76b584afc3af1708b3bf6c5cb51fc84a7008246cd93b7b7/numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95", size = 16847544, upload-time = "2026-05-15T20:24:59.746Z" }, + { url = "https://files.pythonhosted.org/packages/56/8f/4fe5e3ba76d858dae1fe79078818c0520447335be0082c0dedf82719cc08/numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70", size = 14889039, upload-time = "2026-05-15T20:25:03.179Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6f/79f195abf922ecc43e7d0eb6cc969462a71b524a35bcd1fa26b4a1d7406a/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f", size = 5394106, upload-time = "2026-05-15T20:25:05.659Z" }, + { url = "https://files.pythonhosted.org/packages/58/6f/79cd6247205802bcbd10b40ea087e20ded526e10e9be224d34de832b216e/numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83", size = 6708718, upload-time = "2026-05-15T20:25:08.071Z" }, + { url = "https://files.pythonhosted.org/packages/d7/22/5f378a9d4633c98f28c4709d4144b1a4630c5c09e109d2e781e2d26c8fe1/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38", size = 15798292, upload-time = "2026-05-15T20:25:10.689Z" }, + { url = "https://files.pythonhosted.org/packages/63/1c/cec582febef798c99888892d92dc1d28dfe29cb427c41f44d13d0dec208f/numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198", size = 16747406, upload-time = "2026-05-15T20:25:13.879Z" }, + { url = "https://files.pythonhosted.org/packages/b1/dc/d358a16a6fec86cf736b8fbe67386044b3fa2aded1a80cff90e836799301/numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20", size = 12504085, upload-time = "2026-05-15T20:25:16.667Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.21.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/21/a7d5c126d5b557715ef81098f3db2fe20f622a039ff2e626af28d674ab80/ruff-0.15.13.tar.gz", hash = "sha256:f9d89f17f7ba7fb2ed42921f0df75da797a9a5d71bc39049e2c687cf2baf44b7", size = 4678180, upload-time = "2026-05-14T13:44:37.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/61/11d458dc6ac22504fd8e237b29dfd40504c7fbbcc8930402cfe51a8e63ed/ruff-0.15.13-py3-none-linux_armv6l.whl", hash = "sha256:444b580fc72fd6887e650acd3e575e18cdc79dbcf42fb4030b491057921f61f8", size = 10738279, upload-time = "2026-05-14T13:44:18.7Z" }, + { url = "https://files.pythonhosted.org/packages/86/ca/caa871ee7be718c45256fada4e16a218ee3e33f0c4a46b729a60a24912e6/ruff-0.15.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6590d009e7cb7ebf36f83dbdd44a3fa48a0994ff6f1cdc1b08006abe58f98dc7", size = 11124798, upload-time = "2026-05-14T13:44:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/d3/19/43f5f2e568dddde567fc41f8471f9432c09563e19d3e617a48cfa52f8f0a/ruff-0.15.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1c26d2f66163deeb6e08d8b39fbbe983ce3c71cea06a6d7591cfd1421793c629", size = 10460761, upload-time = "2026-05-14T13:44:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/99/df/cf938cd6de3003178f03ad7c1ea2a6c099468c03a35037985070b37e76be/ruff-0.15.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbd6f94b434f896308e4d57fb7bfde0d02b99f7a64b3bdab0fdfa6a864203a5", size = 10804451, upload-time = "2026-05-14T13:44:25.221Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7d/5d0973129b154ded2225729169d7068f26b467760b146493fde138415f23/ruff-0.15.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3259f3be4d181bda591da5db2571aed6853c6a048157756448020bc6c5cd22", size = 10534285, upload-time = "2026-05-14T13:44:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e3/6b999bbc66cd51e5f073842bc2a3995e99c5e0e72e16b15e7261f7abf57a/ruff-0.15.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae9c17e5eb4430c154e76abc25d79a318190f5a997f38fb6b114416c5319ffc9", size = 11312063, upload-time = "2026-05-14T13:44:11.274Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/642639e9f5db04f1e97fbd6e091c6fd20725bdf072fb114d00eefb9e6eb8/ruff-0.15.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e2e39bff6c341f4b577a21b801326fab0b11847f48fcaa83f00a113c9b3cb55", size = 12183079, upload-time = "2026-05-14T13:44:01.634Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/7585735f6b53b0f12de13618b2f7d250a844f018822efc899df2e7b8295f/ruff-0.15.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8d9a8e08013542e94d3220bc5b62cc3e5ef87c5f74bff367d3fac14fab013e6", size = 11440833, upload-time = "2026-05-14T13:43:59.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/bf1a0803d077e679cfeee5f2f67290a0fa79c7385b5d9a8c17b9db2c48f0/ruff-0.15.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc411dfebe5eebe55ce041c6ae080eb7668955e866daa2fbb16692a784f1c4ca", size = 11434486, upload-time = "2026-05-14T13:44:27.761Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4e/62c9b999875d4f14db80f277c030578f5e249c9852d65b7ac7ad0b43c041/ruff-0.15.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:768494eb08b9cee54e2fd27969966f74db5a57f6eaa7a90fcb3306af34dfc4bd", size = 11385189, upload-time = "2026-05-14T13:44:13.704Z" }, + { url = "https://files.pythonhosted.org/packages/fc/89/7e959047a104df3eb12863447c110140191fc5b6c4f379ea2e803fcdb0e4/ruff-0.15.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fb75f9a3a7e42ffe117d734494e6c5e5cb3565d66e12612cb63d0e572a41a5b6", size = 10781380, upload-time = "2026-05-14T13:43:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/52/5fd18f3b88cab63e88aa11516b3b4e1e5f720e5c330f8dbe5c26210f41f8/ruff-0.15.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8cb74dd33bb2f6613faf7fc03b660053b5ac4f80e706d5788c6335e2a8048d51", size = 10540605, upload-time = "2026-05-14T13:44:20.748Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e0/9e35f338990d3e41a82875ff7053ffe97541dae81c9d02143177f381d572/ruff-0.15.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7ef823f817fcd191dc934e984be9cf4094f808effa16f2542ad8e821ba02bbf2", size = 11036554, upload-time = "2026-05-14T13:44:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/c2/13/070fb048c24080fba188f66371e2a92785be257ad02242066dc7255ac6e9/ruff-0.15.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f345a13937bd7f09f6f5d19fa0721b0c103e00e7f62bc67089a8e5e037719e0b", size = 11528133, upload-time = "2026-05-14T13:44:22.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8c/b1e1666aef7fc6555094d73ae6cd981701781ae85b97ceefc0eebd0b4668/ruff-0.15.13-py3-none-win32.whl", hash = "sha256:4044f94208b3b05ba0fc4a4abd0558cf4d6459bd18325eead7fd8cc66f909b41", size = 10721455, upload-time = "2026-05-14T13:44:35.697Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a6/870a3e8a50590bb92be184ad928c2922f088b00d9dc5c5ec7b924ee08c22/ruff-0.15.13-py3-none-win_amd64.whl", hash = "sha256:7064884d442b7d477b4e7473d12da7f08851d2b1982763c5d3f388a19468a1a4", size = 11900409, upload-time = "2026-05-14T13:44:30.389Z" }, + { url = "https://files.pythonhosted.org/packages/9b/36/9c015cd052fca743dae8cb2aeb16b551444787467db42ceab0fc968865af/ruff-0.15.13-py3-none-win_arm64.whl", hash = "sha256:2471da9bd1068c8c064b5fd9c0c4b6dddffd6369cb1cd68b29993b1709ff1b21", size = 11179336, upload-time = "2026-05-14T13:44:33.026Z" }, +] + +[[package]] +name = "spinterp" +source = { editable = "." } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "zensical" }, +] +docs = [ + { name = "zensical" }, +] +lint = [ + { name = "ruff" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, +] + +[package.metadata] +requires-dist = [{ name = "numpy" }] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, + { name = "ruff", specifier = "==0.15.*" }, + { name = "zensical", specifier = ">=0.0.42" }, +] +docs = [{ name = "zensical", specifier = ">=0.0.42" }] +lint = [{ name = "ruff", specifier = "==0.15.*" }] +test = [ + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "pytest-xdist", specifier = ">=3.8.0" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "zensical" +version = "0.0.42" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/dd/04e89ab92aed1ef9e36c76ef095fb587ffcbe4162aa7f3fe6d63aafade4a/zensical-0.0.42.tar.gz", hash = "sha256:cc346b833868a59412fe8d8498a152be90be9f3d8fb87e1f1a1c2e1146cbae1b", size = 3931093, upload-time = "2026-05-15T10:22:45.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/19/2ca4e52769307959f7485d4c5da7b24787339787c1cbc371885cef448e50/zensical-0.0.42-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bffd7a34b570fa3ccadf1d23babb0f7c4851c6b626e4fc8ed9f21c2eaae85968", size = 12705326, upload-time = "2026-05-15T10:22:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/2c/82/0832b0d2c0c2800174141d5519a017105d3dace9194e2c29730e7a676adf/zensical-0.0.42-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee1a79789f9462ef44a4b6ebbfc8b5bf4b2447607da8bc5b35bc9c4ce4ea2370", size = 12568663, upload-time = "2026-05-15T10:22:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/ac/87/272b3998322958ca38f09323d2347cb121dfc851477c36962b71319242a5/zensical-0.0.42-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e9a5d508ce8d1b07d8417f0623be476f6b37d445ab4356481a71e613a7979d6", size = 12948460, upload-time = "2026-05-15T10:22:13.792Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1b/e5f153401f162f48cae2d58e96b95fd39ba5bd1728fb5881a60e502f4e1d/zensical-0.0.42-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fbc0951a676e48afe7df3a9b2a30958dcf9c426ed2480972d3c04d6de485ba3", size = 12913460, upload-time = "2026-05-15T10:22:16.791Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5186b4204bdfdf132851b7515a37b9602bfc153fb601db5fb244339bae52/zensical-0.0.42-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0e96e53f39b9e4b929a25d9df70bd7fa8217166a854e2c8f3185983dd01500", size = 13276704, upload-time = "2026-05-15T10:22:19.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/df/b57b5fcc631ac7a4b4c6834d8cf0b88d3fca37c9db42fc6bbf9f097200ed/zensical-0.0.42-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7d586e57436d603e88acd856864f99f0771aef24bf6560b2de238417bd3817c", size = 12987069, upload-time = "2026-05-15T10:22:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3a/b326a44a065d98e89b472645ad33037201e3385340c2e6e35627b18ab3fa/zensical-0.0.42-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3c026f023330d67f986a94b68ffd36dc5066882e697e1125c37308d8d684135c", size = 13124195, upload-time = "2026-05-15T10:22:25.543Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/823740a662e357a8826dc8eeb87e06705e64219b2774430bc555f7c53d57/zensical-0.0.42-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e5908bc09cf5c1c50c9504241e37f89955daf3e89ba1b9d71c17972578b24804", size = 13182981, upload-time = "2026-05-15T10:22:28.89Z" }, + { url = "https://files.pythonhosted.org/packages/80/6d/9fe261267ac36a7d57051d790022408e9043bc925c9ad21971a1e5b6c3e8/zensical-0.0.42-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0bf96b55f0a44e8716bcb334a16380ed56772b555145da775a7d8ac8678cb6f", size = 13332666, upload-time = "2026-05-15T10:22:32.249Z" }, + { url = "https://files.pythonhosted.org/packages/9b/57/9b0e4f131a7ad15cf1aca081748ea7336c084fb8e16be202a6bed32f595c/zensical-0.0.42-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:47cd99583738a8ab03fac4080741275c56e741a06dc8edfb541f4c1649a5ae69", size = 13270817, upload-time = "2026-05-15T10:22:35.388Z" }, + { url = "https://files.pythonhosted.org/packages/bb/fd/bdb85cc444e4146e8970a22e48a903bfed5bf83276ad7d755caa415dda64/zensical-0.0.42-cp310-abi3-win32.whl", hash = "sha256:83090e53fba061967ecb3dff81500b1900f288bae108bf54084a2aeb6648ebd0", size = 12256227, upload-time = "2026-05-15T10:22:38.869Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b9/09d1f735c8e6d3eb61d176ed5ebcf658b65b126d7d4bbc03a7d366a1e17d/zensical-0.0.42-cp310-abi3-win_amd64.whl", hash = "sha256:2e4304e103f9cd5c637045bbae1ff29de3009ab01b16e99c2fd6d4bbceb7a3ee", size = 12486598, upload-time = "2026-05-15T10:22:42.158Z" }, +]