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
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        bs = torch.cat([bs, F_int], dim=1)
363    
364    if a is not None: #Modified Greens function
365        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)
366        bs += c * f_mod
367    
368    
369    if BM_alpha is not None: #Burton-Miller
370        
371        if BM_mode == 'analytical': 
372            bs_a_grad = torch.stack(forward_model_grad(centres, board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1)
373            bs_norm_grad = torch.sum(bs_a_grad * mesh_norms.unsqueeze(3), dim=1)
374
375
376            if internal_points is not None: #CHIEF
377                # 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)
378                p_n = internal_points.shape[1]
379                M = board.shape[0]
380                # int_bs_grad = torch.zeros_like(int_bs_grad)[:,0,:]
381                int_bs_grad = torch.zeros((1,p_n,M))
382
383                bs_norm_grad = torch.cat([bs_norm_grad,int_bs_grad], dim=1)
384
385
386            
387            bs = bs - BM_alpha * bs_norm_grad
388        else:
389            bs_grad = (bs-bs_grad)/h
390            bs = bs - BM_alpha * (bs-bs_grad)/h
391
392
393    return bs   
394
395 
396def 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, 
397              a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 
398              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:
399    '''
400    Computes H for the BEM model \n
401    :param scatterer: The mesh used (as a `vedo` `mesh` object)
402    :param board: Transducers to use 
403    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
404    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
405    :param p_ref: The value to use for p_ref
406    :param norms: Tensor of normals for transduers
407    :param k: wavenumber
408    :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
409    :param internal_points: The internal points to use for CHIEF based BEM
410    :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)
411    :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)
412    :param smooth_distance: amount to add to distances to avoid explosion over small values
413    :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)
414    :param h: finite difference step for Burton-Miller BEM
415    :param BM_alpha: constant alpha to use in Burton-Miller BEM
416    :return H: H
417    '''
418
419    # internal_points = None
420
421    centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device)
422    M = centres.shape[0]
423
424    if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3):
425            internal_points = internal_points.permute(0,2,1)
426
427    
428    # if internal_points is not None: print(internal_points.shape)
429    
430
431
432    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)
433    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)
434
435
436    if use_LU:
437        LU, pivots = torch.linalg.lu_factor(A)
438        H = torch.linalg.lu_solve(LU, pivots, bs)
439    elif use_OLS: 
440       
441        H = torch.linalg.lstsq(A,bs, rcond=1e-6).solution    
442    else:
443         H = torch.linalg.solve(A,bs)
444    
445    # H = H / (1-eta*1j)
446
447    # exit()
448    H = H[:,:M,: ]
449    
450    # print(torch.linalg.eig(A))
451
452    # exit()
453
454    # print((k*H, A@H, bs), H/bs)
455    # exit()
456
457    if return_components: return H,A,bs
458    return H
459
460
461
462def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 
463                           print_lines:bool=False, cache_name:str|None=None, p_ref = Constants.P_ref, 
464                           norms:Tensor|None=None, method:Literal['OLS','LU', 'INV']='LU', k:float=Constants.k,alphas:float|Tensor = 1, betas:float|Tensor = 0, 
465                           a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 
466                           CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor:
467    '''
468    Get H using cache system. Expects a folder named BEMCache in `path`\n
469
470    :param scatterer: The mesh used (as a `vedo` `mesh` object)
471    :param board: Transducers to use 
472    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
473    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
474    :param p_ref: The value to use for p_ref
475    :param norms: Tensor of normals for transduers
476    :param k: wavenumber
477    :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
478    :param internal_points: The internal points to use for CHIEF based BEM
479    :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)
480    :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)
481    :param smooth_distance: amount to add to distances to avoid explosion over small values
482    :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)
483    :param h: finite difference step for Burton-Miller BEM
484    :param BM_alpha: constant alpha to use in Burton-Miller BEM
485    :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it
486    :param path: path to folder containing `BEMCache/ `
487    :param print_lines: if true prints messages detaling progress
488    :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`
489    :return H: H tensor
490    '''
491
492    use_OLS=False
493    use_LU = False
494    
495    if method == "OLS":
496        use_OLS = True
497    elif method == "LU":
498        use_LU = True
499    
500    
501    if use_cache_H:
502        
503        if cache_name is None:
504            ps = locals()
505            ps.__delitem__('scatterer')
506            cache_name = scatterer.filename+"--" + str(ps)
507            cache_name = hashlib.md5(cache_name.encode()).hexdigest()
508        f_name = path+"/BEMCache/"  +  cache_name + ".bin"
509        # print(f_name)
510
511        try:
512            if print_lines: print("Trying to load H at", f_name ,"...")
513            H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE)
514        except FileNotFoundError: 
515            if print_lines: print("Not found, computing H...")
516            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)
517            try:
518                f = open(f_name,"wb")
519            except FileNotFoundError as e:
520                print(e)
521                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")
522            pickle.dump(H,f)
523            f.close()
524    else:
525        if print_lines: print("Computing H...")
526        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)
527
528    return H
529
530def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False,
531               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, 
532               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:
533    '''
534    Computes E in the BEM model\n
535    :param scatterer: The mesh used (as a `vedo` `mesh` object)
536    :param board: Transducers to use 
537    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
538    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
539    :param p_ref: The value to use for p_ref
540    :param norms: Tensor of normals for transduers
541    :param k: wavenumber
542    :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
543    :param internal_points: The internal points to use for CHIEF based BEM
544    :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)
545    :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)
546    :param smooth_distance: amount to add to distances to avoid explosion over small values
547    :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)
548    :param h: finite difference step for Burton-Miller BEM
549    :param BM_alpha: constant alpha to use in Burton-Miller BEM
550    :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it
551    :param path: path to folder containing `BEMCache/ `
552    :param print_lines: if true prints messages detaling progress
553    :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`
554
555    :return E: Propagation matrix for BEM E
556
557    ```Python
558    from acoustools.Mesh import load_scatterer
559    from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H
560    from acoustools.Utilities import create_points, TOP_BOARD
561    from acoustools.Solvers import wgs
562    from acoustools.Visualiser import Visualise
563
564    import torch
565
566    path = "../../BEMMedia"
567    scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08)
568    
569    p = create_points(N=1,B=1,y=0,x=0,z=0)
570    
571    H = compute_H(scatterer, TOP_BOARD)
572    E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H)
573    x = wgs(p,board=TOP_BOARD,A=E)
574    
575    A = torch.tensor((-0.12,0, 0.12))
576    B = torch.tensor((0.12,0, 0.12))
577    C = torch.tensor((-0.12,0, -0.12))
578
579    Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure],
580                colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}],
581                vmax=8621, show=True,res=[256,256])
582    ```
583    
584    '''
585    if board is None:
586        board = TOP_BOARD
587
588    if norms is None: #Transducer Norms
589        norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE)
590
591    if print_lines: print("H...")
592    
593    if H is None:
594        H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines,p_ref=p_ref,
595                                   norms=norms, method=H_method, k=k, betas=betas, a=a, c=c, internal_points=internal_points,
596                                     smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius, alphas=alphas).to(DTYPE)
597        
598    if print_lines: print("G...")
599    G = compute_G(points, scatterer, k=k, betas=betas,alphas=alphas, smooth_distance=smooth_distance).to(DTYPE)
600    
601    if print_lines: print("F...")
602    F = forward_model_batched(points,board,p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius).to(DTYPE)  
603    # if a is not None:
604    #     F += c * forward_model_batched(a,board, p_ref=p_ref,norms=norms)
605    
606    if print_lines: print("E...")
607
608    E = F+G@H
609
610
611    torch.cuda.empty_cache()
612    if return_components:
613        return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE)
614    return E.to(DTYPE)
615
616
617
618
619
620def __get_G_partial(points:Tensor, scatterer:Mesh, board:Tensor|None=None, return_components:bool=False, k=Constants.k) -> tuple[Tensor, Tensor, Tensor]:
621    '''
622    @private
623    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
624    Computes gradient of the G matrix in BEM \n
625    :param points: Points to propagate to
626    :param scatterer: The mesh used (as a `vedo` `mesh` object)
627    :param board: Ignored
628    :param return_components: if true will return the subparts used to compute
629    :return: Gradient of the G matrix in BEM
630    '''
631    #Bk3. Pg. 26
632    # if board is None:
633    #     board = TRANSDUCERS
634
635    areas = get_areas(scatterer)
636    centres = get_centres_as_points(scatterer)
637    normals = get_normals_as_points(scatterer)
638
639
640    N = points.shape[2]
641    M = centres.shape[2]
642
643
644    # points = points.unsqueeze(3).expand(-1,-1,-1,M)
645    # centres = centres.unsqueeze(2).expand(-1,-1,N,-1)
646    points  = points.unsqueeze(3)  # [B, 3, N, 1]
647    centres = centres.unsqueeze(2)  # [B, 3, 1, M]
648
649    diff = (points - centres)
650    diff_square = diff**2
651    distances = torch.sqrt(torch.sum(diff_square, 1))
652    distances_expanded = distances.unsqueeze(1)#.expand((1,3,N,M))
653    distances_expanded_square = distances_expanded**2
654    distances_expanded_cube = distances_expanded**3
655
656    # G  =  e^(ikd) / 4pi d
657    G = areas * torch.exp(1j * k * distances_expanded) / (4*3.1415*distances_expanded)
658
659    #Ga =  [i*da * e^{ikd} * (kd+i) / 4pi d^2]
660
661    #d = distance
662    #da = -(at - a)^2 / d
663    da = diff / distances_expanded
664    kd = k * distances_expanded
665    phase = torch.exp(1j*kd)
666    Ga =  areas * ( (1j*da*phase * (kd + 1j))/ (4*3.1415*distances_expanded_square))
667
668    #P = (ik - 1/d)
669    P = (1j*k - 1/distances_expanded)
670    #Pa = da / d^2 = (diff / d^2) /d
671    Pa = diff / distances_expanded_cube
672
673    #C = (diff \cdot normals) / distances
674
675    nx = normals[:,0]
676    ny = normals[:,1]
677    nz = normals[:,2]
678
679    dx = diff[:,0,:]
680    dy = diff[:,1,:]
681    dz = diff[:,2,:]
682
683    n_dot_d = nx*dx + ny*dy + nz*dz
684
685    C = (n_dot_d) / distances
686
687
688    distance_square = distances**2
689
690
691    Cx = 1/distance_square * (nx * distances - (n_dot_d * dx) / distances)
692    Cy = 1/distance_square * (ny * distances - (n_dot_d * dy) / distances)
693    Cz = 1/distance_square * (nz * distances - (n_dot_d * dz) / distances)
694
695    Cx.unsqueeze_(1)
696    Cy.unsqueeze_(1)
697    Cz.unsqueeze_(1)
698
699    Ca = torch.cat([Cx, Cy, Cz],axis=1)
700
701    grad_G = Ga*P*C + G*P*Ca + G*Pa*C
702
703    grad_G =  -grad_G.to(DTYPE)
704
705    if return_components:
706        return grad_G[:,0,:], grad_G[:,1,:], grad_G[:,2,:], G,P,C,Ga,Pa, Ca
707    
708    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 equation for 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 equation for 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.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

