src.acoustools.Utilities.Piston_model_gradients

  1from acoustools.Utilities.Boards import TRANSDUCERS
  2from acoustools.Utilities.Setup import device, DTYPE
  3import acoustools.Constants as Constants
  4
  5import torch
  6from torch import Tensor
  7
  8
  9def compute_gradients(points, transducers = TRANSDUCERS, p_ref = Constants.P_ref, k=Constants.k, transducer_radius = Constants.radius, transducer_norms=None):
 10    '''
 11    @private
 12    Computes the components to be used in the analytical gradient of the piston model, shouldnt be useed use `forward_model_grad` to get the gradient \\
 13    `points` Point position to compute propagation to \\
 14    `transducers` The Transducer array, default two 16x16 arrays \\
 15    Returns (F,G,H, partialFpartialX, partialGpartialX, partialHpartialX, partialFpartialU, partialUpartiala)
 16    '''
 17    B = points.shape[0]
 18    N = points.shape[2]
 19    M = transducers.shape[0]
 20
 21    if type(p_ref) == float or type(p_ref) == int:
 22        p_ref = torch.ones(1,M,1, device=device, dtype=DTYPE) * p_ref
 23    
 24    if type(k) == float or type(k) == int:
 25        k = torch.ones(1,M,1, device=device, dtype=DTYPE) * k
 26    k_expanded = k.unsqueeze(3)
 27
 28    if transducer_norms is None:
 29        transducer_norms = (torch.zeros_like(transducers) + torch.tensor([0,0,1], device=device)) * torch.sign(transducers[:,2].real).unsqueeze(1).to(DTYPE)
 30    transducer_norms_exp = transducer_norms.unsqueeze(0).unsqueeze(3)
 31    
 32    transducers = torch.unsqueeze(transducers,2)
 33    transducers = transducers.expand((B,-1,-1,N))
 34    points = torch.unsqueeze(points,1)
 35    points = points.expand((-1,M,-1,-1))
 36
 37    diff =  points - transducers
 38    diff = diff + 0j
 39    distances = torch.sqrt(torch.sum(diff**2, 2)) 
 40
 41    sin_theta = torch.norm(torch.cross(diff, transducer_norms_exp, dim=2),2, dim=2) / distances
 42    partialFpartialU = -1* (k**2 * transducer_radius**2)/4 * sin_theta + (k**4 * transducer_radius**4)/48 * sin_theta**3
 43    
 44    
 45    partialUpartiala = torch.sum(diff * transducer_norms_exp, dim=2) / distances #d/dx sin(x) = cos(x)
 46    partialUpartiala = partialUpartiala.unsqueeze(2)
 47
 48    partialFpartialU = torch.unsqueeze(partialFpartialU,2)
 49    partialFpartialU = partialFpartialU.expand((-1,-1,3,-1))
 50    partialFpartialX  = partialFpartialU * partialUpartiala
 51
 52    #Grad of Pref / d(xt,t)
 53    dist_expand = torch.unsqueeze(distances,2)
 54    dist_expand = dist_expand.expand((-1,-1,3,-1))
 55
 56    partialGpartialX = (p_ref.unsqueeze(3) * diff) / dist_expand**3
 57
 58    #Grad of e^ikd(xt,t)
 59    partialHpartialX = 1j * k_expanded * (diff / dist_expand) * torch.exp(1j * k_expanded * dist_expand)
 60
 61    #Combine
 62    bessel_arg=k*transducer_radius*sin_theta
 63    F=1-torch.pow(bessel_arg,2)/8+torch.pow(bessel_arg,4)/192
 64    F = torch.unsqueeze(F,2)
 65    F = F.expand((-1,-1,3,-1))
 66
 67    G = p_ref.unsqueeze(3) / dist_expand
 68    H = torch.exp(1j * k_expanded * dist_expand)
 69
 70
 71    return F,G,H, partialFpartialX, partialGpartialX, partialHpartialX, partialFpartialU, partialUpartiala
 72
 73def forward_model_grad(points:Tensor, transducers:Tensor|None = None, p_ref=Constants.P_ref, k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None) -> tuple[Tensor]:
 74    '''
 75    Computes the analytical gradient of the piston model\n
 76    :param points: Point position to compute propagation to 
 77    :param transducers: The Transducer array, default two 16x16 arrays 
 78    :return: derivative of forward model wrt x,y,z position
 79
 80    ```Python
 81    from acoustools.Utilities import forward_model_grad
 82
 83    Fx, Fy, Fz = forward_model_grad(points,transducers=board)
 84    Px  = torch.abs(Fx@activations) #gradient wrt x position
 85    Py  = torch.abs(Fy@activations) #gradient wrt y position
 86    Pz  = torch.abs(Fz@activations) #gradient wrt z position
 87
 88    ```
 89    '''
 90    if transducers is None:
 91        transducers=TRANSDUCERS
 92    
 93
 94    F,G,H, partialFpartialX, partialGpartialX, partialHpartialX,_,_ = compute_gradients(points, transducers, p_ref=p_ref,k=k, transducer_radius=transducer_radius, transducer_norms=transducer_norms)
 95    derivative = G*(H*partialFpartialX + F*partialHpartialX) + F*H*partialGpartialX
 96    derivative = derivative.to(device).to(DTYPE) # minus here to match f.d -> not 100% sure why its needed
 97
 98
 99    return derivative[:,:,0,:].permute((0,2,1)), derivative[:,:,1,:].permute((0,2,1)), derivative[:,:,2,:].permute((0,2,1))
