sparse-grid 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from .grid import SparseGrid
4
+
5
+
6
+ try:
7
+ __version__ = version(__name__)
8
+ except PackageNotFoundError:
9
+ __version__ = "unknown"
10
+
11
+ __all__ = ["SparseGrid"]
sparse_grid/grid.py ADDED
@@ -0,0 +1,208 @@
1
+ """Sparse-grid container and algorithms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import math
7
+
8
+ from .point import GridPoint
9
+ from .utils import cross, eval_basis_1d
10
+
11
+
12
+ EVEN_DIVISOR = 2
13
+
14
+
15
+ class SparseGrid:
16
+ """Regular sparse grid over a box domain.
17
+
18
+ A grid is defined by:
19
+ - `dim`: number of dimensions,
20
+ - `level`: sparse-grid level,
21
+ - `indices`: generated hierarchical indices,
22
+ - `g_p`: map from index tuple to `GridPoint`.
23
+ """
24
+
25
+ def __init__(self, dim: int = 1, level: int = 1) -> None:
26
+ self.dim = dim
27
+ self.level = level
28
+ self.g_p: dict[tuple[int, ...], GridPoint] = {}
29
+ self.indices: list[list[int]] = []
30
+ self.domain = ((0.0, 1.0),) * dim
31
+ self.action = self.eval_action
32
+
33
+ def print_grid(self) -> None:
34
+ """Print the currently active hierarchical subspace index."""
35
+ print(self.h_space)
36
+
37
+ def eval_action(self) -> None:
38
+ """Accumulate contribution of the current hierarchical subspace."""
39
+ basis = copy.deepcopy(self.eval_per_dim[0][self.h_space[0] - 1][0])
40
+ value = self.eval_per_dim[0][self.h_space[0] - 1][1]
41
+ for i in range(1, self.dim):
42
+ value *= self.eval_per_dim[i][self.h_space[i] - 1][1]
43
+ basis += self.eval_per_dim[i][self.h_space[i] - 1][0]
44
+ self.value += self.g_p[tuple(basis)].hv * value
45
+
46
+ def eval_funct(self, x: list[float]) -> float:
47
+ """Evaluate a sparse grid function.
48
+
49
+ Hierarchical values have to be set.
50
+
51
+ Parameters
52
+ ----------
53
+ x
54
+ Evaluation point in physical coordinates.
55
+
56
+ Returns
57
+ -------
58
+ float
59
+ Evaluated sparse-grid function value at `x`.
60
+ """
61
+ self.value = 0.0
62
+ self.eval_per_dim: list[list[list[list[int]] | float]] = []
63
+ for i in range(self.dim):
64
+ self.eval_per_dim.append([])
65
+ for j in range(1, self.level + 1):
66
+ pos = (x[i] - self.domain[i][0]) / (
67
+ self.domain[i][1] - self.domain[i][0]
68
+ )
69
+ basis = int(math.ceil(pos * 2 ** (j - 1)) * 2 - 1)
70
+ if basis == -1:
71
+ basis = 1
72
+ self.eval_per_dim[i].append([[j, basis]])
73
+ else:
74
+ self.eval_per_dim[i].append([[j, basis]])
75
+ self.eval_per_dim[i][j - 1].append(
76
+ eval_basis_1d(
77
+ x[i], self.eval_per_dim[i][j - 1][0], self.domain[i]
78
+ )
79
+ )
80
+ self.action = self.eval_action
81
+ self.loop_hier_spaces()
82
+ return self.value
83
+
84
+ def loop_hier_spaces(self) -> None:
85
+ """Iterate over all hierarchical subspaces of the sparse grid."""
86
+ for i in range(1, self.level + 1):
87
+ self.h_space = [i]
88
+ self.loop_hier_spaces_rec(self.dim - 1, self.level - (i - 1))
89
+
90
+ def loop_hier_spaces_rec(self, dim: int, level: int) -> None:
91
+ """Recursively traverse hierarchical subspaces.
92
+
93
+ Parameters
94
+ ----------
95
+ dim
96
+ Remaining dimensions to recurse through.
97
+ level
98
+ Remaining level budget for sparse-grid admissibility.
99
+ """
100
+ if dim > 1:
101
+ for i in range(1, level + 1):
102
+ self.h_space.append(i)
103
+ self.loop_hier_spaces_rec(dim - 1, level - (i - 1))
104
+ self.h_space.pop()
105
+ else:
106
+ for i in range(1, level + 1):
107
+ self.h_space.append(i)
108
+ self.action()
109
+ self.h_space.pop()
110
+
111
+ def generate_points(self) -> None:
112
+ """Generate sparse-grid indices and corresponding `GridPoint` values."""
113
+ self.indices = self.generate_points_rec(self.dim, self.level)
114
+ for i in range(len(self.indices)):
115
+ self.g_p[tuple(self.indices[i])] = GridPoint(
116
+ self.indices[i], self.domain
117
+ )
118
+
119
+ def generate_points_rec(
120
+ self, dim: int, level: int, cur_level: int | None = None
121
+ ) -> list[list[int]]:
122
+ """Run over all hierarchical subspaces and add all their indices.
123
+
124
+ Returns
125
+ -------
126
+ list[list[int]]
127
+ All generated sparse-grid multi-indices.
128
+ """
129
+ basis_cur = []
130
+ if cur_level is None:
131
+ cur_level = 1
132
+ for i in range(1, 2 ** (cur_level) + 1, 2):
133
+ basis_cur.append([cur_level, i])
134
+ if dim == 1 and cur_level == level:
135
+ return basis_cur
136
+ if dim == 1:
137
+ basis_cur += self.generate_points_rec(dim, level, cur_level + 1)
138
+ return basis_cur
139
+ if cur_level == level:
140
+ return cross(
141
+ basis_cur,
142
+ self.generate_points_rec(dim - 1, level - cur_level + 1),
143
+ )
144
+ return cross(
145
+ basis_cur, self.generate_points_rec(dim - 1, level - cur_level + 1)
146
+ ) + self.generate_points_rec(dim, level, cur_level + 1)
147
+
148
+ def nodal_2_hier_1d(
149
+ self, node: list[int], i: int, j: int, dim: int
150
+ ) -> None:
151
+ """Apply one-dimensional nodal-to-hierarchical transform.
152
+
153
+ Parameters
154
+ ----------
155
+ node
156
+ Fixed index components from all dimensions except `dim`.
157
+ i
158
+ Level index in transformed dimension.
159
+ j
160
+ Position index in transformed dimension.
161
+ dim
162
+ Dimension currently being transformed.
163
+ """
164
+ left = [i - 1, j // 2]
165
+ right = [i - 1, j // 2 + 1]
166
+ while left[1] % EVEN_DIVISOR == 0 and left[0] > 0:
167
+ left = [left[0] - 1, left[1] // EVEN_DIVISOR]
168
+ while right[1] % EVEN_DIVISOR == 0 and right[0] > 0:
169
+ right = [right[0] - 1, right[1] // EVEN_DIVISOR]
170
+ if len(node) > EVEN_DIVISOR:
171
+ pre_cur_dim = node[0 : 2 * dim]
172
+ post_cur_dim = node[2 * dim : len(node) + 1]
173
+ index = [*pre_cur_dim, i, j, *post_cur_dim]
174
+ left = [*pre_cur_dim, *left, *post_cur_dim]
175
+ right = [*pre_cur_dim, *right, *post_cur_dim]
176
+ elif dim == 0:
177
+ index = [i, j, *node]
178
+ left += node
179
+ right += node
180
+ else:
181
+ index = [*node, i, j]
182
+ left = [*node, *left]
183
+ right = [*node, *right]
184
+
185
+ if left[2 * dim] == 0:
186
+ if right[2 * dim] != 0:
187
+ self.g_p[tuple(index)].hv -= 0.5 * self.g_p[tuple(right)].hv
188
+ elif right[2 * dim] == 0:
189
+ self.g_p[tuple(index)].hv -= 0.5 * self.g_p[tuple(left)].hv
190
+ else:
191
+ self.g_p[tuple(index)].hv -= 0.5 * (
192
+ self.g_p[tuple(left)].hv + self.g_p[tuple(right)].hv
193
+ )
194
+
195
+ def nodal_2_hier(self) -> None:
196
+ """Convert all nodal values in the grid to hierarchical values."""
197
+ for i in range(len(self.indices)):
198
+ self.g_p[tuple(self.indices[i])].hv = self.g_p[
199
+ tuple(self.indices[i])
200
+ ].fv
201
+ for d in range(self.dim):
202
+ for i in range(self.level, 0, -1):
203
+ indices = self.generate_points_rec(
204
+ self.dim - 1, self.level - i + 1
205
+ )
206
+ for j in range(1, 2**i + 1, 2):
207
+ for k in range(len(indices)):
208
+ self.nodal_2_hier_1d(indices[k], i, j, d)
sparse_grid/point.py ADDED
@@ -0,0 +1,65 @@
1
+ """Point model used by sparse-grid containers."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class GridPoint:
7
+ """Representation of a sparse-grid point.
8
+
9
+ Stores the Cartesian position and both nodal (`fv`) and hierarchical
10
+ (`hv`) function values.
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ index: list[int] | None = None,
16
+ domain: tuple[tuple[float, float], ...] | None = None,
17
+ ) -> None:
18
+ self.hv: float = 0.0
19
+ self.fv: float = 0.0
20
+ if index is None:
21
+ self.pos: list[float] = []
22
+ else:
23
+ self.pos = self.point_position(index, domain)
24
+
25
+ @staticmethod
26
+ def point_position(
27
+ index: list[int], domain: tuple[tuple[float, float], ...] | None = None
28
+ ) -> list[float]:
29
+ """Convert a level/position index into physical coordinates.
30
+
31
+ Parameters
32
+ ----------
33
+ index
34
+ Interleaved multi-index `[l_1, p_1, ..., l_d, p_d]`.
35
+ domain
36
+ Optional domain bounds per dimension. If omitted, `[0, 1]^d`
37
+ is assumed.
38
+
39
+ Returns
40
+ -------
41
+ list[float]
42
+ Point coordinates in physical space.
43
+ """
44
+ coord = []
45
+ if domain is None:
46
+ for i in range(len(index) // 2):
47
+ coord.append(index[2 * i + 1] / 2.0 ** index[2 * i])
48
+ else:
49
+ for i in range(len(index) // 2):
50
+ coord.append(
51
+ (domain[i][1] - domain[i][0])
52
+ * index[2 * i + 1]
53
+ / 2.0 ** index[2 * i]
54
+ + domain[i][0]
55
+ )
56
+ return coord
57
+
58
+ def print_point(self) -> None:
59
+ """Print point coordinates as a tab-separated line."""
60
+ if not self.pos:
61
+ return
62
+ out = ""
63
+ for i in range(len(self.pos)):
64
+ out += str(self.pos[i]) + "\t"
65
+ print(out)
sparse_grid/utils.py ADDED
@@ -0,0 +1,48 @@
1
+ """Utility functions for sparse-grid basis and index operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def cross(*args: list[list[int]]) -> list[list[int]]:
7
+ """Compute pairwise cross-product (concatenation) of two index lists.
8
+
9
+ Parameters
10
+ ----------
11
+ *args
12
+ Two index collections to combine.
13
+
14
+ Returns
15
+ -------
16
+ list[list[int]]
17
+ Cross-product of index lists.
18
+ """
19
+ ans: list[list[int]] = []
20
+ for arg in args[0]:
21
+ for arg2 in args[1]:
22
+ ans.append([*arg, *arg2])
23
+ return ans
24
+
25
+
26
+ def eval_basis_1d(
27
+ x: float, basis: list[int], interval: tuple[float, float] | None = None
28
+ ) -> float:
29
+ """Evaluate a one-dimensional hat basis function.
30
+
31
+ Parameters
32
+ ----------
33
+ x
34
+ Evaluation coordinate.
35
+ basis
36
+ Basis index as `[level, position]`.
37
+ interval
38
+ Optional physical interval. If omitted, `[0, 1]` is used.
39
+
40
+ Returns
41
+ -------
42
+ float
43
+ Value of 1-D hat basis at `x`.
44
+ """
45
+ if interval is None:
46
+ return 1.0 - abs(x * 2 ** basis[0] - basis[1])
47
+ pos = (x - interval[0]) / (interval[1] - interval[0])
48
+ return 1.0 - abs(pos * 2 ** basis[0] - basis[1])
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: sparse_grid
3
+ Version: 0.1.0
4
+ Summary: A Python Sparse Grid Package
5
+ Project-URL: homepage, https://eggzec.github.io/sparse_grid/
6
+ Project-URL: documentation, https://eggzec.github.io/sparse_grid/
7
+ Project-URL: source, https://github.com/eggzec/sparse_grid
8
+ Project-URL: releasenotes, https://github.com/eggzec/sparse_grid/releases/latest
9
+ Project-URL: issues, https://github.com/eggzec/sparse_grid/issues
10
+ Author-email: John Burkardt <jvb25@pitt.edu>
11
+ Maintainer-email: Saud Zahir <m.saud.zahir@gmail.com>, M Laraib Ali <laraibg786@outlook.com>
12
+ License-Expression: BSD-3-Clause
13
+ License-File: LICENSE
14
+ Keywords: python,sparse grids
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: License :: OSI Approved :: BSD License
19
+ Classifier: Operating System :: MacOS
20
+ Classifier: Operating System :: Microsoft :: Windows
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Operating System :: POSIX
23
+ Classifier: Operating System :: Unix
24
+ Classifier: Programming Language :: Python
25
+ Classifier: Programming Language :: Python :: 3
26
+ Classifier: Programming Language :: Python :: 3 :: Only
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Programming Language :: Python :: 3.13
31
+ Classifier: Programming Language :: Python :: 3.14
32
+ Classifier: Topic :: Scientific/Engineering
33
+ Classifier: Topic :: Software Development
34
+ Requires-Python: >=3.10
35
+ Description-Content-Type: text/markdown
36
+
37
+ # sparse_grid
38
+ A Python Sparse Grid Package
@@ -0,0 +1,8 @@
1
+ sparse_grid/__init__.py,sha256=sUxwtupOjnr1kpS7sq_Eew7p1xGDL8PJnZHFVkUrPVA,217
2
+ sparse_grid/grid.py,sha256=x8WTyrJoUfcFL8Kr3TFIghjteybnoETXbXPCR9G05HQ,7329
3
+ sparse_grid/point.py,sha256=TMosHAVx1YjHEh5hbVdbtfYWWhP5pdaM9hLj0WtnBXY,1880
4
+ sparse_grid/utils.py,sha256=sGB1o0XkDg6VqyAsBqz9vcQfoi457yTcU-6NYpEmc84,1181
5
+ sparse_grid-0.1.0.dist-info/METADATA,sha256=ao51leYce0OYaNf_JEUaRSiZB_N-yPhpt_de7uop1wA,1659
6
+ sparse_grid-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
+ sparse_grid-0.1.0.dist-info/licenses/LICENSE,sha256=c-1kDTg-Q5oCK9YE5DaPC3Mu5pbw-WGcCljrX8Y1b6c,1493
8
+ sparse_grid-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, eggzec
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.