2 Object oriented approach for the classical multiple scattering problem.
6 - Implement per-scatterer lMax
7 - This means that Scattering.TMatrices either can not be a single array with a fixed
8 (N, 2, nelem, 2, nelem) shape but rather list of (2, nelem, 2, nelem) with nelem varying
9 per particle or some of its elements have to be unused. Anyways, there has to be some kind of
20 from qpms_c
import get_mn_y
, trans_calculator
# TODO be explicit about what is imported
21 from .qpms_p
import cart2sph
, nelem2lMax
# TODO be explicit about what is imported
22 from .timetrack
import _time_b
, _time_e
24 class Scattering(object):
27 This is the most general class for a system of scatterers
28 in a non-lossy homogeneous background
29 to be solved with the multiple_scattering method. The scatterers,
30 as long as they comply with the disjoint circumscribed sphere
31 hypothesis, can each have any position in the 3D space and
34 Note that this object describes the scattering problem only for
35 a single given frequency, as the T-matrices and wavelenght
36 otherwise differ and all the computationally demanding
37 parts have to be done for each frequency. However,
38 the object can be recycled for many incident field shapes
39 at the given frequency.
41 Attributes should be perhaps later redefined to be read-only
42 (or make descriptors for them).
45 positions: (N,3)-shaped real array
46 TMatrices: (N,2,nelem,2,nelem)-shaped array
47 k_0 (float): Wave number for the space between scatterers.
52 k_0 (float): Wave number for the space between scatterers.
53 lMax (int): Absolute maximum l for all scatterers. Depending on implementation,
54 lMax can be smaller for some individual scatterers in certain subclasses.
55 FIXME: here it is still implemented as constant lMax for all sites, see #!
56 prepared (bool): Keeps information whether the interaction matrix has
57 already been built and factorized.
62 def __init__(self
, positions
, TMatrices
, k_0
, lMax
= None, verbose
=False, J_scat
=3):
64 self
.positions
= positions
.reshape((-1, positions
.shape
[-1]))
65 self
.interaction_matrix
= None
66 self
.N
= self
.positions
.shape
[0]
68 self
.lMax
= lMax
if lMax
else nelem2lMax(TMatrices
.shape
[-1])
69 self
.tc
= trans_calculator(self
.lMax
)
70 nelem
= self
.lMax
* (self
.lMax
+ 2) #!
73 self
.TMatrices
= np
.broadcast_to(TMatrices
, (self
.N
,2,nelem
,2,nelem
))
74 if np
.isnan(np
.min(TMatrices
)):
75 warnings
.warn("Some TMatrices contain NaNs. Expect invalid results")
76 if np
.isnan(np
.min(positions
)):
77 warnings
.warn("positions contain NaNs. Expect invalid results")
79 warnings
.warn("k_0 is NaN. Expect invalid results")
83 def prepare(self
, keep_interaction_matrix
= False, verbose
=False):
84 btime
= _time_b(verbose
)
86 if self
.interaction_matrix
is None:
87 self
.build_interaction_matrix(verbose
=verbose
)
88 self
.lupiv
= scipy
.linalg
.lu_factor(self
.interaction_matrix
,overwrite_a
= not keep_interaction_matrix
)
89 if not keep_interaction_matrix
:
90 self
.interaction_matrix
= None
92 _time_e(btime
, verbose
)
94 def build_interaction_matrix(self
,verbose
= False):
95 btime
= _time_b(verbose
)
97 my
, ny
= get_mn_y(self
.lMax
)
99 leftmatrix
= np
.zeros((N
,2,nelem
,N
,2,nelem
), dtype
=complex)
100 sbtime
= _time_b(verbose
, step
= 'Calculating interparticle translation coefficients')
104 for yi in range(nelem):
105 for yj in range(nelem):
107 d_i2j = cart2sph(self.positions[j]-self.positions[i])
108 a = Ã(my[yj],ny[yj],my[yi],ny[yi],kdlj=d_i2j[0]*self.k_0,θlj=d_i2j[1],φlj=d_i2j[2],r_ge_d=False,J=self.J_scat)
109 b = B̃(my[yj],ny[yj],my[yi],ny[yi],kdlj=d_i2j[0]*self.k_0,θlj=d_i2j[1],φlj=d_i2j[2],r_ge_d=False,J=self.J_scat)
110 leftmatrix[j,0,yj,i,0,yi] = a
111 leftmatrix[j,1,yj,i,1,yi] = a
112 leftmatrix[j,0,yj,i,1,yi] = b
113 leftmatrix[j,1,yj,i,0,yi] = b
115 kdji
= cart2sph(self
.positions
[:,nx
,:] - self
.positions
[nx
,:,:])
116 kdji
[:,:,0] *= self
.k_0
117 # get_AB array structure: [j,yj,i,yi]
118 a
, b
= self
.tc
.get_AB(my
[nx
,:,nx
,nx
],ny
[nx
,:,nx
,nx
],my
[nx
,nx
,nx
,:],ny
[nx
,nx
,nx
,:],
119 (kdji
[:,:,0])[:,nx
,:,nx
], (kdji
[:,:,1])[:,nx
,:,nx
], (kdji
[:,:,2])[:,nx
,:,nx
],
121 mask
= np
.broadcast_to(np
.eye(N
,dtype
=bool)[:,nx
,:,nx
],(N
,nelem
,N
,nelem
))
122 a
[mask
] = 0 # no self-translations
124 leftmatrix
[:,0,:,:,0,:] = a
125 leftmatrix
[:,1,:,:,1,:] = a
126 leftmatrix
[:,0,:,:,1,:] = b
127 leftmatrix
[:,1,:,:,0,:] = b
128 _time_e(sbtime
, verbose
, step
= 'Calculating interparticle translation coefficients')
129 # at this point, leftmatrix is the translation matrix
130 n2id
= np
.identity(2*nelem
)
131 n2id
.shape
= (2,nelem
,2,nelem
)
133 leftmatrix
[j
] = - np
.tensordot(self
.TMatrices
[j
],leftmatrix
[j
],axes
=([-2,-1],[0,1]))
134 # at this point, jth row of leftmatrix is that of -MT
135 leftmatrix
[j
,:,:,j
,:,:] += n2id
136 # now we are done, 1-MT
137 leftmatrix
.shape
=(N
*2*nelem
,N
*2*nelem
)
138 self
.interaction_matrix
= leftmatrix
139 _time_e(btime
, verbose
)
141 def scatter(self
, pq_0
, verbose
= False):
143 pq_0 is (N, nelem, 2)-shaped array
145 btime
= _time_b(verbose
)
146 if math
.isnan(np
.min(pq_0
)):
147 warnings
.warn("The incident field expansion contains NaNs. Expect invalid results.")
148 self
.prepare(verbose
=verbose
)
149 pq_0
= np
.broadcast_to(pq_0
, (self
.N
,2,self
.nelem
))
150 MP_0
= np
.empty((self
.N
,2,self
.nelem
),dtype
=np
.complex_
)
151 for j
in range(self
.N
):
152 MP_0
[j
] = np
.tensordot(self
.TMatrices
[j
], pq_0
[j
],axes
=([-2,-1],[-2,-1]))
153 MP_0
.shape
= (self
.N
*2*self
.nelem
,)
154 solvebtime
= _time_b(verbose
,step
='Solving the linear equation')
155 ab
= scipy
.linalg
.lu_solve(self
.lupiv
, MP_0
)
156 if math
.isnan(np
.min(ab
)):
157 warnings
.warn("Got NaN in the scattering result. Damn.")
159 _time_e(solvebtime
, verbose
, step
='Solving the linear equation')
160 ab
.shape
= (self
.N
,2,self
.nelem
)
161 _time_e(btime
, verbose
)
164 def scatter_constmultipole(self
, pq_0_c
, verbose
= False):
165 btime
= _time_b(verbose
)
167 self
.prepare(verbose
=verbose
)
170 pq_0_c
= np
.full((2,nelem
),1)
171 ab
= np
.empty((2,nelem
,N
*2*nelem
), dtype
=complex)
172 for N_or_M
in range(2):
173 for yy
in range(nelem
):
174 pq_0
= np
.zeros((2,nelem
),dtype
=np
.complex_
)
175 pq_0
[N_or_M
,yy
] = pq_0_c
[N_or_M
,yy
]
176 pq_0
= np
.broadcast_to(pq_0
, (N
,2,nelem
))
177 MP_0
= np
.empty((N
,2,nelem
),dtype
=np
.complex_
)
179 MP_0
[j
] = np
.tensordot(self
.TMatrices
[j
], pq_0
[j
],axes
=([-2,-1],[-2,-1]))
180 MP_0
.shape
= (N
*2*nelem
,)
181 ab
[N_or_M
,yy
] = scipy
.linalg
.lu_solve(self
.lupiv
,MP_0
)
182 ab
.shape
= (2,nelem
,N
,2,nelem
)
183 _time_e(btime
, verbose
)
186 class LatticeScattering(Scattering
):
187 def __init__(self
, lattice_spec
, k_0
, zSym
= False):
192 class Scattering_2D_lattice_rectcells(Scattering):
193 def __init__(self, rectcell_dims, rectcell_elem_positions, cellspec, k_0, rectcell_TMatrices = None, TMatrices = None, lMax = None, verbose=False, J_scat=3):
195 cellspec: dvojice ve tvaru (seznam_zaplněnosti, seznam_pozic)
197 if (rectcell_TMatrices is None) == (TMatrices is None):
198 raise ValueError('Either rectcell_TMatrices or TMatrices has to be given')
199 ###self.positions = ZDE JSEM SKONČIL
201 self.positions = positions
202 self.interaction_matrix = None
203 self.N = positions.shape[0]
205 self.lMax = lMax if lMax else nelem2lMax(TMatrices.shape[-1])
206 nelem = lMax * (lMax + 2) #!
207 self.nelem = nelem #!
208 self.prepared = False
209 self.TMatrices = np.broadcast_to(TMatrices, (self.N,2,nelem,2,nelem))
212 class Scattering_2D_zsym(Scattering
):
213 def __init__(self
, positions
, TMatrices
, k_0
, lMax
= None, verbose
=False, J_scat
=3):
214 Scattering
.__init
__(self
, positions
, TMatrices
, k_0
, lMax
, verbose
, J_scat
)
215 #TODO some checks on TMatrices symmetry
216 self
.TE_yz
= np
.arange(self
.nelem
)
217 self
.TM_yz
= self
.TE_yz
218 self
.my
, self
.ny
= get_mn_y(self
.lMax
)
219 self
.TE_NMz
= (self
.my
+ self
.ny
) % 2
220 self
.TM_NMz
= 1 - self
.TE_NMz
221 self
.tc
= trans_calculator(self
.lMax
)
222 # TODO možnost zadávat T-matice rovnou ve zhuštěné podobě
223 TMatrices_TE
= TMatrices
[...,self
.TE_NMz
[:,nx
],self
.TE_yz
[:,nx
],self
.TE_NMz
[nx
,:],self
.TE_yz
[nx
,:]]
224 TMatrices_TM
= TMatrices
[...,self
.TM_NMz
[:,nx
],self
.TM_yz
[:,nx
],self
.TM_NMz
[nx
,:],self
.TM_yz
[nx
,:]]
225 self
.TMatrices_TE
= np
.broadcast_to(TMatrices_TE
, (self
.N
, self
.nelem
, self
.nelem
))
226 self
.TMatrices_TM
= np
.broadcast_to(TMatrices_TM
, (self
.N
, self
.nelem
, self
.nelem
))
227 self
.prepared_TE
= False
228 self
.prepared_TM
= False
229 self
.interaction_matrix_TE
= None
230 self
.interaction_matrix_TM
= None
232 def prepare_partial(self
, TE_or_TM
, keep_interaction_matrix
= False, verbose
=False):
236 btime
= _time_b(verbose
)
237 if (TE_or_TM
== 0): #TE
238 if not self
.prepared_TE
:
239 if self
.interaction_matrix_TE
is None:
240 self
.build_interaction_matrix(0, verbose
)
241 sbtime
= _time_b(verbose
, step
= 'Calculating LU decomposition of the interaction matrix, TE part')
242 self
.lupiv_TE
= scipy
.linalg
.lu_factor(self
.interaction_matrix_TE
, overwrite_a
= not keep_interaction_matrix
)
243 _time_e(sbtime
, verbose
, step
= 'Calculating LU decomposition of the interaction matrix, TE part')
244 if(np
.isnan(np
.min(self
.lupiv_TE
[0])) or np
.isnan(np
.min(self
.lupiv_TE
[1]))):
245 warnings
.warn("LU factorisation of interaction matrix contains NaNs. Expect invalid results.")
246 self
.prepared_TE
= True
247 if (TE_or_TM
== 1): #TM
248 if not self
.prepared_TM
:
249 if self
.interaction_matrix_TM
is None:
250 self
.build_interaction_matrix(1, verbose
)
251 sbtime
= _time_b(verbose
, step
= 'Calculating LU decomposition of the interaction matrix, TM part')
252 self
.lupiv_TM
= scipy
.linalg
.lu_factor(self
.interaction_matrix_TM
, overwrite_a
= not keep_interaction_matrix
)
253 _time_e(sbtime
, verbose
, step
= 'Calculating LU decomposition of the interaction matrix, TM part')
254 if(np
.isnan(np
.min(self
.lupiv_TM
[0])) or np
.isnan(np
.min(self
.lupiv_TM
[1]))):
255 warnings
.warn("LU factorisation of interaction matrix contains NaNs. Expect invalid results.")
256 self
.prepared_TM
= True
257 _time_e(btime
, verbose
)
259 def prepare(self
, keep_interaction_matrix
= False, verbose
=False):
260 btime
= _time_b(verbose
)
261 if not self
.prepared
:
262 self
.prepare_partial(0, keep_interaction_matrix
, verbose
)
263 self
.prepare_partial(1, keep_interaction_matrix
, verbose
)
265 _time_e(btime
, verbose
)
267 def build_interaction_matrix(self
,TE_or_TM
= None, verbose
= False):
269 btime
= _time_b(verbose
)
271 my
, ny
= get_mn_y(self
.lMax
)
273 idm
= np
.identity(nelem
)
276 elif (TE_or_TM
== 1):
278 elif (TE_or_TM
is None):
280 sbtime
= _time_b(verbose
, step
= 'Calculating interparticle translation coefficients')
281 kdji
= cart2sph(self
.positions
[:,nx
,:] - self
.positions
[nx
,:,:], allow2d
=True)
282 kdji
[:,:,0] *= self
.k_0
283 # get_AB array structure: [j,yj,i,yi]
284 # FIXME I could save some memory by calculating only half of these coefficients
285 a
, b
= self
.tc
.get_AB(my
[nx
,:,nx
,nx
],ny
[nx
,:,nx
,nx
],my
[nx
,nx
,nx
,:],ny
[nx
,nx
,nx
,:],
286 (kdji
[:,:,0])[:,nx
,:,nx
], (kdji
[:,:,1])[:,nx
,:,nx
], (kdji
[:,:,2])[:,nx
,:,nx
],
288 mask
= np
.broadcast_to(np
.eye(N
,dtype
=bool)[:,nx
,:,nx
],(N
,nelem
,N
,nelem
))
289 a
[mask
] = 0 # no self-translations
291 if np
.isnan(np
.min(a
)) or np
.isnan(np
.min(b
)):
292 warnings
.warn("Some of the translation coefficients is a NaN. Expect invalid results.")
293 _time_e(sbtime
, verbose
, step
= 'Calculating interparticle translation coefficients')
295 leftmatrix
= np
.zeros((N
,nelem
,N
,nelem
), dtype
=complex)
299 mask
= np
.broadcast_to((((yi
- yj
) % 2) == 0),(N
,nelem
,N
,nelem
))
300 leftmatrix
[mask
] = a
[mask
]
301 mask
= np
.broadcast_to((((yi
- yj
) % 2) != 0),(N
,nelem
,N
,nelem
))
302 leftmatrix
[mask
] = b
[mask
]
303 """ # we use to calculate the AB coefficients here
306 for yi in range(nelem):
307 for yj in range(nelem):
308 d_i2j = cart2sph(self.positions[j]-self.positions[i])
309 if ((yi - yj) % 2) == 0:
310 tr = Ã(my[yj],ny[yj],my[yi],ny[yi],kdlj=d_i2j[0]*self.k_0,θlj=d_i2j[1],φlj=d_i2j[2],r_ge_d=False,J=self.J_scat)
312 tr = B̃(my[yj],ny[yj],my[yi],ny[yi],kdlj=d_i2j[0]*self.k_0,θlj=d_i2j[1],φlj=d_i2j[2],r_ge_d=False,J=self.J_scat)
313 leftmatrix[j,yj,i,yi] = tr
314 leftmatrix[i,yi,j,yj] = tr if (0 == (my[yj]+my[yi]) % 2) else -tr
315 _time_e(sbtime, verbose, step = 'Calculating interparticle translation coefficients, T%s part' % ('M' if EoM else 'E'))
318 leftmatrix
[j
] = - np
.tensordot(self
.TMatrices_TM
[j
] if EoM
else self
.TMatrices_TE
[j
],leftmatrix
[j
],
320 leftmatrix
[j
,:,j
,:] += idm
321 leftmatrix
.shape
= (self
.N
*self
.nelem
, self
.N
*self
.nelem
)
322 if np
.isnan(np
.min(leftmatrix
)):
323 warnings
.warn("Interaction matrix contains some NaNs. Expect invalid results.")
325 self
.interaction_matrix_TE
= leftmatrix
327 self
.interaction_matrix_TM
= leftmatrix
330 _time_e(btime
, verbose
)
332 def scatter_partial(self
, TE_or_TM
, pq_0
, verbose
= False):
334 pq_0 is (N, nelem)-shaped array
336 btime
= _time_b(verbose
)
337 self
.prepare_partial(TE_or_TM
, verbose
= verbose
)
338 pq_0
= np
.broadcast_to(pq_0
, (self
.N
, self
.nelem
))
339 MP_0
= np
.empty((self
.N
,self
.nelem
),dtype
=np
.complex_
)
340 for j
in range(self
.N
):
342 MP_0
[j
] = np
.tensordot(self
.TMatrices_TM
[j
], pq_0
[j
], axes
=([-1],[-1]))
344 MP_0
[j
] = np
.tensordot(self
.TMatrices_TE
[j
], pq_0
[j
], axes
=([-1],[-1]))
345 MP_0
.shape
= (self
.N
*self
.nelem
,)
346 solvebtime
= _time_b(verbose
,step
='Solving the linear equation')
347 ab
= scipy
.linalg
.lu_solve(self
.lupiv_TM
if TE_or_TM
else self
.lupiv_TE
, MP_0
)
348 _time_e(solvebtime
, verbose
, step
='Solving the linear equation')
349 ab
.shape
= (self
.N
, self
.nelem
)
350 _time_e(btime
,verbose
)
353 def scatter(self
, pq_0
, verbose
= False):
356 pq_0 is (N, nelem, 2)-shaped array
358 btime
= _time_b(verbose
)
359 raise Exception('Not yet implemented')
360 self
.prepare(verbose
=verbose
)
361 pq_0
= np
.broadcast_to(pq_0
, (self
.N
,2,self
.nelem
))
362 MP_0
= np
.empty((N
,2,nelem
),dtype
=np
.complex_
)
363 for j
in range(self
.N
):
364 MP_0
[j
] = np
.tensordot(self
.TMatrices
[j
], pq_0
[j
],axes
=([-2,-1],[-2,-1]))
365 MP_0
.shape
= (N
*2*self
.nelem
,)
366 solvebtime
= _time_b(verbose
,step
='Solving the linear equation')
367 ab
= scipy
.linalg
.lu_solve(self
.lupiv
, MP_0
)
368 _time_e(solvebtime
, verbose
, step
='Solving the linear equation')
369 ab
.shape
= (N
,2,nelem
)
370 _time_e(btime
, verbose
)
373 def forget_matrices(self
):
375 Free interaction matrices and set the respective flags
376 (useful when memory is a bottleneck).
378 self
.interaction_matrix_TE
= None
379 self
.interaction_matrix_TM
= None
382 self
.prepared_TE
= False
383 self
.prepared_TM
= False
384 self
.prepared
= False