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.
- sparse_grid/__init__.py +11 -0
- sparse_grid/grid.py +208 -0
- sparse_grid/point.py +65 -0
- sparse_grid/utils.py +48 -0
- sparse_grid-0.1.0.dist-info/METADATA +38 -0
- sparse_grid-0.1.0.dist-info/RECORD +8 -0
- sparse_grid-0.1.0.dist-info/WHEEL +4 -0
- sparse_grid-0.1.0.dist-info/licenses/LICENSE +28 -0
sparse_grid/__init__.py
ADDED
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,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.
|