2 Cythonized parts of QPMS; mostly wrappers over the C data structures
3 to make them available in Python.
6 # Cythonized parts of QPMS here
7 # -----------------------------
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
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
34 if(pgsl_errno_ignorelist
):
36 while pgsl_errno_ignorelist
[i
] != 0:
37 if gsl_errno
== pgsl_errno_ignorelist
[i
]:
41 pgsl_err_reason
= reason
42 pgsl_errno
= gsl_errno
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
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
58 cdef const
int *ignorelist_old
59 cdef gsl_error_handler_t
*old_handler
61 cdef object ignorelist_python
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"]:
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
)
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
)
84 pgsl_check_err
(retval
= None
, ignore
= self.ignorelist_python
)
86 def __dealloc__
(self):
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.
96 errno_was
= pgsl_errno
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
:
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
)
119 if n
<= 0 and qpms_pg_is_finite_axial
(cls
):
120 raise ValueError("For finite axial groups, n argument must be positive")
122 self.G
.orientation
= orientation
.qd
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
):
132 def __ge__
(PointGroup
self, PointGroup other
):
133 if qpms_pg_is_subgroup
(other
.G
, self.G
):
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
)
146 cdef qpms_irot3_t
*arr
147 arr
= qpms_pg_elems
(NULL
, self.G
)
149 for i
in range(len(self)):
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
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
216 for i
in range(order
):
217 self.G
[0].irreps
[iri
].m
[i
] = irrep
[permlist
[i
]]
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
230 order
= self.G
[0].order
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
])
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
)
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
251 return self.G
[0].order
254 def TRIVIAL
(): # quite ugly
255 from .symmetries
import point_group_info
256 return FinitePointGroup
(point_group_info
['trivial_g'])
259 cdef class FinitePointGroupElement
:
261 cdef readonly FinitePointGroup G
262 cdef readonly qpms_gmi_t gmi
263 def __cinit__
(self, FinitePointGroup G
, qpms_gmi_t gmi
):
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.
281 Particle position in cartesian coordinates
283 t : TMatrixGenerator or CTMatrix or TMatrixInterpolator
284 T-matrix specification for the particle.
287 WSWF basis specification for the particle. Might be omitted if
290 cdef TMatrixGenerator tgen
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
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
:
309 if isinstance(tgen
.holder
, CTMatrix
):
310 spec
= (<CTMatrix
>tgen
.holder
).spec
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.
327 return <uintptr_t
> &(self.p
)
329 cdef qpms_particle_t cval
(Particle
self):
330 '''Provides a copy for assigning in cython code'''
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
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
)
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
:
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
)
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
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.
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.
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
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
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)
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
)
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
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
):
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
)
508 self = ScatteringSystem
()
509 self.medium_holder
= mediumgen
511 self.tmgobjs
= tmgobjs
513 pyssw
= _ScatteringSystemAtOmega
()
515 pyssw
.ss_pyref
= self
518 def __call__
(self, cdouble omega
):
520 cdef _ScatteringSystemAtOmega pyssw
= _ScatteringSystemAtOmega
()
521 pyssw
.ssw
= qpms_scatsys_at_omega
(self.s
, omega
)
522 pyssw
.ss_pyref
= self
525 def __dealloc__
(self):
527 qpms_scatsys_free
(self.s
)
529 property particles_tmi
:
534 for pi
in range(self.s
[0].p_count
):
535 r
.append
(self.s
[0].p
[pi
])
538 property particle_count
:
539 """Number of particles in the system (or unit cell)"""
542 return self.s
[0].p_count
544 """Length of the full excitation coefficient vector"""
547 return self.s
[0].fecv_size
548 property saecv_sizes
:
549 """Lengths of the partial symmetry-adapted excitation coefficient vectors"""
552 return [self.s
[0].saecv_sizes
[i
]
553 for i
in range(self.s
[0].sym
[0].nirreps
)]
554 property irrep_names
:
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
)]
561 """Number of irreducible representations of the scattering system point symmetry group"""
564 return self.s
[0].sym
[0].nirreps
565 property lattice_dimension
:
567 return self.s
[0].lattice_dimension
569 property lattice_basis
:
570 """Direct lattice basis vectors for periodic system in 3D cartesian coordinates"""
572 cdef int d
= self.lattice_dimension
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"""
584 cdef int d
= self.lattice_dimension
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"""
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
)])
601 """Gives the BaseSpecs of all particles in the internal particles' order, in the form of arrays that can be fed to BaseSpec constructor"""
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
:
610 if self.lattice_dimension
:
611 return self.s
[0].per
.unitcell_volume
616 """Ewald parameter η (only relevant for periodic systems)
618 This is the default value that will be copied into
619 _ScatteringSystemAtOmega by __call__"""
622 if self.lattice_dimension
:
623 return self.s
[0].per
.eta
627 def __set__
(self, eta
):
629 if self.lattice_dimension
:
630 self.s
[0].per
.eta
= eta
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.
640 vect : array_like of shape (self.fecv_size,)
641 The full excitation coefficient vector to be converted.
643 Index of the irreducible representation.
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.
653 unpack_vector : The beckward (not exactly inverse) operation.
654 pack_matrix : Corresponding conversion for matrices.
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
)
668 def unpack_vector
(self, packed
, iri
):
669 """Unpacks an "irrep-packed" excitation coefficient vector to full coordinates.
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.
677 Index of the irreducible representation.
681 vect : ndarray of shape (self.fecv_size,)
682 The contribution to a full excitation coefficient vector from iri'th irrep.
686 pack_vector : The inverse operation.
687 unpack_matrix : Corresponding conversion for matrices.
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],
702 def pack_matrix
(self, fullmatrix
, iri
):
703 """Converts (projects) a matrix into an irrep subspace.
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.
710 Index of the irreducible representation.
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.
720 unpack_matrix : The beckward (not exactly inverse) operation.
721 pack_vector : Corresponding conversion for excitation coefficient vectors.
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],
737 def unpack_matrix
(self, packedmatrix
, iri
):
738 """Unpacks an "irrep-packed" excitation coefficient vector to full coordinates.
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
747 Index of the irreducible representation.
751 fullmatrix : ndarray of shape (self.fecv_size, self.fecv_size)
752 The iri'th irrep contribution to a full matrix.
756 pack_matrix : The inverse operation.
757 unpack_vector : Corresponding conversion for excitation coefficient vectors.
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],
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).
784 Wave number of the medium
786 blochvector : array_like of shape (3,)
787 Bloch vector (only for periodic systems)
790 Optionally, one can replace Hankel functions of the first kind with different
794 Ewald parameter η, only relevant for periodic lattices;
795 if set to 0 (default), the actual value is determined
800 ScatteringSystemAtOmega.translation_matrix_full : Translation matrix at a given frequency.
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")
813 if blochvector
is None
: raise ValueError("Valid blochvector must be specified for periodic system")
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
)
822 def translation_matrix_packed
(self, cdouble wavenumber
, qpms_iri_t iri
, J
= QPMS_HANKEL_PLUS
):
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
)
832 property fullvec_psizes
:
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
842 property fullvec_poffsets
:
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
):
850 offset
+= qpms_ss_bspec_pi
(self.s
, pi
)[0].n
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
864 def planewave_full
(self, k_cart
, E_cart
):
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
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)
885 def find_modes
(self, cdouble omega_centre
, double omega_rr
, double omega_ri
, iri
= None
,
887 size_t contour_points
= 20, double rank_tol
= 1e-4, size_t rank_min_sel
=1,
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
)
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
)
938 'eigval_inside_metric':eigval_inside_metric
,
940 'residuals':residuals
,
941 'eigval_err':eigval_err
,
942 'ranktest_SV':ranktest_SV
,
944 'blochvector': blochvector
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)
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]
971 res
= qpms_scatsys_scattered_E__alt
(self.s
, btyp_c
, wavenumber
, &scv_view
[0], pos
)
973 res
= qpms_scatsys_scattered_E
(self.s
, btyp_c
, wavenumber
, &scv_view
[0], pos
)
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
986 cdef cart2_t k_
, b1
, b2
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
1002 omegas_v
[i
] = omegas_c
[i
]
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):
1019 """Ewald parameter η"""
1021 return self.sswk
.eta
1022 def __set__
(self, double eta
):
1026 def scattered_E
(self, scatcoeffvector_full
, evalpos
, btyp
=QPMS_HANKEL_PLUS
):
1027 """Evaluate electric field for a given excitation coefficient vector (periodic system)
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.
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)
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
)
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?
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):
1091 qpms_scatsys_at_omega_free
(self.ssw
)
1093 def apply_Tmatrices_full
(self, a
):
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
)
1106 cdef qpms_scatsys_at_omega_t
*rawpointer
(self):
1109 def scatter_solver
(self, iri
=None
, k
=None
):
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
)
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
)
1121 cdef _ScatteringSystemAtOmegaK sswk
= _ScatteringSystemAtOmegaK
()
1122 sswk
.sswk
.ssw
= self.ssw
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
)
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
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
):
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
1150 sswk
= self._sswk
(k
)
1151 qpms_scatsyswk_build_modeproblem_matrix_full
(&target_view
[0][0], &sswk
.sswk
)
1153 qpms_scatsysw_build_modeproblem_matrix_full
(&target_view
[0][0], self.ssw
)
1156 def modeproblem_matrix_packed
(self, qpms_iri_t iri
, version
='pR'):
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'):
1167 qpms_scatsysw_build_modeproblem_matrix_irrep_packed
(&target_view
[0][0], self.ssw
, iri
)
1169 qpms_scatsysw_build_modeproblem_matrix_irrep_packed_serial
(&target_view
[0][0], self.ssw
, iri
)
1172 def translation_matrix_full
(self, blochvector
= None
):
1173 """Constructs full translation matrix of a scattering system at given frequency.
1177 blochvector : array_like of shape (3,)
1178 Bloch vector (only for periodic systems)
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
)
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
1197 scatcoeffvector_full: array_like of shape (self.fecv_size,)
1198 Onedimensional excitation coefficient vector, describing SVWFs leaving
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.
1207 array_like of complex, with the same shape as `evalpos`
1208 Electric field at the positions given in `evalpos`.
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)
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]
1235 res
= qpms_scatsysw_scattered_E__alt
(self.ssw
, btyp_c
, &scv_view
[0], pos
)
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
1252 def __cinit__
(self, _ScatteringSystemAtOmega ssw
, sswk
=None
, iri
=None
):
1257 # TODO? pre-allocate the matrix with numpy to make it transparent?
1259 self.lu
= qpms_scatsysw_build_modeproblem_matrix_full_LU
(
1260 NULL
, NULL
, ssw
.rawpointer
())
1262 self.lu
= qpms_scatsysw_build_modeproblem_matrix_irrep_packed_LU
(
1263 NULL
, NULL
, ssw
.rawpointer
(), iri
)
1265 # TODO check sswk validity
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
)
1274 return None
if self.lu
.full
else self.lu
.iri
1276 def __call__
(self, a_inc
):
1278 cdef qpms_iri_t iri
= -1;
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
)))
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
)
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
):
1313 def linton_gamma_real
(double 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
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)"
1350 raise ValueError("Real space dimensionality (%d) cannot be smaller than"
1351 "the dimensionality of the lattice (%d) embedded into it."
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")
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
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
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
1391 if(l2d_reciprocalBasis2pi
(b1
, b2
, &rb1
, &rb2
) != 0):
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
):
1415 retval
= ewald3_sigma0
(&result
, &err
, self.c
, eta
, wavenumber
)
1417 raise RuntimeError("ewald3_sigma0 returned non-zero value (%d)" % retval
)
1419 return (result
, err
)
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]
1429 pshift
.x
= particle_shift
[0]
1430 pshift
.y
= particle_shift
[1]
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
1438 err
= np
.empty
((n
,), dtype
=np
.double
)
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
)
1447 return (result
, err
)
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]
1457 pshift
.x
= particle_shift
[0]
1458 pshift
.y
= particle_shift
[1]
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
1466 err
= np
.empty
((n
,), dtype
=np
.double
)
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
)
1476 return (result
, err
)