2019-07-14 20:19:13 +03:00
""" @package qpms_c
Cythonized parts of QPMS ; mostly wrappers over the C data structures
to make them available in Python .
"""
2017-04-19 17:43:24 +03:00
# Cythonized parts of QPMS here
# -----------------------------
2016-03-27 12:56:54 +03:00
import numpy as np
2019-08-10 10:34:55 +03:00
from .qpms_cdefs cimport *
from .cyquaternions cimport IRot3 , CQuat
from .cybspec cimport BaseSpec
from .cycommon cimport make_c_string
2020-03-22 11:44:20 +02:00
from .cycommon import string_c2py , PointGroupClass , BesselType
2020-02-13 19:05:11 +02:00
from .cytmatrices cimport CTMatrix , TMatrixFunction , TMatrixGenerator , TMatrixInterpolator
2020-03-08 13:12:56 +02:00
from .cymaterials cimport EpsMuGenerator , EpsMu
2019-08-10 10:15:08 +03:00
from libc.stdlib cimport malloc , free , calloc
2019-08-20 12:46:28 +03:00
import warnings
2022-06-21 05:45:59 +03:00
def qpms_library_version ( ) :
return " git_ " + qpms_version_git_sha1 . decode ( ' utf-8 ' )
2020-07-03 15:10:15 +03:00
from cython.parallel cimport prange , parallel
from cython cimport boundscheck , wraparound
2019-08-20 12:46:28 +03:00
# Set custom GSL error handler. N.B. this is obviously not thread-safe.
cdef char *pgsl_err_reason
cdef char *pgsl_err_file
cdef int pgsl_err_line
cdef int pgsl_errno = 0
cdef int *pgsl_errno_ignorelist = NULL # list of ignored error codes, terminated by zero
# This error handler only sets the variables above
cdef void pgsl_error_handler ( const char * reason , const char * _file , const int line , const int gsl_errno ) :
global pgsl_err_reason , pgsl_err_file , pgsl_err_line , pgsl_errno , pgsl_errno_ignorelist
cdef size_t i
if ( pgsl_errno_ignorelist ) :
i = 0
while pgsl_errno_ignorelist [ i ] != 0 :
if gsl_errno == pgsl_errno_ignorelist [ i ] :
return
i + = 1
pgsl_err_file = _file
pgsl_err_reason = reason
pgsl_errno = gsl_errno
pgsl_err_line = line
return
cdef const int * pgsl_set_ignorelist ( const int * new_ignorelist ) :
global pgsl_errno_ignorelist
cdef const int *oldlist = pgsl_errno_ignorelist
pgsl_errno_ignorelist = new_ignorelist
return oldlist
cdef class pgsl_ignore_error ( ) :
''' Context manager for setting a temporary list of errnos ignored by pgsl_error_handler.
Always sets pgsl_error_handler .
Performs pgsl_check_err ( ) on exit unless
'''
cdef const int *ignorelist_old
cdef gsl_error_handler_t *old_handler
cdef bint final_check
cdef object ignorelist_python
cdef int *ignorelist
def __cinit__ ( self , * ignorelist , * * kwargs ) :
self . ignorelist = < int * > calloc ( ( len ( ignorelist ) + 1 ) , sizeof ( int ) )
self . ignorelist_python = ignorelist
for i in range ( len ( ignorelist ) ) :
self . ignorelist [ i ] = ignorelist [ i ]
if " final_check " in kwargs . keys ( ) and not kwargs [ " final_check " ] :
final_check = True
final_check = False
def __enter__ ( self ) :
global pgsl_error_handler
self . ignorelist_old = pgsl_set_ignorelist ( self . ignorelist )
self . old_handler = gsl_set_error_handler ( pgsl_error_handler )
return
def __exit__ ( self , type , value , traceback ) :
global pgsl_errno_ignorelist , pgsl_error_handler
pgsl_set_ignorelist ( self . ignorelist_old )
gsl_set_error_handler ( self . old_handler )
if self . final_check :
pgsl_check_err ( retval = None , ignore = self . ignorelist_python )
def __dealloc__ ( self ) :
free ( self . ignorelist )
def pgsl_check_err ( retval = None , ignorelist = None ) :
global pgsl_err_reason , pgsl_err_file , pgsl_err_line , pgsl_errno
''' Check for possible errors encountered by pgsl_error_handler.
Takes return value of a function as an optional argument , which is now ignored .
'''
cdef int errno_was
if ( pgsl_errno != 0 ) :
errno_was = pgsl_errno
pgsl_errno = 0
raise RuntimeError ( " Error %d in GSL calculation in %s : %d : %s " % ( errno_was ,
string_c2py ( pgsl_err_file ) , pgsl_err_line , string_c2py ( pgsl_err_reason ) ) )
if ( retval is not None and retval != 0 and ignorelist is not None and retval not in ignorelist ) :
warnings . warn ( " Got non-zero return value %d " % retval )
if retval is not None :
return retval
else :
return 0
def set_gsl_pythonic_error_handling ( ) :
'''
Sets pgsl_error_handler as the GSL error handler to avoid crashing .
'''
gsl_set_error_handler ( pgsl_error_handler )
2017-05-02 00:18:01 +03:00
2019-07-24 18:26:12 +03:00
cdef class PointGroup :
cdef readonly qpms_pointgroup_t G
def __init__ ( self , cls , qpms_gmi_t n = 0 , IRot3 orientation = IRot3 ( ) ) :
cls = PointGroupClass ( cls )
self . G . c = cls
if n < = 0 and qpms_pg_is_finite_axial ( cls ) :
raise ValueError ( " For finite axial groups, n argument must be positive " )
self . G . n = n
self . G . orientation = orientation . qd
def __len__ ( self ) :
return qpms_pg_order ( self . G . c , self . G . n ) ;
def __le__ ( PointGroup self , PointGroup other ) :
if qpms_pg_is_subgroup ( self . G , other . G ) :
return True
else :
return False
def __ge__ ( PointGroup self , PointGroup other ) :
if qpms_pg_is_subgroup ( other . G , self . G ) :
return True
else :
return False
def __lt__ ( PointGroup self , PointGroup other ) :
return qpms_pg_is_subgroup ( self . G , other . G ) and not qpms_pg_is_subgroup ( other . G , self . G )
def __eq__ ( PointGroup self , PointGroup other ) :
return qpms_pg_is_subgroup ( self . G , other . G ) and qpms_pg_is_subgroup ( other . G , self . G )
def __gt__ ( PointGroup self , PointGroup other ) :
return not qpms_pg_is_subgroup ( self . G , other . G ) and qpms_pg_is_subgroup ( other . G , self . G )
def elems ( self ) :
els = list ( )
cdef qpms_irot3_t *arr
arr = qpms_pg_elems ( NULL , self . G )
cdef IRot3 q
for i in range ( len ( self ) ) :
q = IRot3 ( )
q . cset ( arr [ i ] )
els . append ( q )
free ( arr )
return els
2019-02-28 13:38:00 +02:00
cdef class FinitePointGroup :
'''
Wrapper over the qpms_finite_group_t structure .
2019-03-02 21:32:46 +02:00
TODO more functionality to make it better usable in Python
( group element class at least )
2019-02-28 13:38:00 +02:00
'''
2019-03-02 21:32:46 +02:00
def __cinit__ ( self , info ) :
'''Constructs a FinitePointGroup from PointGroupInfo'''
# TODO maybe I might use a try..finally statement to avoid leaks
# First, generate all basic data from info
2019-03-07 07:09:44 +02:00
permlist = info . deterministic_elemlist ( )
2019-03-02 21:32:46 +02:00
cdef int order = len ( permlist )
permindices = { perm : i for i , perm in enumerate ( permlist ) } # 'invert' permlist
2019-03-02 21:43:11 +02:00
identity = info . permgroup . identity
2019-03-02 22:16:14 +02:00
# We use calloc to avoid calling free to unitialized pointers
self . G = < qpms_finite_group_t * > calloc ( 1 , sizeof ( qpms_finite_group_t ) )
2019-03-02 21:32:46 +02:00
if not self . G : raise MemoryError
self . G [ 0 ] . name = make_c_string ( info . name )
self . G [ 0 ] . order = order
self . G [ 0 ] . idi = permindices [ identity ]
self . G [ 0 ] . mt = < qpms_gmi_t * > malloc ( sizeof ( qpms_gmi_t ) * order * order )
if not self . G [ 0 ] . mt : raise MemoryError
for i in range ( order ) :
for j in range ( order ) :
self . G [ 0 ] . mt [ i * order + j ] = permindices [ permlist [ i ] * permlist [ j ] ]
self . G [ 0 ] . invi = < qpms_gmi_t * > malloc ( sizeof ( qpms_gmi_t ) * order )
if not self . G [ 0 ] . invi : raise MemoryError
for i in range ( order ) :
self . G [ 0 ] . invi [ i ] = permindices [ permlist [ i ] * * - 1 ]
self . G [ 0 ] . ngens = len ( info . permgroupgens )
self . G [ 0 ] . gens = < qpms_gmi_t * > malloc ( sizeof ( qpms_gmi_t ) * self . G [ 0 ] . ngens )
if not self . G [ 0 ] . gens : raise MemoryError
for i in range ( self . G [ 0 ] . ngens ) :
2019-03-02 22:16:14 +02:00
self . G [ 0 ] . gens [ i ] = permindices [ info . permgroupgens [ i ] ]
2019-03-02 21:32:46 +02:00
self . G [ 0 ] . permrep = < char * * > calloc ( order , sizeof ( char * ) )
if not self . G [ 0 ] . permrep : raise MemoryError
for i in range ( order ) :
self . G [ 0 ] . permrep [ i ] = make_c_string ( str ( permlist [ i ] ) )
if not self . G [ 0 ] . permrep [ i ] : raise MemoryError
self . G [ 0 ] . permrep_nelem = info . permgroup . degree
if info . rep3d is not None :
self . G [ 0 ] . rep3d = < qpms_irot3_t * > malloc ( order * sizeof ( qpms_irot3_t ) )
for i in range ( order ) :
self . G [ 0 ] . rep3d [ i ] = info . rep3d [ permlist [ i ] ] . qd
self . G [ 0 ] . nirreps = len ( info . irreps )
self . G [ 0 ] . irreps = < qpms_finite_group_irrep_t * > calloc ( self . G [ 0 ] . nirreps , sizeof ( qpms_finite_group_irrep_t ) )
if not self . G [ 0 ] . irreps : raise MemoryError
cdef int dim
2019-03-13 23:13:41 +02:00
for iri , irname in enumerate ( sorted ( info . irreps . keys ( ) ) ) :
irrep = info . irreps [ irname ]
2019-03-02 21:32:46 +02:00
is1d = isinstance ( irrep [ identity ] , ( int , float , complex ) )
dim = 1 if is1d else irrep [ identity ] . shape [ 0 ]
self . G [ 0 ] . irreps [ iri ] . dim = dim
self . G [ 0 ] . irreps [ iri ] . name = < char * > make_c_string ( irname )
if not self . G [ 0 ] . irreps [ iri ] . name : raise MemoryError
self . G [ 0 ] . irreps [ iri ] . m = < cdouble * > malloc ( dim * dim * sizeof ( cdouble ) * order )
if not self . G [ 0 ] . irreps [ iri ] . m : raise MemoryError
if is1d :
for i in range ( order ) :
self . G [ 0 ] . irreps [ iri ] . m [ i ] = irrep [ permlist [ i ] ]
else :
for i in range ( order ) :
for row in range ( dim ) :
for col in range ( dim ) :
self . G [ 0 ] . irreps [ iri ] . m [ i * dim * dim + row * dim + col ] = irrep [ permlist [ i ] ] [ row , col ]
2019-03-02 22:16:14 +02:00
self . G [ 0 ] . elemlabels = < char * * > 0 # Elem labels not yet implemented
2019-03-02 21:32:46 +02:00
self . owns_data = True
def __dealloc__ ( self ) :
cdef qpms_gmi_t order
if self . owns_data :
if self . G :
order = self . G [ 0 ] . order
free ( self . G [ 0 ] . name )
free ( self . G [ 0 ] . mt )
free ( self . G [ 0 ] . invi )
free ( self . G [ 0 ] . gens )
if self . G [ 0 ] . permrep :
for i in range ( order ) : free ( self . G [ 0 ] . permrep [ i ] )
free ( self . G [ 0 ] . permrep )
if self . G [ 0 ] . elemlabels : # this is not even contructed right now
for i in range ( order ) : free ( self . G [ 0 ] . elemlabels [ i ] )
if self . G [ 0 ] . irreps :
for iri in range ( self . G [ 0 ] . nirreps ) :
free ( self . G [ 0 ] . irreps [ iri ] . name )
free ( self . G [ 0 ] . irreps [ iri ] . m )
free ( self . G [ 0 ] . irreps )
free ( self . G )
self . G = < qpms_finite_group_t * > 0
self . owns_data = False
2020-02-29 01:35:12 +02:00
property order : # LPTODO might instead be __len__() if iterable at some point
def __get__ ( self ) :
return self . G [ 0 ] . order
@staticmethod
def TRIVIAL ( ) : # quite ugly
from .symmetries import point_group_info
return FinitePointGroup ( point_group_info [ ' trivial_g ' ] )
2019-03-02 21:32:46 +02:00
cdef class FinitePointGroupElement :
'''TODO'''
cdef readonly FinitePointGroup G
cdef readonly qpms_gmi_t gmi
def __cinit__ ( self , FinitePointGroup G , qpms_gmi_t gmi ) :
self . G = G
self . gmi = gmi
2019-02-28 13:38:00 +02:00
cdef class Particle :
'''
Wrapper over the qpms_particle_t structure .
'''
2019-03-01 14:44:00 +02:00
cdef qpms_particle_t p
2020-01-15 03:18:21 +02:00
cdef readonly TMatrixFunction f # Reference to ensure correct reference counting
2019-02-28 15:51:06 +02:00
2020-01-15 03:18:21 +02:00
def __cinit__ ( Particle self , pos , t , bspec = None ) :
2020-04-03 21:31:33 +03:00
""" Particle object constructor.
Parameters
- - - - - - - - - -
pos : ( x , y , z )
Particle position in cartesian coordinates
t : TMatrixGenerator or CTMatrix or TMatrixInterpolator
T - matrix specification for the particle .
bspec : BaseSpec
WSWF basis specification for the particle . Might be omitted if
t is a CTMatrix .
"""
2020-01-15 03:18:21 +02:00
cdef TMatrixGenerator tgen
cdef BaseSpec spec
2019-03-01 17:39:42 +02:00
if ( len ( pos ) > = 2 and len ( pos ) < 4 ) :
self . p . pos . x = pos [ 0 ]
self . p . pos . y = pos [ 1 ]
self . p . pos . z = pos [ 2 ] if len ( pos ) == 3 else 0
else :
raise ValueError ( " Position argument has to contain 3 or 2 cartesian coordinates " )
2020-01-15 03:18:21 +02:00
if isinstance ( t , CTMatrix ) :
tgen = TMatrixGenerator ( t )
2020-02-13 19:05:11 +02:00
elif isinstance ( t , TMatrixInterpolator ) :
tgen = TMatrixGenerator ( t )
warnings . warn ( " Initialising a particle with interpolated T-matrix values. Imaginary frequencies will be discarded and mode search algorithm will yield nonsense (just saying). " )
2020-01-15 03:18:21 +02:00
elif isinstance ( t , TMatrixGenerator ) :
tgen = < TMatrixGenerator > t
else : raise TypeError ( ' t must be either CTMatrix or TMatrixGenerator, was %s ' % str ( type ( t ) ) )
if bspec is not None :
spec = bspec
else :
if isinstance ( tgen . holder , CTMatrix ) :
spec = ( < CTMatrix > tgen . holder ) . spec
else :
2020-01-22 13:37:22 +02:00
raise ValueError ( " bspec argument must be specified separately for %s " % str ( type ( t ) ) )
2020-01-15 03:18:21 +02:00
self . f = TMatrixFunction ( tgen , spec )
self . p . tmg = self . f . rawpointer ( )
# TODO non-trivial transformations later; if modified, do not forget to update ScatteringSystem constructor
self . p . op = qpms_tmatrix_operation_noop
def __dealloc__ ( self ) :
qpms_tmatrix_operation_clear ( & self . p . op )
2019-02-28 15:51:06 +02:00
2019-03-01 17:39:42 +02:00
cdef qpms_particle_t *rawpointer ( Particle self ) :
''' Pointer to the qpms_particle_p structure.
'''
return & ( self . p )
property rawpointer :
def __get__ ( self ) :
return < uintptr_t > & ( self . p )
2019-03-03 02:56:06 +02:00
cdef qpms_particle_t cval ( Particle self ) :
'''Provides a copy for assigning in cython code'''
return self . p
2019-03-01 17:39:42 +02:00
property x :
def __get__ ( self ) :
return self . p . pos . x
def __set__ ( self , x ) :
self . p . pos . x = x
property y :
def __get__ ( self ) :
return self . p . pos . y
def __set__ ( self , y ) :
self . p . pos . y = y
property z :
def __get__ ( self ) :
return self . p . pos . z
def __set__ ( self , z ) :
self . p . pos . z = z
property pos :
def __get__ ( self ) :
return ( self . p . pos . x , self . p . pos . y , self . p . pos . z )
def __set__ ( self , pos ) :
if ( len ( pos ) > = 2 and len ( pos ) < 4 ) :
self . p . pos . x = pos [ 0 ]
self . p . pos . y = pos [ 1 ]
self . p . pos . z = pos [ 2 ] if len ( pos ) == 3 else 0
else :
raise ValueError ( " Position argument has to contain 3 or 2 cartesian coordinates " )
2019-02-28 13:38:00 +02:00
2019-03-26 13:33:22 +02:00
cpdef void scatsystem_set_nthreads ( long n ) :
qpms_scatsystem_set_nthreads ( n )
return
2020-01-15 03:18:21 +02:00
2019-02-28 13:38:00 +02:00
cdef class ScatteringSystem :
'''
2020-04-03 21:31:33 +03:00
Wrapper over the C qpms_scatsys_t structure , representing a collection
of scatterers , finite or periodic .
Currently , it does not have a standard constructor . Use the
ScatteringSystem . create ( ) method instead .
2019-02-28 13:38:00 +02:00
'''
2020-01-15 03:18:21 +02:00
cdef list tmgobjs # here we keep the references to occuring TMatrixFunctions (and hence BaseSpecs and TMatrixGenerators)
2019-03-03 02:05:24 +02:00
#cdef list Tmatrices # Here we keep the references to occuring T-matrices
2020-01-16 08:51:05 +02:00
cdef EpsMuGenerator medium_holder # Here we keep the reference to medium generator
2019-03-03 02:05:24 +02:00
cdef qpms_scatsys_t *s
2020-01-22 13:37:22 +02:00
cdef FinitePointGroup sym
cdef qpms_iri_t iri_py2c ( self , iri , allow_None = True ) :
if iri is None and allow_None :
return QPMS_NO_IRREP
cdef qpms_iri_t nir = self . nirreps
cdef qpms_iri_t ciri = iri
if ciri < 0 or ciri > nir :
raise ValueError ( " Invalid irrep index %s (of %d irreps) " , str ( iri ) , self . nirreps )
return ciri
2020-01-15 03:18:21 +02:00
def check_s ( self ) : # cdef instead?
if self . s == < qpms_scatsys_t * > NULL :
raise ValueError ( " ScatteringSystem ' s s-pointer not set. You must not use the default constructor; use the create() method instead " )
#TODO is there a way to disable the constructor outside this module?
@staticmethod # We don't have any "standard" constructor for this right now
2020-02-29 01:35:12 +02:00
def create ( particles , medium , cdouble omega , FinitePointGroup sym = FinitePointGroup . TRIVIAL ( ) ,
latticebasis = None ) : # TODO tolerances
2020-04-03 21:31:33 +03:00
""" (Non-standard) constructor of ScatteringSystem
Parameters
- - - - - - - - - -
particles : list of Particles objects
Scatterers to be included in the system . These scatterers are then
copied around by the constructor using the point group symmetry defined in sym .
medium : EpsMu or EpsMuGenerator
Material properties of the background medium .
omega : complex
Any valid angular frequency for the initial T - matrix evaluation .
It must be a value which all the T - matrix generators and interpolators
referenced by particles can evaluate .
sym : FinitePointGroup
Symmetry group for a finite system .
Defaults to the trivial point group FinitePointGroup . TRIVIAL ( ) .
Currently , this must be left trivial for periodic systems .
latticebasis : None or array_like of float type .
Lattice base vectors of a periodic system in cartesian coordinates .
The shape must be [ d ] [ 3 ] , where d = 1 , 2 or 3 determines the lattice dimension .
Defaults to None , i . e . a finite system .
Returns
- - - - - - -
ss : ScatteringSystem
Object representing the system of compact scatterers .
ssw : _ScatteringSystemAtOmega
Object representing ss evaluated at omega ( angular frequency used for the
initial evaluation ) .
Note
- - - -
Currently , the ScatterinSystem ' s T-matrices need to be evaluated for at least
one frequency in order to initialise the symmetry structure . For this reason ,
this " constructor " creates an instance of ScatteringSystem and an instance
of _ScatteringSystemAtOmega at the same time , so that the evaluated T - matrices
can be used right away if needed . This is why this approach is used instead
of the usual __init__ method .
"""
2020-02-29 01:35:12 +02:00
if latticebasis is not None and sym . order != 1 :
raise NotImplementedError ( " Periodic systems don ' t currently support nontrivial point group symmetries " )
2020-01-15 03:18:21 +02:00
# These we are going to construct
cdef ScatteringSystem self
cdef _ScatteringSystemAtOmega pyssw
2019-03-03 02:05:24 +02:00
cdef qpms_scatsys_t orig # This should be automatically init'd to 0 (CHECKME)
2020-01-15 03:18:21 +02:00
cdef qpms_ss_pi_t pi , p_count = len ( particles )
cdef qpms_ss_tmi_t tmi , tm_count = 0
cdef qpms_ss_tmgi_t tmgi , tmg_count = 0
cdef qpms_scatsys_at_omega_t *ssw
cdef qpms_scatsys_t *ss
cdef Particle p
tmgindices = dict ( )
tmgobjs = list ( )
2019-03-03 02:05:24 +02:00
tmindices = dict ( )
2020-01-15 03:18:21 +02:00
tmlist = list ( )
for p in particles : # find and enumerate unique t-matrix generators
if p . p . op . typ != QPMS_TMATRIX_OPERATION_NOOP :
raise NotImplementedError ( " currently, only no-op T-matrix operations are allowed in ScatteringSystem constructor " )
2020-01-28 21:28:07 +02:00
#tmg_key = id(p.f) # This causes a different generator for each particle -> SUPER SLOW
tmg_key = ( id ( p . f . generator ) , id ( p . f . spec ) )
2020-01-15 03:18:21 +02:00
if tmg_key not in tmgindices :
tmgindices [ tmg_key ] = tmg_count
tmgobjs . append ( p . f ) # Save the references on BaseSpecs and TMatrixGenerators (via TMatrixFunctions)
tmg_count + = 1
# Following lines have to be adjusted when nontrivial operations allowed:
tm_derived_key = ( tmg_key , None ) # TODO unique representation of p.p.op instead of None
if tm_derived_key not in tmindices :
tmindices [ tm_derived_key ] = tm_count
tmlist . append ( tm_derived_key )
2019-03-03 02:05:24 +02:00
tm_count + = 1
2020-01-16 08:51:05 +02:00
cdef EpsMuGenerator mediumgen = EpsMuGenerator ( medium )
orig . medium = mediumgen . g
2020-01-15 03:18:21 +02:00
orig . tmg_count = tmg_count
2019-03-03 02:05:24 +02:00
orig . tm_count = tm_count
orig . p_count = p_count
try :
2020-01-15 03:18:21 +02:00
orig . tmg = < qpms_tmatrix_function_t * > malloc ( orig . tmg_count * sizeof ( orig . tmg [ 0 ] ) )
if not orig . tmg : raise MemoryError
orig . tm = < qpms_ss_derived_tmatrix_t * > malloc ( orig . tm_count * sizeof ( orig . tm [ 0 ] ) )
2019-03-03 02:05:24 +02:00
if not orig . tm : raise MemoryError
orig . p = < qpms_particle_tid_t * > malloc ( orig . p_count * sizeof ( orig . p [ 0 ] ) )
if not orig . p : raise MemoryError
2020-01-15 03:18:21 +02:00
for tmgi in range ( orig . tmg_count ) :
orig . tmg [ tmgi ] = ( < TMatrixFunction? > tmgobjs [ tmgi ] ) . raw ( )
2019-03-03 02:05:24 +02:00
for tmi in range ( tm_count ) :
2020-01-15 03:18:21 +02:00
tm_derived_key = tmlist [ tmi ]
2020-01-16 08:51:05 +02:00
tmgi = tmgindices [ tm_derived_key [ 0 ] ]
2020-01-15 03:18:21 +02:00
orig . tm [ tmi ] . tmgi = tmgi
orig . tm [ tmi ] . op = qpms_tmatrix_operation_noop # TODO adjust when notrivial operations allowed
2019-03-03 02:05:24 +02:00
for pi in range ( p_count ) :
2020-01-15 03:18:21 +02:00
p = particles [ pi ]
2020-01-28 21:28:07 +02:00
tmg_key = ( id ( p . f . generator ) , id ( p . f . spec ) )
2020-01-15 03:18:21 +02:00
tm_derived_key = ( tmg_key , None ) # TODO unique representation of p.p.op instead of None
orig . p [ pi ] . pos = p . cval ( ) . pos
orig . p [ pi ] . tmatrix_id = tmindices [ tm_derived_key ]
2020-02-29 01:35:12 +02:00
if latticebasis is not None : # periodic system
assert ( len ( latticebasis ) < = 3 and len ( latticebasis ) > 0 )
orig . lattice_dimension = len ( latticebasis )
for d in range ( len ( latticebasis ) ) :
2020-03-06 14:27:59 +02:00
orig . per . lattice_basis [ d ] = { ' x ' : latticebasis [ d ] [ 0 ] , ' y ' : latticebasis [ d ] [ 1 ] , ' z ' : latticebasis [ d ] [ 2 ] if len ( latticebasis [ d ] ) > = 3 else 0 }
2020-02-29 01:35:12 +02:00
else : orig . lattice_dimension = 0
2020-01-15 03:18:21 +02:00
ssw = qpms_scatsys_apply_symmetry ( & orig , sym . rawpointer ( ) , omega , & QPMS_TOLERANCE_DEFAULT )
ss = ssw [ 0 ] . ss
2019-03-03 02:05:24 +02:00
finally :
2020-01-15 03:18:21 +02:00
free ( orig . tmg )
2019-03-03 02:05:24 +02:00
free ( orig . tm )
free ( orig . p )
2020-01-15 03:18:21 +02:00
self = ScatteringSystem ( )
2020-01-16 08:51:05 +02:00
self . medium_holder = mediumgen
2020-01-15 03:18:21 +02:00
self . s = ss
self . tmgobjs = tmgobjs
2020-01-22 13:37:22 +02:00
self . sym = sym
2020-01-15 03:18:21 +02:00
pyssw = _ScatteringSystemAtOmega ( )
pyssw . ssw = ssw
pyssw . ss_pyref = self
return self , pyssw
def __call__ ( self , cdouble omega ) :
self . check_s ( )
cdef _ScatteringSystemAtOmega pyssw = _ScatteringSystemAtOmega ( )
pyssw . ssw = qpms_scatsys_at_omega ( self . s , omega )
pyssw . ss_pyref = self
2020-01-21 11:52:45 +02:00
return pyssw
2019-03-03 02:05:24 +02:00
def __dealloc__ ( self ) :
2020-01-15 03:18:21 +02:00
if ( self . s ) :
qpms_scatsys_free ( self . s )
2019-02-26 00:40:41 +02:00
2019-10-07 15:15:34 +03:00
property particles_tmi :
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-03 03:39:10 +02:00
r = list ( )
cdef qpms_ss_pi_t pi
for pi in range ( self . s [ 0 ] . p_count ) :
r . append ( self . s [ 0 ] . p [ pi ] )
return r
2020-07-03 18:50:44 +03:00
property particle_count :
"""Number of particles in the system (or unit cell)"""
def __get__ ( self ) :
self . check_s ( )
return self . s [ 0 ] . p_count
2019-03-08 13:10:01 +02:00
property fecv_size :
2020-04-03 21:31:33 +03:00
"""Length of the full excitation coefficient vector"""
2020-01-15 03:18:21 +02:00
def __get__ ( self ) :
self . check_s ( )
return self . s [ 0 ] . fecv_size
2020-04-03 21:31:33 +03:00
property saecv_sizes :
"""Lengths of the partial symmetry-adapted excitation coefficient vectors"""
2019-03-08 15:39:37 +02:00
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
return [ self . s [ 0 ] . saecv_sizes [ i ]
for i in range ( self . s [ 0 ] . sym [ 0 ] . nirreps ) ]
2019-03-08 13:10:01 +02:00
property irrep_names :
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
return [ string_c2py ( self . s [ 0 ] . sym [ 0 ] . irreps [ iri ] . name )
if ( self . s [ 0 ] . sym [ 0 ] . irreps [ iri ] . name ) else None
2019-03-08 13:10:01 +02:00
for iri in range ( self . s [ 0 ] . sym [ 0 ] . nirreps ) ]
2020-04-03 21:31:33 +03:00
property nirreps :
"""Number of irreducible representations of the scattering system point symmetry group"""
2020-01-15 03:18:21 +02:00
def __get__ ( self ) :
self . check_s ( )
return self . s [ 0 ] . sym [ 0 ] . nirreps
2020-03-06 14:27:59 +02:00
property lattice_dimension :
def __get__ ( self ) :
return self . s [ 0 ] . lattice_dimension
2020-07-03 18:50:44 +03:00
property lattice_basis :
"""Direct lattice basis vectors for periodic system in 3D cartesian coordinates"""
def __get__ ( self ) :
cdef int d = self . lattice_dimension
if d == 0 :
return None
else :
return np . array ( [ [ self . s [ 0 ] . per . lattice_basis [ i ] . x ,
self . s [ 0 ] . per . lattice_basis [ i ] . y ,
self . s [ 0 ] . per . lattice_basis [ i ] . z ,
] for i in range ( d ) ] )
property reciprocal_basis :
"""Reciprocal lattice basis vectors for periodic system in 3D cartesian coordinates"""
def __get__ ( self ) :
cdef int d = self . lattice_dimension
if d == 0 :
return None
else :
return np . array ( [ [ self . s [ 0 ] . per . reciprocal_basis [ i ] . x ,
self . s [ 0 ] . per . reciprocal_basis [ i ] . y ,
self . s [ 0 ] . per . reciprocal_basis [ i ] . z ,
] for i in range ( d ) ] )
def bspec_pi ( self , qpms_ss_pi_t pi ) :
"""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"""
self . check_s ( )
cdef qpms_ss_pi_t pcount = self . s [ 0 ] . p_count
if pi > = pcount or pi < 0 : raise IndexError ( " Invalid particle index " )
cdef const qpms_vswf_set_spec_t *bspec = qpms_ss_bspec_pi ( self . s , pi )
return np . array ( [ bspec [ 0 ] . ilist [ i ] for i in range ( bspec [ 0 ] . n ) ] )
property bspecs :
"""Gives the BaseSpecs of all particles in the internal particles' order, in the form of arrays that can be fed to BaseSpec constructor"""
def __get__ ( self ) :
self . check_s ( )
cdef qpms_ss_pi_t pi , pcount = self . s [ 0 ] . p_count
return [ self . bspec_pi ( pi ) for pi in range ( pcount ) ]
2020-03-06 14:27:59 +02:00
property unitcell_volume :
def __get__ ( self ) :
self . check_s ( )
if self . lattice_dimension :
return self . s [ 0 ] . per . unitcell_volume
else :
return None
2019-03-08 13:10:01 +02:00
2020-04-21 23:34:49 +03:00
property eta :
2020-04-22 16:18:16 +03:00
""" Ewald parameter η (only relevant for periodic systems)
This is the default value that will be copied into
_ScatteringSystemAtOmega by __call__ """
2020-04-21 23:34:49 +03:00
def __get__ ( self ) :
self . check_s ( )
if self . lattice_dimension :
return self . s [ 0 ] . per . eta
else :
return None
def __set__ ( self , eta ) :
self . check_s ( )
if self . lattice_dimension :
self . s [ 0 ] . per . eta = eta
else :
raise AttributeError ( " Cannot set Ewald parameter for finite system " ) # different exception?
2019-03-08 13:10:01 +02:00
def pack_vector ( self , vect , iri ) :
2020-04-03 21:31:33 +03:00
""" Converts (projects) a full excitation coefficient vector into an irrep subspace.
Parameters
- - - - - - - - - -
vect : array_like of shape ( self . fecv_size , )
The full excitation coefficient vector to be converted .
iri : int
Index of the irreducible representation .
Returns
- - - - - - -
packed : ndarray of shape ( self . saecv_sizes [ iri ] , )
" irrep-packed " excitation vector : the part of vect belonging to the
irrep indexed by iri , in terms of given irrep ' s internal coordinates.
See Also
- - - - - - - -
unpack_vector : The beckward ( not exactly inverse ) operation .
pack_matrix : Corresponding conversion for matrices .
"""
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
if len ( vect ) != self . fecv_size :
raise ValueError ( " Length of a full vector has to be %d , not %d "
% ( self . fecv_size , len ( vect ) ) )
2019-03-08 13:10:01 +02:00
vect = np . array ( vect , dtype = complex , copy = False , order = ' C ' )
cdef cdouble [: : 1 ] vect_view = vect ;
2019-03-08 15:39:37 +02:00
cdef np .ndarray [np .complex_t , ndim = 1 ] target_np = np . empty (
( self . saecv_sizes [ iri ] , ) , dtype = complex , order = ' C ' )
2019-03-08 13:10:01 +02:00
cdef cdouble [: : 1 ] target_view = target_np
qpms_scatsys_irrep_pack_vector ( & target_view [ 0 ] , & vect_view [ 0 ] , self . s , iri )
return target_np
2020-04-03 21:31:33 +03:00
2019-03-08 13:10:01 +02:00
def unpack_vector ( self , packed , iri ) :
2020-04-03 21:31:33 +03:00
""" Unpacks an " irrep-packed " excitation coefficient vector to full coordinates.
Parameters
- - - - - - - - - -
packed : array_like of shape ( self . saecv_sizes [ iri ] , )
Excitation coefficient vector part belonging to the irreducible representation
indexed by iri , in terms of given irrep ' s internal coordinates.
iri : int
Index of the irreducible representation .
Returns
- - - - - - -
vect : ndarray of shape ( self . fecv_size , )
The contribution to a full excitation coefficient vector from iri ' th irrep.
See Also
- - - - - - - -
pack_vector : The inverse operation .
unpack_matrix : Corresponding conversion for matrices .
"""
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
if len ( packed ) != self . saecv_sizes [ iri ] :
raise ValueError ( " Length of %d . irrep-packed vector has to be %d , not %d "
% ( iri , self . saecv_sizes , len ( packed ) ) )
2019-03-08 13:10:01 +02:00
packed = np . array ( packed , dtype = complex , copy = False , order = ' C ' )
cdef cdouble [: : 1 ] packed_view = packed
2019-03-08 15:39:37 +02:00
cdef np .ndarray [np .complex_t , ndim = 1 ] target_np = np . empty (
( self . fecv_size , ) , dtype = complex )
2019-03-08 13:10:01 +02:00
cdef cdouble [: : 1 ] target_view = target_np
2019-03-08 15:39:37 +02:00
qpms_scatsys_irrep_unpack_vector ( & target_view [ 0 ] , & packed_view [ 0 ] ,
self . s , iri , 0 )
return target_np
2020-04-03 21:31:33 +03:00
2019-03-08 15:39:37 +02:00
def pack_matrix ( self , fullmatrix , iri ) :
2020-04-03 21:31:33 +03:00
""" Converts (projects) a matrix into an irrep subspace.
Parameters
- - - - - - - - - -
fullmatrix : array_like of shape ( self . fecv_size , self . fecv_size )
The full matrix ( operating on the exctitation coefficient vectors ) to be converted .
iri : int
Index of the irreducible representation .
Returns
- - - - - - -
packedmatrix : ndarray of shape ( self . saecv_sizes [ iri ] , self . saecv_sizes [ iri ] )
" irrep-packed " matrix : the part of fullmatrix belonging to the
irrep indexed by iri , in terms of given irrep ' s internal coordinates.
See Also
- - - - - - - -
unpack_matrix : The beckward ( not exactly inverse ) operation .
pack_vector : Corresponding conversion for excitation coefficient vectors .
"""
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
cdef size_t flen = self . s [ 0 ] . fecv_size
cdef size_t rlen = self . saecv_sizes [ iri ]
fullmatrix = np . array ( fullmatrix , dtype = complex , copy = False , order = ' C ' )
if fullmatrix . shape != ( flen , flen ) :
raise ValueError ( " Full matrix shape should be ( %d , %d ), is %s . "
% ( flen , flen , repr ( fullmatrix . shape ) ) )
cdef cdouble [: , : : 1 ] fullmatrix_view = fullmatrix
cdef np .ndarray [np .complex_t , ndim = 2 ] target_np = np . empty (
( rlen , rlen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target_np
qpms_scatsys_irrep_pack_matrix ( & target_view [ 0 ] [ 0 ] , & fullmatrix_view [ 0 ] [ 0 ] ,
self . s , iri )
return target_np
def unpack_matrix ( self , packedmatrix , iri ) :
2020-04-03 21:31:33 +03:00
""" Unpacks an " irrep-packed " excitation coefficient vector to full coordinates.
Parameters
- - - - - - - - - -
packedmatrix : array_like of shape ( self . saecv_sizes [ iri ] , self . saecv_sizes [ iri ] )
A matrix ( operating on the excitation coefficient vectors ) part belonging to the
irreducible representation indexed by iri , in terms of given irrep ' s internal
coordinates .
iri : int
Index of the irreducible representation .
Returns
- - - - - - -
fullmatrix : ndarray of shape ( self . fecv_size , self . fecv_size )
The iri ' th irrep contribution to a full matrix.
See Also
- - - - - - - -
pack_matrix : The inverse operation .
unpack_vector : Corresponding conversion for excitation coefficient vectors .
"""
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-08 15:39:37 +02:00
cdef size_t flen = self . s [ 0 ] . fecv_size
cdef size_t rlen = self . saecv_sizes [ iri ]
packedmatrix = np . array ( packedmatrix , dtype = complex , copy = False , order = ' C ' )
if packedmatrix . shape != ( rlen , rlen ) :
raise ValueError ( " Packed matrix shape should be ( %d , %d ), is %s . "
% ( rlen , rlen , repr ( packedmatrix . shape ) ) )
cdef cdouble [: , : : 1 ] packedmatrix_view = packedmatrix
cdef np .ndarray [np .complex_t , ndim = 2 ] target_np = np . empty (
( flen , flen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target_np
qpms_scatsys_irrep_unpack_matrix ( & target_view [ 0 ] [ 0 ] , & packedmatrix_view [ 0 ] [ 0 ] ,
self . s , iri , 0 )
2019-03-08 13:10:01 +02:00
return target_np
2020-04-22 16:18:16 +03:00
def translation_matrix_full ( self , cdouble wavenumber , blochvector = None , J = QPMS_HANKEL_PLUS , double eta = 0 ) :
2020-04-20 17:31:34 +03:00
""" Constructs full translation matrix of a scattering system.
This method enables to use any wave number for the background medium ignoring the
background EpsMuGenerator ) , using only system ' s particle positions (and lattice
basis for infinite system ) .
Parameters
- - - - - - - - - -
wavenumber : complex
Wave number of the medium
blochvector : array_like of shape ( 3 , )
Bloch vector ( only for periodic systems )
J : BesselType
Optionally , one can replace Hankel functions of the first kind with different
Bessel functions .
2020-04-22 16:18:16 +03:00
eta : float
Ewald parameter η , only relevant for periodic lattices ;
if set to 0 ( default ) , the actual value is determined
automatically .
2020-04-20 17:31:34 +03:00
See Also
- - - - - - - -
ScatteringSystemAtOmega . translation_matrix_full : Translation matrix at a given frequency .
"""
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-09 14:29:43 +02:00
cdef size_t flen = self . s [ 0 ] . fecv_size
cdef np .ndarray [np .complex_t , ndim = 2 ] target = np . empty (
( flen , flen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target
2020-03-06 14:27:59 +02:00
cdef cart3_t blochvector_c
if self . lattice_dimension == 0 :
if blochvector is None :
qpms_scatsys_build_translation_matrix_e_full ( & target_view [ 0 ] [ 0 ] , self . s , wavenumber , J )
else : raise ValueError ( " Can ' t use blochvector with non-periodic system " )
else :
if blochvector is None : raise ValueError ( " Valid blochvector must be specified for periodic system " )
else :
if J != QPMS_HANKEL_PLUS :
raise NotImplementedError ( " Translation operators based on other than Hankel+ functions not supperted in periodic systems " )
blochvector_c = { ' x ' : blochvector [ 0 ] , ' y ' : blochvector [ 1 ] , ' z ' : blochvector [ 2 ] }
2020-04-20 17:31:34 +03:00
with pgsl_ignore_error ( 15 ) :
2020-04-22 16:18:16 +03:00
qpms_scatsys_periodic_build_translation_matrix_full ( & target_view [ 0 ] [ 0 ] , self . s , wavenumber , & blochvector_c , eta )
2019-07-22 01:08:41 +03:00
return target
2020-04-07 21:01:18 +03:00
def translation_matrix_packed ( self , cdouble wavenumber , qpms_iri_t iri , J = QPMS_HANKEL_PLUS ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-07-22 01:08:41 +03:00
cdef size_t rlen = self . saecv_sizes [ iri ]
cdef np .ndarray [np .complex_t , ndim = 2 ] target = np . empty (
( rlen , rlen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target
qpms_scatsys_build_translation_matrix_e_irrep_packed ( & target_view [ 0 ] [ 0 ] ,
2020-04-07 21:01:18 +03:00
self . s , iri , wavenumber , J )
2019-03-09 14:29:43 +02:00
return target
2019-03-13 22:35:35 +02:00
2019-10-07 15:15:34 +03:00
property fullvec_psizes :
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-13 22:35:35 +02:00
cdef np .ndarray [int32_t , ndim = 1 ] ar = np . empty ( ( self . s [ 0 ] . p_count , ) , dtype = np . int32 )
cdef int32_t [: : 1 ] ar_view = ar
for pi in range ( self . s [ 0 ] . p_count ) :
2020-07-03 18:50:44 +03:00
ar_view [ pi ] = qpms_ss_bspec_pi ( self . s , pi ) [ 0 ] . n
2019-03-13 22:35:35 +02:00
return ar
2019-03-09 14:29:43 +02:00
2019-07-15 16:36:37 +03:00
2019-10-07 15:15:34 +03:00
property fullvec_poffsets :
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-13 22:35:35 +02:00
cdef np .ndarray [intptr_t , ndim = 1 ] ar = np . empty ( ( self . s [ 0 ] . p_count , ) , dtype = np . intp )
cdef intptr_t [: : 1 ] ar_view = ar
cdef intptr_t offset = 0
for pi in range ( self . s [ 0 ] . p_count ) :
ar_view [ pi ] = offset
2020-03-18 09:34:51 +02:00
offset + = qpms_ss_bspec_pi ( self . s , pi ) [ 0 ] . n
2019-03-13 22:35:35 +02:00
return ar
2019-03-09 14:29:43 +02:00
2019-10-07 15:15:34 +03:00
property positions :
def __get__ ( self ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-03-13 22:35:35 +02:00
cdef np .ndarray [np .double_t , ndim = 2 ] ar = np . empty ( ( self . s [ 0 ] . p_count , 3 ) , dtype = float )
cdef np .double_t [: , : : 1 ] ar_view = ar
for pi in range ( self . s [ 0 ] . p_count ) :
ar_view [ pi , 0 ] = self . s [ 0 ] . p [ pi ] . pos . x
ar_view [ pi , 1 ] = self . s [ 0 ] . p [ pi ] . pos . y
ar_view [ pi , 2 ] = self . s [ 0 ] . p [ pi ] . pos . z
return ar
2019-07-15 16:36:37 +03:00
def planewave_full ( self , k_cart , E_cart ) :
2020-01-15 03:18:21 +02:00
self . check_s ( )
2019-07-23 15:50:31 +03:00
k_cart = np . array ( k_cart )
E_cart = np . array ( E_cart )
2019-07-15 16:36:37 +03:00
if k_cart . shape != ( 3 , ) or E_cart . shape != ( 3 , ) :
raise ValueError ( " k_cart and E_cart must be ndarrays of shape (3,) " )
cdef qpms_incfield_planewave_params_t p
p . use_cartesian = 1
p . k . cart . x = < cdouble > k_cart [ 0 ]
p . k . cart . y = < cdouble > k_cart [ 1 ]
p . k . cart . z = < cdouble > k_cart [ 2 ]
p . E . cart . x = < cdouble > E_cart [ 0 ]
p . E . cart . y = < cdouble > E_cart [ 1 ]
p . E . cart . z = < cdouble > E_cart [ 2 ]
cdef np .ndarray [np .complex_t , ndim = 1 ] target_np = np . empty (
( self . fecv_size , ) , dtype = complex )
cdef cdouble [: : 1 ] target_view = target_np
qpms_scatsys_incident_field_vector_full ( & target_view [ 0 ] ,
self . s , qpms_incfield_planewave , < void * > & p , 0 )
return target_np
2020-01-22 13:37:22 +02:00
def find_modes ( self , cdouble omega_centre , double omega_rr , double omega_ri , iri = None ,
2020-03-08 13:12:56 +02:00
blochvector = None ,
2020-01-21 18:51:06 +02:00
size_t contour_points = 20 , double rank_tol = 1e-4 , size_t rank_min_sel = 1 ,
double res_tol = 0 ) :
"""
Attempts to find the eigenvalues and eigenvectors using Beyn ' s algorithm.
"""
2020-03-08 13:12:56 +02:00
cdef beyn_result_t *res
cdef double blochvector_c [3]
if self . lattice_dimension == 0 :
assert ( blochvector is None )
res = qpms_scatsys_finite_find_eigenmodes ( self . s , self . iri_py2c ( iri ) ,
2020-01-21 18:51:06 +02:00
omega_centre , omega_rr , omega_ri , contour_points ,
rank_tol , rank_min_sel , res_tol )
2020-03-08 13:12:56 +02:00
else :
if iri is not None : raise NotImplementedError ( " Irreps decomposition not yet supported for periodic systems " )
blochvector_c [ 0 ] = blochvector [ 0 ]
blochvector_c [ 1 ] = blochvector [ 1 ]
blochvector_c [ 2 ] = blochvector [ 2 ] if len ( blochvector ) > 2 else 0
res = qpms_scatsys_periodic_find_eigenmodes ( self . s , blochvector_c ,
omega_centre , omega_rr , omega_ri , contour_points , rank_tol , rank_min_sel , res_tol )
2020-01-21 18:51:06 +02:00
if res == NULL : raise RuntimeError
2020-03-06 14:27:59 +02:00
cdef size_t neig = res [ 0 ] . neig , i , j
2020-01-21 18:51:06 +02:00
cdef size_t vlen = res [ 0 ] . vlen # should be equal to self.s.fecv_size
cdef np .ndarray [complex , ndim = 1 ] eigval = np . empty ( ( neig , ) , dtype = complex )
cdef cdouble [: : 1 ] eigval_v = eigval
cdef np .ndarray [complex , ndim = 1 ] eigval_err = np . empty ( ( neig , ) , dtype = complex )
cdef cdouble [: : 1 ] eigval_err_v = eigval_err
cdef np .ndarray [double , ndim = 1 ] residuals = np . empty ( ( neig , ) , dtype = np . double )
cdef double [: : 1 ] residuals_v = residuals
cdef np .ndarray [complex , ndim = 2 ] eigvec = np . empty ( ( neig , vlen ) , dtype = complex )
cdef cdouble [: , : : 1 ] eigvec_v = eigvec
cdef np .ndarray [double , ndim = 1 ] ranktest_SV = np . empty ( ( vlen ) , dtype = np . double )
cdef double [: : 1 ] ranktest_SV_v = ranktest_SV
for i in range ( neig ) :
eigval_v [ i ] = res [ 0 ] . eigval [ i ]
eigval_err_v [ i ] = res [ 0 ] . eigval_err [ i ]
residuals_v [ i ] = res [ 0 ] . residuals [ i ]
for j in range ( vlen ) :
eigvec_v [ i , j ] = res [ 0 ] . eigvec [ i * vlen + j ]
for i in range ( vlen ) :
ranktest_SV_v [ i ] = res [ 0 ] . ranktest_SV [ i ]
zdist = eigval - omega_centre
eigval_inside_metric = np . hypot ( zdist . real / omega_rr , zdist . imag / omega_ri )
beyn_result_free ( res )
retdict = {
' eigval ' : eigval ,
' eigval_inside_metric ' : eigval_inside_metric ,
' eigvec ' : eigvec ,
' residuals ' : residuals ,
' eigval_err ' : eigval_err ,
' ranktest_SV ' : ranktest_SV ,
2020-01-22 13:37:22 +02:00
' iri ' : iri ,
2020-03-08 13:12:56 +02:00
' blochvector ' : blochvector
2020-01-21 18:51:06 +02:00
}
return retdict
2020-07-03 14:20:07 +03:00
@boundscheck ( False )
2020-07-20 12:18:01 +03:00
def scattered_E ( self , cdouble wavenumber , scatcoeffvector_full , evalpos , btyp = BesselType . HANKEL_PLUS , bint alt = False ) :
# TODO DOC, periodic systems
if self . lattice_dimension != 0 : # TODO enable also periodic systems
raise NotImplementedError ( " For periodic system, use _ScatteringSystemAtOmegaK.scattered_E " )
2020-03-22 11:44:20 +02:00
cdef qpms_bessel_t btyp_c = BesselType ( btyp )
2020-03-21 17:56:44 +02:00
evalpos = np . array ( evalpos , dtype = float , copy = False )
2020-03-21 08:11:16 +02:00
if evalpos . shape [ - 1 ] != 3 :
raise ValueError ( " Last dimension of evalpos has to be 3 " )
2020-03-21 17:56:44 +02:00
cdef np .ndarray [double , ndim = 2 ] evalpos_a = evalpos . reshape ( - 1 , 3 )
2020-07-20 12:18:01 +03:00
cdef np .ndarray [dtype = complex , ndim = 1 ] scv = np . array ( scatcoeffvector_full , copy = False , dtype = complex )
2020-03-21 17:56:44 +02:00
cdef cdouble [: : 1 ] scv_view = scv
cdef np .ndarray [complex , ndim = 2 ] results = np . empty ( ( evalpos_a . shape [ 0 ] , 3 ) , dtype = complex )
2020-03-21 08:11:16 +02:00
cdef ccart3_t res
cdef cart3_t pos
2020-07-03 14:20:07 +03:00
cdef Py_ssize_t i
2020-07-03 15:10:15 +03:00
with nogil , parallel ( ) , boundscheck ( False ) , wraparound ( False ) :
2020-07-03 14:20:07 +03:00
for i in prange ( evalpos_a . shape [ 0 ] ) :
pos . x = evalpos_a [ i , 0 ]
pos . y = evalpos_a [ i , 1 ]
pos . z = evalpos_a [ i , 2 ]
if alt :
res = qpms_scatsys_scattered_E__alt ( self . s , btyp_c , wavenumber , & scv_view [ 0 ] , pos )
else :
res = qpms_scatsys_scattered_E ( self . s , btyp_c , wavenumber , & scv_view [ 0 ] , pos )
results [ i , 0 ] = res . x
results [ i , 1 ] = res . y
results [ i , 2 ] = res . z
2020-03-21 17:56:44 +02:00
return results . reshape ( evalpos . shape )
2020-03-08 13:12:56 +02:00
def empty_lattice_modes_xy ( EpsMu epsmu , reciprocal_basis , wavevector , double maxomega ) :
''' Empty (2D, xy-plane) lattice mode (diffraction order) frequencies of a non-dispersive medium.
reciprocal_basis is of the " mutliplied by 2п " type .
'''
cdef double *omegas_c
cdef size_t n
cdef cart2_t k_ , b1 , b2
k_ . x = wavevector [ 0 ]
k_ . y = wavevector [ 1 ]
b1 . x = reciprocal_basis [ 0 ] [ 0 ]
b1 . y = reciprocal_basis [ 0 ] [ 1 ]
b2 . x = reciprocal_basis [ 1 ] [ 0 ]
b2 . y = reciprocal_basis [ 1 ] [ 1 ]
if ( epsmu . n . imag != 0 ) :
warnings . warn ( " Got complex refractive index " , epsmu . n , " ignoring the imaginary part " )
refindex = epsmu . n . real
n = qpms_emptylattice2_modes_maxfreq ( & omegas_c , b1 , b2 , BASIS_RTOL ,
k_ , GSL_CONST_MKSA_SPEED_OF_LIGHT / refindex , maxomega )
cdef np .ndarray [double , ndim = 1 ] omegas = np . empty ( ( n , ) , dtype = np . double )
cdef double [: : 1 ] omegas_v = omegas
cdef size_t i
for i in range ( n ) :
omegas_v [ i ] = omegas_c [ i ]
free ( omegas_c )
return omegas
2020-03-06 14:27:59 +02:00
cdef class _ScatteringSystemAtOmegaK :
'''
Wrapper over the C qpms_scatsys_at_omega_k_t structure
2020-07-20 12:18:01 +03:00
This represents an infinite periodic system with a given ( fixed ) frequency and Bloch vector .
2020-03-06 14:27:59 +02:00
'''
cdef qpms_scatsys_at_omega_k_t sswk
cdef _ScatteringSystemAtOmega ssw_pyref
cdef qpms_scatsys_at_omega_k_t *rawpointer ( self ) :
return & self . sswk
2020-04-26 17:10:47 +03:00
property eta :
"""Ewald parameter η"""
def __get__ ( self ) :
return self . sswk . eta
def __set__ ( self , double eta ) :
self . sswk . eta = eta
2020-05-31 14:13:56 +03:00
2020-07-03 14:20:07 +03:00
@boundscheck ( False )
2020-07-20 12:18:01 +03:00
def scattered_E ( self , scatcoeffvector_full , evalpos , btyp = QPMS_HANKEL_PLUS ) :
""" Evaluate electric field for a given excitation coefficient vector (periodic system)
Parameters
- - - - - - - - - -
scatcoeffvector_full : array_like of shape ( self . fecv_size , )
Onedimensional excitation coefficient vector , describing SVWFs leaving
the scatterers inside the representative unit cell .
evalpos : array_like of floats and shape ( . . . , 3 )
Evaluation points in cartesian coordinates .
Returns
- - - - - - -
2020-07-23 07:23:47 +03:00
ndarray of complex , with the same shape as `evalpos`
Electric field at the positions given in `evalpos` , in cartesian coordinates .
2020-07-20 12:18:01 +03:00
"""
2020-05-31 14:13:56 +03:00
if ( btyp != QPMS_HANKEL_PLUS ) :
raise NotImplementedError ( " Only first kind Bessel function-based fields are supported " )
cdef qpms_bessel_t btyp_c = BesselType ( btyp )
evalpos = np . array ( evalpos , dtype = float , copy = False )
if evalpos . shape [ - 1 ] != 3 :
raise ValueError ( " Last dimension of evalpos has to be 3 " )
cdef np .ndarray [double , ndim = 2 ] evalpos_a = evalpos . reshape ( - 1 , 3 )
2020-07-20 12:18:01 +03:00
cdef np .ndarray [dtype = complex , ndim = 1 ] scv = np . array ( scatcoeffvector_full , dtype = complex , copy = False )
2020-05-31 14:13:56 +03:00
cdef cdouble [: : 1 ] scv_view = scv
cdef np .ndarray [complex , ndim = 2 ] results = np . empty ( ( evalpos_a . shape [ 0 ] , 3 ) , dtype = complex )
cdef ccart3_t res
cdef cart3_t pos
2020-07-03 14:20:07 +03:00
cdef Py_ssize_t i
with nogil , wraparound ( False ) , parallel ( ) :
for i in prange ( evalpos_a . shape [ 0 ] ) :
pos . x = evalpos_a [ i , 0 ]
pos . y = evalpos_a [ i , 1 ]
pos . z = evalpos_a [ i , 2 ]
res = qpms_scatsyswk_scattered_E ( & self . sswk , btyp_c , & scv_view [ 0 ] , pos )
results [ i , 0 ] = res . x
results [ i , 1 ] = res . y
results [ i , 2 ] = res . z
2020-05-31 14:13:56 +03:00
return results . reshape ( evalpos . shape )
2020-07-23 07:23:47 +03:00
def scattered_field_basis ( self , evalpos , btyp = QPMS_HANKEL_PLUS ) :
# TODO examples
""" Evaluate scattered field " basis " (periodic system)
This function enables the evaluation of " scattered " fields
generated by the system for many different excitation
coefficients vectors , without the expensive re - evaluation of the respective
translation operators for each excitation coefficient vector .
Parameters
- - - - - - - - - -
evalpos : array_like of floats and shape ( . . . , 3 )
Evaluation points in cartesian coordinates .
Returns
- - - - - - -
ndarray of complex , with the shape `evalpos.shape[:-1] + (self.fecv_size, 3)`
" Basis " fields at the positions given in `evalpos` , in cartesian coordinates .
"""
if ( btyp != QPMS_HANKEL_PLUS ) :
raise NotImplementedError ( " Only first kind Bessel function-based fields are supported " )
cdef qpms_bessel_t btyp_c = BesselType ( btyp )
cdef Py_ssize_t fecv_size = self . fecv_size
evalpos = np . array ( evalpos , dtype = float , copy = False )
if evalpos . shape [ - 1 ] != 3 :
raise ValueError ( " Last dimension of evalpos has to be 3 " )
cdef np .ndarray [double , ndim = 2 ] evalpos_a = evalpos . reshape ( - 1 , 3 )
cdef np .ndarray [complex , ndim = 3 ] results = np . empty ( ( evalpos_a . shape [ 0 ] , fecv_size , 3 ) , dtype = complex )
cdef ccart3_t *res
res = < ccart3_t * > malloc ( fecv_size * sizeof ( ccart3_t ) )
cdef cart3_t pos
cdef Py_ssize_t i , j
with nogil , wraparound ( False ) , parallel ( ) :
for i in prange ( evalpos_a . shape [ 0 ] ) :
pos . x = evalpos_a [ i , 0 ]
pos . y = evalpos_a [ i , 1 ]
pos . z = evalpos_a [ i , 2 ]
qpms_scatsyswk_scattered_field_basis ( res , & self . sswk , btyp_c , pos )
for j in range ( fecv_size ) :
results [ i , j , 0 ] = res [ j ] . x
results [ i , j , 1 ] = res [ j ] . y
results [ i , j , 2 ] = res [ j ] . z
free ( res )
return results . reshape ( evalpos . shape [ : - 1 ] + ( self . fecv_size , 3 ) )
2020-07-20 12:18:01 +03:00
property fecv_size :
def __get__ ( self ) : return self . ssw_pyref . ss_pyref . fecv_size
2020-03-06 14:27:59 +02:00
2020-01-15 03:18:21 +02:00
cdef class _ScatteringSystemAtOmega :
'''
Wrapper over the C qpms_scatsys_at_omega_t structure
that keeps the T - matrix and background data evaluated
at specific frequency .
'''
cdef qpms_scatsys_at_omega_t *ssw
cdef ScatteringSystem ss_pyref
def check ( self ) : # cdef instead?
if not self . ssw :
raise ValueError ( " _ScatteringSystemAtOmega ' s ssw-pointer not set. You must not use the default constructor; ScatteringSystem.create() instead " )
self . ss_pyref . check_s ( )
#TODO is there a way to disable the constructor outside this module?
2020-03-06 14:27:59 +02:00
def ensure_finite ( self ) :
if self . ssw [ 0 ] . ss [ 0 ] . lattice_dimension != 0 :
raise NotImplementedError ( " Operation not supported for periodic systems " )
2020-01-15 03:18:21 +02:00
def __dealloc__ ( self ) :
if ( self . ssw ) :
qpms_scatsys_at_omega_free ( self . ssw )
2019-07-22 16:56:03 +03:00
def apply_Tmatrices_full ( self , a ) :
2020-01-15 03:18:21 +02:00
self . check ( )
2019-07-22 16:56:03 +03:00
if len ( a ) != self . fecv_size :
raise ValueError ( " Length of a full vector has to be %d , not %d "
% ( self . fecv_size , len ( a ) ) )
a = np . array ( a , dtype = complex , copy = False , order = ' C ' )
cdef cdouble [: : 1 ] a_view = a ;
cdef np .ndarray [np .complex_t , ndim = 1 ] target_np = np . empty (
( self . fecv_size , ) , dtype = complex , order = ' C ' )
cdef cdouble [: : 1 ] target_view = target_np
2020-01-15 03:18:21 +02:00
qpms_scatsysw_apply_Tmatrices_full ( & target_view [ 0 ] , & a_view [ 0 ] , self . ssw )
2019-07-22 16:56:03 +03:00
return target_np
2019-07-23 15:50:31 +03:00
2020-01-15 03:18:21 +02:00
cdef qpms_scatsys_at_omega_t *rawpointer ( self ) :
return self . ssw
2019-07-23 15:50:31 +03:00
2020-03-06 14:27:59 +02:00
def scatter_solver ( self , iri = None , k = None ) :
2020-01-15 03:18:21 +02:00
self . check ( )
2020-03-06 14:27:59 +02:00
cdef _ScatteringSystemAtOmegaK sswk # used only for periodic systems
if ( self . ssw [ 0 ] . ss [ 0 ] . lattice_dimension == 0 ) :
return ScatteringMatrix ( self , iri = iri )
else :
if iri is not None :
raise NotImplementedError ( " Irrep decomposition not (yet) supported for periodic systems " )
2020-03-27 14:48:15 +02:00
sswk = self . _sswk ( k )
2020-03-06 14:27:59 +02:00
return ScatteringMatrix ( ssw = self , sswk = sswk , iri = None )
2019-07-23 15:50:31 +03:00
2020-03-27 14:48:15 +02:00
def _sswk ( self , k ) :
cdef _ScatteringSystemAtOmegaK sswk = _ScatteringSystemAtOmegaK ( )
sswk . sswk . ssw = self . ssw
2020-07-20 12:18:01 +03:00
sswk . ssw_pyref = self
2020-03-27 14:48:15 +02:00
sswk . sswk . k [ 0 ] = k [ 0 ]
sswk . sswk . k [ 1 ] = k [ 1 ]
sswk . sswk . k [ 2 ] = k [ 2 ]
2020-04-26 17:10:47 +03:00
sswk . eta = qpms_ss_adjusted_eta ( self . ssw [ 0 ] . ss , self . ssw [ 0 ] . wavenumber , sswk . sswk . k )
2020-03-27 14:48:15 +02:00
return sswk
2020-01-15 03:18:21 +02:00
property fecv_size :
def __get__ ( self ) : return self . ss_pyref . fecv_size
property saecv_sizes :
def __get__ ( self ) : return self . ss_pyref . saecv_sizes
property irrep_names :
def __get__ ( self ) : return self . ss_pyref . irrep_names
property nirreps :
def __get__ ( self ) : return self . ss_pyref . nirreps
2020-03-06 14:27:59 +02:00
property wavenumber :
def __get__ ( self ) : return self . ssw [ 0 ] . wavenumber
2020-01-15 03:18:21 +02:00
2020-03-06 14:27:59 +02:00
def modeproblem_matrix_full ( self , k = None ) :
2020-01-15 03:18:21 +02:00
self . check ( )
2020-01-20 17:30:36 +02:00
cdef size_t flen = self . ss_pyref . s [ 0 ] . fecv_size
2020-01-15 03:18:21 +02:00
cdef np .ndarray [np .complex_t , ndim = 2 ] target = np . empty (
( flen , flen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target
2020-03-27 14:48:15 +02:00
cdef _ScatteringSystemAtOmegaK sswk
if k is not None :
sswk = self . _sswk ( k )
qpms_scatsyswk_build_modeproblem_matrix_full ( & target_view [ 0 ] [ 0 ] , & sswk . sswk )
else :
qpms_scatsysw_build_modeproblem_matrix_full ( & target_view [ 0 ] [ 0 ] , self . ssw )
2020-01-15 03:18:21 +02:00
return target
def modeproblem_matrix_packed ( self , qpms_iri_t iri , version = ' pR ' ) :
self . check ( )
2020-03-06 14:27:59 +02:00
self . ensure_finite ( )
2020-01-15 03:18:21 +02:00
cdef size_t rlen = self . saecv_sizes [ iri ]
cdef np .ndarray [np .complex_t , ndim = 2 ] target = np . empty (
( rlen , rlen ) , dtype = complex , order = ' C ' )
cdef cdouble [: , : : 1 ] target_view = target
if ( version == ' R ' ) :
qpms_scatsysw_build_modeproblem_matrix_irrep_packed_orbitorderR ( & target_view [ 0 ] [ 0 ] , self . ssw , iri )
elif ( version == ' pR ' ) :
with nogil :
qpms_scatsysw_build_modeproblem_matrix_irrep_packed ( & target_view [ 0 ] [ 0 ] , self . ssw , iri )
else :
qpms_scatsysw_build_modeproblem_matrix_irrep_packed_serial ( & target_view [ 0 ] [ 0 ] , self . ssw , iri )
return target
2020-01-14 10:09:17 +02:00
2020-03-06 14:27:59 +02:00
def translation_matrix_full ( self , blochvector = None ) :
2020-04-20 17:31:34 +03:00
""" Constructs full translation matrix of a scattering system at given frequency.
Parameters
- - - - - - - - - -
blochvector : array_like of shape ( 3 , )
Bloch vector ( only for periodic systems )
See Also
- - - - - - - -
ScatteringSystem . translation_matrix_full : translation matrix for any wavenumber
"""
2020-03-06 14:27:59 +02:00
return self . ss_pyref . translation_matrix_full ( wavenumber = self . wavenumber , blochvector = blochvector )
2020-04-07 21:01:18 +03:00
def translation_matrix_packed ( self , iri , J = QPMS_HANKEL_PLUS ) :
return self . ss_pyref . translation_matrix_packed ( wavenumber = self . wavenumber , iri = iri , J = J )
2020-07-03 15:10:15 +03:00
@boundscheck ( False )
2020-07-20 12:18:01 +03:00
def scattered_E ( self , scatcoeffvector_full , evalpos , blochvector = None , btyp = QPMS_HANKEL_PLUS , bint alt = False ) :
# FIXME TODO this obviously does not work for periodic systems
""" Evaluate electric field for a given excitation coefficient vector
Parameters
- - - - - - - - - -
scatcoeffvector_full : array_like of shape ( self . fecv_size , )
Onedimensional excitation coefficient vector , describing SVWFs leaving
the scatterers .
evalpos : array_like of floats and shape ( . . . , 3 )
Evaluation points in cartesian coordinates .
blochvector : array_like or None
Bloch vector , must be supplied ( non - None ) for periodic systems , else None .
Returns
- - - - - - -
array_like of complex , with the same shape as `evalpos`
Electric field at the positions given in `evalpos` .
See Also
- - - - - - - -
_ScatteringSystemAtOmegaK . scattered_E : Evaluate scattered field in an infinite periodic system .
"""
if ( self . ssw [ 0 ] . ss [ 0 ] . lattice_dimension != 0 ) :
if blochvector is None :
raise ValueError ( " For a periodic system, blochvector must be specified. " )
return self . _sswk ( blochvector ) . scattered_E ( scatcoeffvector_full , evalpos = evalpos , btyp = btyp )
2020-03-22 11:44:20 +02:00
cdef qpms_bessel_t btyp_c = BesselType ( btyp )
2020-03-21 17:56:44 +02:00
evalpos = np . array ( evalpos , dtype = float , copy = False )
2020-03-21 08:11:16 +02:00
if evalpos . shape [ - 1 ] != 3 :
raise ValueError ( " Last dimension of evalpos has to be 3 " )
2020-03-21 17:56:44 +02:00
cdef np .ndarray [double , ndim = 2 ] evalpos_a = evalpos . reshape ( - 1 , 3 )
2020-07-20 12:18:01 +03:00
cdef np .ndarray [dtype = complex , ndim = 1 ] scv = np . array ( scatcoeffvector_full , dtype = complex , copy = False )
2020-03-21 17:56:44 +02:00
cdef cdouble [: : 1 ] scv_view = scv
cdef np .ndarray [complex , ndim = 2 ] results = np . empty ( ( evalpos_a . shape [ 0 ] , 3 ) , dtype = complex )
2020-03-21 08:11:16 +02:00
cdef ccart3_t res
cdef cart3_t pos
2020-07-03 14:20:07 +03:00
cdef Py_ssize_t i
2020-07-03 15:10:15 +03:00
with wraparound ( False ) , nogil , parallel ( ) :
for i in prange ( evalpos_a . shape [ 0 ] ) :
pos . x = evalpos_a [ i , 0 ]
pos . y = evalpos_a [ i , 1 ]
pos . z = evalpos_a [ i , 2 ]
if alt :
res = qpms_scatsysw_scattered_E__alt ( self . ssw , btyp_c , & scv_view [ 0 ] , pos )
else :
res = qpms_scatsysw_scattered_E ( self . ssw , btyp_c , & scv_view [ 0 ] , pos )
results [ i , 0 ] = res . x
results [ i , 1 ] = res . y
results [ i , 2 ] = res . z
2020-03-21 17:56:44 +02:00
return results . reshape ( evalpos . shape )
2020-03-06 14:27:59 +02:00
2020-01-14 10:09:17 +02:00
2019-07-23 15:50:31 +03:00
cdef class ScatteringMatrix :
'''
Wrapper over the C qpms_ss_LU structure that keeps the factorised mode problem matrix .
'''
2020-01-15 03:18:21 +02:00
cdef _ScatteringSystemAtOmega ssw # Here we keep the reference to the parent scattering system
2020-03-06 14:27:59 +02:00
cdef _ScatteringSystemAtOmegaK sswk
2019-07-23 15:50:31 +03:00
cdef qpms_ss_LU lu
2020-03-06 14:27:59 +02:00
def __cinit__ ( self , _ScatteringSystemAtOmega ssw , sswk = None , iri = None ) :
2020-01-15 03:18:21 +02:00
ssw . check ( )
self . ssw = ssw
2020-03-06 14:27:59 +02:00
if sswk is None :
ssw . ensure_finite ( )
# TODO? pre-allocate the matrix with numpy to make it transparent?
if iri is None :
self . lu = qpms_scatsysw_build_modeproblem_matrix_full_LU (
NULL , NULL , ssw . rawpointer ( ) )
else :
self . lu = qpms_scatsysw_build_modeproblem_matrix_irrep_packed_LU (
NULL , NULL , ssw . rawpointer ( ) , iri )
2019-07-23 15:50:31 +03:00
else :
2020-03-06 14:27:59 +02:00
# TODO check sswk validity
self . sswk = sswk
self . lu = qpms_scatsyswk_build_modeproblem_matrix_full_LU ( NULL , NULL , self . sswk . rawpointer ( ) )
2019-07-23 15:50:31 +03:00
def __dealloc__ ( self ) :
qpms_ss_LU_free ( self . lu )
2019-07-22 16:56:03 +03:00
2019-07-23 15:50:31 +03:00
property iri :
def __get__ ( self ) :
return None if self . lu . full else self . lu . iri
def __call__ ( self , a_inc ) :
cdef size_t vlen
cdef qpms_iri_t iri = - 1 ;
if self . lu . full :
2020-01-15 03:18:21 +02:00
vlen = self . lu . ssw [ 0 ] . ss [ 0 ] . fecv_size
2019-07-23 15:50:31 +03:00
if len ( a_inc ) != vlen :
raise ValueError ( " Length of a full coefficient vector has to be %d , not %d "
% ( vlen , len ( a_inc ) ) )
else :
iri = self . lu . iri
2020-01-15 03:18:21 +02:00
vlen = self . lu . ssw [ 0 ] . ss [ 0 ] . saecv_sizes [ iri ]
2019-07-23 15:50:31 +03:00
if len ( a_inc ) != vlen :
raise ValueError ( " Length of a %d . irrep packed coefficient vector has to be %d , not %d "
% ( iri , vlen , len ( a_inc ) ) )
a_inc = np . array ( a_inc , dtype = complex , copy = False , order = ' C ' )
cdef const cdouble [: : 1 ] a_view = a_inc ;
cdef np .ndarray f = np . empty ( ( vlen , ) , dtype = complex , order = ' C ' )
cdef cdouble [: : 1 ] f_view = f
qpms_scatsys_scatter_solve ( & f_view [ 0 ] , & a_view [ 0 ] , self . lu )
return f
2019-07-22 16:56:03 +03:00
2019-08-14 11:04:42 +03:00
def pitau ( double theta , qpms_l_t lMax , double csphase = - 1 ) :
2019-08-13 17:18:39 +03:00
if ( abs ( csphase ) != 1 ) :
raise ValueError ( " csphase must be 1 or -1, is %g " % csphase )
cdef size_t nelem = qpms_lMax2nelem ( lMax )
cdef np .ndarray [np .float_t , ndim = 1 ] lega = np . empty ( ( nelem , ) , dtype = float )
cdef np .ndarray [np .float_t , ndim = 1 ] pia = np . empty ( ( nelem , ) , dtype = float )
cdef np .ndarray [np .float_t , ndim = 1 ] taua = np . empty ( ( nelem , ) , dtype = float )
cdef double [: : 1 ] leg = lega
cdef double [: : 1 ] pi = pia
cdef double [: : 1 ] tau = taua
qpms_pitau_fill ( & leg [ 0 ] , & pi [ 0 ] , & tau [ 0 ] , theta , lMax , csphase )
return ( lega , pia , taua )
2019-08-20 12:46:28 +03:00
2019-10-05 20:38:04 +03:00
def linton_gamma ( cdouble x ) :
return clilgamma ( x )
def linton_gamma_real ( double x ) :
return lilgamma ( x )
2019-10-06 01:30:29 +03:00
def gamma_inc ( double a , cdouble x , int m = 0 ) :
2019-08-20 12:46:28 +03:00
cdef qpms_csf_result res
with pgsl_ignore_error ( 15 ) : #15 is underflow
2019-10-06 01:30:29 +03:00
complex_gamma_inc_e ( a , x , m , & res )
2019-08-20 12:46:28 +03:00
return ( res . val , res . err )
def gamma_inc_series ( double a , cdouble x ) :
cdef qpms_csf_result res
with pgsl_ignore_error ( 15 ) : #15 is underflow
cx_gamma_inc_series_e ( a , x , & res )
return ( res . val , res . err )
def gamma_inc_CF ( double a , cdouble x ) :
cdef qpms_csf_result res
with pgsl_ignore_error ( 15 ) : #15 is underflow
cx_gamma_inc_CF_e ( a , x , & res )
return ( res . val , res . err )
2019-08-25 18:58:05 +03:00
2019-08-25 21:17:23 +03:00
def lll_reduce ( basis , double delta = 0.75 ) :
2019-08-25 18:58:05 +03:00
"""
Lattice basis reduction with the Lenstra - Lenstra - Lovász algorithm .
basis is array_like with dimensions ( n , d ) , where
n is the size of the basis ( dimensionality of the lattice )
and d is the dimensionality of the space into which the lattice
is embedded .
"""
2019-08-25 19:27:45 +03:00
basis = np . array ( basis , copy = True , order = ' C ' , dtype = np . double )
2019-08-25 18:58:05 +03:00
if len ( basis . shape ) != 2 :
raise ValueError ( " Expected two-dimensional array (got %d -dimensional) "
% len ( basis . shape ) )
cdef size_t n , d
n , d = basis . shape
if n > d :
raise ValueError ( " Real space dimensionality ( %d ) cannot be smaller than "
" the dimensionality of the lattice ( %d ) embedded into it. "
% ( d , n ) )
cdef double [: , : ] basis_view = basis
2019-08-25 21:17:23 +03:00
if 0 != qpms_reduce_lattice_basis ( & basis_view [ 0 , 0 ] , n , d , delta ) :
2019-08-25 18:58:05 +03:00
raise RuntimeError ( " Something weird happened " )
return basis
2019-10-02 22:07:26 +03:00
cdef PGen get_PGen_direct ( direct_basis , bint include_origin = False , double layers = 30 ) :
dba = np . array ( direct_basis )
if not ( dba . shape == ( 2 , 2 ) ) :
raise NotImplementedError
cdef cart2_t b1 , b2
b1 . x = dba [ 0 , 0 ]
b1 . y = dba [ 0 , 1 ]
b2 . x = dba [ 1 , 0 ]
b2 . y = dba [ 0 , 1 ]
cdef double maxR = layers * max ( cart2norm ( b1 ) , cart2norm ( b2 ) )
return PGen_xyWeb_new ( b1 , b2 , BASIS_RTOL , CART2_ZERO , 0 , include_origin , maxR , False )
cdef double get_unitcell_volume ( direct_basis ) :
dba = np . array ( direct_basis )
if not ( dba . shape == ( 2 , 2 ) ) :
raise NotImplementedError
cdef cart2_t b1 , b2
b1 . x = dba [ 0 , 0 ]
b1 . y = dba [ 0 , 1 ]
b2 . x = dba [ 1 , 0 ]
b2 . y = dba [ 0 , 1 ]
return l2d_unitcell_area ( b1 , b2 )
cdef PGen get_PGen_reciprocal2pi ( direct_basis , double layers = 30 ) :
dba = np . array ( direct_basis )
if not ( dba . shape == ( 2 , 2 ) ) :
raise NotImplementedError
cdef cart2_t b1 , b2 , rb1 , rb2
b1 . x = dba [ 0 , 0 ]
b1 . y = dba [ 0 , 1 ]
b2 . x = dba [ 1 , 0 ]
b2 . y = dba [ 0 , 1 ]
if ( l2d_reciprocalBasis2pi ( b1 , b2 , & rb1 , & rb2 ) != 0 ) :
raise RuntimeError
cdef double maxK = layers * max ( cart2norm ( rb1 ) , cart2norm ( rb2 ) )
return PGen_xyWeb_new ( rb1 , rb2 , BASIS_RTOL , CART2_ZERO ,
0 , True , maxK , False )
cdef class Ewald3Calculator :
''' Wrapper class over qpms_ewald3_constants_t.
Mainly for testing low - level scalar Ewald summation functionality . '''
cdef qpms_ewald3_constants_t *c
def __cinit__ ( self , qpms_l_t lMax , int csphase = - 1 ) :
if ( csphase != - 1 and csphase != 1 ) :
raise ValueError ( " csphase must be +1 or -1, not %d " % csphase )
self . c = qpms_ewald3_constants_init ( lMax , csphase )
def __dealloc__ ( self ) :
qpms_ewald3_constants_free ( self . c )
def sigma0 ( self , double eta , cdouble wavenumber , do_err = False ) :
cdef int retval
cdef double err
cdef cdouble result
retval = ewald3_sigma0 ( & result , & err , self . c , eta , wavenumber )
if retval :
raise RuntimeError ( " ewald3_sigma0 returned non-zero value ( %d ) " % retval )
if do_err :
return ( result , err )
else :
return result
def sigma_short ( self , double eta , cdouble wavenumber , direct_basis , wavevector , particle_shift , do_err = False ) :
# FIXME now only 2d XY lattice in 3D is implemented here, we don't even do proper dimensionality checks.
cdef cart3_t beta , pshift
beta . x = wavevector [ 0 ]
beta . y = wavevector [ 1 ]
beta . z = 0
pshift . x = particle_shift [ 0 ]
pshift . y = particle_shift [ 1 ]
pshift . z = 0
cdef qpms_l_t n = self . c [ 0 ] . nelem_sc
cdef np .ndarray [complex , ndim = 1 ] result = np . empty ( ( n , ) , dtype = complex )
cdef cdouble [: : 1 ] result_v = result
cdef np .ndarray [double , ndim = 1 ] err
cdef double [: : 1 ] err_v
if do_err :
err = np . empty ( ( n , ) , dtype = np . double )
err_v = err
cdef bint include_origin = not ( particle_shift [ 0 ] == 0 and particle_shift [ 1 ] == 0 )
cdef PGen rgen = get_PGen_direct ( direct_basis , include_origin )
cdef int retval = ewald3_sigma_short ( & result_v [ 0 ] , & err_v [ 0 ] if do_err else NULL ,
self . c , eta , wavenumber , LAT_2D_IN_3D_XYONLY , & rgen , False , beta , pshift )
if rgen . stateData : PGen_destroy ( & rgen )
if retval : raise RuntimeError ( " ewald3_sigma_short returned %d " % retval )
if do_err :
return ( result , err )
else :
return result
def sigma_long ( self , double eta , cdouble wavenumber , direct_basis , wavevector , particle_shift , do_err = False ) :
# FIXME now only 2d XY lattice in 3D is implemented here, we don't even do proper dimensionality checks.
cdef cart3_t beta , pshift
beta . x = wavevector [ 0 ]
beta . y = wavevector [ 1 ]
beta . z = 0
pshift . x = particle_shift [ 0 ]
pshift . y = particle_shift [ 1 ]
pshift . z = 0
cdef qpms_l_t n = self . c [ 0 ] . nelem_sc
cdef np .ndarray [complex , ndim = 1 ] result = np . empty ( ( n , ) , dtype = complex )
cdef cdouble [: : 1 ] result_v = result
cdef np .ndarray [double , ndim = 1 ] err
cdef double [: : 1 ] err_v
if do_err :
err = np . empty ( ( n , ) , dtype = np . double )
err_v = err
cdef PGen kgen = get_PGen_reciprocal2pi ( direct_basis )
cdef double unitcell_volume = get_unitcell_volume ( direct_basis )
cdef int retval = ewald3_sigma_long ( & result_v [ 0 ] , & err_v [ 0 ] if do_err else NULL ,
self . c , eta , wavenumber , unitcell_volume , LAT_2D_IN_3D_XYONLY , & kgen , False , beta , pshift )
if kgen . stateData : PGen_destroy ( & kgen )
if retval : raise RuntimeError ( " ewald3_sigma_long returned %d " % retval )
if do_err :
return ( result , err )
else :
return result
2019-08-25 18:58:05 +03:00