Skip to content

Commit a49aa51

Browse files
ftschindlersdrave
authored andcommitted
add first draft of BestApproximationReductor
1 parent 3069084 commit a49aa51

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import numpy as np
2+
3+
from pymor.core.defaults import defaults
4+
from pymor.models.black_box import BlackBoxModel, NumpyBlackBoxModel
5+
from pymor.models.interface import Model
6+
from pymor.reductors.basic import ProjectionBasedReductor
7+
from pymor.vectorarrays.block import BlockVectorSpace
8+
from pymor.vectorarrays.numpy import NumpyVectorSpace
9+
10+
11+
class BestApproximationReductor(ProjectionBasedReductor):
12+
"""Generic reductor using best-approximation onto a reduced basis.
13+
14+
_Note_ that the resulting model does does not bring any computational benefits.
15+
16+
We achieve restriction to sub-basis by using the projected operators as a dimension tag.
17+
"""
18+
19+
@defaults('check_orthonormality', 'check_tol')
20+
def __init__(
21+
self,
22+
fom: Model,
23+
basis=None, # dict or vectorarray
24+
check_orthonormality=True,
25+
check_tol=1e-3,
26+
):
27+
if isinstance(basis, (list, tuple)):
28+
assert isinstance(fom.solution_space, BlockVectorSpace)
29+
assert len(basis) == len(fom.solution_space.subspaces)
30+
assert all(b in s for b, s in zip(basis, fom.solution_space.subspaces))
31+
basis = {f'RB_{i}': b for i, b in enumerate(basis)}
32+
else:
33+
basis = basis or fom.solution_space.empty()
34+
assert basis in fom.solution_space
35+
basis = {'RB': basis}
36+
super().__init__(
37+
fom,
38+
basis,
39+
check_orthonormality=check_orthonormality,
40+
check_tol=check_tol,
41+
)
42+
self.__auto_init(locals())
43+
44+
def project_operators(self):
45+
return {key: len(basis) for key, basis in self.basis.items()}
46+
47+
def project_operators_to_subbasis(self, dims):
48+
return dims
49+
50+
def reconstruct(self, u):
51+
if len(self.bases) == 1:
52+
super().reconstruct(u)
53+
else:
54+
assert isinstance(self.fom.solution_space, BlockVectorSpace)
55+
assert isinstance(u.space, BlockVectorSpace)
56+
Us_blocks = [None for i in range(len(self.basis))]
57+
for i in range(len(self.basis)):
58+
basis = self.basis[f'RB_{i}']
59+
u_block = u.blocks[i]
60+
dim = u_block.dim
61+
assert dim <= len(basis)
62+
Us_blocks[i] = basis[:dim].lincomb(u_block.to_numpy())
63+
return self.fom.solution_space.make_array(Us_blocks)
64+
65+
def build_rom(self, projected_operators, error_estimator):
66+
if len(self.bases) == 1:
67+
# TODO: this should conceptually work for a BlockVectorSpace as well,
68+
# but I did not test project_onto_basis and subsequent solve
69+
assert not isinstance(self.fom.solution_space, BlockVectorSpace)
70+
dim = projected_operators['RB']
71+
assert dim <= len(self.basis['RB'])
72+
73+
def project_onto_basis(U):
74+
# See https://docs.pymor.org/2024-1-2/tutorial_basis_generation.html#a-trivial-reduced-basis
75+
G = self.basis[:dim].gramian()
76+
R = self.basis[:dim].inner(U)
77+
return np.linalg.solve(G, R)
78+
79+
rom = NumpyBlackBoxModel(
80+
dim,
81+
self.fom.parameters,
82+
lambda mu: project_onto_basis(self.fom.solve(mu)),
83+
)
84+
rom.disable_logging()
85+
return rom
86+
87+
else:
88+
assert isinstance(self.fom.solution_space, BlockVectorSpace)
89+
dims = projected_operators
90+
assert isinstance(dims, dict)
91+
assert all(key in self.basis for key in dims)
92+
max_dim = max(dims.values())
93+
assert all(dims[key] <= max_dim for key in self.basis)
94+
dims = [min(dims[f'RB_{i}'], max_dim) for i in range(len(self.basis))]
95+
# dims = {key: min(dim, max_dim) for key, dim in self.dims.items()}
96+
97+
blocked_RB_space = BlockVectorSpace(NumpyVectorSpace(d) for d in dims)
98+
99+
def project_onto_basis(blocked_U):
100+
# TODO: replace zeros by something uninitialised?
101+
projected_U = np.zeros((np.sum(dims), len(blocked_U)))
102+
for i in range(len(self.basis)):
103+
U = blocked_U.blocks[i]
104+
basis = self.basis[f'RB_{i}']
105+
dim = dims[i]
106+
# See https://docs.pymor.org/2024-1-2/tutorial_basis_generation.html#a-trivial-reduced-basis
107+
G = basis[:dim].gramian()
108+
R = basis[:dim].inner(U)
109+
u = np.linalg.solve(G, R)
110+
assert u.shape == (dim, len(blocked_U))
111+
projected_U[int(np.sum(dims[:i])):int(np.sum(dims[:i]) + dim), :] = u[:, :]
112+
return blocked_RB_space.from_numpy(projected_U)
113+
114+
rom = BlackBoxModel(
115+
blocked_RB_space,
116+
self.fom.parameters,
117+
lambda mu: project_onto_basis(self.fom.solve(mu)),
118+
)
119+
rom.disable_logging()
120+
return rom

0 commit comments

Comments
 (0)