romtools.hyper_reduction.deim
Implementation of DEIM technique for hyper-reduction
1# 2# ************************************************************************ 3# 4# ROM Tools and Workflows 5# Copyright 2019 National Technology & Engineering Solutions of Sandia,LLC 6# (NTESS) 7# 8# Under the terms of Contract DE-NA0003525 with NTESS, the 9# U.S. Government retains certain rights in this software. 10# 11# ROM Tools and Workflows is licensed under BSD-3-Clause terms of use: 12# 13# Redistribution and use in source and binary forms, with or without 14# modification, are permitted provided that the following conditions 15# are met: 16# 17# 1. Redistributions of source code must retain the above copyright 18# notice, this list of conditions and the following disclaimer. 19# 20# 2. Redistributions in binary form must reproduce the above copyright 21# notice, this list of conditions and the following disclaimer in the 22# documentation and/or other materials provided with the distribution. 23# 24# 3. Neither the name of the copyright holder nor the names of its 25# contributors may be used to endorse or promote products derived 26# from this software without specific prior written permission. 27# 28# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 31# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 32# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 33# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 37# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 38# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 39# POSSIBILITY OF SUCH DAMAGE. 40# 41# Questions? Contact Eric Parish (ejparis@sandia.gov) 42# 43# ************************************************************************ 44# 45 46'''Implementation of DEIM technique for hyper-reduction''' 47 48import numpy as np 49import romtools.linalg.linalg as la 50 51 52 53def _deim_get_indices_sharedmem(U): 54 ''' 55 Implementation of the discrete empirical method as described in Algorithm 1 of 56 S. Chaturantabut and D. C. Sorensen, "Discrete Empirical Interpolation for 57 nonlinear model reduction," doi: 10.1109/CDC.2009.5400045. 58 59 Args: 60 $\\mathbf{U} \\in \\mathbb{R}^{m \\times n}$, where 61 $m$ is the number of DOFs and 62 $n$ is the number of samples. 63 Function basis in matrix format 64 65 Returns: 66 $\\mathrm{indices} \\in \\mathbb{I}^{n}$: sample mesh indices 67 ''' 68 69 m = np.shape(U)[1] 70 first_index = la.argmax(np.abs(U[:, 0])) 71 indices = first_index 72 for ell in range(1, m): 73 LHS = U[indices, 0:ell] 74 RHS = U[indices, ell] 75 if ell == 1: 76 LHS = np.ones((1, 1))*LHS 77 RHS = np.ones(1)*RHS 78 C = np.linalg.solve(LHS, RHS) 79 80 residual = U[:, ell] - U[:, 0:ell] @ C 81 index_to_add = la.argmax(np.abs(residual)) 82 indices = np.append(indices, index_to_add) 83 return indices 84 85 86class _dist_deim_data: 87 def __init__(self, i, r): 88 self.local_indices = np.array([int(i)]) 89 self.owning_ranks = np.array([int(r)]) 90 91 def append(self, i, r): 92 '''Adds the local index and rank to self.local_indices and self.owning_ranks.''' 93 self.local_indices = np.append(self.local_indices , int(i)) 94 self.owning_ranks = np.append(self.owning_ranks, int(r)) 95 96 97def _deim_get_indices_distributed(U, comm): 98 m = np.shape(U)[1] 99 local_index, foundRank = la.argmax(np.abs(U[:, 0]), comm) 100 result = _dist_deim_data(local_index, foundRank) 101 if m == 1: 102 return result.local_indices, result.owning_ranks 103 104 myRank = comm.Get_rank() 105 LHS, RHS, C = np.array([]), np.array([]), np.array([]) 106 for ell in range(1, m): 107 indices = result.local_indices[result.owning_ranks==myRank] 108 LHS = np.array([]) if indices.size == 0 else U[indices, 0:ell] 109 RHS = np.array([]) if indices.size == 0 else U[indices, ell] 110 111 A, b = la.move_distributed_linear_system_to_rank_zero(LHS, RHS, comm) 112 if myRank == 0: 113 C = np.linalg.solve(A, b) 114 C = comm.bcast(C, root=0) 115 116 residual = U[:, ell] - U[:, 0:ell] @ C 117 local_index, foundRank = la.argmax(np.abs(residual), comm) 118 result.append(local_index, foundRank) 119 120 return result.local_indices, result.owning_ranks 121 122 123def deim_get_indices(U, comm=None): 124 ''' 125 Implementation of the discrete empirical method as described in Algorithm 1 of 126 S. Chaturantabut and D. C. Sorensen, "Discrete Empirical Interpolation for 127 nonlinear model reduction," doi: 10.1109/CDC.2009.5400045. 128 129 Args: 130 $\\mathbf{U} \\in \\mathbb{R}^{m \\times n}$, where 131 $m$ is the number of DOFs and 132 $n$ is the number of samples. 133 Function basis in matrix format. 134 comm: Optional communicator object. If none, algorithm assumes shared-memory data. 135 136 Returns: 137 if comm==None: 138 $\\mathrm{indices} \\in \\mathbb{I}^{n}$: sample mesh indices 139 140 else: 141 sample mesh local indices and the corresponding owing ranks 142 ''' 143 144 assert np.shape(U)[1] >= 1, "deim requires a basis matrix with at least one basis vector (one column)" 145 146 if comm: 147 return _deim_get_indices_distributed(U, comm) 148 149 return _deim_get_indices_sharedmem(U) 150 151 152def _deim_multi_state_get_indices_sharedmem(U): 153 all_indices = np.zeros(0, dtype=int) 154 n_var = U.shape[0] 155 for i in range(0, n_var): 156 data_matrix = U[i] 157 indices = deim_get_indices(data_matrix) 158 all_indices = np.unique(np.append(all_indices, indices)) 159 return all_indices 160 161 162def _deim_multi_state_get_indices_distributed(U, comm): 163 all_local_indices = np.zeros(0, dtype=int) 164 all_ranks = np.zeros(0, dtype=int) 165 n_var = U.shape[0] 166 for i in range(0, n_var): 167 data_matrix = U[i] 168 indices, ranks = deim_get_indices(data_matrix, comm) 169 # print(my_rank, ":::", indices, ranks) 170 all_local_indices = np.append(all_local_indices, indices) 171 all_ranks = np.append(all_ranks, ranks) 172 173 # from here we need to operate on ranks and indices together 174 # so stack them for convenience 175 M = np.vstack((all_local_indices, all_ranks)) 176 177 # first, sort based on rankID 178 M = np.unique(M, axis=1) 179 inds = M[1,:].argsort() 180 M = M[:, inds] 181 182 # once we are here, M is sorted based on the ranks, but could look like this: 183 184 # M = [ 0 14 13 5 15 4 2 6 8 10 4 4 14 7 8 3 6] 185 # [ 0 0 0 0 0 0 0 1 1 1 1 2 2 2 2 2 2] 186 187 # we do an additional step where we sort based on the local index within each rank 188 189 rank_ids = np.unique(M[1,:]) 190 for rank in rank_ids: 191 locs = np.where(M[1,:] == rank) 192 # only need to sort the 0-th row since we are sorting locally for each rank with same rank id 193 M[0, locs] = np.sort(M[0, locs]) 194 195 # return local indices and ranks separately 196 return M[0,:], M[1,:] 197 198 199def multi_state_deim_get_indices(U, comm=None): 200 ''' 201 Version of DEIM for multi-state systems. 202 203 We perform DEIM on each state variable, and 204 then return the union of all indices. 205 Repeated indices are removed. 206 207 208 Args: 209 $\\mathbf{U} \\in \\mathbb{R}^{l \\times m \\times n}$, where 210 $l$ is the number of variables, 211 $m$ is the number of DOFs and 212 $n$ the number of samples. 213 Multi-dimensional function basis in tensor format. 214 215 Returns: 216 if comm==None: 217 $\\mathrm{indices} \\in \\mathbb{I}^{n}$: sample mesh indices 218 219 else: 220 sample mesh local indices and the corresponding owing ranks 221 ''' 222 shape = np.shape(U) 223 assert len(shape) == 3 224 assert shape[2] >= 1, "deim requires a basis matrix with at least one basis vector (one column)" 225 226 if comm: 227 return _deim_multi_state_get_indices_distributed(U, comm) 228 229 return _deim_multi_state_get_indices_sharedmem(U) 230 231 232 233def deim_get_approximation_matrix(function_basis, sample_indices): 234 ''' 235 Given a function basis $\\mathbf{U}$ and sample indices defining $\\mathbf{P}$, we compute 236 $$ \\mathbf{U} \\mathrm{pinv}( \\mathbf{P}^T \\mathbf{U})$$ 237 which comprises the matrix needed for the DEIM approximation to $\\mathbf{f}$ 238 239 Args: 240 function_basis: ($m$, $n$) array, where $m$ is the number of DOFs and $n$ the number of basis functions. 241 Basis for function to be approximated. 242 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 243 244 Returns: 245 deim_matrix: ($n$,$n_s$) array. DEIM approximation basis 246 ''' 247 sampled_function_basis = function_basis[sample_indices] 248 PU_pinv = np.linalg.pinv(sampled_function_basis) 249 deim_matrix = function_basis @ PU_pinv 250 return deim_matrix 251 252 253def multi_state_deim_get_test_basis(test_basis, function_basis, sample_indices): 254 ''' 255 For multistate systems. Constructs an independent DEIM basis for each state variable using uniform sample indices 256 Args: 257 test_basis: ($n_{var}$, $m$, $k$) array, where 258 $n_{var}$ is the number of state variables, 259 $m$ is the number of DOFs and 260 $k$ is the number of basis functions. Test basis in projection scheme 261 function_basis: ($n_{var}$, m, n) array, where 262 $n_{var}$ is the number of state variables, 263 $m$ is the number of DOFs and 264 $n$ is the number of basis functions. 265 Basis for function to be approximated. 266 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 267 268 Returns: 269 deim_test_basis: ($n_{var}$, $n_s$, $k$) array, where 270 $n_{var}$ is the number of state variables, 271 $n_s$ is the number of sample points, and 272 $k$ the number of basis functions. 273 DEIM test basis matrix. 274 275 ''' 276 n_var = function_basis.shape[0] 277 deim_test_basis = deim_get_test_basis( 278 test_basis[0], function_basis[0], sample_indices 279 ) 280 deim_test_basis = deim_test_basis[None] 281 for i in range(1, n_var): 282 deim_test_basis_i = deim_get_test_basis( 283 test_basis[i], function_basis[i], sample_indices 284 ) 285 deim_test_basis = np.append(deim_test_basis, deim_test_basis_i[None], axis=0) 286 return deim_test_basis 287 288 289def deim_get_test_basis(test_basis, function_basis, sample_indices, comm=None): 290 ''' 291 Given a test basis $\\mathbf{\\Phi}$, a function basis $\\mathbf{U}$, and 292 sample indices defining $\\mathbf{P}$, we compute 293 $$[ \\mathbf{\\Phi}^T \\mathbf{U} \\mathrm{pinv}( \\mathbf{P}^T \\mathbf{U}) ]^T$$ 294 which comprises the "test basis" for the DEIM approximation for 295 $\\mathbf{\\Phi}^T \\mathbf{f}$ 296 297 Args: 298 test_basis: ($m$, $k$) array, where 299 $m$ is the number of DOFs and 300 $k$ is the number of basis functions. Test basis in projection scheme 301 function_basis: ($m$, $n$) array, where 302 $m$ is the number of DOFs and 303 $n$ is the number of basis functions. Basis for function to be approximated. 304 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 305 comm: Optional communicator object. If none, algorithm assumes shared-memory data. 306 307 Returns: 308 deim_test_basis: ($n_s$, $k$) array, where 309 $n_s$ is the number of sample points and 310 $k$ the number of basis functions. DEIM test basis matrix. 311 312 ''' 313 # Determine sampled_function_basis, allowing for empty ranks 314 sampled_function_basis = np.empty((0, function_basis.shape[1])) 315 if len(sample_indices) > 0: 316 sampled_function_basis = function_basis[sample_indices] 317 318 # pinv(P^T U) ^T 319 PU_pinv_transpose = la.pinv(sampled_function_basis, comm=comm) 320 321 # Phi^T U (distributed operation) 322 phi_U = np.empty((test_basis.shape[1], function_basis.shape[1])) 323 la.product("T", "N", 1, test_basis, function_basis, 0, phi_U, comm=comm) 324 325 # Gather pinvs if distributed 326 if comm is not None: 327 local_pinvs = comm.allgather(PU_pinv_transpose) 328 PU_pinv_transpose = np.vstack(local_pinvs) 329 330 # phi_U pinv (local operation) 331 deim_test_basis = np.empty((phi_U.shape[0], PU_pinv_transpose.shape[0])) 332 la.product("N", "T", 1, phi_U, PU_pinv_transpose, 0, deim_test_basis) 333 334 return deim_test_basis.transpose()
124def deim_get_indices(U, comm=None): 125 ''' 126 Implementation of the discrete empirical method as described in Algorithm 1 of 127 S. Chaturantabut and D. C. Sorensen, "Discrete Empirical Interpolation for 128 nonlinear model reduction," doi: 10.1109/CDC.2009.5400045. 129 130 Args: 131 $\\mathbf{U} \\in \\mathbb{R}^{m \\times n}$, where 132 $m$ is the number of DOFs and 133 $n$ is the number of samples. 134 Function basis in matrix format. 135 comm: Optional communicator object. If none, algorithm assumes shared-memory data. 136 137 Returns: 138 if comm==None: 139 $\\mathrm{indices} \\in \\mathbb{I}^{n}$: sample mesh indices 140 141 else: 142 sample mesh local indices and the corresponding owing ranks 143 ''' 144 145 assert np.shape(U)[1] >= 1, "deim requires a basis matrix with at least one basis vector (one column)" 146 147 if comm: 148 return _deim_get_indices_distributed(U, comm) 149 150 return _deim_get_indices_sharedmem(U)
Implementation of the discrete empirical method as described in Algorithm 1 of S. Chaturantabut and D. C. Sorensen, "Discrete Empirical Interpolation for nonlinear model reduction," doi: 10.1109/CDC.2009.5400045.
Arguments:
- $\mathbf{U} \in \mathbb{R}^{m \times n}$, where $m$ is the number of DOFs and $n$ is the number of samples. Function basis in matrix format.
- comm: Optional communicator object. If none, algorithm assumes shared-memory data.
Returns:
if comm==None: $\mathrm{indices} \in \mathbb{I}^{n}$: sample mesh indices
else: sample mesh local indices and the corresponding owing ranks
200def multi_state_deim_get_indices(U, comm=None): 201 ''' 202 Version of DEIM for multi-state systems. 203 204 We perform DEIM on each state variable, and 205 then return the union of all indices. 206 Repeated indices are removed. 207 208 209 Args: 210 $\\mathbf{U} \\in \\mathbb{R}^{l \\times m \\times n}$, where 211 $l$ is the number of variables, 212 $m$ is the number of DOFs and 213 $n$ the number of samples. 214 Multi-dimensional function basis in tensor format. 215 216 Returns: 217 if comm==None: 218 $\\mathrm{indices} \\in \\mathbb{I}^{n}$: sample mesh indices 219 220 else: 221 sample mesh local indices and the corresponding owing ranks 222 ''' 223 shape = np.shape(U) 224 assert len(shape) == 3 225 assert shape[2] >= 1, "deim requires a basis matrix with at least one basis vector (one column)" 226 227 if comm: 228 return _deim_multi_state_get_indices_distributed(U, comm) 229 230 return _deim_multi_state_get_indices_sharedmem(U)
Version of DEIM for multi-state systems.
We perform DEIM on each state variable, and then return the union of all indices. Repeated indices are removed.
Arguments:
- $\mathbf{U} \in \mathbb{R}^{l \times m \times n}$, where $l$ is the number of variables, $m$ is the number of DOFs and $n$ the number of samples. Multi-dimensional function basis in tensor format.
Returns:
if comm==None: $\mathrm{indices} \in \mathbb{I}^{n}$: sample mesh indices
else: sample mesh local indices and the corresponding owing ranks
234def deim_get_approximation_matrix(function_basis, sample_indices): 235 ''' 236 Given a function basis $\\mathbf{U}$ and sample indices defining $\\mathbf{P}$, we compute 237 $$ \\mathbf{U} \\mathrm{pinv}( \\mathbf{P}^T \\mathbf{U})$$ 238 which comprises the matrix needed for the DEIM approximation to $\\mathbf{f}$ 239 240 Args: 241 function_basis: ($m$, $n$) array, where $m$ is the number of DOFs and $n$ the number of basis functions. 242 Basis for function to be approximated. 243 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 244 245 Returns: 246 deim_matrix: ($n$,$n_s$) array. DEIM approximation basis 247 ''' 248 sampled_function_basis = function_basis[sample_indices] 249 PU_pinv = np.linalg.pinv(sampled_function_basis) 250 deim_matrix = function_basis @ PU_pinv 251 return deim_matrix
Given a function basis $\mathbf{U}$ and sample indices defining $\mathbf{P}$, we compute $$ \mathbf{U} \mathrm{pinv}( \mathbf{P}^T \mathbf{U})$$ which comprises the matrix needed for the DEIM approximation to $\mathbf{f}$
Arguments:
- function_basis: ($m$, $n$) array, where $m$ is the number of DOFs and $n$ the number of basis functions. Basis for function to be approximated.
- sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points.
Returns:
deim_matrix: ($n$,$n_s$) array. DEIM approximation basis
254def multi_state_deim_get_test_basis(test_basis, function_basis, sample_indices): 255 ''' 256 For multistate systems. Constructs an independent DEIM basis for each state variable using uniform sample indices 257 Args: 258 test_basis: ($n_{var}$, $m$, $k$) array, where 259 $n_{var}$ is the number of state variables, 260 $m$ is the number of DOFs and 261 $k$ is the number of basis functions. Test basis in projection scheme 262 function_basis: ($n_{var}$, m, n) array, where 263 $n_{var}$ is the number of state variables, 264 $m$ is the number of DOFs and 265 $n$ is the number of basis functions. 266 Basis for function to be approximated. 267 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 268 269 Returns: 270 deim_test_basis: ($n_{var}$, $n_s$, $k$) array, where 271 $n_{var}$ is the number of state variables, 272 $n_s$ is the number of sample points, and 273 $k$ the number of basis functions. 274 DEIM test basis matrix. 275 276 ''' 277 n_var = function_basis.shape[0] 278 deim_test_basis = deim_get_test_basis( 279 test_basis[0], function_basis[0], sample_indices 280 ) 281 deim_test_basis = deim_test_basis[None] 282 for i in range(1, n_var): 283 deim_test_basis_i = deim_get_test_basis( 284 test_basis[i], function_basis[i], sample_indices 285 ) 286 deim_test_basis = np.append(deim_test_basis, deim_test_basis_i[None], axis=0) 287 return deim_test_basis
For multistate systems. Constructs an independent DEIM basis for each state variable using uniform sample indices
Arguments:
- test_basis: ($n_{var}$, $m$, $k$) array, where $n_{var}$ is the number of state variables, $m$ is the number of DOFs and $k$ is the number of basis functions. Test basis in projection scheme
- function_basis: ($n_{var}$, m, n) array, where $n_{var}$ is the number of state variables, $m$ is the number of DOFs and $n$ is the number of basis functions. Basis for function to be approximated.
- sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points.
Returns:
deim_test_basis: ($n_{var}$, $n_s$, $k$) array, where $n_{var}$ is the number of state variables, $n_s$ is the number of sample points, and $k$ the number of basis functions. DEIM test basis matrix.
290def deim_get_test_basis(test_basis, function_basis, sample_indices, comm=None): 291 ''' 292 Given a test basis $\\mathbf{\\Phi}$, a function basis $\\mathbf{U}$, and 293 sample indices defining $\\mathbf{P}$, we compute 294 $$[ \\mathbf{\\Phi}^T \\mathbf{U} \\mathrm{pinv}( \\mathbf{P}^T \\mathbf{U}) ]^T$$ 295 which comprises the "test basis" for the DEIM approximation for 296 $\\mathbf{\\Phi}^T \\mathbf{f}$ 297 298 Args: 299 test_basis: ($m$, $k$) array, where 300 $m$ is the number of DOFs and 301 $k$ is the number of basis functions. Test basis in projection scheme 302 function_basis: ($m$, $n$) array, where 303 $m$ is the number of DOFs and 304 $n$ is the number of basis functions. Basis for function to be approximated. 305 sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points. 306 comm: Optional communicator object. If none, algorithm assumes shared-memory data. 307 308 Returns: 309 deim_test_basis: ($n_s$, $k$) array, where 310 $n_s$ is the number of sample points and 311 $k$ the number of basis functions. DEIM test basis matrix. 312 313 ''' 314 # Determine sampled_function_basis, allowing for empty ranks 315 sampled_function_basis = np.empty((0, function_basis.shape[1])) 316 if len(sample_indices) > 0: 317 sampled_function_basis = function_basis[sample_indices] 318 319 # pinv(P^T U) ^T 320 PU_pinv_transpose = la.pinv(sampled_function_basis, comm=comm) 321 322 # Phi^T U (distributed operation) 323 phi_U = np.empty((test_basis.shape[1], function_basis.shape[1])) 324 la.product("T", "N", 1, test_basis, function_basis, 0, phi_U, comm=comm) 325 326 # Gather pinvs if distributed 327 if comm is not None: 328 local_pinvs = comm.allgather(PU_pinv_transpose) 329 PU_pinv_transpose = np.vstack(local_pinvs) 330 331 # phi_U pinv (local operation) 332 deim_test_basis = np.empty((phi_U.shape[0], PU_pinv_transpose.shape[0])) 333 la.product("N", "T", 1, phi_U, PU_pinv_transpose, 0, deim_test_basis) 334 335 return deim_test_basis.transpose()
Given a test basis $\mathbf{\Phi}$, a function basis $\mathbf{U}$, and sample indices defining $\mathbf{P}$, we compute $$[ \mathbf{\Phi}^T \mathbf{U} \mathrm{pinv}( \mathbf{P}^T \mathbf{U}) ]^T$$ which comprises the "test basis" for the DEIM approximation for $\mathbf{\Phi}^T \mathbf{f}$
Arguments:
- test_basis: ($m$, $k$) array, where $m$ is the number of DOFs and $k$ is the number of basis functions. Test basis in projection scheme
- function_basis: ($m$, $n$) array, where $m$ is the number of DOFs and $n$ is the number of basis functions. Basis for function to be approximated.
- sample_indices: ($n_s$, ) array, where $n_s$ is the number of sample points. Sampling points.
- comm: Optional communicator object. If none, algorithm assumes shared-memory data.
Returns:
deim_test_basis: ($n_s$, $k$) array, where $n_s$ is the number of sample points and $k$ the number of basis functions. DEIM test basis matrix.