src.acoustools.BEM.Forward_models

  1import torch
  2from torch import Tensor
  3
  4from vedo import Mesh
  5
  6import hashlib
  7import pickle
  8
  9import acoustools.Constants as Constants
 10
 11from acoustools.Utilities import device, DTYPE, forward_model_batched, TOP_BOARD
 12from acoustools.Mesh import get_normals_as_points, board_name, get_centres_as_points
 13
 14def compute_green_derivative(y:Tensor,x:Tensor,norms:Tensor,B:int,N:int,M:int, return_components:bool=False) -> Tensor:
 15    '''
 16    Computes the derivative of greens function \n
 17    :param y: y in greens function - location of the source of sound
 18    :param x: x in greens function - location of the point to be propagated to
 19    :param norms: norms to y 
 20    :param B: Batch dimension
 21    :param N: size of x
 22    :param M: size of y
 23    :param return_components: if true will return the subparts used to compute the derivative \n
 24    :return: returns the partial derivative of greeens fucntion wrt y
 25    '''
 26    norms= norms.real
 27    vecs = y.real-x.real
 28
 29 
 30    distance = torch.sqrt(torch.sum((vecs)**2,dim=3))
 31
 32    norms = norms.expand(B,N,-1,-1)
 33
 34    
 35    # norm_norms = torch.norm(norms,2,dim=3) # === 1
 36    # vec_norms = torch.norm(vecs,2,dim=3) # === distance?
 37    # print(vec_norms == distance)
 38    angles = (torch.sum(norms*vecs,3) / (distance))
 39
 40    del norms, vecs
 41    torch.cuda.empty_cache()
 42
 43    A = ((torch.exp(1j*Constants.k*distance))/(4*torch.pi*distance))
 44    B = (1j*Constants.k - 1/(distance))
 45    
 46    del distance
 47    # torch.cuda.empty_cache()
 48
 49    partial_greens = A*B*angles
 50    
 51    if not return_components:
 52        del A,B,angles
 53    torch.cuda.empty_cache()
 54
 55    
 56    partial_greens[partial_greens.isnan()] = 1
 57
 58
 59    if return_components:
 60        return partial_greens, A,B,angles
 61    
 62    return partial_greens
 63
 64def compute_G(points: Tensor, scatterer: Mesh) -> Tensor:
 65    '''
 66    Computes G in the BEM model\n
 67    :param points: The points to propagate to
 68    :param scatterer: The mesh used (as a `vedo` `mesh` object)
 69    :return G: `torch.Tensor` of G
 70    '''
 71    torch.cuda.empty_cache()
 72    areas = torch.Tensor(scatterer.celldata["Area"]).to(device).real
 73    B = points.shape[0]
 74    N = points.shape[2]
 75    M = areas.shape[0]
 76    areas = areas.expand((B,N,-1))
 77
 78    #Compute the partial derivative of Green's Function
 79
 80    #Firstly compute the distances from mesh points -> control points
 81    centres = torch.tensor(scatterer.cell_centers().points).to(device).real #Uses centre points as position of mesh
 82    centres = centres.expand((B,N,-1,-1))
 83    
 84    # print(points.shape)
 85    # p = torch.reshape(points,(B,N,3))
 86    p = torch.permute(points,(0,2,1)).real
 87    p = torch.unsqueeze(p,2).expand((-1,-1,M,-1))
 88
 89    #Compute cosine of angle between mesh normal and point
 90    # scatterer.compute_normals()
 91    # norms = torch.tensor(scatterer.cell_normals).to(device)
 92    norms = get_normals_as_points(scatterer,permute_to_points=False).real
 93  
 94    partial_greens = compute_green_derivative(centres,p,norms, B,N,M)
 95    
 96    G = areas * partial_greens
 97    return G
 98
 99 