Computes G in the BEM model

Parameters
  • points: The points to propagate to
  • scatterer: The mesh used (as a vedo mesh object)
  • 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 equation for 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 equation for more details)
Returns

torch.Tensor of 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.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.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 vedo mesh object)
  • 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 equation for 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 equation for 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.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        bs = torch.cat([bs, F_int], dim=1)
365    
366    if a is not None: #Modified Greens function
367        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)
368        bs += c * f_mod
369    
370    
371    if BM_alpha is not None: #Burton-Miller
372        
373        if BM_mode == 'analytical': 
374            bs_a_grad = torch.stack(forward_model_grad(centres, board, p_ref=p_ref, k=k, transducer_radius=transducer_radius), dim=1)
375            bs_norm_grad = torch.sum(bs_a_grad * mesh_norms.unsqueeze(3), dim=1)
376
377
378            if internal_points is not None: #CHIEF
379                # 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)
380                p_n = internal_points.shape[1]
381                M = board.shape[0]
382                # int_bs_grad = torch.zeros_like(int_bs_grad)[:,0,:]
383                int_bs_grad = torch.zeros((1,p_n,M))
384
385                bs_norm_grad = torch.cat([bs_norm_grad,int_bs_grad], dim=1)
386
387
388            
389            bs = bs - BM_alpha * bs_norm_grad
390        else:
391            bs_grad = (bs-bs_grad)/h
392            bs = bs - BM_alpha * (bs-bs_grad)/h
393
394
395    return bs   

