src.acoustools.BEM.Forward_models
1import torch 2from torch import Tensor 3 4from vedo import Mesh 5from typing import Literal 6import hashlib 7import pickle 8 9 10import acoustools.Constants as Constants 11 12from acoustools.Utilities import device, DTYPE, forward_model_batched, TOP_BOARD, create_points 13from acoustools.Mesh import get_normals_as_points, get_centres_as_points, get_areas, get_centre_of_mass_as_points 14 15from acoustools.Utilities import forward_model_grad 16 17 18 19 20 21def compute_green_derivative(y:Tensor,x:Tensor,norms:Tensor,B:int,N:int,M:int, return_components:bool=False, 22 a:Tensor=None, c:Tensor=None, k=Constants.k, smooth_distance:float=0) -> Tensor: 23 ''' 24 Computes the derivative of greens function \n 25 :param y: y in greens function - location of the source of sound 26 :param x: x in greens function - location of the point to be propagated to 27 :param norms: norms to y 28 :param B: Batch dimension 29 :param N: size of x 30 :param M: size of y 31 :param return_components: if true will return the subparts used to compute the derivative \n 32 :param k: Wavenumber to use 33 :param smooth_distance: amount to add to distances to avoid explosion over small values 34 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 35 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 36 :return: returns the partial derivative of greeens fucntion wrt y 37 ''' 38 norms= norms.real 39 40 vecs = y.real-x.real 41 42 43 distance = torch.sqrt(torch.sum((vecs)**2,dim=3)) 44 45 if a is None: #Were computing with a OR we have no a to begin with 46 if len(vecs.shape) > 4: #Vecs isnt expanded - we must never have had an a 47 norms = norms.unsqueeze(4).expand(B,N,-1,-1,1) 48 else: #vecs included a 49 norms = norms.expand(B,N,-1,-1) 50 else: 51 norms = norms.expand(B,N,-1,-1) 52 53 54 # norm_norms = torch.norm(norms,2,dim=3) # === 1x 55 # vec_norms = torch.norm(vecs,2,dim=3) # === distance? 56 # print(vec_norms == distance) 57 angles = (torch.sum(norms*vecs,3) / (distance)) 58 59 # del norms, vecs 60 torch.cuda.empty_cache() 61 62 # distance = distance + smooth_distance 63 distance = distance.clamp_min(smooth_distance) 64 65 66 A = 1 * greens(y,x,distance=distance,k=k) 67 ik_d = (1j*k - 1/(distance)) 68 69 del distance 70 # torch.cuda.empty_cache() 71 72 partial_greens = A*ik_d*angles 73 74 # if not return_components: 75 # del A,B,angles 76 torch.cuda.empty_cache() 77 78 79 80 if a is not None: 81 n_a = a.shape[2] 82 # a = a.permute(0,2,1) 83 a = a.unsqueeze(1).unsqueeze(2) 84 a = a.expand(B,N,M,3,n_a).clone() 85 y = y.unsqueeze(4).expand(B,N,M,3,n_a) 86 g_mod = torch.sum(c*compute_green_derivative(y, a, norms, B, N, M,k=k),dim=3) #Allow for multiple a's 87 partial_greens += g_mod 88 89 90 partial_greens[partial_greens.isnan()] = 0 91 if return_components: 92 return partial_greens, A,ik_d,angles 93 94 95 return partial_greens 96 97def greens(y:Tensor,x:Tensor, k:float=Constants.k, distance=None): 98 ''' 99 Computes greens function for a source at y and a point at x\n 100 :param y: source location 101 :param x: point location 102 :param k: wavenumber 103 :param distance: precomputed distances from y->x 104 :returns greens function: 105 ''' 106 if distance is None: 107 vecs = y.real-x.real 108 distance = torch.sqrt(torch.sum((vecs)**2,dim=3)) 109 green = torch.exp(1j*k*distance) / (4*Constants.pi*distance) 110 111 return green 112 113def compute_G(points: Tensor, scatterer: Mesh, k:float=Constants.k, alphas:float|Tensor=1, betas:float|Tensor = 0, a:Tensor=None, c:Tensor=None, smooth_distance:float=0) -> Tensor: 114 ''' 115 Computes G in the BEM model\n 116 :param points: The points to propagate to 117 :param scatterer: The mesh used (as a `vedo` `mesh` object) 118 :param k: wavenumber 119 :param alphas: Absorbance of each element, can be Tensor for element-wise attribution or a number for all elements. If Tensor, should have shape [B,M] where M is the mesh size, B is the batch size and will normally be 1 120 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 121 :param smooth_distance: amount to add to distances to avoid explosion over small values 122 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 123 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 124 :return G: `torch.Tensor` of G 125 ''' 126 torch.cuda.empty_cache() 127 areas = torch.Tensor(scatterer.celldata["Area"]).to(device).real 128 B = points.shape[0] 129 N = points.shape[2] 130 M = areas.shape[0] 131 areas = areas.expand((B,N,-1)) 132 133 #Compute the partial derivative of Green's Function 134 135 #Firstly compute the distances from mesh points -> control points 136 centres = torch.tensor(scatterer.cell_centers().points).to(device).real #Uses centre points as position of mesh 137 centres = centres.expand((B,N,-1,-1)) 138 139 # print(points.shape) 140 # p = torch.reshape(points,(B,N,3)) 141 p = torch.permute(points,(0,2,1)).real 142 p = torch.unsqueeze(p,2).expand((-1,-1,M,-1)) 143 144 #Compute cosine of angle between mesh normal and point 145 # scatterer.compute_normals() 146 # norms = torch.tensor(scatterer.cell_normals).to(device) 147 norms = get_normals_as_points(scatterer,permute_to_points=False).real.expand((B,N,-1,-1)) 148 149 # centres_p = get_centres_as_points(scatterer) 150 partial_greens = compute_green_derivative(centres,p,norms, B,N,M, a=a, c=c, k=k,smooth_distance=smooth_distance ) 151 152 if ((type(betas) in [int, float]) and betas != 0) or (type(betas) is Tensor and (betas != 0).any()): #Either β non 0 and type(β) is number or β is Tensor and any elemenets non 0 153 green = greens(centres, p, k=k) * 1j * k * betas 154 partial_greens += green 155 156 G = areas * partial_greens 157 158 159 if ((type(alphas) in [int, float]) and alphas != 1) or (type(alphas) is Tensor and (alphas != 1).any()): 160 #Does this need to be in A too? 161 if type(alphas) is Tensor: 162 alphas = alphas.unsqueeze(1) 163 alphas = alphas.expand(B, N, M) 164 vecs = p - centres 165 angle = torch.sum(vecs * norms, dim=3) #Technically not the cosine of the angle - would need to /distance but as we only care about the sign then it doesnt matter 166 angle = angle.real 167 if type(alphas) is Tensor: 168 G[angle>0] = G[angle>0] * alphas[angle>0] 169 else: 170 G[angle>0] = G[angle>0] * alphas 171 172 return G.to(DTYPE) 173 174 175 176def augment_A_CHIEF(A:Tensor, internal_points:Tensor, CHIEF_mode:Literal['square', 'rect'] = 'square', centres:Tensor=None, norms:Tensor=None, areas:Tensor=None, scatterer:Mesh=None, k:float=Constants.k): 177 ''' 178 Augments an A matrix with CHIEF BEM equations\n 179 180 :param A: A matrix 181 :param internal_points: The internal points to use for CHIEF based BEM 182 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 183 :param centres: mesh centres 184 :param norms: mesh normals 185 :param areas: mesh aeras 186 :param scatterer: mesh 187 :param k: wavenumber 188 ''' 189 # if internal_points is not None: print(internal_points.shape) 190 191 if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3): 192 internal_points = internal_points.permute(0,2,1) 193 194 if areas is None: areas = torch.tensor(scatterer.celldata["Area"], dtype=DTYPE, device=device) 195 if centres is None: centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 196 if norms is None: norms = get_normals_as_points(scatterer, permute_to_points=False) 197 198 199 200 P = internal_points.shape[1] 201 M = centres.shape[0] 202 203 m_int = centres.unsqueeze(0).unsqueeze(1) 204 int_p = internal_points.unsqueeze(2) 205 # G_block = greens(m_int,int_p, k=k) 206 207 int_norms = norms.unsqueeze(1) 208 G_block = -compute_green_derivative(m_int,int_p, int_norms, 1, P, M, k=k) 209 G_block = G_block * areas[None,None,:] 210 211 G_block_t = G_block.mT 212 zero_block = torch.zeros((1, P, P), device=device, dtype=DTYPE) 213 214 215 # return torch.cat((A, G_block), dim=1) 216 if CHIEF_mode.lower() == 'square': 217 A_aux = torch.cat((A, G_block_t), dim=2) 218 219 GtZ = torch.cat((G_block, zero_block), dim=2) 220 A = torch.cat((A_aux, GtZ), dim=1) 221 elif CHIEF_mode.lower() == 'rect': 222 A = torch.cat([A, G_block], dim= 1) 223 else: 224 raise RuntimeError(f"Invalid CHIEF Mode {CHIEF_mode}, should be on of [square, rect]") 225 226 return A 227 228 229 230 231def compute_A(scatterer: Mesh, k:float=Constants.k, alphas:float|Tensor = 1, betas:float|Tensor = 0, a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, BM_mode:str='fd') -> Tensor: 232 ''' 233 Computes A for the computation of H in the BEM model\n 234 :param scatterer: The mesh used (as a `vedo` `mesh` object) 235 :param k: wavenumber 236 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 237 :param smooth_distance: amount to add to distances to avoid explosion over small values 238 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 239 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 240 :param internal_points: The internal points to use for CHIEF based BEM 241 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 242 :param h: finite difference step for Burton-Miller BEM 243 :param BM_alpha: constant alpha to use in Burton-Miller BEM 244 :param BM_mode: Mode to use for Burton-Miller BEM - should be fd for finite differences 245 246 247 :return A: A tensor 248 ''' 249 250 251 252 areas = torch.tensor(scatterer.celldata["Area"], dtype=DTYPE, device=device) 253 centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 254 norms = get_normals_as_points(scatterer, permute_to_points=False) 255 256 M = centres.shape[0] 257 # if internal_points is not None: 258 # M = M + internal_points.shape[2] 259 260 if h is not None: #We want to do fin-diff BM 261 if BM_alpha is None: #We are in the grad step 262 centres = centres - h * norms.squeeze(0) 263 else: 264 if BM_mode != 'analytical': 265 Aminus = compute_A(scatterer=scatterer, k=k, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=None) 266 Aplus = compute_A(scatterer=scatterer, k=k, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=-h, BM_alpha=None) 267 268 else: #This will need to move as h is not required for analytical 269 A_grad = torch.stack(__get_G_partial(centres.unsqueeze(0).permute(0,2,1), scatterer, None, k=k), dim=1) 270 # A_grad = A_grad.permute(0,1,3,2) 271 272 n = norms.permute(0,2,1).unsqueeze(3) 273 A_norm = -1 * torch.sum(A_grad * n , dim=1) 274 275 m = centres.expand((M, M, 3)) 276 m_prime = centres.unsqueeze(1).expand((M, M, 3)) 277 278 279 partial_greens = compute_green_derivative(m.unsqueeze_(0), m_prime.unsqueeze_(0), norms, 1, M, M, a=a, c=c, k=k,smooth_distance=smooth_distance) 280 281 282 if ((type(betas) in [int, float]) and betas != 0) or (isinstance(betas, Tensor) and (betas != 0).any()): 283 green = greens(m, m_prime, k=k) * 1j * k * betas 284 partial_greens += green 285 286 A = -partial_greens * areas 287 A[:, torch.eye(M, dtype=torch.bool, device=device)] = 0.5 288 289 290 if internal_points is not None: #CHIEF 291 292 A = augment_A_CHIEF(A, internal_points, CHIEF_mode, centres, norms, areas, scatterer,k=k) 293 294 295 if BM_alpha is not None: #Burton-Miller F.D 296 297 if BM_mode == 'analytical': 298 A_grad = A_norm 299 300 else: 301 A_grad = (Aplus - Aminus) / (2*h) 302 303 304 A_grad[:, torch.eye(A_grad.shape[2], dtype=torch.bool, device=device)] = 0.5 305 A = A - BM_alpha * A_grad 306 307 A[:, torch.eye(A_grad.shape[2], dtype=torch.bool, device=device)] = 0.5 308 309 310 if ((type(alphas) in [int, float]) and alphas != 1) or (type(alphas) is Tensor and (alphas != 1).any()): 311 if type(alphas) is Tensor: 312 alphas = alphas.unsqueeze(1) 313 alphas = alphas.expand(1, M, M) 314 vecs = m_prime - m 315 angle = torch.sum(vecs * norms, dim=3) #Technically not the cosine of the angle - would need to /distance but as we only care about the sign then it doesnt matter 316 angle = angle.real 317 if type(alphas) is Tensor: 318 A[angle>0] = A[angle>0] * alphas[angle>0] 319 else: 320 A[angle>0] = A[angle>0] * alphas 321 322 return A.to(DTYPE) 323 324 325def compute_bs(scatterer: Mesh, board:Tensor, p_ref=Constants.P_ref, norms:Tensor|None=None, 326 a:Tensor=None, c:Tensor=None, k=Constants.k, internal_points:Tensor=None, h:float=None, 327 BM_alpha:complex=None, BM_mode:str='analytical', transducer_radius = Constants.radius) -> Tensor: 328 ''' 329 Computes B for the computation of H in the BEM model\n 330 :param scatterer: The mesh used (as a `vedo` `mesh` object) 331 :param board: Transducers to use 332 :param p_ref: The value to use for p_ref 333 :param norms: Tensor of normals for transduers 334 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 335 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 336 :param internal_points: The internal points to use for CHIEF based BEM 337 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 338 :param h: finite difference step for Burton-Miller BEM 339 :param BM_alpha: constant alpha to use in Burton-Miller BEM 340 :param BM_mode: Mode to use for Burton-Miller BEM - should be analytical 341 :param k: wavenumber 342 :return B: B tensor 343 ''' 344 345 if norms is None: 346 norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE) 347 348 349 centres = torch.tensor(scatterer.cell_centers().points).to(DTYPE).to(device).T.unsqueeze_(0) 350 if h is not None: #Burton-Miller F.D 351 mesh_norms = get_normals_as_points(scatterer, permute_to_points=True) 352 if BM_alpha is None: #We are in the grad step 353 centres = centres - h * mesh_norms.squeeze(0) 354 elif BM_mode != 'analytical': 355 bs_grad = compute_bs(scatterer=scatterer, board=board, p_ref=p_ref, norms=norms, a=a, c=c, k=k, internal_points=internal_points,h=h, BM_alpha=None, transducer_radius=transducer_radius) 356 357 bs = forward_model_batched(centres,board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 358 359 360 if internal_points is not None: #CHIEF 361 # F_int = forward_model_batched(internal_points.permute(0,2,1), board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 362 F_int = forward_model_batched(internal_points, board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 363 bs = torch.cat([bs, F_int], dim=1) 364 365 if a is not None: #Modified Greens function 366 f_mod = torch.sum(forward_model_batched(a,board, p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius), dim=1, keepdim=True) 367 bs += c * f_mod 368 369 370 if BM_alpha is not None: #Burton-Miller 371 372 if BM_mode == 'analytical': 373 bs_a_grad = torch.stack(forward_model_grad(centres, board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1) 374 bs_norm_grad = torch.sum(bs_a_grad * mesh_norms.unsqueeze(3), dim=1) 375 376 377 if internal_points is not None: #CHIEF 378 # int_bs_grad = torch.stack(forward_model_grad(internal_points.permute(0,2,1), board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1) 379 p_n = internal_points.shape[1] 380 M = board.shape[0] 381 # int_bs_grad = torch.zeros_like(int_bs_grad)[:,0,:] 382 int_bs_grad = torch.zeros((1,p_n,M)) 383 384 bs_norm_grad = torch.cat([bs_norm_grad,int_bs_grad], dim=1) 385 386 387 388 bs = bs - BM_alpha * bs_norm_grad 389 else: 390 bs_grad = (bs-bs_grad)/h 391 bs = bs - BM_alpha * (bs-bs_grad)/h 392 393 394 return bs 395 396 397def compute_H(scatterer: Mesh, board:Tensor ,use_LU:bool=True, use_OLS:bool = False, p_ref = Constants.P_ref, norms:Tensor|None=None, k:float=Constants.k, alphas:float|Tensor = 1, betas:float|Tensor = 0, 398 a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 399 return_components:bool=False, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius = Constants.radius, A:Tensor|None=None, bs:Tensor|None=None) -> Tensor: 400 ''' 401 Computes H for the BEM model \n 402 :param scatterer: The mesh used (as a `vedo` `mesh` object) 403 :param board: Transducers to use 404 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 405 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 406 :param p_ref: The value to use for p_ref 407 :param norms: Tensor of normals for transduers 408 :param k: wavenumber 409 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 410 :param internal_points: The internal points to use for CHIEF based BEM 411 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 412 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 413 :param smooth_distance: amount to add to distances to avoid explosion over small values 414 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 415 :param h: finite difference step for Burton-Miller BEM 416 :param BM_alpha: constant alpha to use in Burton-Miller BEM 417 :return H: H 418 ''' 419 420 # internal_points = None 421 422 centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 423 M = centres.shape[0] 424 425 if bs is None: bs = compute_bs(scatterer,board,p_ref=p_ref,norms=norms,a=a,c=c, k=k,internal_points=internal_points, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 426 427 428 if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3): 429 internal_points = internal_points.permute(0,2,1) 430 431 432 # if internal_points is not None: print(internal_points.shape) 433 434 435 436 if A is None: A = compute_A(scatterer, alphas=alphas, betas=betas, a=a, c=c, k=k,internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha) 437 438 439 # print(A.shape, torch.linalg.matrix_rank(A), A.det(), torch.linalg.cond(A)) 440 # print(A) 441 # print(A.inverse()) 442 443 if use_LU: 444 LU, pivots = torch.linalg.lu_factor(A) 445 H = torch.linalg.lu_solve(LU, pivots, bs) 446 elif use_OLS: 447 448 H = torch.linalg.lstsq(A,bs, rcond=1e-6).solution 449 else: 450 H = torch.linalg.solve(A,bs) 451 452 # H = H / (1-eta*1j) 453 # exit() 454 455 H_clipped = H[:,:M,: ] 456 457 # print(torch.linalg.eig(A)) 458 459 # exit() 460 461 # print((k*H, A@H, bs), H/bs) 462 # exit() 463 464 if return_components: return H_clipped,A,bs, H 465 466 467 return H_clipped 468 469 470 471def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 472 print_lines:bool=False, cache_name:str|None=None, p_ref = Constants.P_ref, 473 norms:Tensor|None=None, method:Literal['OLS','LU', 'INV']='LU', k:float=Constants.k,alphas:float|Tensor = 1, betas:float|Tensor = 0, 474 a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 475 CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor: 476 ''' 477 Get H using cache system. Expects a folder named BEMCache in `path`\n 478 479 :param scatterer: The mesh used (as a `vedo` `mesh` object) 480 :param board: Transducers to use 481 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 482 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 483 :param p_ref: The value to use for p_ref 484 :param norms: Tensor of normals for transduers 485 :param k: wavenumber 486 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 487 :param internal_points: The internal points to use for CHIEF based BEM 488 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 489 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 490 :param smooth_distance: amount to add to distances to avoid explosion over small values 491 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 492 :param h: finite difference step for Burton-Miller BEM 493 :param BM_alpha: constant alpha to use in Burton-Miller BEM 494 :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it 495 :param path: path to folder containing `BEMCache/ ` 496 :param print_lines: if true prints messages detaling progress 497 :param method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use `torch.linalg.solve` 498 :return H: H tensor 499 ''' 500 501 use_OLS=False 502 use_LU = False 503 504 if method == "OLS": 505 use_OLS = True 506 elif method == "LU": 507 use_LU = True 508 509 510 if use_cache_H: 511 512 if cache_name is None: 513 ps = locals() 514 ps.__delitem__('scatterer') 515 cache_name = scatterer.filename+"--" + str(ps) 516 cache_name = hashlib.md5(cache_name.encode()).hexdigest() 517 f_name = path+"/BEMCache/" + cache_name + ".bin" 518 # print(f_name) 519 520 try: 521 if print_lines: print("Trying to load H at", f_name ,"...") 522 H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE) 523 except FileNotFoundError: 524 if print_lines: print("Not found, computing H...") 525 H = compute_H(scatterer,board,use_LU=use_LU,use_OLS=use_OLS,norms=norms, k=k, alphas=alphas, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode,h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 526 try: 527 f = open(f_name,"wb") 528 except FileNotFoundError as e: 529 print(e) 530 raise FileNotFoundError("AcousTools BEM expects a directory named BEMCache inside of `path' in order to use the cache and this was not found. Check this directory exists") 531 pickle.dump(H,f) 532 f.close() 533 else: 534 if print_lines: print("Computing H...") 535 H = compute_H(scatterer,board, p_ref=p_ref,norms=norms,use_LU=use_LU,use_OLS=use_OLS, k=k, alphas=alphas, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 536 537 return H 538 539def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False, 540 H:Tensor|None=None,path:str="Media", return_components:bool=False, p_ref = Constants.P_ref, norms:Tensor|None=None, H_method:Literal['OLS','LU', 'INV']=None, 541 k:float=Constants.k, betas:float|Tensor = 0, alphas:float|Tensor=1, a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor: 542 ''' 543 Computes E in the BEM model\n 544 :param scatterer: The mesh used (as a `vedo` `mesh` object) 545 :param board: Transducers to use 546 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 547 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 548 :param p_ref: The value to use for p_ref 549 :param norms: Tensor of normals for transduers 550 :param k: wavenumber 551 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 552 :param internal_points: The internal points to use for CHIEF based BEM 553 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 554 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 555 :param smooth_distance: amount to add to distances to avoid explosion over small values 556 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 557 :param h: finite difference step for Burton-Miller BEM 558 :param BM_alpha: constant alpha to use in Burton-Miller BEM 559 :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it 560 :param path: path to folder containing `BEMCache/ ` 561 :param print_lines: if true prints messages detaling progress 562 :param method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use `torch.linalg.solve` 563 564 :return E: Propagation matrix for BEM E 565 566 ```Python 567 from acoustools.Mesh import load_scatterer 568 from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H 569 from acoustools.Utilities import create_points, TOP_BOARD 570 from acoustools.Solvers import wgs 571 from acoustools.Visualiser import Visualise 572 573 import torch 574 575 path = "../../BEMMedia" 576 scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08) 577 578 p = create_points(N=1,B=1,y=0,x=0,z=0) 579 580 H = compute_H(scatterer, TOP_BOARD) 581 E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H) 582 x = wgs(p,board=TOP_BOARD,A=E) 583 584 A = torch.tensor((-0.12,0, 0.12)) 585 B = torch.tensor((0.12,0, 0.12)) 586 C = torch.tensor((-0.12,0, -0.12)) 587 588 Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure], 589 colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}], 590 vmax=8621, show=True,res=[256,256]) 591 ``` 592 593 ''' 594 if board is None: 595 board = TOP_BOARD 596 597 if norms is None: #Transducer Norms 598 norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE) 599 600 if print_lines: print("H...") 601 602 if H is None: 603 H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines,p_ref=p_ref, 604 norms=norms, method=H_method, k=k, betas=betas, a=a, c=c, internal_points=internal_points, 605 smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius, alphas=alphas).to(DTYPE) 606 607 if print_lines: print("G...") 608 G = compute_G(points, scatterer, k=k, betas=betas,alphas=alphas, smooth_distance=smooth_distance).to(DTYPE) 609 610 if print_lines: print("F...") 611 F = forward_model_batched(points,board,p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius).to(DTYPE) 612 # if a is not None: 613 # F += c * forward_model_batched(a,board, p_ref=p_ref,norms=norms) 614 615 if print_lines: print("E...") 616 617 E = F+G@H 618 619 620 torch.cuda.empty_cache() 621 if return_components: 622 return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE) 623 return E.to(DTYPE) 624 625 626 627 628 629def __get_G_partial(points:Tensor, scatterer:Mesh, board:Tensor|None=None, return_components:bool=False, k=Constants.k) -> tuple[Tensor, Tensor, Tensor]: 630 ''' 631 @private 632 here so it can be used in Burton-Miller BEM above - not ideal for it to be here so this should be refactored at some point 633 Computes gradient of the G matrix in BEM \n 634 :param points: Points to propagate to 635 :param scatterer: The mesh used (as a `vedo` `mesh` object) 636 :param board: Ignored 637 :param return_components: if true will return the subparts used to compute 638 :return: Gradient of the G matrix in BEM 639 ''' 640 #Bk3. Pg. 26 641 # if board is None: 642 # board = TRANSDUCERS 643 644 areas = get_areas(scatterer) 645 centres = get_centres_as_points(scatterer) 646 normals = get_normals_as_points(scatterer) 647 648 649 N = points.shape[2] 650 M = centres.shape[2] 651 652 653 # points = points.unsqueeze(3).expand(-1,-1,-1,M) 654 # centres = centres.unsqueeze(2).expand(-1,-1,N,-1) 655 points = points.unsqueeze(3) # [B, 3, N, 1] 656 centres = centres.unsqueeze(2) # [B, 3, 1, M] 657 658 diff = (points - centres) 659 diff_square = diff**2 660 distances = torch.sqrt(torch.sum(diff_square, 1)) 661 distances_expanded = distances.unsqueeze(1)#.expand((1,3,N,M)) 662 distances_expanded_square = distances_expanded**2 663 distances_expanded_cube = distances_expanded**3 664 665 # G = e^(ikd) / 4pi d 666 G = areas * torch.exp(1j * k * distances_expanded) / (4*3.1415*distances_expanded) 667 668 #Ga = [i*da * e^{ikd} * (kd+i) / 4pi d^2] 669 670 #d = distance 671 #da = -(at - a)^2 / d 672 da = diff / distances_expanded 673 kd = k * distances_expanded 674 phase = torch.exp(1j*kd) 675 Ga = areas * ( (1j*da*phase * (kd + 1j))/ (4*3.1415*distances_expanded_square)) 676 677 #P = (ik - 1/d) 678 P = (1j*k - 1/distances_expanded) 679 #Pa = da / d^2 = (diff / d^2) /d 680 Pa = diff / distances_expanded_cube 681 682 #C = (diff \cdot normals) / distances 683 684 nx = normals[:,0] 685 ny = normals[:,1] 686 nz = normals[:,2] 687 688 dx = diff[:,0,:] 689 dy = diff[:,1,:] 690 dz = diff[:,2,:] 691 692 n_dot_d = nx*dx + ny*dy + nz*dz 693 694 C = (n_dot_d) / distances 695 696 697 distance_square = distances**2 698 699 700 Cx = 1/distance_square * (nx * distances - (n_dot_d * dx) / distances) 701 Cy = 1/distance_square * (ny * distances - (n_dot_d * dy) / distances) 702 Cz = 1/distance_square * (nz * distances - (n_dot_d * dz) / distances) 703 704 Cx.unsqueeze_(1) 705 Cy.unsqueeze_(1) 706 Cz.unsqueeze_(1) 707 708 Ca = torch.cat([Cx, Cy, Cz],axis=1) 709 710 grad_G = Ga*P*C + G*P*Ca + G*Pa*C 711 712 grad_G = -grad_G.to(DTYPE) 713 714 if return_components: 715 return grad_G[:,0,:], grad_G[:,1,:], grad_G[:,2,:], G,P,C,Ga,Pa, Ca 716 717 return grad_G[:,0,:], grad_G[:,1,:], grad_G[:,2,:]
def
compute_green_derivative( y: torch.Tensor, x: torch.Tensor, norms: torch.Tensor, B: int, N: int, M: int, return_components: bool = False, a: torch.Tensor = None, c: torch.Tensor = None, k=732.7329804081634, smooth_distance: float = 0) -> torch.Tensor:
23def compute_green_derivative(y:Tensor,x:Tensor,norms:Tensor,B:int,N:int,M:int, return_components:bool=False, 24 a:Tensor=None, c:Tensor=None, k=Constants.k, smooth_distance:float=0) -> Tensor: 25 ''' 26 Computes the derivative of greens function \n 27 :param y: y in greens function - location of the source of sound 28 :param x: x in greens function - location of the point to be propagated to 29 :param norms: norms to y 30 :param B: Batch dimension 31 :param N: size of x 32 :param M: size of y 33 :param return_components: if true will return the subparts used to compute the derivative \n 34 :param k: Wavenumber to use 35 :param smooth_distance: amount to add to distances to avoid explosion over small values 36 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 37 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 38 :return: returns the partial derivative of greeens fucntion wrt y 39 ''' 40 norms= norms.real 41 42 vecs = y.real-x.real 43 44 45 distance = torch.sqrt(torch.sum((vecs)**2,dim=3)) 46 47 if a is None: #Were computing with a OR we have no a to begin with 48 if len(vecs.shape) > 4: #Vecs isnt expanded - we must never have had an a 49 norms = norms.unsqueeze(4).expand(B,N,-1,-1,1) 50 else: #vecs included a 51 norms = norms.expand(B,N,-1,-1) 52 else: 53 norms = norms.expand(B,N,-1,-1) 54 55 56 # norm_norms = torch.norm(norms,2,dim=3) # === 1x 57 # vec_norms = torch.norm(vecs,2,dim=3) # === distance? 58 # print(vec_norms == distance) 59 angles = (torch.sum(norms*vecs,3) / (distance)) 60 61 # del norms, vecs 62 torch.cuda.empty_cache() 63 64 # distance = distance + smooth_distance 65 distance = distance.clamp_min(smooth_distance) 66 67 68 A = 1 * greens(y,x,distance=distance,k=k) 69 ik_d = (1j*k - 1/(distance)) 70 71 del distance 72 # torch.cuda.empty_cache() 73 74 partial_greens = A*ik_d*angles 75 76 # if not return_components: 77 # del A,B,angles 78 torch.cuda.empty_cache() 79 80 81 82 if a is not None: 83 n_a = a.shape[2] 84 # a = a.permute(0,2,1) 85 a = a.unsqueeze(1).unsqueeze(2) 86 a = a.expand(B,N,M,3,n_a).clone() 87 y = y.unsqueeze(4).expand(B,N,M,3,n_a) 88 g_mod = torch.sum(c*compute_green_derivative(y, a, norms, B, N, M,k=k),dim=3) #Allow for multiple a's 89 partial_greens += g_mod 90 91 92 partial_greens[partial_greens.isnan()] = 0 93 if return_components: 94 return partial_greens, A,ik_d,angles 95 96 97 return partial_greens
Computes the derivative of greens function
Parameters
- y: y in greens function - location of the source of sound
- x: x in greens function - location of the point to be propagated to
- norms: norms to y
- B: Batch dimension
- N: size of x
- M: size of y
return_components: if true will return the subparts used to compute the derivative
k: Wavenumber to use
- smooth_distance: amount to add to distances to avoid explosion over small values
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details)
Returns
returns the partial derivative of greeens fucntion wrt y
def
greens( y: torch.Tensor, x: torch.Tensor, k: float = 732.7329804081634, distance=None):
99def greens(y:Tensor,x:Tensor, k:float=Constants.k, distance=None): 100 ''' 101 Computes greens function for a source at y and a point at x\n 102 :param y: source location 103 :param x: point location 104 :param k: wavenumber 105 :param distance: precomputed distances from y->x 106 :returns greens function: 107 ''' 108 if distance is None: 109 vecs = y.real-x.real 110 distance = torch.sqrt(torch.sum((vecs)**2,dim=3)) 111 green = torch.exp(1j*k*distance) / (4*Constants.pi*distance) 112 113 return green
Computes greens function for a source at y and a point at x
Parameters
- y: source location
- x: point location
- k: wavenumber
- distance: precomputed distances from y->x :returns greens function:
def
compute_G( points: torch.Tensor, scatterer: vedo.mesh.core.Mesh, k: float = 732.7329804081634, alphas: float | torch.Tensor = 1, betas: float | torch.Tensor = 0, a: torch.Tensor = None, c: torch.Tensor = None, smooth_distance: float = 0) -> torch.Tensor:
115def compute_G(points: Tensor, scatterer: Mesh, k:float=Constants.k, alphas:float|Tensor=1, betas:float|Tensor = 0, a:Tensor=None, c:Tensor=None, smooth_distance:float=0) -> Tensor: 116 ''' 117 Computes G in the BEM model\n 118 :param points: The points to propagate to 119 :param scatterer: The mesh used (as a `vedo` `mesh` object) 120 :param k: wavenumber 121 :param alphas: Absorbance of each element, can be Tensor for element-wise attribution or a number for all elements. If Tensor, should have shape [B,M] where M is the mesh size, B is the batch size and will normally be 1 122 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 123 :param smooth_distance: amount to add to distances to avoid explosion over small values 124 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 125 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 126 :return G: `torch.Tensor` of G 127 ''' 128 torch.cuda.empty_cache() 129 areas = torch.Tensor(scatterer.celldata["Area"]).to(device).real 130 B = points.shape[0] 131 N = points.shape[2] 132 M = areas.shape[0] 133 areas = areas.expand((B,N,-1)) 134 135 #Compute the partial derivative of Green's Function 136 137 #Firstly compute the distances from mesh points -> control points 138 centres = torch.tensor(scatterer.cell_centers().points).to(device).real #Uses centre points as position of mesh 139 centres = centres.expand((B,N,-1,-1)) 140 141 # print(points.shape) 142 # p = torch.reshape(points,(B,N,3)) 143 p = torch.permute(points,(0,2,1)).real 144 p = torch.unsqueeze(p,2).expand((-1,-1,M,-1)) 145 146 #Compute cosine of angle between mesh normal and point 147 # scatterer.compute_normals() 148 # norms = torch.tensor(scatterer.cell_normals).to(device) 149 norms = get_normals_as_points(scatterer,permute_to_points=False).real.expand((B,N,-1,-1)) 150 151 # centres_p = get_centres_as_points(scatterer) 152 partial_greens = compute_green_derivative(centres,p,norms, B,N,M, a=a, c=c, k=k,smooth_distance=smooth_distance ) 153 154 if ((type(betas) in [int, float]) and betas != 0) or (type(betas) is Tensor and (betas != 0).any()): #Either β non 0 and type(β) is number or β is Tensor and any elemenets non 0 155 green = greens(centres, p, k=k) * 1j * k * betas 156 partial_greens += green 157 158 G = areas * partial_greens 159 160 161 if ((type(alphas) in [int, float]) and alphas != 1) or (type(alphas) is Tensor and (alphas != 1).any()): 162 #Does this need to be in A too? 163 if type(alphas) is Tensor: 164 alphas = alphas.unsqueeze(1) 165 alphas = alphas.expand(B, N, M) 166 vecs = p - centres 167 angle = torch.sum(vecs * norms, dim=3) #Technically not the cosine of the angle - would need to /distance but as we only care about the sign then it doesnt matter 168 angle = angle.real 169 if type(alphas) is Tensor: 170 G[angle>0] = G[angle>0] * alphas[angle>0] 171 else: 172 G[angle>0] = G[angle>0] * alphas 173 174 return G.to(DTYPE)
Computes G in the BEM model
Parameters
- points: The points to propagate to
- scatterer: The mesh used (as a
vedomeshobject) - k: wavenumber
- alphas: Absorbance of each element, can be Tensor for element-wise attribution or a number for all elements. If Tensor, should have shape [B,M] where M is the mesh size, B is the batch size and will normally be 1
- betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements
- smooth_distance: amount to add to distances to avoid explosion over small values
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details)
Returns
torch.Tensorof G
def
augment_A_CHIEF( A: torch.Tensor, internal_points: torch.Tensor, CHIEF_mode: Literal['square', 'rect'] = 'square', centres: torch.Tensor = None, norms: torch.Tensor = None, areas: torch.Tensor = None, scatterer: vedo.mesh.core.Mesh = None, k: float = 732.7329804081634):
178def augment_A_CHIEF(A:Tensor, internal_points:Tensor, CHIEF_mode:Literal['square', 'rect'] = 'square', centres:Tensor=None, norms:Tensor=None, areas:Tensor=None, scatterer:Mesh=None, k:float=Constants.k): 179 ''' 180 Augments an A matrix with CHIEF BEM equations\n 181 182 :param A: A matrix 183 :param internal_points: The internal points to use for CHIEF based BEM 184 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 185 :param centres: mesh centres 186 :param norms: mesh normals 187 :param areas: mesh aeras 188 :param scatterer: mesh 189 :param k: wavenumber 190 ''' 191 # if internal_points is not None: print(internal_points.shape) 192 193 if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3): 194 internal_points = internal_points.permute(0,2,1) 195 196 if areas is None: areas = torch.tensor(scatterer.celldata["Area"], dtype=DTYPE, device=device) 197 if centres is None: centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 198 if norms is None: norms = get_normals_as_points(scatterer, permute_to_points=False) 199 200 201 202 P = internal_points.shape[1] 203 M = centres.shape[0] 204 205 m_int = centres.unsqueeze(0).unsqueeze(1) 206 int_p = internal_points.unsqueeze(2) 207 # G_block = greens(m_int,int_p, k=k) 208 209 int_norms = norms.unsqueeze(1) 210 G_block = -compute_green_derivative(m_int,int_p, int_norms, 1, P, M, k=k) 211 G_block = G_block * areas[None,None,:] 212 213 G_block_t = G_block.mT 214 zero_block = torch.zeros((1, P, P), device=device, dtype=DTYPE) 215 216 217 # return torch.cat((A, G_block), dim=1) 218 if CHIEF_mode.lower() == 'square': 219 A_aux = torch.cat((A, G_block_t), dim=2) 220 221 GtZ = torch.cat((G_block, zero_block), dim=2) 222 A = torch.cat((A_aux, GtZ), dim=1) 223 elif CHIEF_mode.lower() == 'rect': 224 A = torch.cat([A, G_block], dim= 1) 225 else: 226 raise RuntimeError(f"Invalid CHIEF Mode {CHIEF_mode}, should be on of [square, rect]") 227 228 return A
Augments an A matrix with CHIEF BEM equations
Parameters
- A: A matrix
- internal_points: The internal points to use for CHIEF based BEM
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- centres: mesh centres
- norms: mesh normals
- areas: mesh aeras
- scatterer: mesh
- k: wavenumber
def
compute_A( scatterer: vedo.mesh.core.Mesh, k: float = 732.7329804081634, alphas: float | torch.Tensor = 1, betas: float | torch.Tensor = 0, a: torch.Tensor = None, c: torch.Tensor = None, internal_points: torch.Tensor = None, smooth_distance: float = 0, CHIEF_mode: Literal['square', 'rect'] = 'square', h: float = None, BM_alpha: complex = None, BM_mode: str = 'fd') -> torch.Tensor:
233def compute_A(scatterer: Mesh, k:float=Constants.k, alphas:float|Tensor = 1, betas:float|Tensor = 0, a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, BM_mode:str='fd') -> Tensor: 234 ''' 235 Computes A for the computation of H in the BEM model\n 236 :param scatterer: The mesh used (as a `vedo` `mesh` object) 237 :param k: wavenumber 238 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 239 :param smooth_distance: amount to add to distances to avoid explosion over small values 240 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 241 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 242 :param internal_points: The internal points to use for CHIEF based BEM 243 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 244 :param h: finite difference step for Burton-Miller BEM 245 :param BM_alpha: constant alpha to use in Burton-Miller BEM 246 :param BM_mode: Mode to use for Burton-Miller BEM - should be fd for finite differences 247 248 249 :return A: A tensor 250 ''' 251 252 253 254 areas = torch.tensor(scatterer.celldata["Area"], dtype=DTYPE, device=device) 255 centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 256 norms = get_normals_as_points(scatterer, permute_to_points=False) 257 258 M = centres.shape[0] 259 # if internal_points is not None: 260 # M = M + internal_points.shape[2] 261 262 if h is not None: #We want to do fin-diff BM 263 if BM_alpha is None: #We are in the grad step 264 centres = centres - h * norms.squeeze(0) 265 else: 266 if BM_mode != 'analytical': 267 Aminus = compute_A(scatterer=scatterer, k=k, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=None) 268 Aplus = compute_A(scatterer=scatterer, k=k, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=-h, BM_alpha=None) 269 270 else: #This will need to move as h is not required for analytical 271 A_grad = torch.stack(__get_G_partial(centres.unsqueeze(0).permute(0,2,1), scatterer, None, k=k), dim=1) 272 # A_grad = A_grad.permute(0,1,3,2) 273 274 n = norms.permute(0,2,1).unsqueeze(3) 275 A_norm = -1 * torch.sum(A_grad * n , dim=1) 276 277 m = centres.expand((M, M, 3)) 278 m_prime = centres.unsqueeze(1).expand((M, M, 3)) 279 280 281 partial_greens = compute_green_derivative(m.unsqueeze_(0), m_prime.unsqueeze_(0), norms, 1, M, M, a=a, c=c, k=k,smooth_distance=smooth_distance) 282 283 284 if ((type(betas) in [int, float]) and betas != 0) or (isinstance(betas, Tensor) and (betas != 0).any()): 285 green = greens(m, m_prime, k=k) * 1j * k * betas 286 partial_greens += green 287 288 A = -partial_greens * areas 289 A[:, torch.eye(M, dtype=torch.bool, device=device)] = 0.5 290 291 292 if internal_points is not None: #CHIEF 293 294 A = augment_A_CHIEF(A, internal_points, CHIEF_mode, centres, norms, areas, scatterer,k=k) 295 296 297 if BM_alpha is not None: #Burton-Miller F.D 298 299 if BM_mode == 'analytical': 300 A_grad = A_norm 301 302 else: 303 A_grad = (Aplus - Aminus) / (2*h) 304 305 306 A_grad[:, torch.eye(A_grad.shape[2], dtype=torch.bool, device=device)] = 0.5 307 A = A - BM_alpha * A_grad 308 309 A[:, torch.eye(A_grad.shape[2], dtype=torch.bool, device=device)] = 0.5 310 311 312 if ((type(alphas) in [int, float]) and alphas != 1) or (type(alphas) is Tensor and (alphas != 1).any()): 313 if type(alphas) is Tensor: 314 alphas = alphas.unsqueeze(1) 315 alphas = alphas.expand(1, M, M) 316 vecs = m_prime - m 317 angle = torch.sum(vecs * norms, dim=3) #Technically not the cosine of the angle - would need to /distance but as we only care about the sign then it doesnt matter 318 angle = angle.real 319 if type(alphas) is Tensor: 320 A[angle>0] = A[angle>0] * alphas[angle>0] 321 else: 322 A[angle>0] = A[angle>0] * alphas 323 324 return A.to(DTYPE)
Computes A for the computation of H in the BEM model
Parameters
- scatterer: The mesh used (as a
vedomeshobject) - k: wavenumber
- betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements
- smooth_distance: amount to add to distances to avoid explosion over small values
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - internal_points: The internal points to use for CHIEF based BEM
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- h: finite difference step for Burton-Miller BEM
- BM_alpha: constant alpha to use in Burton-Miller BEM
- BM_mode: Mode to use for Burton-Miller BEM - should be fd for finite differences
Returns
A tensor
def
compute_bs( scatterer: vedo.mesh.core.Mesh, board: torch.Tensor, p_ref=3.4000000000000004, norms: torch.Tensor | None = None, a: torch.Tensor = None, c: torch.Tensor = None, k=732.7329804081634, internal_points: torch.Tensor = None, h: float = None, BM_alpha: complex = None, BM_mode: str = 'analytical', transducer_radius=0.0045) -> torch.Tensor:
327def compute_bs(scatterer: Mesh, board:Tensor, p_ref=Constants.P_ref, norms:Tensor|None=None, 328 a:Tensor=None, c:Tensor=None, k=Constants.k, internal_points:Tensor=None, h:float=None, 329 BM_alpha:complex=None, BM_mode:str='analytical', transducer_radius = Constants.radius) -> Tensor: 330 ''' 331 Computes B for the computation of H in the BEM model\n 332 :param scatterer: The mesh used (as a `vedo` `mesh` object) 333 :param board: Transducers to use 334 :param p_ref: The value to use for p_ref 335 :param norms: Tensor of normals for transduers 336 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 337 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 338 :param internal_points: The internal points to use for CHIEF based BEM 339 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 340 :param h: finite difference step for Burton-Miller BEM 341 :param BM_alpha: constant alpha to use in Burton-Miller BEM 342 :param BM_mode: Mode to use for Burton-Miller BEM - should be analytical 343 :param k: wavenumber 344 :return B: B tensor 345 ''' 346 347 if norms is None: 348 norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE) 349 350 351 centres = torch.tensor(scatterer.cell_centers().points).to(DTYPE).to(device).T.unsqueeze_(0) 352 if h is not None: #Burton-Miller F.D 353 mesh_norms = get_normals_as_points(scatterer, permute_to_points=True) 354 if BM_alpha is None: #We are in the grad step 355 centres = centres - h * mesh_norms.squeeze(0) 356 elif BM_mode != 'analytical': 357 bs_grad = compute_bs(scatterer=scatterer, board=board, p_ref=p_ref, norms=norms, a=a, c=c, k=k, internal_points=internal_points,h=h, BM_alpha=None, transducer_radius=transducer_radius) 358 359 bs = forward_model_batched(centres,board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 360 361 362 if internal_points is not None: #CHIEF 363 # F_int = forward_model_batched(internal_points.permute(0,2,1), board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 364 F_int = forward_model_batched(internal_points, board, p_ref=p_ref,norms=norms,k=k, transducer_radius=transducer_radius) 365 bs = torch.cat([bs, F_int], dim=1) 366 367 if a is not None: #Modified Greens function 368 f_mod = torch.sum(forward_model_batched(a,board, p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius), dim=1, keepdim=True) 369 bs += c * f_mod 370 371 372 if BM_alpha is not None: #Burton-Miller 373 374 if BM_mode == 'analytical': 375 bs_a_grad = torch.stack(forward_model_grad(centres, board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1) 376 bs_norm_grad = torch.sum(bs_a_grad * mesh_norms.unsqueeze(3), dim=1) 377 378 379 if internal_points is not None: #CHIEF 380 # int_bs_grad = torch.stack(forward_model_grad(internal_points.permute(0,2,1), board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1) 381 p_n = internal_points.shape[1] 382 M = board.shape[0] 383 # int_bs_grad = torch.zeros_like(int_bs_grad)[:,0,:] 384 int_bs_grad = torch.zeros((1,p_n,M)) 385 386 bs_norm_grad = torch.cat([bs_norm_grad,int_bs_grad], dim=1) 387 388 389 390 bs = bs - BM_alpha * bs_norm_grad 391 else: 392 bs_grad = (bs-bs_grad)/h 393 bs = bs - BM_alpha * (bs-bs_grad)/h 394 395 396 return bs
Computes B for the computation of H in the BEM model
Parameters
- scatterer: The mesh used (as a
vedomeshobject) - board: Transducers to use
- p_ref: The value to use for p_ref
- norms: Tensor of normals for transduers
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - internal_points: The internal points to use for CHIEF based BEM
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- h: finite difference step for Burton-Miller BEM
- BM_alpha: constant alpha to use in Burton-Miller BEM
- BM_mode: Mode to use for Burton-Miller BEM - should be analytical
- k: wavenumber
Returns
B tensor
def
compute_H( scatterer: vedo.mesh.core.Mesh, board: torch.Tensor, use_LU: bool = True, use_OLS: bool = False, p_ref=3.4000000000000004, norms: torch.Tensor | None = None, k: float = 732.7329804081634, alphas: float | torch.Tensor = 1, betas: float | torch.Tensor = 0, a: torch.Tensor = None, c: torch.Tensor = None, internal_points: torch.Tensor = None, smooth_distance: float = 0, return_components: bool = False, CHIEF_mode: Literal['square', 'rect'] = 'square', h: float = None, BM_alpha: complex = None, transducer_radius=0.0045, A: torch.Tensor | None = None, bs: torch.Tensor | None = None) -> torch.Tensor:
399def compute_H(scatterer: Mesh, board:Tensor ,use_LU:bool=True, use_OLS:bool = False, p_ref = Constants.P_ref, norms:Tensor|None=None, k:float=Constants.k, alphas:float|Tensor = 1, betas:float|Tensor = 0, 400 a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 401 return_components:bool=False, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius = Constants.radius, A:Tensor|None=None, bs:Tensor|None=None) -> Tensor: 402 ''' 403 Computes H for the BEM model \n 404 :param scatterer: The mesh used (as a `vedo` `mesh` object) 405 :param board: Transducers to use 406 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 407 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 408 :param p_ref: The value to use for p_ref 409 :param norms: Tensor of normals for transduers 410 :param k: wavenumber 411 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 412 :param internal_points: The internal points to use for CHIEF based BEM 413 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 414 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 415 :param smooth_distance: amount to add to distances to avoid explosion over small values 416 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 417 :param h: finite difference step for Burton-Miller BEM 418 :param BM_alpha: constant alpha to use in Burton-Miller BEM 419 :return H: H 420 ''' 421 422 # internal_points = None 423 424 centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device) 425 M = centres.shape[0] 426 427 if bs is None: bs = compute_bs(scatterer,board,p_ref=p_ref,norms=norms,a=a,c=c, k=k,internal_points=internal_points, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 428 429 430 if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3): 431 internal_points = internal_points.permute(0,2,1) 432 433 434 # if internal_points is not None: print(internal_points.shape) 435 436 437 438 if A is None: A = compute_A(scatterer, alphas=alphas, betas=betas, a=a, c=c, k=k,internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha) 439 440 441 # print(A.shape, torch.linalg.matrix_rank(A), A.det(), torch.linalg.cond(A)) 442 # print(A) 443 # print(A.inverse()) 444 445 if use_LU: 446 LU, pivots = torch.linalg.lu_factor(A) 447 H = torch.linalg.lu_solve(LU, pivots, bs) 448 elif use_OLS: 449 450 H = torch.linalg.lstsq(A,bs, rcond=1e-6).solution 451 else: 452 H = torch.linalg.solve(A,bs) 453 454 # H = H / (1-eta*1j) 455 # exit() 456 457 H_clipped = H[:,:M,: ] 458 459 # print(torch.linalg.eig(A)) 460 461 # exit() 462 463 # print((k*H, A@H, bs), H/bs) 464 # exit() 465 466 if return_components: return H_clipped,A,bs, H 467 468 469 return H_clipped
Computes H for the BEM model
Parameters
- scatterer: The mesh used (as a
vedomeshobject) - board: Transducers to use
- use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
- use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
- p_ref: The value to use for p_ref
- norms: Tensor of normals for transduers
- k: wavenumber
- betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements
- internal_points: The internal points to use for CHIEF based BEM
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - smooth_distance: amount to add to distances to avoid explosion over small values
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- h: finite difference step for Burton-Miller BEM
- BM_alpha: constant alpha to use in Burton-Miller BEM
Returns
H
def
get_cache_or_compute_H( scatterer: vedo.mesh.core.Mesh, board, use_cache_H: bool = True, path: str = 'Media', print_lines: bool = False, cache_name: str | None = None, p_ref=3.4000000000000004, norms: torch.Tensor | None = None, method: Literal['OLS', 'LU', 'INV'] = 'LU', k: float = 732.7329804081634, alphas: float | torch.Tensor = 1, betas: float | torch.Tensor = 0, a: torch.Tensor = None, c: torch.Tensor = None, internal_points: torch.Tensor = None, smooth_distance: float = 0, CHIEF_mode: Literal['square', 'rect'] = 'square', h: float = None, BM_alpha: complex = None, transducer_radius=0.0045) -> torch.Tensor:
473def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 474 print_lines:bool=False, cache_name:str|None=None, p_ref = Constants.P_ref, 475 norms:Tensor|None=None, method:Literal['OLS','LU', 'INV']='LU', k:float=Constants.k,alphas:float|Tensor = 1, betas:float|Tensor = 0, 476 a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 477 CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor: 478 ''' 479 Get H using cache system. Expects a folder named BEMCache in `path`\n 480 481 :param scatterer: The mesh used (as a `vedo` `mesh` object) 482 :param board: Transducers to use 483 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 484 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 485 :param p_ref: The value to use for p_ref 486 :param norms: Tensor of normals for transduers 487 :param k: wavenumber 488 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 489 :param internal_points: The internal points to use for CHIEF based BEM 490 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 491 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 492 :param smooth_distance: amount to add to distances to avoid explosion over small values 493 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 494 :param h: finite difference step for Burton-Miller BEM 495 :param BM_alpha: constant alpha to use in Burton-Miller BEM 496 :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it 497 :param path: path to folder containing `BEMCache/ ` 498 :param print_lines: if true prints messages detaling progress 499 :param method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use `torch.linalg.solve` 500 :return H: H tensor 501 ''' 502 503 use_OLS=False 504 use_LU = False 505 506 if method == "OLS": 507 use_OLS = True 508 elif method == "LU": 509 use_LU = True 510 511 512 if use_cache_H: 513 514 if cache_name is None: 515 ps = locals() 516 ps.__delitem__('scatterer') 517 cache_name = scatterer.filename+"--" + str(ps) 518 cache_name = hashlib.md5(cache_name.encode()).hexdigest() 519 f_name = path+"/BEMCache/" + cache_name + ".bin" 520 # print(f_name) 521 522 try: 523 if print_lines: print("Trying to load H at", f_name ,"...") 524 H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE) 525 except FileNotFoundError: 526 if print_lines: print("Not found, computing H...") 527 H = compute_H(scatterer,board,use_LU=use_LU,use_OLS=use_OLS,norms=norms, k=k, alphas=alphas, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode,h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 528 try: 529 f = open(f_name,"wb") 530 except FileNotFoundError as e: 531 print(e) 532 raise FileNotFoundError("AcousTools BEM expects a directory named BEMCache inside of `path' in order to use the cache and this was not found. Check this directory exists") 533 pickle.dump(H,f) 534 f.close() 535 else: 536 if print_lines: print("Computing H...") 537 H = compute_H(scatterer,board, p_ref=p_ref,norms=norms,use_LU=use_LU,use_OLS=use_OLS, k=k, alphas=alphas, betas=betas, a=a, c=c, internal_points=internal_points, smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius) 538 539 return H
Get H using cache system. Expects a folder named BEMCache in path
Parameters
- scatterer: The mesh used (as a
vedomeshobject) - board: Transducers to use
- use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
- use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
- p_ref: The value to use for p_ref
- norms: Tensor of normals for transduers
- k: wavenumber
- betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements
- internal_points: The internal points to use for CHIEF based BEM
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - smooth_distance: amount to add to distances to avoid explosion over small values
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- h: finite difference step for Burton-Miller BEM
- BM_alpha: constant alpha to use in Burton-Miller BEM
- use_cache_H: If true uses the cache system, otherwise computes H and does not save it
- path: path to folder containing
BEMCache/ - print_lines: if true prints messages detaling progress
- method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use
torch.linalg.solve
Returns
H tensor
def
compute_E( scatterer: vedo.mesh.core.Mesh, points: torch.Tensor, board: torch.Tensor | None = None, use_cache_H: bool = True, print_lines: bool = False, H: torch.Tensor | None = None, path: str = 'Media', return_components: bool = False, p_ref=3.4000000000000004, norms: torch.Tensor | None = None, H_method: Literal['OLS', 'LU', 'INV'] = None, k: float = 732.7329804081634, betas: float | torch.Tensor = 0, alphas: float | torch.Tensor = 1, a: torch.Tensor = None, c: torch.Tensor = None, internal_points: torch.Tensor = None, smooth_distance: float = 0, CHIEF_mode: Literal['square', 'rect'] = 'square', h: float = None, BM_alpha: complex = None, transducer_radius=0.0045) -> torch.Tensor:
541def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False, 542 H:Tensor|None=None,path:str="Media", return_components:bool=False, p_ref = Constants.P_ref, norms:Tensor|None=None, H_method:Literal['OLS','LU', 'INV']=None, 543 k:float=Constants.k, betas:float|Tensor = 0, alphas:float|Tensor=1, a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor: 544 ''' 545 Computes E in the BEM model\n 546 :param scatterer: The mesh used (as a `vedo` `mesh` object) 547 :param board: Transducers to use 548 :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion 549 :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion 550 :param p_ref: The value to use for p_ref 551 :param norms: Tensor of normals for transduers 552 :param k: wavenumber 553 :param betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements 554 :param internal_points: The internal points to use for CHIEF based BEM 555 :param a: position to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 556 :param c: constant to use for a modified-green's function based BEM formulation (see (18) in `Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equation` for more details) 557 :param smooth_distance: amount to add to distances to avoid explosion over small values 558 :param CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner) 559 :param h: finite difference step for Burton-Miller BEM 560 :param BM_alpha: constant alpha to use in Burton-Miller BEM 561 :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it 562 :param path: path to folder containing `BEMCache/ ` 563 :param print_lines: if true prints messages detaling progress 564 :param method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use `torch.linalg.solve` 565 566 :return E: Propagation matrix for BEM E 567 568 ```Python 569 from acoustools.Mesh import load_scatterer 570 from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H 571 from acoustools.Utilities import create_points, TOP_BOARD 572 from acoustools.Solvers import wgs 573 from acoustools.Visualiser import Visualise 574 575 import torch 576 577 path = "../../BEMMedia" 578 scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08) 579 580 p = create_points(N=1,B=1,y=0,x=0,z=0) 581 582 H = compute_H(scatterer, TOP_BOARD) 583 E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H) 584 x = wgs(p,board=TOP_BOARD,A=E) 585 586 A = torch.tensor((-0.12,0, 0.12)) 587 B = torch.tensor((0.12,0, 0.12)) 588 C = torch.tensor((-0.12,0, -0.12)) 589 590 Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure], 591 colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}], 592 vmax=8621, show=True,res=[256,256]) 593 ``` 594 595 ''' 596 if board is None: 597 board = TOP_BOARD 598 599 if norms is None: #Transducer Norms 600 norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE) 601 602 if print_lines: print("H...") 603 604 if H is None: 605 H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines,p_ref=p_ref, 606 norms=norms, method=H_method, k=k, betas=betas, a=a, c=c, internal_points=internal_points, 607 smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius, alphas=alphas).to(DTYPE) 608 609 if print_lines: print("G...") 610 G = compute_G(points, scatterer, k=k, betas=betas,alphas=alphas, smooth_distance=smooth_distance).to(DTYPE) 611 612 if print_lines: print("F...") 613 F = forward_model_batched(points,board,p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius).to(DTYPE) 614 # if a is not None: 615 # F += c * forward_model_batched(a,board, p_ref=p_ref,norms=norms) 616 617 if print_lines: print("E...") 618 619 E = F+G@H 620 621 622 torch.cuda.empty_cache() 623 if return_components: 624 return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE) 625 return E.to(DTYPE)
Computes E in the BEM model
Parameters
- scatterer: The mesh used (as a
vedomeshobject) - board: Transducers to use
- use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
- use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
- p_ref: The value to use for p_ref
- norms: Tensor of normals for transduers
- k: wavenumber
- betas: Ratio of impedances of medium and scattering material for each element, can be Tensor for element-wise attribution or a number for all elements
- internal_points: The internal points to use for CHIEF based BEM
- a: position to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - c: constant to use for a modified-green's function based BEM formulation (see (18) in
Eliminating the fictitious frequency problem in BEM solutions of the external Helmholtz equationfor more details) - smooth_distance: amount to add to distances to avoid explosion over small values
- CHIEF_mode: Mode for CHIEF -> augment A to be either rectangular (only appended to one axis) or square (appended to both with a 0 block in the corner)
- h: finite difference step for Burton-Miller BEM
- BM_alpha: constant alpha to use in Burton-Miller BEM
- use_cache_H: If true uses the cache system, otherwise computes H and does not save it
- path: path to folder containing
BEMCache/ - print_lines: if true prints messages detaling progress
- method: Method to use to compute H: One of OLS (Least Squares), LU. (LU decomposition). If INV (or anything else) will use
torch.linalg.solve
Returns
Propagation matrix for BEM E
from acoustools.Mesh import load_scatterer
from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H
from acoustools.Utilities import create_points, TOP_BOARD
from acoustools.Solvers import wgs
from acoustools.Visualiser import Visualise
import torch
path = "../../BEMMedia"
scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08)
p = create_points(N=1,B=1,y=0,x=0,z=0)
H = compute_H(scatterer, TOP_BOARD)
E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H)
x = wgs(p,board=TOP_BOARD,A=E)
A = torch.tensor((-0.12,0, 0.12))
B = torch.tensor((0.12,0, 0.12))
C = torch.tensor((-0.12,0, -0.12))
Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure],
colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}],
vmax=8621, show=True,res=[256,256])