100def compute_A(scatterer: Mesh) -> Tensor:
101    '''
102    Computes A for the computation of H in the BEM model\n
103    :param scatterer: The mesh used (as a `vedo` `mesh` object)
104    :return A: A tensor
105    '''
106
107    areas = torch.Tensor(scatterer.celldata["Area"]).to(device)
108
109    centres = torch.tensor(scatterer.cell_centers().points).to(device)
110    m = centres
111    M = m.shape[0]
112    m = m.expand((M,M,3))
113
114    m_prime = m.clone()
115    m_prime = m_prime.permute((1,0,2))
116
117    # norms = torch.tensor(scatterer.cell_normals).to(device)
118    norms = get_normals_as_points(scatterer,permute_to_points=False)
119
120    green = compute_green_derivative(m.unsqueeze_(0),m_prime.unsqueeze_(0),norms,1,M,M)
121    # areas = areas.unsqueeze(0).T.expand((-1,M)).unsqueeze(0)
122    A = green * areas * -1
123    eye = torch.eye(M).to(bool)
124    A[:,eye] = 0.5
125
126    return A.to(DTYPE)
127
128 
129def compute_bs(scatterer: Mesh, board:Tensor) -> Tensor:
130    '''
131    Computes B for the computation of H in the BEM model\n
132    :param scatterer: The mesh used (as a `vedo` `mesh` object)
133    :param board: Transducers to use 
134    :return B: B tensor
135    '''
136    centres = torch.tensor(scatterer.cell_centers().points).to(device).T.unsqueeze_(0)
137    bs = forward_model_batched(centres,board)
138    return bs.to(DTYPE)
139
140 
141def compute_H(scatterer: Mesh, board:Tensor ,use_LU:bool=True, use_OLS:bool = False) -> Tensor:
142    '''
143    Computes H for the BEM model \n
144    :param scatterer: The mesh used (as a `vedo` `mesh` object)
145    :param board: Transducers to use 
146    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
147    :return H: H
148    '''
149    
150    A = compute_A(scatterer)
151    bs = compute_bs(scatterer,board)
152    if use_LU:
153        LU, pivots = torch.linalg.lu_factor(A)
154        H = torch.linalg.lu_solve(LU, pivots, bs)
155    elif use_OLS:
156       
157        H = torch.linalg.lstsq(A,bs).solution    
158    else:
159         H = torch.linalg.solve(A,bs)
160
161    return H
162
163
164
165def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 
166                           print_lines:bool=False, cache_name:str|None=None,use_LU:bool=True) -> Tensor:
167    '''
168    Get H using cache system. Expects a folder named BEMCache in `path`\n
169    :param scatterer: The mesh used (as a `vedo` `mesh` object)
170    :param  board: Transducers to use 
171    :param use_cache_H_grad: If true uses the cache system, otherwise computes H and does not save it
172    :param path: path to folder containing `BEMCache/ `
173    :param print_lines: if true prints messages detaling progress
174    :param use_LU: If true use LU decomopsition to solve for H
175    :return H: H tensor
176    '''
177    
178    if use_cache_H:
179        
180        if cache_name is None:
181            cache_name = scatterer.filename+"--"+ board_name(board)
182            cache_name = hashlib.md5(cache_name.encode()).hexdigest()
183        f_name = path+"/BEMCache/"  +  cache_name + ".bin"
184        # print(f_name)
185
186        try:
187            if print_lines: print("Trying to load H at", f_name ,"...")
188            H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE)
189        except FileNotFoundError: 
190            if print_lines: print("Not found, computing H...")
191            H = compute_H(scatterer,board,use_LU=use_LU)
192            f = open(f_name,"wb")
193            pickle.dump(H,f)
194            f.close()
195    else:
196        if print_lines: print("Computing H...")
197        H = compute_H(scatterer,board)
198
199    return H
200
201def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False,
202               H:Tensor|None=None,path:str="Media", return_components:bool=False) -> Tensor:
203    '''
204    Computes E in the BEM model\n
205    :param scatterer: The mesh used (as a `vedo` `mesh` object)
206    :param board: Transducers to use, if `None` then `acoustools.Utilities.TOP_BOARD` is used
207    :param use_cache_H_grad: If true uses the cache system, otherwise computes H and does not save it
208    :param print_lines: if true prints messages detaling progress
209    :param H: Precomputed H - if None H will be compute
210    :param path: path to folder containing `BEMCache/`
211    :param return_components: if true will return the subparts used to compute, F,G,H
212    :return E: Propagation matrix for BEM E
213
214    ```Python
215    from acoustools.Mesh import load_scatterer
216    from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H
217    from acoustools.Utilities import create_points, TOP_BOARD
218    from acoustools.Solvers import wgs
219    from acoustools.Visualiser import Visualise
220
221    import torch
222
223    path = "../../BEMMedia"
224    scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08)
225    
226    p = create_points(N=1,B=1,y=0,x=0,z=0)
227    
228    H = compute_H(scatterer, TOP_BOARD)
229    E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H)
230    x = wgs(p,board=TOP_BOARD,A=E)
231    
232    A = torch.tensor((-0.12,0, 0.12))
233    B = torch.tensor((0.12,0, 0.12))
234    C = torch.tensor((-0.12,0, -0.12))
235
236    Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure],
237                colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}],
238                vmax=8621, show=True,res=[256,256])
239    ```
240    
241    '''
242    if board is None:
243        board = TOP_BOARD
244
245    if print_lines: print("H...")
246    
247    if H is None:
248        H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines).to(DTYPE)
249        
250    if print_lines: print("G...")
251    G = compute_G(points, scatterer).to(DTYPE)
252    
253    if print_lines: print("F...")
254    F = forward_model_batched(points,board).to(DTYPE)
255    
256    if print_lines: print("E...")
257
258    E = F+G@H
259
260    torch.cuda.empty_cache()
261    if return_components:
262        return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE)
263    return E.to(DTYPE)
def compute_green_derivative( y: torch.Tensor, x: torch.Tensor, norms: torch.Tensor, B: int, N: int, M: int, return_components: bool = False) -> torch.Tensor:
16def compute_green_derivative(y:Tensor,x:Tensor,norms:Tensor,B:int,N:int,M:int, return_components:bool=False) -> Tensor:
17    '''
18    Computes the derivative of greens function \n
19    :param y: y in greens function - location of the source of sound
20    :param x: x in greens function - location of the point to be propagated to
21    :param norms: norms to y 
22    :param B: Batch dimension
23    :param N: size of x
24    :param M: size of y
25    :param return_components: if true will return the subparts used to compute the derivative \n
26    :return: returns the partial derivative of greeens fucntion wrt y
27    '''
28    norms= norms.real
29    vecs = y.real-x.real
30
31 
32    distance = torch.sqrt(torch.sum((vecs)**2,dim=3))
33
34    norms = norms.expand(B,N,-1,-1)
35
36    
37    # norm_norms = torch.norm(norms,2,dim=3) # === 1
38    # vec_norms = torch.norm(vecs,2,dim=3) # === distance?
39    # print(vec_norms == distance)
40    angles = (torch.sum(norms*vecs,3) / (distance))
41
42    del norms, vecs
43    torch.cuda.empty_cache()
44
45    A = ((torch.exp(1j*Constants.k*distance))/(4*torch.pi*distance))
46    B = (1j*Constants.k - 1/(distance))
47    
48    del distance
49    # torch.cuda.empty_cache()
50
51    partial_greens = A*B*angles
52    
53    if not return_components:
54        del A,B,angles
55    torch.cuda.empty_cache()
56
57    
58    partial_greens[partial_greens.isnan()] = 1
59
60
61    if return_components:
62        return partial_greens, A,B,angles
63    
64    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
Returns