Computes B for the computation of H in the BEM model

Parameters
  • scatterer: The mesh used (as a vedo mesh object)
  • 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 equation for 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 equation for 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.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:
398def 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, 
399              a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 
400              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:
401    '''
402    Computes H for the BEM model \n
403    :param scatterer: The mesh used (as a `vedo` `mesh` object)
404    :param board: Transducers to use 
405    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
406    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
407    :param p_ref: The value to use for p_ref
408    :param norms: Tensor of normals for transduers
409    :param k: wavenumber
410    :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
411    :param internal_points: The internal points to use for CHIEF based BEM
412    :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)
413    :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)
414    :param smooth_distance: amount to add to distances to avoid explosion over small values
415    :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)
416    :param h: finite difference step for Burton-Miller BEM
417    :param BM_alpha: constant alpha to use in Burton-Miller BEM
418    :return H: H
419    '''
420
421    # internal_points = None
422
423    centres = torch.tensor(scatterer.cell_centers().points, dtype=DTYPE, device=device)
424    M = centres.shape[0]
425
426    if internal_points is not None and (internal_points.shape[1] == 3 and internal_points.shape[2] != 3):
427            internal_points = internal_points.permute(0,2,1)
428
429    
430    # if internal_points is not None: print(internal_points.shape)
431    
432
433
434    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)
435    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)
436
437
438    if use_LU:
439        LU, pivots = torch.linalg.lu_factor(A)
440        H = torch.linalg.lu_solve(LU, pivots, bs)
441    elif use_OLS: 
442       
443        H = torch.linalg.lstsq(A,bs, rcond=1e-6).solution    
444    else:
445         H = torch.linalg.solve(A,bs)
446    
447    # H = H / (1-eta*1j)
448
449    # exit()
450    H = H[:,:M,: ]
451    
452    # print(torch.linalg.eig(A))
453
454    # exit()
455
456    # print((k*H, A@H, bs), H/bs)
457    # exit()
458
459    if return_components: return H,A,bs
460    return H

