Docstrings, array dtypes, fix _sswk "constructor"
[qpms.git] / qpms / qpms_c.pyx
blob0b5c6be3d376e1aa490d96bdec05fd61c01fa003
1 """@package qpms_c
2 Cythonized parts of QPMS; mostly wrappers over the C data structures
3 to make them available in Python.
4 """
6 # Cythonized parts of QPMS here
7 # -----------------------------
9 import numpy as np
10 from .qpms_cdefs cimport *
11 from .cyquaternions cimport IRot3, CQuat
12 from .cybspec cimport BaseSpec
13 from .cycommon cimport make_c_string
14 from .cycommon import string_c2py, PointGroupClass, BesselType
15 from .cytmatrices cimport CTMatrix, TMatrixFunction, TMatrixGenerator, TMatrixInterpolator
16 from .cymaterials cimport EpsMuGenerator, EpsMu
17 from libc.stdlib cimport malloc, free, calloc
18 import warnings
20 from cython.parallel cimport prange, parallel
21 from cython cimport boundscheck, wraparound
23 # Set custom GSL error handler. N.B. this is obviously not thread-safe.
24 cdef char *pgsl_err_reason
25 cdef char *pgsl_err_file
26 cdef int pgsl_err_line
27 cdef int pgsl_errno = 0
28 cdef int *pgsl_errno_ignorelist = NULL # list of ignored error codes, terminated by zero
30 # This error handler only sets the variables above
31 cdef void pgsl_error_handler(const char *reason, const char *_file, const int line, const int gsl_errno):
32 global pgsl_err_reason, pgsl_err_file, pgsl_err_line, pgsl_errno, pgsl_errno_ignorelist
33 cdef size_t i
34 if(pgsl_errno_ignorelist):
35 i = 0
36 while pgsl_errno_ignorelist[i] != 0:
37 if gsl_errno == pgsl_errno_ignorelist[i]:
38 return
39 i += 1
40 pgsl_err_file = _file
41 pgsl_err_reason = reason
42 pgsl_errno = gsl_errno
43 pgsl_err_line = line
44 return
46 cdef const int* pgsl_set_ignorelist(const int *new_ignorelist):
47 global pgsl_errno_ignorelist
48 cdef const int *oldlist = pgsl_errno_ignorelist
49 pgsl_errno_ignorelist = new_ignorelist
50 return oldlist
52 cdef class pgsl_ignore_error():
53 '''Context manager for setting a temporary list of errnos ignored by pgsl_error_handler.
54 Always sets pgsl_error_handler.
56 Performs pgsl_check_err() on exit unless
57 '''
58 cdef const int *ignorelist_old
59 cdef gsl_error_handler_t *old_handler
60 cdef bint final_check
61 cdef object ignorelist_python
63 cdef int *ignorelist
64 def __cinit__(self, *ignorelist, **kwargs):
65 self.ignorelist = <int*>calloc((len(ignorelist)+1), sizeof(int))
66 self.ignorelist_python = ignorelist
67 for i in range(len(ignorelist)):
68 self.ignorelist[i] = ignorelist[i]
69 if "final_check" in kwargs.keys() and not kwargs["final_check"]:
70 final_check = True
71 final_check = False
73 def __enter__(self):
74 global pgsl_error_handler
75 self.ignorelist_old = pgsl_set_ignorelist(self.ignorelist)
76 self.old_handler = gsl_set_error_handler(pgsl_error_handler)
77 return
79 def __exit__(self, type, value, traceback):
80 global pgsl_errno_ignorelist, pgsl_error_handler
81 pgsl_set_ignorelist(self.ignorelist_old)
82 gsl_set_error_handler(self.old_handler)
83 if self.final_check:
84 pgsl_check_err(retval = None, ignore = self.ignorelist_python)
86 def __dealloc__(self):
87 free(self.ignorelist)
89 def pgsl_check_err(retval = None, ignorelist = None):
90 global pgsl_err_reason, pgsl_err_file, pgsl_err_line, pgsl_errno
91 '''Check for possible errors encountered by pgsl_error_handler.
92 Takes return value of a function as an optional argument, which is now ignored.
93 '''
94 cdef int errno_was
95 if (pgsl_errno != 0):
96 errno_was = pgsl_errno
97 pgsl_errno = 0
98 raise RuntimeError("Error %d in GSL calculation in %s:%d: %s" % (errno_was,
99 string_c2py(pgsl_err_file), pgsl_err_line, string_c2py(pgsl_err_reason)))
100 if (retval is not None and retval != 0 and ignorelist is not None and retval not in ignorelist):
101 warnings.warn("Got non-zero return value %d" % retval)
102 if retval is not None:
103 return retval
104 else:
105 return 0
107 def set_gsl_pythonic_error_handling():
109 Sets pgsl_error_handler as the GSL error handler to avoid crashing.
111 gsl_set_error_handler(pgsl_error_handler)
113 cdef class PointGroup:
114 cdef readonly qpms_pointgroup_t G
116 def __init__(self, cls, qpms_gmi_t n = 0, IRot3 orientation = IRot3()):
117 cls = PointGroupClass(cls)
118 self.G.c = cls
119 if n <= 0 and qpms_pg_is_finite_axial(cls):
120 raise ValueError("For finite axial groups, n argument must be positive")
121 self.G.n = n
122 self.G.orientation = orientation.qd
124 def __len__(self):
125 return qpms_pg_order(self.G.c, self.G.n);
127 def __le__(PointGroup self, PointGroup other):
128 if qpms_pg_is_subgroup(self.G, other.G):
129 return True
130 else:
131 return False
132 def __ge__(PointGroup self, PointGroup other):
133 if qpms_pg_is_subgroup(other.G, self.G):
134 return True
135 else:
136 return False
137 def __lt__(PointGroup self, PointGroup other):
138 return qpms_pg_is_subgroup(self.G, other.G) and not qpms_pg_is_subgroup(other.G, self.G)
139 def __eq__(PointGroup self, PointGroup other):
140 return qpms_pg_is_subgroup(self.G, other.G) and qpms_pg_is_subgroup(other.G, self.G)
141 def __gt__(PointGroup self, PointGroup other):
142 return not qpms_pg_is_subgroup(self.G, other.G) and qpms_pg_is_subgroup(other.G, self.G)
144 def elems(self):
145 els = list()
146 cdef qpms_irot3_t *arr
147 arr = qpms_pg_elems(NULL, self.G)
148 cdef IRot3 q
149 for i in range(len(self)):
150 q = IRot3()
151 q.cset(arr[i])
152 els.append(q)
153 free(arr)
154 return els
156 cdef class FinitePointGroup:
158 Wrapper over the qpms_finite_group_t structure.
160 TODO more functionality to make it better usable in Python
161 (group element class at least)
164 def __cinit__(self, info):
165 '''Constructs a FinitePointGroup from PointGroupInfo'''
166 # TODO maybe I might use a try..finally statement to avoid leaks
167 # First, generate all basic data from info
168 permlist = info.deterministic_elemlist()
169 cdef int order = len(permlist)
170 permindices = {perm: i for i, perm in enumerate(permlist)} # 'invert' permlist
171 identity = info.permgroup.identity
172 # We use calloc to avoid calling free to unitialized pointers
173 self.G = <qpms_finite_group_t *>calloc(1,sizeof(qpms_finite_group_t))
174 if not self.G: raise MemoryError
175 self.G[0].name = make_c_string(info.name)
176 self.G[0].order = order
177 self.G[0].idi = permindices[identity]
178 self.G[0].mt = <qpms_gmi_t *>malloc(sizeof(qpms_gmi_t) * order * order)
179 if not self.G[0].mt: raise MemoryError
180 for i in range(order):
181 for j in range(order):
182 self.G[0].mt[i*order + j] = permindices[permlist[i] * permlist[j]]
183 self.G[0].invi = <qpms_gmi_t *>malloc(sizeof(qpms_gmi_t) * order)
184 if not self.G[0].invi: raise MemoryError
185 for i in range(order):
186 self.G[0].invi[i] = permindices[permlist[i]**-1]
187 self.G[0].ngens = len(info.permgroupgens)
188 self.G[0].gens = <qpms_gmi_t *>malloc(sizeof(qpms_gmi_t) * self.G[0].ngens)
189 if not self.G[0].gens: raise MemoryError
190 for i in range(self.G[0].ngens):
191 self.G[0].gens[i] = permindices[info.permgroupgens[i]]
192 self.G[0].permrep = <char **>calloc(order, sizeof(char *))
193 if not self.G[0].permrep: raise MemoryError
194 for i in range(order):
195 self.G[0].permrep[i] = make_c_string(str(permlist[i]))
196 if not self.G[0].permrep[i]: raise MemoryError
197 self.G[0].permrep_nelem = info.permgroup.degree
198 if info.rep3d is not None:
199 self.G[0].rep3d = <qpms_irot3_t *>malloc(order * sizeof(qpms_irot3_t))
200 for i in range(order):
201 self.G[0].rep3d[i] = info.rep3d[permlist[i]].qd
202 self.G[0].nirreps = len(info.irreps)
203 self.G[0].irreps = <qpms_finite_group_irrep_t *>calloc(self.G[0].nirreps, sizeof(qpms_finite_group_irrep_t))
204 if not self.G[0].irreps: raise MemoryError
205 cdef int dim
206 for iri, irname in enumerate(sorted(info.irreps.keys())):
207 irrep = info.irreps[irname]
208 is1d = isinstance(irrep[identity], (int, float, complex))
209 dim = 1 if is1d else irrep[identity].shape[0]
210 self.G[0].irreps[iri].dim = dim
211 self.G[0].irreps[iri].name = <char *>make_c_string(irname)
212 if not self.G[0].irreps[iri].name: raise MemoryError
213 self.G[0].irreps[iri].m = <cdouble *>malloc(dim*dim*sizeof(cdouble)*order)
214 if not self.G[0].irreps[iri].m: raise MemoryError
215 if is1d:
216 for i in range(order):
217 self.G[0].irreps[iri].m[i] = irrep[permlist[i]]
218 else:
219 for i in range(order):
220 for row in range(dim):
221 for col in range(dim):
222 self.G[0].irreps[iri].m[i*dim*dim + row*dim + col] = irrep[permlist[i]][row,col]
223 self.G[0].elemlabels = <char **> 0 # Elem labels not yet implemented
224 self.owns_data = True
226 def __dealloc__(self):
227 cdef qpms_gmi_t order
228 if self.owns_data:
229 if self.G:
230 order = self.G[0].order
231 free(self.G[0].name)
232 free(self.G[0].mt)
233 free(self.G[0].invi)
234 free(self.G[0].gens)
235 if self.G[0].permrep:
236 for i in range(order): free(self.G[0].permrep[i])
237 free(self.G[0].permrep)
238 if self.G[0].elemlabels: # this is not even contructed right now
239 for i in range(order): free(self.G[0].elemlabels[i])
240 if self.G[0].irreps:
241 for iri in range(self.G[0].nirreps):
242 free(self.G[0].irreps[iri].name)
243 free(self.G[0].irreps[iri].m)
244 free(self.G[0].irreps)
245 free(self.G)
246 self.G = <qpms_finite_group_t *>0
247 self.owns_data = False
249 property order: # LPTODO might instead be __len__() if iterable at some point
250 def __get__(self):
251 return self.G[0].order
253 @staticmethod
254 def TRIVIAL(): # quite ugly
255 from .symmetries import point_group_info
256 return FinitePointGroup(point_group_info['trivial_g'])
259 cdef class FinitePointGroupElement:
260 '''TODO'''
261 cdef readonly FinitePointGroup G
262 cdef readonly qpms_gmi_t gmi
263 def __cinit__(self, FinitePointGroup G, qpms_gmi_t gmi):
264 self.G = G
265 self.gmi = gmi
267 cdef class Particle:
269 Wrapper over the qpms_particle_t structure.
271 cdef qpms_particle_t p
272 cdef readonly TMatrixFunction f # Reference to ensure correct reference counting
275 def __cinit__(Particle self, pos, t, bspec = None):
276 """Particle object constructor.
278 Parameters
279 ----------
280 pos : (x, y, z)
281 Particle position in cartesian coordinates
283 t : TMatrixGenerator or CTMatrix or TMatrixInterpolator
284 T-matrix specification for the particle.
286 bspec : BaseSpec
287 WSWF basis specification for the particle. Might be omitted if
288 t is a CTMatrix.
290 cdef TMatrixGenerator tgen
291 cdef BaseSpec spec
292 if(len(pos)>=2 and len(pos) < 4):
293 self.p.pos.x = pos[0]
294 self.p.pos.y = pos[1]
295 self.p.pos.z = pos[2] if len(pos)==3 else 0
296 else:
297 raise ValueError("Position argument has to contain 3 or 2 cartesian coordinates")
298 if isinstance(t, CTMatrix):
299 tgen = TMatrixGenerator(t)
300 elif isinstance(t, TMatrixInterpolator):
301 tgen = TMatrixGenerator(t)
302 warnings.warn("Initialising a particle with interpolated T-matrix values. Imaginary frequencies will be discarded and mode search algorithm will yield nonsense (just saying).")
303 elif isinstance(t, TMatrixGenerator):
304 tgen = <TMatrixGenerator>t
305 else: raise TypeError('t must be either CTMatrix or TMatrixGenerator, was %s' % str(type(t)))
306 if bspec is not None:
307 spec = bspec
308 else:
309 if isinstance(tgen.holder, CTMatrix):
310 spec = (<CTMatrix>tgen.holder).spec
311 else:
312 raise ValueError("bspec argument must be specified separately for %s" % str(type(t)))
313 self.f = TMatrixFunction(tgen, spec)
314 self.p.tmg = self.f.rawpointer()
315 # TODO non-trivial transformations later; if modified, do not forget to update ScatteringSystem constructor
316 self.p.op = qpms_tmatrix_operation_noop
318 def __dealloc__(self):
319 qpms_tmatrix_operation_clear(&self.p.op)
321 cdef qpms_particle_t *rawpointer(Particle self):
322 '''Pointer to the qpms_particle_p structure.
324 return &(self.p)
325 property rawpointer:
326 def __get__(self):
327 return <uintptr_t> &(self.p)
329 cdef qpms_particle_t cval(Particle self):
330 '''Provides a copy for assigning in cython code'''
331 return self.p
333 property x:
334 def __get__(self):
335 return self.p.pos.x
336 def __set__(self,x):
337 self.p.pos.x = x
338 property y:
339 def __get__(self):
340 return self.p.pos.y
341 def __set__(self,y):
342 self.p.pos.y = y
343 property z:
344 def __get__(self):
345 return self.p.pos.z
346 def __set__(self,z):
347 self.p.pos.z = z
348 property pos:
349 def __get__(self):
350 return (self.p.pos.x, self.p.pos.y, self.p.pos.z)
351 def __set__(self, pos):
352 if(len(pos)>=2 and len(pos) < 4):
353 self.p.pos.x = pos[0]
354 self.p.pos.y = pos[1]
355 self.p.pos.z = pos[2] if len(pos)==3 else 0
356 else:
357 raise ValueError("Position argument has to contain 3 or 2 cartesian coordinates")
359 cpdef void scatsystem_set_nthreads(long n):
360 qpms_scatsystem_set_nthreads(n)
361 return
364 cdef class ScatteringSystem:
366 Wrapper over the C qpms_scatsys_t structure, representing a collection
367 of scatterers, finite or periodic.
369 Currently, it does not have a standard constructor. Use the
370 ScatteringSystem.create() method instead.
372 cdef list tmgobjs # here we keep the references to occuring TMatrixFunctions (and hence BaseSpecs and TMatrixGenerators)
373 #cdef list Tmatrices # Here we keep the references to occuring T-matrices
374 cdef EpsMuGenerator medium_holder # Here we keep the reference to medium generator
375 cdef qpms_scatsys_t *s
376 cdef FinitePointGroup sym
378 cdef qpms_iri_t iri_py2c(self, iri, allow_None = True):
379 if iri is None and allow_None:
380 return QPMS_NO_IRREP
381 cdef qpms_iri_t nir = self.nirreps
382 cdef qpms_iri_t ciri = iri
383 if ciri < 0 or ciri > nir:
384 raise ValueError("Invalid irrep index %s (of %d irreps)", str(iri), self.nirreps)
385 return ciri
387 def check_s(self): # cdef instead?
388 if self.s == <qpms_scatsys_t *>NULL:
389 raise ValueError("ScatteringSystem's s-pointer not set. You must not use the default constructor; use the create() method instead")
390 #TODO is there a way to disable the constructor outside this module?
392 @staticmethod # We don't have any "standard" constructor for this right now
393 def create(particles, medium, cdouble omega, FinitePointGroup sym = FinitePointGroup.TRIVIAL(),
394 latticebasis = None): # TODO tolerances
395 """(Non-standard) constructor of ScatteringSystem
397 Parameters
398 ----------
399 particles : list of Particles objects
400 Scatterers to be included in the system. These scatterers are then
401 copied around by the constructor using the point group symmetry defined in sym.
402 medium : EpsMu or EpsMuGenerator
403 Material properties of the background medium.
404 omega : complex
405 Any valid angular frequency for the initial T-matrix evaluation.
406 It must be a value which all the T-matrix generators and interpolators
407 referenced by particles can evaluate.
408 sym : FinitePointGroup
409 Symmetry group for a finite system.
410 Defaults to the trivial point group FinitePointGroup.TRIVIAL().
411 Currently, this must be left trivial for periodic systems.
412 latticebasis : None or array_like of float type.
413 Lattice base vectors of a periodic system in cartesian coordinates.
414 The shape must be [d][3], where d = 1, 2 or 3 determines the lattice dimension.
415 Defaults to None, i.e. a finite system.
417 Returns
418 -------
419 ss : ScatteringSystem
420 Object representing the system of compact scatterers.
421 ssw : _ScatteringSystemAtOmega
422 Object representing ss evaluated at omega (angular frequency used for the
423 initial evaluation).
425 Note
426 ----
427 Currently, the ScatterinSystem's T-matrices need to be evaluated for at least
428 one frequency in order to initialise the symmetry structure. For this reason,
429 this "constructor" creates an instance of ScatteringSystem and an instance
430 of _ScatteringSystemAtOmega at the same time, so that the evaluated T-matrices
431 can be used right away if needed. This is why this approach is used instead
432 of the usual __init__ method.
435 if latticebasis is not None and sym.order != 1:
436 raise NotImplementedError("Periodic systems don't currently support nontrivial point group symmetries")
438 # These we are going to construct
439 cdef ScatteringSystem self
440 cdef _ScatteringSystemAtOmega pyssw
442 cdef qpms_scatsys_t orig # This should be automatically init'd to 0 (CHECKME)
443 cdef qpms_ss_pi_t pi, p_count = len(particles)
444 cdef qpms_ss_tmi_t tmi, tm_count = 0
445 cdef qpms_ss_tmgi_t tmgi, tmg_count = 0
447 cdef qpms_scatsys_at_omega_t *ssw
448 cdef qpms_scatsys_t *ss
450 cdef Particle p
452 tmgindices = dict()
453 tmgobjs = list()
454 tmindices = dict()
455 tmlist = list()
456 for p in particles: # find and enumerate unique t-matrix generators
457 if p.p.op.typ != QPMS_TMATRIX_OPERATION_NOOP:
458 raise NotImplementedError("currently, only no-op T-matrix operations are allowed in ScatteringSystem constructor")
459 #tmg_key = id(p.f) # This causes a different generator for each particle -> SUPER SLOW
460 tmg_key = (id(p.f.generator), id(p.f.spec))
461 if tmg_key not in tmgindices:
462 tmgindices[tmg_key] = tmg_count
463 tmgobjs.append(p.f) # Save the references on BaseSpecs and TMatrixGenerators (via TMatrixFunctions)
464 tmg_count += 1
465 # Following lines have to be adjusted when nontrivial operations allowed:
466 tm_derived_key = (tmg_key, None) # TODO unique representation of p.p.op instead of None
467 if tm_derived_key not in tmindices:
468 tmindices[tm_derived_key] = tm_count
469 tmlist.append(tm_derived_key)
470 tm_count += 1
471 cdef EpsMuGenerator mediumgen = EpsMuGenerator(medium)
472 orig.medium = mediumgen.g
473 orig.tmg_count = tmg_count
474 orig.tm_count = tm_count
475 orig.p_count = p_count
476 try:
477 orig.tmg = <qpms_tmatrix_function_t *>malloc(orig.tmg_count * sizeof(orig.tmg[0]))
478 if not orig.tmg: raise MemoryError
479 orig.tm = <qpms_ss_derived_tmatrix_t *>malloc(orig.tm_count * sizeof(orig.tm[0]))
480 if not orig.tm: raise MemoryError
481 orig.p = <qpms_particle_tid_t *>malloc(orig.p_count * sizeof(orig.p[0]))
482 if not orig.p: raise MemoryError
483 for tmgi in range(orig.tmg_count):
484 orig.tmg[tmgi] = (<TMatrixFunction?>tmgobjs[tmgi]).raw()
485 for tmi in range(tm_count):
486 tm_derived_key = tmlist[tmi]
487 tmgi = tmgindices[tm_derived_key[0]]
488 orig.tm[tmi].tmgi = tmgi
489 orig.tm[tmi].op = qpms_tmatrix_operation_noop # TODO adjust when notrivial operations allowed
490 for pi in range(p_count):
491 p = particles[pi]
492 tmg_key = (id(p.f.generator), id(p.f.spec))
493 tm_derived_key = (tmg_key, None) # TODO unique representation of p.p.op instead of None
494 orig.p[pi].pos = p.cval().pos
495 orig.p[pi].tmatrix_id = tmindices[tm_derived_key]
496 if latticebasis is not None: # periodic system
497 assert(len(latticebasis) <= 3 and len(latticebasis) > 0)
498 orig.lattice_dimension = len(latticebasis)
499 for d in range(len(latticebasis)):
500 orig.per.lattice_basis[d] = {'x' : latticebasis[d][0], 'y' : latticebasis[d][1], 'z' : latticebasis[d][2] if len(latticebasis[d]) >= 3 else 0}
501 else: orig.lattice_dimension = 0
502 ssw = qpms_scatsys_apply_symmetry(&orig, sym.rawpointer(), omega, &QPMS_TOLERANCE_DEFAULT)
503 ss = ssw[0].ss
504 finally:
505 free(orig.tmg)
506 free(orig.tm)
507 free(orig.p)
508 self = ScatteringSystem()
509 self.medium_holder = mediumgen
510 self.s = ss
511 self.tmgobjs = tmgobjs
512 self.sym = sym
513 pyssw = _ScatteringSystemAtOmega()
514 pyssw.ssw = ssw
515 pyssw.ss_pyref = self
516 return self, pyssw
518 def __call__(self, cdouble omega):
519 self.check_s()
520 cdef _ScatteringSystemAtOmega pyssw = _ScatteringSystemAtOmega()
521 pyssw.ssw = qpms_scatsys_at_omega(self.s, omega)
522 pyssw.ss_pyref = self
523 return pyssw
525 def __dealloc__(self):
526 if(self.s):
527 qpms_scatsys_free(self.s)
529 property particles_tmi:
530 def __get__(self):
531 self.check_s()
532 r = list()
533 cdef qpms_ss_pi_t pi
534 for pi in range(self.s[0].p_count):
535 r.append(self.s[0].p[pi])
536 return r
538 property particle_count:
539 """Number of particles in the system (or unit cell)"""
540 def __get__(self):
541 self.check_s()
542 return self.s[0].p_count
543 property fecv_size:
544 """Length of the full excitation coefficient vector"""
545 def __get__(self):
546 self.check_s()
547 return self.s[0].fecv_size
548 property saecv_sizes:
549 """Lengths of the partial symmetry-adapted excitation coefficient vectors"""
550 def __get__(self):
551 self.check_s()
552 return [self.s[0].saecv_sizes[i]
553 for i in range(self.s[0].sym[0].nirreps)]
554 property irrep_names:
555 def __get__(self):
556 self.check_s()
557 return [string_c2py(self.s[0].sym[0].irreps[iri].name)
558 if (self.s[0].sym[0].irreps[iri].name) else None
559 for iri in range(self.s[0].sym[0].nirreps)]
560 property nirreps:
561 """Number of irreducible representations of the scattering system point symmetry group"""
562 def __get__(self):
563 self.check_s()
564 return self.s[0].sym[0].nirreps
565 property lattice_dimension:
566 def __get__(self):
567 return self.s[0].lattice_dimension
569 property lattice_basis:
570 """Direct lattice basis vectors for periodic system in 3D cartesian coordinates"""
571 def __get__(self):
572 cdef int d = self.lattice_dimension
573 if d == 0:
574 return None
575 else:
576 return np.array([[self.s[0].per.lattice_basis[i].x,
577 self.s[0].per.lattice_basis[i].y,
578 self.s[0].per.lattice_basis[i].z,
579 ] for i in range(d)])
581 property reciprocal_basis:
582 """Reciprocal lattice basis vectors for periodic system in 3D cartesian coordinates"""
583 def __get__(self):
584 cdef int d = self.lattice_dimension
585 if d == 0:
586 return None
587 else:
588 return np.array([[self.s[0].per.reciprocal_basis[i].x,
589 self.s[0].per.reciprocal_basis[i].y,
590 self.s[0].per.reciprocal_basis[i].z,
591 ] for i in range(d)])
593 def bspec_pi(self, qpms_ss_pi_t pi):
594 """Gives the BaseSpec of a particle defined by an index in the internal particles' order, in the form of an array that can be fed to BaseSpec constructor"""
595 self.check_s()
596 cdef qpms_ss_pi_t pcount = self.s[0].p_count
597 if pi >= pcount or pi < 0: raise IndexError("Invalid particle index")
598 cdef const qpms_vswf_set_spec_t *bspec = qpms_ss_bspec_pi(self.s, pi)
599 return np.array([ bspec[0].ilist[i] for i in range(bspec[0].n)])
600 property bspecs:
601 """Gives the BaseSpecs of all particles in the internal particles' order, in the form of arrays that can be fed to BaseSpec constructor"""
602 def __get__(self):
603 self.check_s()
604 cdef qpms_ss_pi_t pi, pcount = self.s[0].p_count
605 return [self.bspec_pi(pi) for pi in range(pcount)]
607 property unitcell_volume:
608 def __get__(self):
609 self.check_s()
610 if self.lattice_dimension:
611 return self.s[0].per.unitcell_volume
612 else:
613 return None
615 property eta:
616 """Ewald parameter η (only relevant for periodic systems)
618 This is the default value that will be copied into
619 _ScatteringSystemAtOmega by __call__"""
620 def __get__(self):
621 self.check_s()
622 if self.lattice_dimension:
623 return self.s[0].per.eta
624 else:
625 return None
627 def __set__(self, eta):
628 self.check_s()
629 if self.lattice_dimension:
630 self.s[0].per.eta = eta
631 else:
632 raise AttributeError("Cannot set Ewald parameter for finite system") # different exception?
635 def pack_vector(self, vect, iri):
636 """Converts (projects) a full excitation coefficient vector into an irrep subspace.
638 Parameters
639 ----------
640 vect : array_like of shape (self.fecv_size,)
641 The full excitation coefficient vector to be converted.
642 iri : int
643 Index of the irreducible representation.
645 Returns
646 -------
647 packed : ndarray of shape (self.saecv_sizes[iri],)
648 "irrep-packed" excitation vector: the part of vect belonging to the
649 irrep indexed by iri, in terms of given irrep's internal coordinates.
651 See Also
652 --------
653 unpack_vector : The beckward (not exactly inverse) operation.
654 pack_matrix : Corresponding conversion for matrices.
656 self.check_s()
657 if len(vect) != self.fecv_size:
658 raise ValueError("Length of a full vector has to be %d, not %d"
659 % (self.fecv_size, len(vect)))
660 vect = np.array(vect, dtype=complex, copy=False, order='C')
661 cdef cdouble[::1] vect_view = vect;
662 cdef np.ndarray[np.complex_t, ndim=1] target_np = np.empty(
663 (self.saecv_sizes[iri],), dtype=complex, order='C')
664 cdef cdouble[::1] target_view = target_np
665 qpms_scatsys_irrep_pack_vector(&target_view[0], &vect_view[0], self.s, iri)
666 return target_np
668 def unpack_vector(self, packed, iri):
669 """Unpacks an "irrep-packed" excitation coefficient vector to full coordinates.
671 Parameters
672 ----------
673 packed : array_like of shape (self.saecv_sizes[iri],)
674 Excitation coefficient vector part belonging to the irreducible representation
675 indexed by iri, in terms of given irrep's internal coordinates.
676 iri : int
677 Index of the irreducible representation.
679 Returns
680 -------
681 vect : ndarray of shape (self.fecv_size,)
682 The contribution to a full excitation coefficient vector from iri'th irrep.
684 See Also
685 --------
686 pack_vector : The inverse operation.
687 unpack_matrix : Corresponding conversion for matrices.
689 self.check_s()
690 if len(packed) != self.saecv_sizes[iri]:
691 raise ValueError("Length of %d. irrep-packed vector has to be %d, not %d"
692 % (iri, self.saecv_sizes, len(packed)))
693 packed = np.array(packed, dtype=complex, copy=False, order='C')
694 cdef cdouble[::1] packed_view = packed
695 cdef np.ndarray[np.complex_t, ndim=1] target_np = np.empty(
696 (self.fecv_size,), dtype=complex)
697 cdef cdouble[::1] target_view = target_np
698 qpms_scatsys_irrep_unpack_vector(&target_view[0], &packed_view[0],
699 self.s, iri, 0)
700 return target_np
702 def pack_matrix(self, fullmatrix, iri):
703 """Converts (projects) a matrix into an irrep subspace.
705 Parameters
706 ----------
707 fullmatrix : array_like of shape (self.fecv_size, self.fecv_size)
708 The full matrix (operating on the exctitation coefficient vectors) to be converted.
709 iri : int
710 Index of the irreducible representation.
712 Returns
713 -------
714 packedmatrix : ndarray of shape (self.saecv_sizes[iri], self.saecv_sizes[iri])
715 "irrep-packed" matrix: the part of fullmatrix belonging to the
716 irrep indexed by iri, in terms of given irrep's internal coordinates.
718 See Also
719 --------
720 unpack_matrix : The beckward (not exactly inverse) operation.
721 pack_vector : Corresponding conversion for excitation coefficient vectors.
723 self.check_s()
724 cdef size_t flen = self.s[0].fecv_size
725 cdef size_t rlen = self.saecv_sizes[iri]
726 fullmatrix = np.array(fullmatrix, dtype=complex, copy=False, order='C')
727 if fullmatrix.shape != (flen, flen):
728 raise ValueError("Full matrix shape should be (%d,%d), is %s."
729 % (flen, flen, repr(fullmatrix.shape)))
730 cdef cdouble[:,::1] fullmatrix_view = fullmatrix
731 cdef np.ndarray[np.complex_t, ndim=2] target_np = np.empty(
732 (rlen, rlen), dtype=complex, order='C')
733 cdef cdouble[:,::1] target_view = target_np
734 qpms_scatsys_irrep_pack_matrix(&target_view[0][0], &fullmatrix_view[0][0],
735 self.s, iri)
736 return target_np
737 def unpack_matrix(self, packedmatrix, iri):
738 """Unpacks an "irrep-packed" excitation coefficient vector to full coordinates.
740 Parameters
741 ----------
742 packedmatrix : array_like of shape (self.saecv_sizes[iri], self.saecv_sizes[iri])
743 A matrix (operating on the excitation coefficient vectors) part belonging to the
744 irreducible representation indexed by iri, in terms of given irrep's internal
745 coordinates.
746 iri : int
747 Index of the irreducible representation.
749 Returns
750 -------
751 fullmatrix : ndarray of shape (self.fecv_size, self.fecv_size)
752 The iri'th irrep contribution to a full matrix.
754 See Also
755 --------
756 pack_matrix : The inverse operation.
757 unpack_vector : Corresponding conversion for excitation coefficient vectors.
759 self.check_s()
760 cdef size_t flen = self.s[0].fecv_size
761 cdef size_t rlen = self.saecv_sizes[iri]
762 packedmatrix = np.array(packedmatrix, dtype=complex, copy=False, order='C')
763 if packedmatrix.shape != (rlen, rlen):
764 raise ValueError("Packed matrix shape should be (%d,%d), is %s."
765 % (rlen, rlen, repr(packedmatrix.shape)))
766 cdef cdouble[:,::1] packedmatrix_view = packedmatrix
767 cdef np.ndarray[np.complex_t, ndim=2] target_np = np.empty(
768 (flen, flen), dtype=complex, order='C')
769 cdef cdouble[:,::1] target_view = target_np
770 qpms_scatsys_irrep_unpack_matrix(&target_view[0][0], &packedmatrix_view[0][0],
771 self.s, iri, 0)
772 return target_np
774 def translation_matrix_full(self, cdouble wavenumber, blochvector = None, J = QPMS_HANKEL_PLUS, double eta = 0):
775 """Constructs full translation matrix of a scattering system.
777 This method enables to use any wave number for the background medium ignoring the
778 background EpsMuGenerator), using only system's particle positions (and lattice
779 basis for infinite system).
781 Parameters
782 ----------
783 wavenumber : complex
784 Wave number of the medium
786 blochvector : array_like of shape (3,)
787 Bloch vector (only for periodic systems)
789 J : BesselType
790 Optionally, one can replace Hankel functions of the first kind with different
791 Bessel functions.
793 eta : float
794 Ewald parameter η, only relevant for periodic lattices;
795 if set to 0 (default), the actual value is determined
796 automatically.
798 See Also
799 --------
800 ScatteringSystemAtOmega.translation_matrix_full : Translation matrix at a given frequency.
802 self.check_s()
803 cdef size_t flen = self.s[0].fecv_size
804 cdef np.ndarray[np.complex_t, ndim=2] target = np.empty(
805 (flen,flen),dtype=complex, order='C')
806 cdef cdouble[:,::1] target_view = target
807 cdef cart3_t blochvector_c
808 if self.lattice_dimension == 0:
809 if blochvector is None:
810 qpms_scatsys_build_translation_matrix_e_full(&target_view[0][0], self.s, wavenumber, J)
811 else: raise ValueError("Can't use blochvector with non-periodic system")
812 else:
813 if blochvector is None: raise ValueError("Valid blochvector must be specified for periodic system")
814 else:
815 if J != QPMS_HANKEL_PLUS:
816 raise NotImplementedError("Translation operators based on other than Hankel+ functions not supperted in periodic systems")
817 blochvector_c = {'x': blochvector[0], 'y': blochvector[1], 'z': blochvector[2]}
818 with pgsl_ignore_error(15):
819 qpms_scatsys_periodic_build_translation_matrix_full(&target_view[0][0], self.s, wavenumber, &blochvector_c, eta)
820 return target
822 def translation_matrix_packed(self, cdouble wavenumber, qpms_iri_t iri, J = QPMS_HANKEL_PLUS):
823 self.check_s()
824 cdef size_t rlen = self.saecv_sizes[iri]
825 cdef np.ndarray[np.complex_t, ndim=2] target = np.empty(
826 (rlen,rlen),dtype=complex, order='C')
827 cdef cdouble[:,::1] target_view = target
828 qpms_scatsys_build_translation_matrix_e_irrep_packed(&target_view[0][0],
829 self.s, iri, wavenumber, J)
830 return target
832 property fullvec_psizes:
833 def __get__(self):
834 self.check_s()
835 cdef np.ndarray[int32_t, ndim=1] ar = np.empty((self.s[0].p_count,), dtype=np.int32)
836 cdef int32_t[::1] ar_view = ar
837 for pi in range(self.s[0].p_count):
838 ar_view[pi] = qpms_ss_bspec_pi(self.s, pi)[0].n
839 return ar
842 property fullvec_poffsets:
843 def __get__(self):
844 self.check_s()
845 cdef np.ndarray[intptr_t, ndim=1] ar = np.empty((self.s[0].p_count,), dtype=np.intp)
846 cdef intptr_t[::1] ar_view = ar
847 cdef intptr_t offset = 0
848 for pi in range(self.s[0].p_count):
849 ar_view[pi] = offset
850 offset += qpms_ss_bspec_pi(self.s, pi)[0].n
851 return ar
853 property positions:
854 def __get__(self):
855 self.check_s()
856 cdef np.ndarray[np.double_t, ndim=2] ar = np.empty((self.s[0].p_count, 3), dtype=float)
857 cdef np.double_t[:,::1] ar_view = ar
858 for pi in range(self.s[0].p_count):
859 ar_view[pi,0] = self.s[0].p[pi].pos.x
860 ar_view[pi,1] = self.s[0].p[pi].pos.y
861 ar_view[pi,2] = self.s[0].p[pi].pos.z
862 return ar
864 def planewave_full(self, k_cart, E_cart):
865 self.check_s()
866 k_cart = np.array(k_cart)
867 E_cart = np.array(E_cart)
868 if k_cart.shape != (3,) or E_cart.shape != (3,):
869 raise ValueError("k_cart and E_cart must be ndarrays of shape (3,)")
870 cdef qpms_incfield_planewave_params_t p
871 p.use_cartesian = 1
872 p.k.cart.x = <cdouble>k_cart[0]
873 p.k.cart.y = <cdouble>k_cart[1]
874 p.k.cart.z = <cdouble>k_cart[2]
875 p.E.cart.x = <cdouble>E_cart[0]
876 p.E.cart.y = <cdouble>E_cart[1]
877 p.E.cart.z = <cdouble>E_cart[2]
878 cdef np.ndarray[np.complex_t, ndim=1] target_np = np.empty(
879 (self.fecv_size,), dtype=complex)
880 cdef cdouble[::1] target_view = target_np
881 qpms_scatsys_incident_field_vector_full(&target_view[0],
882 self.s, qpms_incfield_planewave, <void *>&p, 0)
883 return target_np
885 def find_modes(self, cdouble omega_centre, double omega_rr, double omega_ri, iri = None,
886 blochvector = None,
887 size_t contour_points = 20, double rank_tol = 1e-4, size_t rank_min_sel=1,
888 double res_tol = 0):
890 Attempts to find the eigenvalues and eigenvectors using Beyn's algorithm.
893 cdef beyn_result_t *res
894 cdef double blochvector_c[3]
895 if self.lattice_dimension == 0:
896 assert(blochvector is None)
897 res = qpms_scatsys_finite_find_eigenmodes(self.s, self.iri_py2c(iri),
898 omega_centre, omega_rr, omega_ri, contour_points,
899 rank_tol, rank_min_sel, res_tol)
900 else:
901 if iri is not None: raise NotImplementedError("Irreps decomposition not yet supported for periodic systems")
902 blochvector_c[0] = blochvector[0]
903 blochvector_c[1] = blochvector[1]
904 blochvector_c[2] = blochvector[2] if len(blochvector) > 2 else 0
905 res = qpms_scatsys_periodic_find_eigenmodes(self.s, blochvector_c,
906 omega_centre, omega_rr, omega_ri, contour_points, rank_tol, rank_min_sel, res_tol)
907 if res == NULL: raise RuntimeError
909 cdef size_t neig = res[0].neig, i, j
910 cdef size_t vlen = res[0].vlen # should be equal to self.s.fecv_size
912 cdef np.ndarray[complex, ndim=1] eigval = np.empty((neig,), dtype=complex)
913 cdef cdouble[::1] eigval_v = eigval
914 cdef np.ndarray[complex, ndim=1] eigval_err = np.empty((neig,), dtype=complex)
915 cdef cdouble[::1] eigval_err_v = eigval_err
916 cdef np.ndarray[double, ndim=1] residuals = np.empty((neig,), dtype=np.double)
917 cdef double[::1] residuals_v = residuals
918 cdef np.ndarray[complex, ndim=2] eigvec = np.empty((neig,vlen),dtype=complex)
919 cdef cdouble[:,::1] eigvec_v = eigvec
920 cdef np.ndarray[double, ndim=1] ranktest_SV = np.empty((vlen), dtype=np.double)
921 cdef double[::1] ranktest_SV_v = ranktest_SV
923 for i in range(neig):
924 eigval_v[i] = res[0].eigval[i]
925 eigval_err_v[i] = res[0].eigval_err[i]
926 residuals_v[i] = res[0].residuals[i]
927 for j in range(vlen):
928 eigvec_v[i,j] = res[0].eigvec[i*vlen + j]
929 for i in range(vlen):
930 ranktest_SV_v[i] = res[0].ranktest_SV[i]
932 zdist = eigval - omega_centre
933 eigval_inside_metric = np.hypot(zdist.real / omega_rr, zdist.imag / omega_ri)
935 beyn_result_free(res)
936 retdict = {
937 'eigval':eigval,
938 'eigval_inside_metric':eigval_inside_metric,
939 'eigvec':eigvec,
940 'residuals':residuals,
941 'eigval_err':eigval_err,
942 'ranktest_SV':ranktest_SV,
943 'iri': iri,
944 'blochvector': blochvector
947 return retdict
949 @boundscheck(False)
950 def scattered_E(self, cdouble wavenumber, scatcoeffvector_full, evalpos, btyp=BesselType.HANKEL_PLUS, bint alt=False):
951 # TODO DOC, periodic systems
952 if self.lattice_dimension != 0: # TODO enable also periodic systems
953 raise NotImplementedError("For periodic system, use _ScatteringSystemAtOmegaK.scattered_E")
954 cdef qpms_bessel_t btyp_c = BesselType(btyp)
955 evalpos = np.array(evalpos, dtype=float, copy=False)
956 if evalpos.shape[-1] != 3:
957 raise ValueError("Last dimension of evalpos has to be 3")
958 cdef np.ndarray[double,ndim=2] evalpos_a = evalpos.reshape(-1,3)
959 cdef np.ndarray[dtype=complex, ndim=1] scv = np.array(scatcoeffvector_full, copy=False, dtype=complex)
960 cdef cdouble[::1] scv_view = scv
961 cdef np.ndarray[complex, ndim=2] results = np.empty((evalpos_a.shape[0],3), dtype=complex)
962 cdef ccart3_t res
963 cdef cart3_t pos
964 cdef Py_ssize_t i
965 with nogil, parallel(), boundscheck(False), wraparound(False):
966 for i in prange(evalpos_a.shape[0]):
967 pos.x = evalpos_a[i,0]
968 pos.y = evalpos_a[i,1]
969 pos.z = evalpos_a[i,2]
970 if alt:
971 res = qpms_scatsys_scattered_E__alt(self.s, btyp_c, wavenumber, &scv_view[0], pos)
972 else:
973 res = qpms_scatsys_scattered_E(self.s, btyp_c, wavenumber, &scv_view[0], pos)
974 results[i,0] = res.x
975 results[i,1] = res.y
976 results[i,2] = res.z
977 return results.reshape(evalpos.shape)
979 def empty_lattice_modes_xy(EpsMu epsmu, reciprocal_basis, wavevector, double maxomega):
980 '''Empty (2D, xy-plane) lattice mode (diffraction order) frequencies of a non-dispersive medium.
982 reciprocal_basis is of the "mutliplied by 2Đż" type.
984 cdef double *omegas_c
985 cdef size_t n
986 cdef cart2_t k_, b1, b2
987 k_.x = wavevector[0]
988 k_.y = wavevector[1]
989 b1.x = reciprocal_basis[0][0]
990 b1.y = reciprocal_basis[0][1]
991 b2.x = reciprocal_basis[1][0]
992 b2.y = reciprocal_basis[1][1]
993 if(epsmu.n.imag != 0):
994 warnings.warn("Got complex refractive index", epsmu.n, "ignoring the imaginary part")
995 refindex = epsmu.n.real
996 n = qpms_emptylattice2_modes_maxfreq(&omegas_c, b1, b2, BASIS_RTOL,
997 k_, GSL_CONST_MKSA_SPEED_OF_LIGHT / refindex, maxomega)
998 cdef np.ndarray[double, ndim=1] omegas = np.empty((n,), dtype=np.double)
999 cdef double[::1] omegas_v = omegas
1000 cdef size_t i
1001 for i in range(n):
1002 omegas_v[i] = omegas_c[i]
1003 free(omegas_c)
1004 return omegas
1006 cdef class _ScatteringSystemAtOmegaK:
1008 Wrapper over the C qpms_scatsys_at_omega_k_t structure
1010 This represents an infinite periodic system with a given (fixed) frequency and Bloch vector.
1012 cdef qpms_scatsys_at_omega_k_t sswk
1013 cdef _ScatteringSystemAtOmega ssw_pyref
1015 cdef qpms_scatsys_at_omega_k_t *rawpointer(self):
1016 return &self.sswk
1018 property eta:
1019 """Ewald parameter η"""
1020 def __get__(self):
1021 return self.sswk.eta
1022 def __set__(self, double eta):
1023 self.sswk.eta = eta
1025 @boundscheck(False)
1026 def scattered_E(self, scatcoeffvector_full, evalpos, btyp=QPMS_HANKEL_PLUS):
1027 """Evaluate electric field for a given excitation coefficient vector (periodic system)
1029 Parameters
1030 ----------
1031 scatcoeffvector_full: array_like of shape (self.fecv_size,)
1032 Onedimensional excitation coefficient vector, describing SVWFs leaving
1033 the scatterers inside the representative unit cell.
1034 evalpos: array_like of floats and shape (..., 3)
1035 Evaluation points in cartesian coordinates.
1037 Returns
1038 -------
1039 array_like of complex, with the same shape as `evalpos`
1040 Electric field at the positions given in `evalpos`.
1042 if(btyp != QPMS_HANKEL_PLUS):
1043 raise NotImplementedError("Only first kind Bessel function-based fields are supported")
1044 cdef qpms_bessel_t btyp_c = BesselType(btyp)
1045 evalpos = np.array(evalpos, dtype=float, copy=False)
1046 if evalpos.shape[-1] != 3:
1047 raise ValueError("Last dimension of evalpos has to be 3")
1048 cdef np.ndarray[double,ndim=2] evalpos_a = evalpos.reshape(-1,3)
1049 cdef np.ndarray[dtype=complex, ndim=1] scv = np.array(scatcoeffvector_full, dtype=complex, copy=False)
1050 cdef cdouble[::1] scv_view = scv
1051 cdef np.ndarray[complex, ndim=2] results = np.empty((evalpos_a.shape[0],3), dtype=complex)
1052 cdef ccart3_t res
1053 cdef cart3_t pos
1054 cdef Py_ssize_t i
1055 with nogil, wraparound(False), parallel():
1056 for i in prange(evalpos_a.shape[0]):
1057 pos.x = evalpos_a[i,0]
1058 pos.y = evalpos_a[i,1]
1059 pos.z = evalpos_a[i,2]
1060 res = qpms_scatsyswk_scattered_E(&self.sswk, btyp_c, &scv_view[0], pos)
1061 results[i,0] = res.x
1062 results[i,1] = res.y
1063 results[i,2] = res.z
1064 return results.reshape(evalpos.shape)
1066 property fecv_size:
1067 def __get__(self): return self.ssw_pyref.ss_pyref.fecv_size
1069 cdef class _ScatteringSystemAtOmega:
1071 Wrapper over the C qpms_scatsys_at_omega_t structure
1072 that keeps the T-matrix and background data evaluated
1073 at specific frequency.
1075 cdef qpms_scatsys_at_omega_t *ssw
1076 cdef ScatteringSystem ss_pyref
1078 def check(self): # cdef instead?
1079 if not self.ssw:
1080 raise ValueError("_ScatteringSystemAtOmega's ssw-pointer not set. You must not use the default constructor; ScatteringSystem.create() instead")
1081 self.ss_pyref.check_s()
1082 #TODO is there a way to disable the constructor outside this module?
1085 def ensure_finite(self):
1086 if self.ssw[0].ss[0].lattice_dimension != 0:
1087 raise NotImplementedError("Operation not supported for periodic systems")
1089 def __dealloc__(self):
1090 if (self.ssw):
1091 qpms_scatsys_at_omega_free(self.ssw)
1093 def apply_Tmatrices_full(self, a):
1094 self.check()
1095 if len(a) != self.fecv_size:
1096 raise ValueError("Length of a full vector has to be %d, not %d"
1097 % (self.fecv_size, len(a)))
1098 a = np.array(a, dtype=complex, copy=False, order='C')
1099 cdef cdouble[::1] a_view = a;
1100 cdef np.ndarray[np.complex_t, ndim=1] target_np = np.empty(
1101 (self.fecv_size,), dtype=complex, order='C')
1102 cdef cdouble[::1] target_view = target_np
1103 qpms_scatsysw_apply_Tmatrices_full(&target_view[0], &a_view[0], self.ssw)
1104 return target_np
1106 cdef qpms_scatsys_at_omega_t *rawpointer(self):
1107 return self.ssw
1109 def scatter_solver(self, iri=None, k=None):
1110 self.check()
1111 cdef _ScatteringSystemAtOmegaK sswk # used only for periodic systems
1112 if(self.ssw[0].ss[0].lattice_dimension == 0):
1113 return ScatteringMatrix(self, iri=iri)
1114 else:
1115 if iri is not None:
1116 raise NotImplementedError("Irrep decomposition not (yet) supported for periodic systems")
1117 sswk = self._sswk(k)
1118 return ScatteringMatrix(ssw=self, sswk=sswk, iri=None)
1120 def _sswk(self, k):
1121 cdef _ScatteringSystemAtOmegaK sswk = _ScatteringSystemAtOmegaK()
1122 sswk.sswk.ssw = self.ssw
1123 sswk.ssw_pyref=self
1124 sswk.sswk.k[0] = k[0]
1125 sswk.sswk.k[1] = k[1]
1126 sswk.sswk.k[2] = k[2]
1127 sswk.eta = qpms_ss_adjusted_eta(self.ssw[0].ss, self.ssw[0].wavenumber, sswk.sswk.k)
1128 return sswk
1130 property fecv_size:
1131 def __get__(self): return self.ss_pyref.fecv_size
1132 property saecv_sizes:
1133 def __get__(self): return self.ss_pyref.saecv_sizes
1134 property irrep_names:
1135 def __get__(self): return self.ss_pyref.irrep_names
1136 property nirreps:
1137 def __get__(self): return self.ss_pyref.nirreps
1138 property wavenumber:
1139 def __get__(self): return self.ssw[0].wavenumber
1142 def modeproblem_matrix_full(self, k=None):
1143 self.check()
1144 cdef size_t flen = self.ss_pyref.s[0].fecv_size
1145 cdef np.ndarray[np.complex_t, ndim=2] target = np.empty(
1146 (flen,flen),dtype=complex, order='C')
1147 cdef cdouble[:,::1] target_view = target
1148 cdef _ScatteringSystemAtOmegaK sswk
1149 if k is not None:
1150 sswk = self._sswk(k)
1151 qpms_scatsyswk_build_modeproblem_matrix_full(&target_view[0][0], &sswk.sswk)
1152 else:
1153 qpms_scatsysw_build_modeproblem_matrix_full(&target_view[0][0], self.ssw)
1154 return target
1156 def modeproblem_matrix_packed(self, qpms_iri_t iri, version='pR'):
1157 self.check()
1158 self.ensure_finite()
1159 cdef size_t rlen = self.saecv_sizes[iri]
1160 cdef np.ndarray[np.complex_t, ndim=2] target = np.empty(
1161 (rlen,rlen),dtype=complex, order='C')
1162 cdef cdouble[:,::1] target_view = target
1163 if (version == 'R'):
1164 qpms_scatsysw_build_modeproblem_matrix_irrep_packed_orbitorderR(&target_view[0][0], self.ssw, iri)
1165 elif (version == 'pR'):
1166 with nogil:
1167 qpms_scatsysw_build_modeproblem_matrix_irrep_packed(&target_view[0][0], self.ssw, iri)
1168 else:
1169 qpms_scatsysw_build_modeproblem_matrix_irrep_packed_serial(&target_view[0][0], self.ssw, iri)
1170 return target
1172 def translation_matrix_full(self, blochvector = None):
1173 """Constructs full translation matrix of a scattering system at given frequency.
1175 Parameters
1176 ----------
1177 blochvector : array_like of shape (3,)
1178 Bloch vector (only for periodic systems)
1180 See Also
1181 --------
1182 ScatteringSystem.translation_matrix_full: translation matrix for any wavenumber
1185 return self.ss_pyref.translation_matrix_full(wavenumber=self.wavenumber, blochvector=blochvector)
1187 def translation_matrix_packed(self, iri, J = QPMS_HANKEL_PLUS):
1188 return self.ss_pyref.translation_matrix_packed(wavenumber=self.wavenumber, iri=iri, J=J)
1190 @boundscheck(False)
1191 def scattered_E(self, scatcoeffvector_full, evalpos, blochvector=None, btyp=QPMS_HANKEL_PLUS, bint alt=False):
1192 # FIXME TODO this obviously does not work for periodic systems
1193 """Evaluate electric field for a given excitation coefficient vector
1195 Parameters
1196 ----------
1197 scatcoeffvector_full: array_like of shape (self.fecv_size,)
1198 Onedimensional excitation coefficient vector, describing SVWFs leaving
1199 the scatterers.
1200 evalpos: array_like of floats and shape (..., 3)
1201 Evaluation points in cartesian coordinates.
1202 blochvector: array_like or None
1203 Bloch vector, must be supplied (non-None) for periodic systems, else None.
1205 Returns
1206 -------
1207 array_like of complex, with the same shape as `evalpos`
1208 Electric field at the positions given in `evalpos`.
1210 See Also
1211 --------
1212 _ScatteringSystemAtOmegaK.scattered_E: Evaluate scattered field in an infinite periodic system.
1214 if(self.ssw[0].ss[0].lattice_dimension != 0):
1215 if blochvector is None:
1216 raise ValueError("For a periodic system, blochvector must be specified.")
1217 return self._sswk(blochvector).scattered_E(scatcoeffvector_full, evalpos=evalpos, btyp=btyp)
1218 cdef qpms_bessel_t btyp_c = BesselType(btyp)
1219 evalpos = np.array(evalpos, dtype=float, copy=False)
1220 if evalpos.shape[-1] != 3:
1221 raise ValueError("Last dimension of evalpos has to be 3")
1222 cdef np.ndarray[double,ndim=2] evalpos_a = evalpos.reshape(-1,3)
1223 cdef np.ndarray[dtype=complex, ndim=1] scv = np.array(scatcoeffvector_full, dtype=complex, copy=False)
1224 cdef cdouble[::1] scv_view = scv
1225 cdef np.ndarray[complex, ndim=2] results = np.empty((evalpos_a.shape[0],3), dtype=complex)
1226 cdef ccart3_t res
1227 cdef cart3_t pos
1228 cdef Py_ssize_t i
1229 with wraparound(False), nogil, parallel():
1230 for i in prange(evalpos_a.shape[0]):
1231 pos.x = evalpos_a[i,0]
1232 pos.y = evalpos_a[i,1]
1233 pos.z = evalpos_a[i,2]
1234 if alt:
1235 res = qpms_scatsysw_scattered_E__alt(self.ssw, btyp_c, &scv_view[0], pos)
1236 else:
1237 res = qpms_scatsysw_scattered_E(self.ssw, btyp_c, &scv_view[0], pos)
1238 results[i,0] = res.x
1239 results[i,1] = res.y
1240 results[i,2] = res.z
1241 return results.reshape(evalpos.shape)
1244 cdef class ScatteringMatrix:
1246 Wrapper over the C qpms_ss_LU structure that keeps the factorised mode problem matrix.
1248 cdef _ScatteringSystemAtOmega ssw # Here we keep the reference to the parent scattering system
1249 cdef _ScatteringSystemAtOmegaK sswk
1250 cdef qpms_ss_LU lu
1252 def __cinit__(self, _ScatteringSystemAtOmega ssw, sswk=None, iri=None):
1253 ssw.check()
1254 self.ssw = ssw
1255 if sswk is None:
1256 ssw.ensure_finite()
1257 # TODO? pre-allocate the matrix with numpy to make it transparent?
1258 if iri is None:
1259 self.lu = qpms_scatsysw_build_modeproblem_matrix_full_LU(
1260 NULL, NULL, ssw.rawpointer())
1261 else:
1262 self.lu = qpms_scatsysw_build_modeproblem_matrix_irrep_packed_LU(
1263 NULL, NULL, ssw.rawpointer(), iri)
1264 else:
1265 # TODO check sswk validity
1266 self.sswk = sswk
1267 self.lu = qpms_scatsyswk_build_modeproblem_matrix_full_LU(NULL, NULL, self.sswk.rawpointer())
1269 def __dealloc__(self):
1270 qpms_ss_LU_free(self.lu)
1272 property iri:
1273 def __get__(self):
1274 return None if self.lu.full else self.lu.iri
1276 def __call__(self, a_inc):
1277 cdef size_t vlen
1278 cdef qpms_iri_t iri = -1;
1279 if self.lu.full:
1280 vlen = self.lu.ssw[0].ss[0].fecv_size
1281 if len(a_inc) != vlen:
1282 raise ValueError("Length of a full coefficient vector has to be %d, not %d"
1283 % (vlen, len(a_inc)))
1284 else:
1285 iri = self.lu.iri
1286 vlen = self.lu.ssw[0].ss[0].saecv_sizes[iri]
1287 if len(a_inc) != vlen:
1288 raise ValueError("Length of a %d. irrep packed coefficient vector has to be %d, not %d"
1289 % (iri, vlen, len(a_inc)))
1290 a_inc = np.array(a_inc, dtype=complex, copy=False, order='C')
1291 cdef const cdouble[::1] a_view = a_inc;
1292 cdef np.ndarray f = np.empty((vlen,), dtype=complex, order='C')
1293 cdef cdouble[::1] f_view = f
1294 qpms_scatsys_scatter_solve(&f_view[0], &a_view[0], self.lu)
1295 return f
1297 def pitau(double theta, qpms_l_t lMax, double csphase = -1):
1298 if(abs(csphase) != 1):
1299 raise ValueError("csphase must be 1 or -1, is %g" % csphase)
1300 cdef size_t nelem = qpms_lMax2nelem(lMax)
1301 cdef np.ndarray[np.float_t, ndim=1] lega = np.empty((nelem,), dtype=float)
1302 cdef np.ndarray[np.float_t, ndim=1] pia = np.empty((nelem,), dtype=float)
1303 cdef np.ndarray[np.float_t, ndim=1] taua = np.empty((nelem,), dtype=float)
1304 cdef double[::1] leg = lega
1305 cdef double[::1] pi = pia
1306 cdef double[::1] tau = taua
1307 qpms_pitau_fill(&leg[0], &pi[0], &tau[0], theta, lMax, csphase)
1308 return (lega, pia, taua)
1310 def linton_gamma(cdouble x):
1311 return clilgamma(x)
1313 def linton_gamma_real(double x):
1314 return lilgamma(x)
1316 def gamma_inc(double a, cdouble x, int m = 0):
1317 cdef qpms_csf_result res
1318 with pgsl_ignore_error(15): #15 is underflow
1319 complex_gamma_inc_e(a, x, m, &res)
1320 return (res.val, res.err)
1322 def gamma_inc_series(double a, cdouble x):
1323 cdef qpms_csf_result res
1324 with pgsl_ignore_error(15): #15 is underflow
1325 cx_gamma_inc_series_e(a, x, &res)
1326 return (res.val, res.err)
1328 def gamma_inc_CF(double a, cdouble x):
1329 cdef qpms_csf_result res
1330 with pgsl_ignore_error(15): #15 is underflow
1331 cx_gamma_inc_CF_e(a, x, &res)
1332 return (res.val, res.err)
1334 def lll_reduce(basis, double delta=0.75):
1336 Lattice basis reduction with the Lenstra-Lenstra-Lovász algorithm.
1338 basis is array_like with dimensions (n, d), where
1339 n is the size of the basis (dimensionality of the lattice)
1340 and d is the dimensionality of the space into which the lattice
1341 is embedded.
1343 basis = np.array(basis, copy=True, order='C', dtype=np.double)
1344 if len(basis.shape) != 2:
1345 raise ValueError("Expected two-dimensional array (got %d-dimensional)"
1346 % len(basis.shape))
1347 cdef size_t n, d
1348 n, d = basis.shape
1349 if n > d:
1350 raise ValueError("Real space dimensionality (%d) cannot be smaller than"
1351 "the dimensionality of the lattice (%d) embedded into it."
1352 % (d, n))
1353 cdef double [:,:] basis_view = basis
1354 if 0 != qpms_reduce_lattice_basis(&basis_view[0,0], n, d, delta):
1355 raise RuntimeError("Something weird happened")
1356 return basis
1359 cdef PGen get_PGen_direct(direct_basis, bint include_origin=False, double layers=30):
1360 dba = np.array(direct_basis)
1361 if not (dba.shape == (2,2)):
1362 raise NotImplementedError
1363 cdef cart2_t b1, b2
1364 b1.x = dba[0,0]
1365 b1.y = dba[0,1]
1366 b2.x = dba[1,0]
1367 b2.y = dba[0,1]
1368 cdef double maxR = layers*max(cart2norm(b1), cart2norm(b2))
1369 return PGen_xyWeb_new(b1, b2, BASIS_RTOL, CART2_ZERO, 0, include_origin, maxR, False)
1371 cdef double get_unitcell_volume(direct_basis):
1372 dba = np.array(direct_basis)
1373 if not (dba.shape == (2,2)):
1374 raise NotImplementedError
1375 cdef cart2_t b1, b2
1376 b1.x = dba[0,0]
1377 b1.y = dba[0,1]
1378 b2.x = dba[1,0]
1379 b2.y = dba[0,1]
1380 return l2d_unitcell_area(b1, b2)
1382 cdef PGen get_PGen_reciprocal2pi(direct_basis, double layers = 30):
1383 dba = np.array(direct_basis)
1384 if not (dba.shape == (2,2)):
1385 raise NotImplementedError
1386 cdef cart2_t b1, b2, rb1, rb2
1387 b1.x = dba[0,0]
1388 b1.y = dba[0,1]
1389 b2.x = dba[1,0]
1390 b2.y = dba[0,1]
1391 if(l2d_reciprocalBasis2pi(b1, b2, &rb1, &rb2) != 0):
1392 raise RuntimeError
1393 cdef double maxK = layers*max(cart2norm(rb1), cart2norm(rb2))
1394 return PGen_xyWeb_new(rb1, rb2, BASIS_RTOL, CART2_ZERO,
1395 0, True, maxK, False)
1397 cdef class Ewald3Calculator:
1398 '''Wrapper class over qpms_ewald3_constants_t.
1400 Mainly for testing low-level scalar Ewald summation functionality.'''
1401 cdef qpms_ewald3_constants_t *c
1403 def __cinit__(self, qpms_l_t lMax, int csphase = -1):
1404 if (csphase != -1 and csphase != 1):
1405 raise ValueError("csphase must be +1 or -1, not %d" % csphase)
1406 self.c = qpms_ewald3_constants_init(lMax, csphase)
1408 def __dealloc__(self):
1409 qpms_ewald3_constants_free(self.c)
1411 def sigma0(self, double eta, cdouble wavenumber, do_err = False):
1412 cdef int retval
1413 cdef double err
1414 cdef cdouble result
1415 retval = ewald3_sigma0(&result, &err, self.c, eta, wavenumber)
1416 if retval:
1417 raise RuntimeError("ewald3_sigma0 returned non-zero value (%d)" % retval)
1418 if do_err:
1419 return (result, err)
1420 else:
1421 return result
1423 def sigma_short(self, double eta, cdouble wavenumber, direct_basis, wavevector, particle_shift, do_err=False):
1424 # FIXME now only 2d XY lattice in 3D is implemented here, we don't even do proper dimensionality checks.
1425 cdef cart3_t beta, pshift
1426 beta.x = wavevector[0]
1427 beta.y = wavevector[1]
1428 beta.z = 0
1429 pshift.x = particle_shift[0]
1430 pshift.y = particle_shift[1]
1431 pshift.z = 0
1432 cdef qpms_l_t n = self.c[0].nelem_sc
1433 cdef np.ndarray[complex, ndim=1] result = np.empty((n,), dtype=complex)
1434 cdef cdouble[::1] result_v = result
1435 cdef np.ndarray[double, ndim=1] err
1436 cdef double[::1] err_v
1437 if do_err:
1438 err = np.empty((n,), dtype=np.double)
1439 err_v = err
1440 cdef bint include_origin = not (particle_shift[0] == 0 and particle_shift[1] == 0)
1441 cdef PGen rgen = get_PGen_direct(direct_basis, include_origin)
1442 cdef int retval = ewald3_sigma_short(&result_v[0], &err_v[0] if do_err else NULL,
1443 self.c, eta, wavenumber, LAT_2D_IN_3D_XYONLY, &rgen, False, beta, pshift)
1444 if rgen.stateData: PGen_destroy(&rgen)
1445 if retval: raise RuntimeError("ewald3_sigma_short returned %d" % retval)
1446 if do_err:
1447 return (result, err)
1448 else:
1449 return result
1451 def sigma_long(self, double eta, cdouble wavenumber, direct_basis, wavevector, particle_shift, do_err=False):
1452 # FIXME now only 2d XY lattice in 3D is implemented here, we don't even do proper dimensionality checks.
1453 cdef cart3_t beta, pshift
1454 beta.x = wavevector[0]
1455 beta.y = wavevector[1]
1456 beta.z = 0
1457 pshift.x = particle_shift[0]
1458 pshift.y = particle_shift[1]
1459 pshift.z = 0
1460 cdef qpms_l_t n = self.c[0].nelem_sc
1461 cdef np.ndarray[complex, ndim=1] result = np.empty((n,), dtype=complex)
1462 cdef cdouble[::1] result_v = result
1463 cdef np.ndarray[double, ndim=1] err
1464 cdef double[::1] err_v
1465 if do_err:
1466 err = np.empty((n,), dtype=np.double)
1467 err_v = err
1468 cdef PGen kgen = get_PGen_reciprocal2pi(direct_basis)
1469 cdef double unitcell_volume = get_unitcell_volume(direct_basis)
1470 cdef int retval = ewald3_sigma_long(&result_v[0], &err_v[0] if do_err else NULL,
1471 self.c, eta, wavenumber, unitcell_volume, LAT_2D_IN_3D_XYONLY, &kgen, False, beta, pshift)
1473 if kgen.stateData: PGen_destroy(&kgen)
1474 if retval: raise RuntimeError("ewald3_sigma_long returned %d" % retval)
1475 if do_err:
1476 return (result, err)
1477 else:
1478 return result