returns the partial derivative of greeens fucntion wrt y

def compute_G(points: torch.Tensor, scatterer: vedo.mesh.Mesh) -> torch.Tensor:
66def compute_G(points: Tensor, scatterer: Mesh) -> Tensor:
67    '''
68    Computes G in the BEM model\n
69    :param points: The points to propagate to
70    :param scatterer: The mesh used (as a `vedo` `mesh` object)
71    :return G: `torch.Tensor` of G
72    '''
73    torch.cuda.empty_cache()
74    areas = torch.Tensor(scatterer.celldata["Area"]).to(device).real
75    B = points.shape[0]
76    N = points.shape[2]
77    M = areas.shape[0]
78    areas = areas.expand((B,N,-1))
79
80    #Compute the partial derivative of Green's Function
81
82    #Firstly compute the distances from mesh points -> control points
83    centres = torch.tensor(scatterer.cell_centers().points).to(device).real #Uses centre points as position of mesh
84    centres = centres.expand((B,N,-1,-1))
85    
86    # print(points.shape)
87    # p = torch.reshape(points,(B,N,3))
88    p = torch.permute(points,(0,2,1)).real
89    p = torch.unsqueeze(p,2).expand((-1,-1,M,-1))
90
91    #Compute cosine of angle between mesh normal and point
92    # scatterer.compute_normals()
93    # norms = torch.tensor(scatterer.cell_normals).to(device)
94    norms = get_normals_as_points(scatterer,permute_to_points=False).real
95  
96    partial_greens = compute_green_derivative(centres,p,norms, B,N,M)
97    
98    G = areas * partial_greens
99    return G