Computes H for the BEM model

Parameters
  • scatterer: The mesh used (as a vedo mesh object)
  • 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 equation for 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 equation for 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.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:
464def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 
465                           print_lines:bool=False, cache_name:str|None=None, p_ref = Constants.P_ref, 
466                           norms:Tensor|None=None, method:Literal['OLS','LU', 'INV']='LU', k:float=Constants.k,alphas:float|Tensor = 1, betas:float|Tensor = 0, 
467                           a:Tensor=None, c:Tensor=None, internal_points:Tensor=None, smooth_distance:float=0, 
468                           CHIEF_mode:Literal['square', 'rect'] = 'square', h:float=None, BM_alpha:complex=None, transducer_radius=Constants.radius) -> Tensor:
469    '''
470    Get H using cache system. Expects a folder named BEMCache in `path`\n
471
472    :param scatterer: The mesh used (as a `vedo` `mesh` object)
473    :param board: Transducers to use 
474    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
475    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
476    :param p_ref: The value to use for p_ref
477    :param norms: Tensor of normals for transduers
478    :param k: wavenumber
479    :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
480    :param internal_points: The internal points to use for CHIEF based BEM
481    :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)
482    :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)
483    :param smooth_distance: amount to add to distances to avoid explosion over small values
484    :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)
485    :param h: finite difference step for Burton-Miller BEM
486    :param BM_alpha: constant alpha to use in Burton-Miller BEM
487    :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it
488    :param path: path to folder containing `BEMCache/ `
489    :param print_lines: if true prints messages detaling progress
490    :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`
491    :return H: H tensor
492    '''
493
494    use_OLS=False
495    use_LU = False
496    
497    if method == "OLS":
498        use_OLS = True
499    elif method == "LU":
500        use_LU = True
501    
502    
503    if use_cache_H:
504        
505        if cache_name is None:
506            ps = locals()
507            ps.__delitem__('scatterer')
508            cache_name = scatterer.filename+"--" + str(ps)
509            cache_name = hashlib.md5(cache_name.encode()).hexdigest()
510        f_name = path+"/BEMCache/"  +  cache_name + ".bin"
511        # print(f_name)
512
513        try:
514            if print_lines: print("Trying to load H at", f_name ,"...")
515            H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE)
516        except FileNotFoundError: 
517            if print_lines: print("Not found, computing H...")
518            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)
519            try:
520                f = open(f_name,"wb")
521            except FileNotFoundError as e:
522                print(e)
523                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")
524            pickle.dump(H,f)
525            f.close()
526    else:
527        if print_lines: print("Computing H...")
528        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)
529
530    return H