100
101
102def forward_model_second_derivative_unmixed(points:Tensor, transducers:Tensor|None = None, p_ref = Constants.P_ref, k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None) ->Tensor:
103    '''
104    Computes the second degree unmixed analytical gradient of the piston model\n
105    :param points: Point position to compute propagation to 
106    :param transducers: The Transducer array, default two 16x16 arrays 
107    :return: second degree unmixed derivatives of forward model wrt x,y,z position Pxx, Pyy, Pzz
108    '''
109
110    #See Bk.2 Pg.314
111
112    if transducers is None:
113        transducers= TRANSDUCERS
114    
115    if type(p_ref) == float or type(p_ref) == int:
116        M = transducers.shape[0]
117        p_ref = torch.ones(1,M,1, device=device, dtype=DTYPE) * p_ref
118    
119    if type(k) == float or type(k) == int:
120        k = torch.ones(1,M,1, device=device, dtype=DTYPE) * k
121    k_expanded = k.unsqueeze(3)
122
123    if transducer_norms is None:
124        transducer_norms = (torch.zeros_like(transducers) + torch.tensor([0,0,1], device=device)) * torch.sign(transducers[:,2].real).unsqueeze(1).to(DTYPE)
125    transducer_norms_exp = transducer_norms.unsqueeze(0).unsqueeze(3)
126
127    B = points.shape[0]
128    N = points.shape[2]
129    M = transducers.shape[0]
130    
131    transducers = torch.unsqueeze(transducers,2)
132    transducers = transducers.expand((B,-1,-1,N))
133    points = torch.unsqueeze(points,1)
134    points = points.expand((-1,M,-1,-1))
135
136    diff = transducers - points + 0j
137    
138    diff_square = diff**2
139    distances = torch.sqrt(torch.sum(diff_square, 2))
140
141    distances_expanded = distances.unsqueeze(2).expand((B,M,3,N))
142    distances_expanded_square = distances_expanded**2
143    distances_expanded_cube = distances_expanded ** 3
144    
145
146    # sin_theta = planar_distance / distances
147    sin_theta = torch.norm(torch.cross(diff, transducer_norms_exp, dim=2),2, dim=2) / distances
148    sin_theta_expand = sin_theta.unsqueeze(2).expand((B,M,3,N))
149    sin_theta_expand_square = sin_theta_expand**2
150
151    p_ref_expand = p_ref.unsqueeze(2).expand((B,-1,3,N))
152
153    dx = diff[:,:,0,:]
154    dy = diff[:,:,1,:]
155    dz = diff[:,:,2,:]
156
157    # F = G * H 
158    # G  = Pref * e^(ikd) / d
159    # H = 1 - (kr sin(theta))^2 / 8 + (kr sin(theta))^4 / 192
160
161    G = p_ref * torch.exp(1j * k * distances) / distances
162
163    kr = k * transducer_radius
164    kr_expand = k_expanded * transducer_radius
165    kr_sine = kr*sin_theta
166    H = 1 - ((kr_sine)**2) / 8 + ((kr_sine)**4)/192 
167
168
169    #(a = {x,y,z})
170    #Faa = 2*Ga*Ha + Gaa * H + G * Haa
171
172    #Ga = Pref * [i*da * e^{ikd} * (kd+i) / d^2]
173
174    #d = distance
175    #da = -(at - a)^2 / d
176
177    da = -1 * diff / distances_expanded #Gradient of distances
178    kd = k_expanded * distances_expanded
179    phase = torch.exp(1j*kd)
180    Ga = p_ref_expand.expand((B,-1,3,N)) * ( (1j*da*phase * (kd + 1j))/ (distances_expanded_square))
181
182    #Gaa = Pref * [ -1/d^3 * e^{ikd} * (da^2 * (k^2*d^2 + 2ik*d - 2) + d*daa * (1-ikd))]
183    #daa = distance_bs / d^3
184    # distance_bs = sum(b_t - b)^2 . b = {x,y,z} \ a
185    distance_xy = diff[:,:,0,:] **2 + diff[:,:,1,:] **2
186    distance_xz = diff[:,:,0,:] **2 + diff[:,:,2,:] **2
187    distance_yz = diff[:,:,1,:] **2 + diff[:,:,2,:] **2
188
189    distance_bs = torch.stack([distance_yz,distance_xz,distance_xy], dim =2)
190    daa = distance_bs / distances_expanded_cube #Second Gradient of distance function
191
192    Gaa =  p_ref_expand * (-1/distances_expanded_cube * torch.exp(1j*kd) * (da**2 * (kd**2 + 2*1j*kd - 2) + distances_expanded *daa * (1-1j * kd)))
193
194    cos_theta = torch.sum(diff * transducer_norms_exp, dim=2) / distances
195    cos_theta_exp = cos_theta.unsqueeze(2)
196
197    sa = da * cos_theta_exp
198
199    Ha = 1/48 * kr_expand**2 * sin_theta_expand * sa * (kr_expand**2 * sin_theta_expand**2 - 12)
200
201    saa = daa * cos_theta_exp - da**2 * sin_theta_expand
202    # saa[saa.isnan()] = 0
203
204    Haa = 1/48 * kr_expand**2 * (3*sa**2 * (kr_expand**2 * sin_theta_expand_square- 4) + sin_theta_expand*saa * (kr_expand**2*sin_theta_expand_square - 12))
205
206    H_expand = H.unsqueeze(2).expand((B,M,3,N))
207    G_expand = G.unsqueeze(2).expand((B,M,3,N))
208    Faa = 2*Ga*Ha + Gaa*H_expand + G_expand*Haa
209    Faa = Faa.to(device).to(DTYPE)
210
211    
212
213    return Faa[:,:,0,:].permute((0,2,1)), Faa[:,:,1,:].permute((0,2,1)), Faa[:,:,2,:].permute((0,2,1))
214
215def forward_model_second_derivative_mixed(points: Tensor, transducers:Tensor|None = None, p_ref = Constants.P_ref,  k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None)->Tensor:
216    '''
217    Computes the second degree mixed analytical gradient of the piston model\n
218    :param points: Point position to compute propagation to 
219    :param transducers: The Transducer array, default two 16x16 arrays 
220    Returns second degree mixed derivatives of forward model wrt x,y,z position - Pxy, Pxz, Pyz
221    '''
222
223    #Bk.2 Pg.317
224
225 
226
227    if transducers is None:
228        transducers= TRANSDUCERS
229
230    if type(p_ref) == float or type(p_ref) == int:
231        M = transducers.shape[0]
232        p_ref = torch.ones(1,M,1, device=device, dtype=DTYPE) * p_ref
233
234    if transducer_norms is None:
235        transducer_norms = (torch.zeros_like(transducers) + torch.tensor([0,0,1], device=device)) * torch.sign(transducers[:,2].real).unsqueeze(1).to(DTYPE)
236    transducer_norms_exp = transducer_norms.unsqueeze(0).unsqueeze(3)
237
238    B = points.shape[0]
239    N = points.shape[2]
240    M = transducers.shape[0]
241
242    if type(k) == float or type(k) == int:
243        k = torch.ones(1,M,1, device=device, dtype=DTYPE) * k
244    k_expanded = k.unsqueeze(3)
245    
246    transducers = torch.unsqueeze(transducers,2)
247    transducers = transducers.expand((B,-1,-1,N))
248    points = torch.unsqueeze(points,1)
249    points = points.expand((-1,M,-1,-1))
250
251    diff = transducers - points + 0j
252    
253    diff_square = diff**2
254    distances = torch.sqrt(torch.sum(diff_square, 2))
255    distances_cube = distances ** 3
256    
257    distances_expanded = distances.unsqueeze(2).expand((B,M,3,N))
258    distances_expanded_square = distances_expanded**2
259    
260    sin_theta = torch.norm(torch.cross(diff, transducer_norms_exp, dim=2),2, dim=2) / distances
261    sin_theta_expand = sin_theta.unsqueeze(2).expand((B,M,3,N))
262
263    dx = diff[:,:,0,:]
264    dy = diff[:,:,1,:]
265    dz = diff[:,:,2,:]
266
267    p_ref_expand = p_ref.unsqueeze(2).expand((B,-1,3,N))
268
269
270    G = p_ref * torch.exp(1j * k * distances) / distances
271
272    kr = k * transducer_radius
273    kr_expanded = k_expanded * transducer_radius
274    kr_sine = kr*sin_theta
275    H = 1 - ((kr_sine)**2) / 8 + ((kr_sine)**4)/192 
276
277    da = -1 * diff / distances_expanded
278    dax = da[:,:,0,:]
279    day = da[:,:,1,:]
280    daz = da[:,:,2,:]
281
282
283    kd_exp = k_expanded * distances_expanded
284    kd = k * distances
285    phase = torch.exp(1j*kd_exp)
286    Ga = p_ref_expand * ( (1j*da*phase * (kd_exp + 1j))/ (distances_expanded_square))
287
288
289    cos_theta = torch.sum(diff * transducer_norms_exp, dim=2) / distances
290    cos_theta_exp = cos_theta.unsqueeze(2)
291    sa = da * cos_theta_exp
292    sx = sa[:,:,0]
293    sy = sa[:,:,1]
294    sz = sa[:,:,2]
295
296
297    Ha = 1/48 * kr_expanded**2 * sin_theta_expand * sa * (kr_expanded**2 * sin_theta_expand**2 - 12)
298
299    dxy = -1*dx*dy / distances_cube
300    dxz = -1*dx*dz / distances_cube
301    dyz = -1*dy*dz / distances_cube
302
303
304    Gxy = (p_ref * torch.exp(1j * kd) * (day * dax * (kd**2 + 2*1j*kd - 2) + distances * dxy * (1 - 1j*kd))) / (-1 * distances_cube)
305    Gxz = (p_ref * torch.exp(1j * kd) * (daz * dax * (kd**2 + 2*1j*kd - 2) + distances * dxz * (1 - 1j*kd))) / (-1 * distances_cube)
306    Gyz = (p_ref * torch.exp(1j * kd) * (day * daz * (kd**2 + 2*1j*kd - 2) + distances * dyz * (1 - 1j*kd))) / (-1 * distances_cube)
307
308
309    Sxy = dxy * cos_theta - dx*dy*sin_theta
310    Sxz = dxz * cos_theta - dx*dz*sin_theta
311    Syz = dyz * cos_theta - dy*dz*sin_theta
312
313
314    Hxy = kr**2 / 48 * (3 * sx * sy * (kr**2 * sin_theta**2 - 4) + sin_theta * Sxy * (kr**2 * sin_theta**2  -12))
315    Hxz = kr**2 / 48 * (3 * sx * sz * (kr**2 * sin_theta**2 - 4) + sin_theta * Sxz * (kr**2 * sin_theta**2  -12))
316    Hyz = kr**2 / 48 * (3 * sy * sz * (kr**2 * sin_theta**2 - 4) + sin_theta * Syz * (kr**2 * sin_theta**2  -12))
317
318
319    #Fab = Ga*Hb + Gb*Ha + Gab * H + G * Hab
320
321    Gx = Ga[:,:,0,:]
322    Gy = Ga[:,:,1,:]
323    Gz = Ga[:,:,2,:]
324
325    Hx = Ha[:,:,0,:]
326    Hy = Ha[:,:,1,:]
327    Hz = Ha[:,:,2,:]
328
329
330    Fxy = Gx * Hy + Gy * Hx + Gxy * H + G*Hxy
331    Fxz = Gx * Hz + Gz * Hx + Gxz * H + G*Hxz
332    Fyz = Gy * Hz + Gz * Hy + Gyz * H + G*Hyz
333
334    Fxy = Fxy.to(device).to(DTYPE)
335    Fxz = Fxz.to(device).to(DTYPE)
336    Fyz = Fyz.to(device).to(DTYPE)
337
338    return Fxy.permute((0,2,1)), Fxz.permute((0,2,1)), Fyz.permute((0,2,1))
def forward_model_grad( points: torch.Tensor, transducers: torch.Tensor | None = None, p_ref=3.4000000000000004, k=732.7329804081634, transducer_radius=0.0045, transducer_norms=None) -> tuple[torch.Tensor]:
 74def forward_model_grad(points:Tensor, transducers:Tensor|None = None, p_ref=Constants.P_ref, k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None) -> tuple[Tensor]:
 75    '''
 76    Computes the analytical gradient of the piston model\n
 77    :param points: Point position to compute propagation to 
 78    :param transducers: The Transducer array, default two 16x16 arrays 
 79    :return: derivative of forward model wrt x,y,z position
 80
 81    ```Python
 82    from acoustools.Utilities import forward_model_grad
 83
 84    Fx, Fy, Fz = forward_model_grad(points,transducers=board)
 85    Px  = torch.abs(Fx@activations) #gradient wrt x position
 86    Py  = torch.abs(Fy@activations) #gradient wrt y position
 87    Pz  = torch.abs(Fz@activations) #gradient wrt z position
 88
 89    ```
 90    '''
 91    if transducers is None:
 92        transducers=TRANSDUCERS
 93    
 94
 95    F,G,H, partialFpartialX, partialGpartialX, partialHpartialX,_,_ = compute_gradients(points, transducers, p_ref=p_ref,k=k, transducer_radius=transducer_radius, transducer_norms=transducer_norms)
 96    derivative = G*(H*partialFpartialX + F*partialHpartialX) + F*H*partialGpartialX
 97    derivative = derivative.to(device).to(DTYPE) # minus here to match f.d -> not 100% sure why its needed
 98
 99