Computes G in the BEM model

Parameters
  • points: The points to propagate to
  • scatterer: The mesh used (as a vedo mesh object)
Returns

torch.Tensor of G

def compute_A(scatterer: vedo.mesh.Mesh) -> torch.Tensor:
102def compute_A(scatterer: Mesh) -> Tensor:
103    '''
104    Computes A for the computation of H in the BEM model\n
105    :param scatterer: The mesh used (as a `vedo` `mesh` object)
106    :return A: A tensor
107    '''
108
109    areas = torch.Tensor(scatterer.celldata["Area"]).to(device)
110
111    centres = torch.tensor(scatterer.cell_centers().points).to(device)
112    m = centres
113    M = m.shape[0]
114    m = m.expand((M,M,3))
115
116    m_prime = m.clone()
117    m_prime = m_prime.permute((1,0,2))
118
119    # norms = torch.tensor(scatterer.cell_normals).to(device)
120    norms = get_normals_as_points(scatterer,permute_to_points=False)
121
122    green = compute_green_derivative(m.unsqueeze_(0),m_prime.unsqueeze_(0),norms,1,M,M)
123    # areas = areas.unsqueeze(0).T.expand((-1,M)).unsqueeze(0)
124    A = green * areas * -1
125    eye = torch.eye(M).to(bool)
126    A[:,eye] = 0.5
127
128    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)
Returns

A tensor

def compute_bs(scatterer: vedo.mesh.Mesh, board: torch.Tensor) -> torch.Tensor:
131def compute_bs(scatterer: Mesh, board:Tensor) -> Tensor:
132    '''
133    Computes B for the computation of H in the BEM model\n
134    :param scatterer: The mesh used (as a `vedo` `mesh` object)
135    :param board: Transducers to use 
136    :return B: B tensor
137    '''
138    centres = torch.tensor(scatterer.cell_centers().points).to(device).T.unsqueeze_(0)
139    bs = forward_model_batched(centres,board)
140    return bs.to(DTYPE)

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
Returns

B tensor