Get H using cache system. Expects a folder named BEMCache in path

Parameters
  • scatterer: The mesh used (as a vedo mesh object)
  • 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 equation for 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 equation for 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.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:
532def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False,
533               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, 
534               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:
535    '''
536    Computes E in the BEM model\n
537    :param scatterer: The mesh used (as a `vedo` `mesh` object)
538    :param board: Transducers to use 
539    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
540    :param use_OLS: if True computes H with OLS, otherwise solves using standard linear inversion
541    :param p_ref: The value to use for p_ref
542    :param norms: Tensor of normals for transduers
543    :param k: wavenumber
544    :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
545    :param internal_points: The internal points to use for CHIEF based BEM
546    :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)
547    :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)
548    :param smooth_distance: amount to add to distances to avoid explosion over small values
549    :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)
550    :param h: finite difference step for Burton-Miller BEM
551    :param BM_alpha: constant alpha to use in Burton-Miller BEM
552    :param use_cache_H: If true uses the cache system, otherwise computes H and does not save it
553    :param path: path to folder containing `BEMCache/ `
554    :param print_lines: if true prints messages detaling progress
555    :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`
556
557    :return E: Propagation matrix for BEM E
558
559    ```Python
560    from acoustools.Mesh import load_scatterer
561    from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H
562    from acoustools.Utilities import create_points, TOP_BOARD
563    from acoustools.Solvers import wgs
564    from acoustools.Visualiser import Visualise
565
566    import torch
567
568    path = "../../BEMMedia"
569    scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08)
570    
571    p = create_points(N=1,B=1,y=0,x=0,z=0)
572    
573    H = compute_H(scatterer, TOP_BOARD)
574    E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H)
575    x = wgs(p,board=TOP_BOARD,A=E)
576    
577    A = torch.tensor((-0.12,0, 0.12))
578    B = torch.tensor((0.12,0, 0.12))
579    C = torch.tensor((-0.12,0, -0.12))
580
581    Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure],
582                colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}],
583                vmax=8621, show=True,res=[256,256])
584    ```
585    
586    '''
587    if board is None:
588        board = TOP_BOARD
589
590    if norms is None: #Transducer Norms
591        norms = (torch.zeros_like(board) + torch.tensor([0,0,1], device=device)) * torch.sign(board[:,2].real).unsqueeze(1).to(DTYPE)
592
593    if print_lines: print("H...")
594    
595    if H is None:
596        H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines,p_ref=p_ref,
597                                   norms=norms, method=H_method, k=k, betas=betas, a=a, c=c, internal_points=internal_points,
598                                     smooth_distance=smooth_distance, CHIEF_mode=CHIEF_mode, h=h, BM_alpha=BM_alpha, transducer_radius=transducer_radius, alphas=alphas).to(DTYPE)
599        
600    if print_lines: print("G...")
601    G = compute_G(points, scatterer, k=k, betas=betas,alphas=alphas, smooth_distance=smooth_distance).to(DTYPE)
602    
603    if print_lines: print("F...")
604    F = forward_model_batched(points,board,p_ref=p_ref,norms=norms, k=k, transducer_radius=transducer_radius).to(DTYPE)  
605    # if a is not None:
606    #     F += c * forward_model_batched(a,board, p_ref=p_ref,norms=norms)
607    
608    if print_lines: print("E...")
609
610    E = F+G@H
611
612
613    torch.cuda.empty_cache()
614    if return_components:
615        return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE)
616    return E.to(DTYPE)

Computes E in the BEM model

Parameters
  • scatterer: The mesh used (as a vedo mesh object)
  • 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 equation for 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 equation for 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])