Skip to content

Commit 052bea4

Browse files
authored
Merge pull request #112 from Pressio/improve_trial_space_doc
trial space: improve flow and some doc details, make `utils` a submodule
2 parents 68d6843 + aaf0e9c commit 052bea4

17 files changed

+109
-130
lines changed

romtools/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484
```
8585
'''
8686

87-
__all__ = ['trial_space', 'trial_space_utils', 'workflows', 'hyper_reduction']
87+
__all__ = ['trial_space', 'workflows', 'hyper_reduction']
8888

8989
__docformat__ = "restructuredtext" # required to generate the license
9090

romtools/trial_space.py romtools/trial_space/__init__.py

+94-115
Original file line numberDiff line numberDiff line change
@@ -44,57 +44,67 @@
4444
#
4545

4646
'''
47-
___
48-
##**Notes**
47+
This module defines the API to work with a trial space.
48+
A trial space is foundational to reduced-order models.
49+
In a ROM, a high-dimensional state is restricted to live within a low-dimensional trial space.
50+
Mathematically, given a "FOM" vector $\\mathbf{u} \\in \\mathbb{R}^{N_{\\mathrm{vars}} N_{\\mathrm{x}}}$, we can write
51+
$$\\mathbf{u} \\approx \\tilde{\\mathbf{u}} \\in \\mathcal{V} + \\mathbf{u}_{\\mathrm{shift}}$$
52+
where
53+
- $\\mathcal{V}$ with $\\text{dim}(\\mathcal{V}) = K \\le N_{\\mathrm{vars}} N_{\\mathrm{x}}$ is the trial space
54+
- $N_{\\mathrm{vars}}$ is the number of PDE variables (e.g., 5 for the compressible Navier-Stokes equations in 3D)
55+
- $N_{\\mathrm{x}}$ is the number of spatial DOFs
56+
57+
Formally, we can describe this low-dimensional representation with a basis and an affine offset,
58+
$$\\tilde{\\mathbf{u}} = \\boldsymbol \\Phi \\hat{\\mathbf{u}} + \\mathbf{u}_{\\mathrm{shift}}$$
59+
where $\\boldsymbol \\Phi \\in \\mathbb{R}^{ N_{\\mathrm{vars}} N_{\\mathrm{x}} \\times K}$ is the basis matrix
60+
($K$ is the number of basis), $\\hat{\\mathbf{u}} \\in \\mathbb{R}^{K}$ are the reduced, or generalized coordinates,
61+
$\\mathbf{u}_{\\mathrm{shift}} \\in \\mathbb{R}^{ N_{\\mathrm{vars}} N_{\\mathrm{x}}}$ is the shift vector (or affine offset),
62+
and, by definition, $\\mathcal{V} \\equiv \\mathrm{range}(\\boldsymbol \\Phi)$.
63+
64+
The `TrialSpace` abstract class defined below encapsulates the information of an affine trial space, $\\mathcal{V}$,
65+
by virtue of providing access to a basis matrix, a shift vector, and the dimensionality of the trial space,
66+
while decoupling this representation from *how* it is computed.
67+
68+
####We rely on a tensor representation!
4969
50-
Like the snapshot data, the basis and the affine offset for a trial space are viewed as tensors,
70+
Our representation of the basis and the affine offset for a trial space is based on tensors
5171
$$\\mathcal{\Phi} \\in \mathbb{R}^{ N_{\\mathrm{vars}} \\times N_{\\mathrm{x}} \\times K},$$
5272
$$\\mathcal{u}_{\\mathrm{shift}} \\in \mathbb{R}^{ N_{\\mathrm{vars}} \\times N_{\\mathrm{x}}}.$$
53-
Here, $N_{\\mathrm{vars}}$ is the number of PDE variables (e.g., 5 for the compressible Navier-Stokes
54-
equations in 3D), $N_{\\mathrm{x}}$ is the number of spatial DOFs, and $K$ is the number of basis
55-
vectors. We emphasize that all tensors are reshaped into 2D matrices,
56-
e.g., when performing SVD.
57-
___
58-
##**Theory**
73+
Internally, we remark that all tensors are reshaped into 2D matrices, e.g., when performing SVD.
5974
75+
###**Content**
6076
61-
A trial space is foundational to reduced-order models.
62-
In a ROM, we restrict a high-dimensional state to live within a low-dimensional trial space.
63-
Mathematically, for a "FOM" vector $\\mathbf{u} \\in \\mathbb{R}^{N_{\\mathrm{vars}} N_{\\mathrm{x}}}$, we represent this as
64-
$$\\mathbf{u} \\approx \\tilde{\\mathbf{u}} \\in \\mathcal{V} + \\mathbf{u}_{\\mathrm{shift}}$$
65-
where $\\mathcal{V}$ with
66-
$\\text{dim}(\\mathcal{V}) = K \\le N_{\\mathrm{vars}} N_{\\mathrm{x}}$
67-
is the trial space. Formally, we can describe this low-dimensional representation with a basis and an affine offset,
68-
$$\\tilde{\\mathbf{u}} = \\boldsymbol \\Phi \\hat{\\mathbf{u}} + \\mathbf{u}_{\\mathrm{shift}}$$
69-
where $\\boldsymbol \\Phi \\in \\mathbb{R}^{ N_{\\mathrm{vars}} N_{\\mathrm{x}} \\times K}$ is the basis matrix,
70-
$\\hat{\\mathbf{u}} \\in \\mathbb{R}^{K}$ are the reduced, or generalized coordinates,
71-
$\\mathbf{u}_{\\mathrm{shift}} \\in \\mathbb{R}^{ N_{\\mathrm{vars}} N_{\\mathrm{x}}}$ is the shift vector (or affine offset), and, by definition,
72-
$\\mathcal{V} \\equiv \\mathrm{range}(\\boldsymbol \\Phi)$.
77+
We currently provide the following concrete classes:
78+
79+
- `DictionaryTrialSpace`: reduced basis trial space without truncation.
80+
81+
- `TrialSpaceFromPOD`: POD trial space computed via SVD.
7382
74-
The trial_space class encapsulates the information of an affine trial space, $\\mathcal{V}$,
75-
by virtue of providing access to a basis matrix, a shift vector, and the dimensionality of the trial space.
76-
Note that, like the snapshot data, we view the basis as a tensor.
83+
- `TrialSpaceFromScaledPOD`: POD trial space computed via scaled SVD.
7784
78-
___
85+
which derive from the abstract class `TrialSpace`. Additionally, we provide two helpers free-functions:
86+
87+
- `tensor_to_matrix`: converts a tensor with shape $[N, M, P]$ to a matrix
88+
representation in which the first two dimension are collapsed $[N M, P]$
89+
90+
- `matrix_to_tensor`: inverse operation of `tensor_to_matrix`
91+
92+
---
7993
##**API**
8094
'''
8195

82-
8396
import abc
8497
import numpy as np
85-
from romtools.trial_space_utils.truncater import Truncater, NoOpTruncater
86-
from romtools.trial_space_utils.shifter import Shifter, NoOpShifter
87-
from romtools.trial_space_utils.scaler import Scaler
88-
from romtools.trial_space_utils.splitter import Splitter, NoOpSplitter
89-
from romtools.trial_space_utils.orthogonalizer import Orthogonalizer, NoOpOrthogonalizer
90-
98+
from romtools.trial_space.utils.truncater import *
99+
from romtools.trial_space.utils.shifter import *
100+
from romtools.trial_space.utils.scaler import *
101+
from romtools.trial_space.utils.splitter import *
102+
from romtools.trial_space.utils.orthogonalizer import *
91103

92104
class TrialSpace(abc.ABC):
93105
'''
94106
Abstract base class for trial space implementations.
95107
96-
This abstract class defines the interface for a trial space.
97-
98108
Methods:
99109
'''
100110

@@ -104,7 +114,7 @@ def get_dimension(self):
104114
Retrieves the dimension of the trial space
105115
106116
Returns:
107-
int: The dimension of the trial space.
117+
`int`: The dimension of the trial space.
108118
109119
Concrete subclasses should implement this method to return the
110120
appropriate dimension for their specific trial space implementation.
@@ -117,7 +127,7 @@ def get_shift_vector(self):
117127
Retrieves the shift vector of the trial space.
118128
119129
Returns:
120-
np.ndarray: The shift vector.
130+
`np.ndarray`: The shift vector in tensorm form.
121131
122132
Concrete subclasses should implement this method to return the shift
123133
vector specific to their trial space implementation.
@@ -130,38 +140,17 @@ def get_basis(self):
130140
Retrieves the basis vectors of the trial space.
131141
132142
Returns:
133-
np.ndarray: The basis of the trial space.
143+
`np.ndarray`: The basis of the trial space in tensor form.
134144
135145
Concrete subclasses should implement this method to return the basis
136146
vectors specific to their trial space implementation.
137147
'''
138148
pass
139149

140150

141-
def tensor_to_matrix(tensor_input):
142-
'''
143-
Converts a tensor of snapshots (N_vars x N_space x N_samples) to a matrix
144-
representation in which the first two dimension are collapsed
145-
(N_vars*N_space x N_samples).
146-
'''
147-
output_tensor = tensor_input.reshape(tensor_input.shape[0]*tensor_input.shape[1],
148-
tensor_input.shape[2])
149-
return output_tensor
150-
151-
152-
def matrix_to_tensor(n_var, matrix_input):
153-
'''
154-
Inverse operation of `tensor_to_matrix`
155-
'''
156-
d1 = int(matrix_input.shape[0] / n_var)
157-
d2 = matrix_input.shape[1]
158-
output_matrix = matrix_input.reshape(n_var, d1, d2)
159-
return output_matrix
160-
161-
162151
class DictionaryTrialSpace(TrialSpace):
163152
'''
164-
##Reduced basis trial space (no truncation).
153+
Reduced basis trial space (no truncation).
165154
166155
Given a snapshot matrix $\\mathbf{S}$, we set the basis to be
167156
@@ -172,10 +161,10 @@ class DictionaryTrialSpace(TrialSpace):
172161
'''
173162
def __init__(self, snapshots, shifter, splitter, orthogonalizer):
174163
'''
175-
Constructor for the reduced basis trial space without truncation.
164+
Constructor.
176165
177166
Args:
178-
snapshots: Snapshot tensor containing solution data
167+
snapshots (np.ndarray): Snapshot data in tensor form $\in \mathbb{R}^{ N_{\\mathrm{vars}} \\times N_{\\mathrm{x}} \\times N_{samples}}$
179168
shifter: Class that shifts the basis.
180169
splitter: Class that splitts the basis.
181170
orthogonalizer: Class that orthogonalizes the basis.
@@ -195,36 +184,26 @@ def __init__(self, snapshots, shifter, splitter, orthogonalizer):
195184

196185
def get_dimension(self):
197186
'''
198-
Retrieves the dimension of trial space
199-
200-
Returns:
201-
int: The dimension of the trial space.
187+
Concrete implementation of `TrialSpace.get_dimension()`
202188
'''
203189
return self.__dimension
204190

205191
def get_shift_vector(self):
206192
'''
207-
Retrieves the shift vector
208-
209-
Returns:
210-
np.ndarray: The shift vector.
211-
193+
Concrete implementation of `TrialSpace.get_shift_vector()`
212194
'''
213195
return self.__shift_vector
214196

215197
def get_basis(self):
216198
'''
217-
Retrieves the basis of the trial space
218-
219-
Returns:
220-
np.ndarray: The basis of the trial space.
199+
Concrete implementation of `TrialSpace.get_basis()`
221200
'''
222201
return self.__basis
223202

224203

225204
class TrialSpaceFromPOD(TrialSpace):
226205
'''
227-
##POD trial space (constructed via SVD).
206+
POD trial space (constructed via SVD).
228207
229208
Given a snapshot matrix $\\mathbf{S}$, we compute the basis $\\boldsymbol \\Phi$ as
230209
@@ -241,17 +220,17 @@ class TrialSpaceFromPOD(TrialSpace):
241220
'''
242221

243222
def __init__(self,
244-
snapshot_tensor,
223+
snapshots,
245224
truncater: Truncater = NoOpTruncater(),
246225
shifter: Shifter = NoOpShifter(),
247226
splitter: Splitter = NoOpSplitter(),
248227
orthogonalizer: Orthogonalizer = NoOpOrthogonalizer(),
249228
svdFnc = None):
250229
'''
251-
Constructor for the POD trial space.
230+
Constructor.
252231
253232
Args:
254-
snapshot_tensor (np.ndarray): Snapshot data tensor
233+
snapshots (np.ndarray): Snapshot data in tensor form $\in \mathbb{R}^{ N_{\\mathrm{vars}} \\times N_{\\mathrm{x}} \\times N_{samples}}$
255234
truncater (Truncater): Class that truncates the basis.
256235
shifter (Shifter): Class that shifts the basis.
257236
splitter (Splitter): Class that splits the basis.
@@ -270,9 +249,9 @@ def __init__(self,
270249
orthogonalization.
271250
'''
272251

273-
n_var = snapshot_tensor.shape[0]
274-
shifted_snapshot_tensor, self.__shift_vector = shifter(snapshot_tensor)
275-
snapshot_matrix = tensor_to_matrix(shifted_snapshot_tensor)
252+
n_var = snapshots.shape[0]
253+
shifted_snapshots, self.__shift_vector = shifter(snapshots)
254+
snapshot_matrix = tensor_to_matrix(shifted_snapshots)
276255
shifted_split_snapshots = splitter(snapshot_matrix)
277256

278257
svd_picked = np.linalg.svd if svdFnc is None else svdFnc
@@ -286,36 +265,26 @@ def __init__(self,
286265

287266
def get_dimension(self):
288267
'''
289-
Retrieves the dimension of the trial space
290-
291-
Returns:
292-
int: The dimension of the trial space.
268+
Concrete implementation of `TrialSpace.get_dimension()`
293269
'''
294270
return self.__dimension
295271

296272
def get_shift_vector(self):
297273
'''
298-
Retrieves the shift vector
299-
300-
Returns:
301-
np.ndarray: The shift vector.
302-
274+
Concrete implementation of `TrialSpace.get_shift_vector()`
303275
'''
304276
return self.__shift_vector
305277

306278
def get_basis(self):
307279
'''
308-
Retrieves the basis of the trial space
309-
310-
Returns:
311-
np.ndarray: The basis of the trial space.
280+
Concrete implementation of `TrialSpace.get_basis()`
312281
'''
313282
return self.__basis
314283

315284

316285
class TrialSpaceFromScaledPOD(TrialSpace):
317286
'''
318-
##POD trial space (constructed via scaled SVD).
287+
POD trial space (constructed via scaled SVD).
319288
320289
Given a snapshot matrix $\\mathbf{S}$, we set the basis to be
321290
@@ -331,17 +300,17 @@ class TrialSpaceFromScaledPOD(TrialSpace):
331300
truncater.
332301
'''
333302

334-
def __init__(self, snapshot_tensor,
303+
def __init__(self, snapshots,
335304
truncater: Truncater,
336305
shifter: Shifter,
337306
scaler: Scaler,
338307
splitter: Splitter,
339308
orthogonalizer: Orthogonalizer):
340309
'''
341-
Constructor for the POD trial space constructed via scaled SVD.
310+
Constructor.
342311
343312
Args:
344-
snapshot_tensor: np.ndarray snapshot tensor
313+
snapshots (np.ndarray): Snapshot data in tensor form $\in \mathbb{R}^{ N_{\\mathrm{vars}} \\times N_{\\mathrm{x}} \\times N_{samples}}$
345314
truncater: Class that truncates the basis.
346315
shifter: Class that shifts the basis.
347316
scaler: Class that scales the basis.
@@ -355,10 +324,10 @@ def __init__(self, snapshot_tensor,
355324
'''
356325

357326
# compute basis
358-
n_var = snapshot_tensor.shape[0]
359-
shifted_snapshot_tensor, self.__shift_vector = shifter(snapshot_tensor)
360-
scaled_shifted_snapshot_tensor = scaler.pre_scaling(shifted_snapshot_tensor)
361-
snapshot_matrix = tensor_to_matrix(scaled_shifted_snapshot_tensor)
327+
n_var = snapshots.shape[0]
328+
shifted_snapshots, self.__shift_vector = shifter(snapshots)
329+
scaled_shifted_snapshots = scaler.pre_scaling(shifted_snapshots)
330+
snapshot_matrix = tensor_to_matrix(scaled_shifted_snapshots)
362331
snapshot_matrix = splitter(snapshot_matrix)
363332

364333
lsv, svals, _ = np.linalg.svd(snapshot_matrix, full_matrices=False)
@@ -372,28 +341,38 @@ def __init__(self, snapshot_tensor,
372341

373342
def get_dimension(self):
374343
'''
375-
Retrieves the dimension of the trial space
376-
377-
Returns:
378-
int: The dimension of the trial space.
344+
Concrete implementation of `TrialSpace.get_dimension()`
379345
'''
380346
return self.__dimension
381347

382348
def get_shift_vector(self):
383349
'''
384-
Retrieves the shift vector
385-
386-
Returns:
387-
np.ndarray: The shift vector.
388-
350+
Concrete implementation of `TrialSpace.get_shift_vector()`
389351
'''
390352
return self.__shift_vector
391353

392354
def get_basis(self):
393355
'''
394-
Retrieves the basis of the trial space
395-
396-
Returns:
397-
np.ndarray: The basis of the trial space.
356+
Concrete implementation of `TrialSpace.get_basis()`
398357
'''
399358
return self.__basis
359+
360+
361+
def tensor_to_matrix(tensor_input):
362+
'''
363+
Converts a tensor with shape $[N, M, P]$ to a matrix representation
364+
in which the first two dimension are collapsed $[N M, P]$.
365+
'''
366+
output_tensor = tensor_input.reshape(tensor_input.shape[0]*tensor_input.shape[1],
367+
tensor_input.shape[2])
368+
return output_tensor
369+
370+
371+
def matrix_to_tensor(n_var, matrix_input):
372+
'''
373+
Inverse operation of `tensor_to_matrix`
374+
'''
375+
d1 = int(matrix_input.shape[0] / n_var)
376+
d2 = matrix_input.shape[1]
377+
output_matrix = matrix_input.reshape(n_var, d1, d2)
378+
return output_matrix

0 commit comments

Comments
 (0)