def compute_H( scatterer: vedo.mesh.Mesh, board: torch.Tensor, use_LU: bool = True, use_OLS: bool = False) -> torch.Tensor:
143def compute_H(scatterer: Mesh, board:Tensor ,use_LU:bool=True, use_OLS:bool = False) -> Tensor:
144    '''
145    Computes H for the BEM model \n
146    :param scatterer: The mesh used (as a `vedo` `mesh` object)
147    :param board: Transducers to use 
148    :param use_LU: if True computes H with LU decomposition, otherwise solves using standard linear inversion
149    :return H: H
150    '''
151    
152    A = compute_A(scatterer)
153    bs = compute_bs(scatterer,board)
154    if use_LU:
155        LU, pivots = torch.linalg.lu_factor(A)
156        H = torch.linalg.lu_solve(LU, pivots, bs)
157    elif use_OLS:
158       
159        H = torch.linalg.lstsq(A,bs).solution    
160    else:
161         H = torch.linalg.solve(A,bs)
162
163    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
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, use_LU: bool = True) -> torch.Tensor:
167def get_cache_or_compute_H(scatterer:Mesh,board,use_cache_H:bool=True, path:str="Media", 
168                           print_lines:bool=False, cache_name:str|None=None,use_LU:bool=True) -> Tensor:
169    '''
170    Get H using cache system. Expects a folder named BEMCache in `path`\n
171    :param scatterer: The mesh used (as a `vedo` `mesh` object)
172    :param  board: Transducers to use 
173    :param use_cache_H_grad: If true uses the cache system, otherwise computes H and does not save it
174    :param path: path to folder containing `BEMCache/ `
175    :param print_lines: if true prints messages detaling progress
176    :param use_LU: If true use LU decomopsition to solve for H
177    :return H: H tensor
178    '''
179    
180    if use_cache_H:
181        
182        if cache_name is None:
183            cache_name = scatterer.filename+"--"+ board_name(board)
184            cache_name = hashlib.md5(cache_name.encode()).hexdigest()
185        f_name = path+"/BEMCache/"  +  cache_name + ".bin"
186        # print(f_name)
187
188        try:
189            if print_lines: print("Trying to load H at", f_name ,"...")
190            H = pickle.load(open(f_name,"rb")).to(device).to(DTYPE)
191        except FileNotFoundError: 
192            if print_lines: print("Not found, computing H...")
193            H = compute_H(scatterer,board,use_LU=use_LU)
194            f = open(f_name,"wb")
195            pickle.dump(H,f)
196            f.close()
197    else:
198        if print_lines: print("Computing H...")
199        H = compute_H(scatterer,board)
200
201    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_cache_H_grad: 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
  • use_LU: If true use LU decomopsition to solve for H
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) -> torch.Tensor:
203def compute_E(scatterer:Mesh, points:Tensor, board:Tensor|None=None, use_cache_H:bool=True, print_lines:bool=False,
204               H:Tensor|None=None,path:str="Media", return_components:bool=False) -> Tensor:
205    '''
206    Computes E in the BEM model\n
207    :param scatterer: The mesh used (as a `vedo` `mesh` object)
208    :param board: Transducers to use, if `None` then `acoustools.Utilities.TOP_BOARD` is used
209    :param use_cache_H_grad: If true uses the cache system, otherwise computes H and does not save it
210    :param print_lines: if true prints messages detaling progress
211    :param H: Precomputed H - if None H will be compute
212    :param path: path to folder containing `BEMCache/`
213    :param return_components: if true will return the subparts used to compute, F,G,H
214    :return E: Propagation matrix for BEM E
215
216    ```Python
217    from acoustools.Mesh import load_scatterer
218    from acoustools.BEM import compute_E, propagate_BEM_pressure, compute_H
219    from acoustools.Utilities import create_points, TOP_BOARD
220    from acoustools.Solvers import wgs
221    from acoustools.Visualiser import Visualise
222
223    import torch
224
225    path = "../../BEMMedia"
226    scatterer = load_scatterer(path+"/Sphere-lam2.stl",dy=-0.06,dz=-0.08)
227    
228    p = create_points(N=1,B=1,y=0,x=0,z=0)
229    
230    H = compute_H(scatterer, TOP_BOARD)
231    E = compute_E(scatterer, p, TOP_BOARD,path=path,H=H)
232    x = wgs(p,board=TOP_BOARD,A=E)
233    
234    A = torch.tensor((-0.12,0, 0.12))
235    B = torch.tensor((0.12,0, 0.12))
236    C = torch.tensor((-0.12,0, -0.12))
237
238    Visualise(A,B,C, x, colour_functions=[propagate_BEM_pressure],
239                colour_function_args=[{"scatterer":scatterer,"board":TOP_BOARD,"path":path,'H':H}],
240                vmax=8621, show=True,res=[256,256])
241    ```
242    
243    '''
244    if board is None:
245        board = TOP_BOARD
246
247    if print_lines: print("H...")
248    
249    if H is None:
250        H = get_cache_or_compute_H(scatterer,board,use_cache_H, path, print_lines).to(DTYPE)
251        
252    if print_lines: print("G...")
253    G = compute_G(points, scatterer).to(DTYPE)
254    
255    if print_lines: print("F...")
256    F = forward_model_batched(points,board).to(DTYPE)
257    
258    if print_lines: print("E...")
259
260    E = F+G@H
261
262    torch.cuda.empty_cache()
263    if return_components:
264        return E.to(DTYPE), F.to(DTYPE), G.to(DTYPE), H.to(DTYPE)
265    return E.to(DTYPE)

Computes E in the BEM model

Parameters
  • scatterer: The mesh used (as a vedo mesh object)
  • board: Transducers to use, if None then acoustools.Utilities.TOP_BOARD is used
  • use_cache_H_grad: If true uses the cache system, otherwise computes H and does not save it
  • print_lines: if true prints messages detaling progress
  • H: Precomputed H - if None H will be compute
  • path: path to folder containing BEMCache/
  • return_components: if true will return the subparts used to compute, F,G,H
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])