100    return derivative[:,:,0,:].permute((0,2,1)), derivative[:,:,1,:].permute((0,2,1)), derivative[:,:,2,:].permute((0,2,1))

Computes the analytical gradient of the piston model

Parameters
  • points: Point position to compute propagation to
  • transducers: The Transducer array, default two 16x16 arrays
Returns

derivative of forward model wrt x,y,z position

from acoustools.Utilities import forward_model_grad

Fx, Fy, Fz = forward_model_grad(points,transducers=board)
Px  = torch.abs(Fx@activations) #gradient wrt x position
Py  = torch.abs(Fy@activations) #gradient wrt y position
Pz  = torch.abs(Fz@activations) #gradient wrt z position
def forward_model_second_derivative_unmixed( points: torch.Tensor, transducers: torch.Tensor | None = None, p_ref=3.4000000000000004, k=732.7329804081634, transducer_radius=0.0045, transducer_norms=None) -> torch.Tensor:
103def forward_model_second_derivative_unmixed(points:Tensor, transducers:Tensor|None = None, p_ref = Constants.P_ref, k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None) ->Tensor:
104    '''
105    Computes the second degree unmixed analytical gradient of the piston model\n
106    :param points: Point position to compute propagation to 
107    :param transducers: The Transducer array, default two 16x16 arrays 
108    :return: second degree unmixed derivatives of forward model wrt x,y,z position Pxx, Pyy, Pzz
109    '''
110
111    #See Bk.2 Pg.314
112
113    if transducers is None:
114        transducers= TRANSDUCERS
115    
116    if type(p_ref) == float or type(p_ref) == int:
117        M = transducers.shape[0]
118        p_ref = torch.ones(1,M,1, device=device, dtype=DTYPE) * p_ref
119    
120    if type(k) == float or type(k) == int:
121        k = torch.ones(1,M,1, device=device, dtype=DTYPE) * k
122    k_expanded = k.unsqueeze(3)
123
124    if transducer_norms is None:
125        transducer_norms = (torch.zeros_like(transducers) + torch.tensor([0,0,1], device=device)) * torch.sign(transducers[:,2].real).unsqueeze(1).to(DTYPE)
126    transducer_norms_exp = transducer_norms.unsqueeze(0).unsqueeze(3)
127
128    B = points.shape[0]
129    N = points.shape[2]
130    M = transducers.shape[0]
131    
132    transducers = torch.unsqueeze(transducers,2)
133    transducers = transducers.expand((B,-1,-1,N))
134    points = torch.unsqueeze(points,1)
135    points = points.expand((-1,M,-1,-1))
136
137    diff = transducers - points + 0j
138    
139    diff_square = diff**2
140    distances = torch.sqrt(torch.sum(diff_square, 2))
141
142    distances_expanded = distances.unsqueeze(2).expand((B,M,3,N))
143    distances_expanded_square = distances_expanded**2
144    distances_expanded_cube = distances_expanded ** 3
145    
146
147    # sin_theta = planar_distance / distances
148    sin_theta = torch.norm(torch.cross(diff, transducer_norms_exp, dim=2),2, dim=2) / distances
149    sin_theta_expand = sin_theta.unsqueeze(2).expand((B,M,3,N))
150    sin_theta_expand_square = sin_theta_expand**2
151
152    p_ref_expand = p_ref.unsqueeze(2).expand((B,-1,3,N))
153
154    dx = diff[:,:,0,:]
155    dy = diff[:,:,1,:]
156    dz = diff[:,:,2,:]
157
158    # F = G * H 
159    # G  = Pref * e^(ikd) / d
160    # H = 1 - (kr sin(theta))^2 / 8 + (kr sin(theta))^4 / 192
161
162    G = p_ref * torch.exp(1j * k * distances) / distances
163
164    kr = k * transducer_radius
165    kr_expand = k_expanded * transducer_radius
166    kr_sine = kr*sin_theta
167    H = 1 - ((kr_sine)**2) / 8 + ((kr_sine)**4)/192 
168
169
170    #(a = {x,y,z})
171    #Faa = 2*Ga*Ha + Gaa * H + G * Haa
172
173    #Ga = Pref * [i*da * e^{ikd} * (kd+i) / d^2]
174
175    #d = distance
176    #da = -(at - a)^2 / d
177
178    da = -1 * diff / distances_expanded #Gradient of distances
179    kd = k_expanded * distances_expanded
180    phase = torch.exp(1j*kd)
181    Ga = p_ref_expand.expand((B,-1,3,N)) * ( (1j*da*phase * (kd + 1j))/ (distances_expanded_square))
182
183    #Gaa = Pref * [ -1/d^3 * e^{ikd} * (da^2 * (k^2*d^2 + 2ik*d - 2) + d*daa * (1-ikd))]
184    #daa = distance_bs / d^3
185    # distance_bs = sum(b_t - b)^2 . b = {x,y,z} \ a
186    distance_xy = diff[:,:,0,:] **2 + diff[:,:,1,:] **2
187    distance_xz = diff[:,:,0,:] **2 + diff[:,:,2,:] **2
188    distance_yz = diff[:,:,1,:] **2 + diff[:,:,2,:] **2
189
190    distance_bs = torch.stack([distance_yz,distance_xz,distance_xy], dim =2)
191    daa = distance_bs / distances_expanded_cube #Second Gradient of distance function
192
193    Gaa =  p_ref_expand * (-1/distances_expanded_cube * torch.exp(1j*kd) * (da**2 * (kd**2 + 2*1j*kd - 2) + distances_expanded *daa * (1-1j * kd)))
194
195    cos_theta = torch.sum(diff * transducer_norms_exp, dim=2) / distances
196    cos_theta_exp = cos_theta.unsqueeze(2)
197
198    sa = da * cos_theta_exp
199
200    Ha = 1/48 * kr_expand**2 * sin_theta_expand * sa * (kr_expand**2 * sin_theta_expand**2 - 12)
201
202    saa = daa * cos_theta_exp - da**2 * sin_theta_expand
203    # saa[saa.isnan()] = 0
204
205    Haa = 1/48 * kr_expand**2 * (3*sa**2 * (kr_expand**2 * sin_theta_expand_square- 4) + sin_theta_expand*saa * (kr_expand**2*sin_theta_expand_square - 12))
206
207    H_expand = H.unsqueeze(2).expand((B,M,3,N))
208    G_expand = G.unsqueeze(2).expand((B,M,3,N))
209    Faa = 2*Ga*Ha + Gaa*H_expand + G_expand*Haa
210    Faa = Faa.to(device).to(DTYPE)
211
212    
213
214    return Faa[:,:,0,:].permute((0,2,1)), Faa[:,:,1,:].permute((0,2,1)), Faa[:,:,2,:].permute((0,2,1))

Computes the second degree unmixed analytical gradient of the piston model

Parameters
  • points: Point position to compute propagation to
  • transducers: The Transducer array, default two 16x16 arrays
Returns

second degree unmixed derivatives of forward model wrt x,y,z position Pxx, Pyy, Pzz

def forward_model_second_derivative_mixed( points: torch.Tensor, transducers: torch.Tensor | None = None, p_ref=3.4000000000000004, k=732.7329804081634, transducer_radius=0.0045, transducer_norms=None) -> torch.Tensor:
216def forward_model_second_derivative_mixed(points: Tensor, transducers:Tensor|None = None, p_ref = Constants.P_ref,  k=Constants.k, transducer_radius=Constants.radius, transducer_norms=None)->Tensor:
217    '''
218    Computes the second degree mixed analytical gradient of the piston model\n
219    :param points: Point position to compute propagation to 
220    :param transducers: The Transducer array, default two 16x16 arrays 
221    Returns second degree mixed derivatives of forward model wrt x,y,z position - Pxy, Pxz, Pyz
222    '''
223
224    #Bk.2 Pg.317
225
226 
227
228    if transducers is None:
229        transducers= TRANSDUCERS
230
231    if type(p_ref) == float or type(p_ref) == int:
232        M = transducers.shape[0]
233        p_ref = torch.ones(1,M,1, device=device, dtype=DTYPE) * p_ref
234
235    if transducer_norms is None:
236        transducer_norms = (torch.zeros_like(transducers) + torch.tensor([0,0,1], device=device)) * torch.sign(transducers[:,2].real).unsqueeze(1).to(DTYPE)
237    transducer_norms_exp = transducer_norms.unsqueeze(0).unsqueeze(3)
238
239    B = points.shape[0]
240    N = points.shape[2]
241    M = transducers.shape[0]
242
243    if type(k) == float or type(k) == int:
244        k = torch.ones(1,M,1, device=device, dtype=DTYPE) * k
245    k_expanded = k.unsqueeze(3)
246    
247    transducers = torch.unsqueeze(transducers,2)
248    transducers = transducers.expand((B,-1,-1,N))
249    points = torch.unsqueeze(points,1)
250    points = points.expand((-1,M,-1,-1))
251
252    diff = transducers - points + 0j
253    
254    diff_square = diff**2
255    distances = torch.sqrt(torch.sum(diff_square, 2))
256    distances_cube = distances ** 3
257    
258    distances_expanded = distances.unsqueeze(2).expand((B,M,3,N))
259    distances_expanded_square = distances_expanded**2
260    
261    sin_theta = torch.norm(torch.cross(diff, transducer_norms_exp, dim=2),2, dim=2) / distances
262    sin_theta_expand = sin_theta.unsqueeze(2).expand((B,M,3,N))
263
264    dx = diff[:,:,0,:]
265    dy = diff[:,:,1,:]
266    dz = diff[:,:,2,:]
267
268    p_ref_expand = p_ref.unsqueeze(2).expand((B,-1,3,N))
269
270
271    G = p_ref * torch.exp(1j * k * distances) / distances
272
273    kr = k * transducer_radius
274    kr_expanded = k_expanded * transducer_radius
275    kr_sine = kr*sin_theta
276    H = 1 - ((kr_sine)**2) / 8 + ((kr_sine)**4)/192 
277
278    da = -1 * diff / distances_expanded
279    dax = da[:,:,0,:]
280    day = da[:,:,1,:]
281    daz = da[:,:,2,:]
282
283
284    kd_exp = k_expanded * distances_expanded
285    kd = k * distances
286    phase = torch.exp(1j*kd_exp)
287    Ga = p_ref_expand * ( (1j*da*phase * (kd_exp + 1j))/ (distances_expanded_square))
288
289
290    cos_theta = torch.sum(diff * transducer_norms_exp, dim=2) / distances
291    cos_theta_exp = cos_theta.unsqueeze(2)
292    sa = da * cos_theta_exp
293    sx = sa[:,:,0]
294    sy = sa[:,:,1]
295    sz = sa[:,:,2]
296
297
298    Ha = 1/48 * kr_expanded**2 * sin_theta_expand * sa * (kr_expanded**2 * sin_theta_expand**2 - 12)
299
300    dxy = -1*dx*dy / distances_cube
301    dxz = -1*dx*dz / distances_cube
302    dyz = -1*dy*dz / distances_cube
303
304
305    Gxy = (p_ref * torch.exp(1j * kd) * (day * dax * (kd**2 + 2*1j*kd - 2) + distances * dxy * (1 - 1j*kd))) / (-1 * distances_cube)
306    Gxz = (p_ref * torch.exp(1j * kd) * (daz * dax * (kd**2 + 2*1j*kd - 2) + distances * dxz * (1 - 1j*kd))) / (-1 * distances_cube)
307    Gyz = (p_ref * torch.exp(1j * kd) * (day * daz * (kd**2 + 2*1j*kd - 2) + distances * dyz * (1 - 1j*kd))) / (-1 * distances_cube)
308
309
310    Sxy = dxy * cos_theta - dx*dy*sin_theta
311    Sxz = dxz * cos_theta - dx*dz*sin_theta
312    Syz = dyz * cos_theta - dy*dz*sin_theta
313
314
315    Hxy = kr**2 / 48 * (3 * sx * sy * (kr**2 * sin_theta**2 - 4) + sin_theta * Sxy * (kr**2 * sin_theta**2  -12))
316    Hxz = kr**2 / 48 * (3 * sx * sz * (kr**2 * sin_theta**2 - 4) + sin_theta * Sxz * (kr**2 * sin_theta**2  -12))
317    Hyz = kr**2 / 48 * (3 * sy * sz * (kr**2 * sin_theta**2 - 4) + sin_theta * Syz * (kr**2 * sin_theta**2  -12))
318
319
320    #Fab = Ga*Hb + Gb*Ha + Gab * H + G * Hab
321
322    Gx = Ga[:,:,0,:]
323    Gy = Ga[:,:,1,:]
324    Gz = Ga[:,:,2,:]
325
326    Hx = Ha[:,:,0,:]
327    Hy = Ha[:,:,1,:]
328    Hz = Ha[:,:,2,:]
329
330
331    Fxy = Gx * Hy + Gy * Hx + Gxy * H + G*Hxy
332    Fxz = Gx * Hz + Gz * Hx + Gxz * H + G*Hxz
333    Fyz = Gy * Hz + Gz * Hy + Gyz * H + G*Hyz
334
335    Fxy = Fxy.to(device).to(DTYPE)
336    Fxz = Fxz.to(device).to(DTYPE)
337    Fyz = Fyz.to(device).to(DTYPE)
338
339    return Fxy.permute((0,2,1)), Fxz.permute((0,2,1)), Fyz.permute((0,2,1))

Computes the second degree mixed analytical gradient of the piston model

Parameters
  • points: Point position to compute propagation to
  • transducers: The Transducer array, default two 16x16 arrays Returns second degree mixed derivatives of forward model wrt x,y,z position - Pxy, Pxz, Pyz