[ Home ] [ News ] [ Contact ] [ Search ]

 About us
 Useful Links
 Publications
 Copyright notice
 nextnano³ documentation

 -> Download Software
 * password protected

 

 
 

Electronic Structure

This chapter comprises:

  1. Storage of data and setup of arrays on electronic structure
  2. Information on how to extract information on electronic structure
  3. Rotation of Hamilton
    3.1 Storage of rotation
    3.2 Setup of rotation
  4. Information on how to assemble the bulk Hamiltonian
  5. Setup of k.p Hamiltonian
  6. Calculate new k.p states
  7. Setup of single-band Schrödinger equation including magnetic field
  8. Separation models: Eigenvalues <--> Thomas-Fermi
    Separation between quantum and continuum states
  9. Calculate eigenvalues of k.p Schrödinger equation
  10. Calculate eigenvalues of single-band Schrödinger equation
  11. Environment where these routines are called

 

 

1. Storage of data on electronic structure

In the main file the arrays for the electronic structure are allocated and defined in the sequence:

CALL setup_quantum_models
CALL setup_kp_parameters
CALL allocate_quantum_states

 

subroutine setup_quantum_models

This subroutine allocates the pointer def_quantum_modelsM in MODULE quantum_models. Here the full information about degeneracies and effective masses is stored. See the comments in the source code for more information.

The structures sgChargeV(1)%qcV, sgChargeV(2)%qcV in MODULE quantum_solutions are allocated and set to default. The variables sgChargeV(1)%qcV, sgChargeV(2)%qcV contain the solution of the single-band Schrödinger equation. More details here.
(1) = el
(2) = hl

The corresponding Fermi levels for the single-band states are defined in MODULE fermi_level:
  fermi_sg_elV
  fermi_sg_hlV
They are allocated in SUBROUTINE setup_quantum_models.

Note: In order to simulate population inversion in lasers, we use different Fermi levels for the quantum states.
So each energy level could have a different Fermi level, in principle.
However, this is not implemented so far. So all electron quantum states have the same electron Fermi level and all hole quantum states have the same hole Fermi level.

To be more flexible each wavefunction has its own position-dependent Fermi level, stored in fermi_sg_elV, fermi_sg_hlV. These are taken for the density calculation. Up to now, we assume one Fermi level for electrons and one for holes.
 --> SUBROUTINE SetNewFermiLevel in MODULE ModifyFermiLevel is used after solving the drift-diffusion equation in order to update the Fermi levels accordingly.

 

subroutine setup_kp_parameters

The k.p parameters in MODULE kp_parameters are defined. These parameters are provided by routines in MODULE input_kp_parameters. Look up documentation there. See script for detailed information on k.p parameters. The storage format is described in more detail in "Input of k.p parameters".
Units: [eV], [Angstrom]

 

subroutine allocate_quantum_states

Allocates k.p quantum states and defines them. The k.p solutions are stored in MODULE quantum_solutions: kpChargeV.
More details here.
Allocates their Fermi levels and sets them to default: MODULE fermi_level:
 fermi_kp_elV,
 fermi_kp_hlV.
For the Fermi levels for each spinor of the k.p equation it holds.
 -> Each quantum cluster has its own Fermi level.
 -> Each eigenvalue has its own Fermi level.
 -> Each boundary condition has its own Fermi level.
So far, all of these different Fermi levels are identical, i.e.
we assume one Fermi level for electrons and one for holes.

The necessary information is provided by the subroutine get_kp_data in MODULE input_quantummodels.
The information about number of k|| and the density integration is obtained via subroutine get_dim_BZ_2D with option
calculate_kL = .FALSE.

 

 

2. How to extract information on electronic structure

In MODULE extract_kp_parameters there are various (auxiliary) functions that provide information about k.p:

MODULE extract_kp_parameters

  • SUBROUTINE get_kp_guenther_parameters
  • SUBROUTINE check_kp
  • SUBROUTINE Get_kp_MaterialParameters
  • SUBROUTINE get_which_kp

 

  • !--------------------------------------------------------------------
     SUBROUTINE check_kp(i,i_qr_guent,num_qr,kind_kpC,kpL)
    !--------------------------------------------------------------------
    !
    Input:
    ! i
    point on Güenther's grid
    !
    !
    Ouput:
    ! kpL = .TRUE.
    if k.p is done and i is contained within a quantum region.
    ! num_qr
    contains the number of the quantum region.
    ! kind_kpC = '8x8_zb','8x8_wz','6x6_zb','6x6_wz'
    ! i_qr_guent =
    number of material point in k.p quantum region.
    !
    !
    Note:
    ! map_qr_guentherV(num_qr)%number_of_points [MODULE gitter1D]
    :
    !
    number of points on material grid in quantum region num_qr.
    ! map_qr_guentherV(num_qr)%guenther_grid_points
    : array
    !
    which contains the coordinates of these points on material grid.
    ! --> map_qr_guentherV(num_qr)%guenther_grid_points(i_qr_guent)=i
    !
    !----------------------------------------------------------------------

    This subroutine returns for any point on material grid the quantum region, if kp is done, if so what kind of kp, and in i_qr_guent the position within the point numbering of that specific quantum region [see above].

 

  • !--------------------------------------------------------------------
    SUBROUTINE get_kp_guenther_parameters(i,parV,kpL)
    !---------------------------------------------------------------------
    !
    Returns for point i on material grid k.p parameters which are used for matrix assembly.
    !
    !
    Note: For zincblende it must be:
    !      L1 = L2 = L
    !      M1 = M2 = M3 = M
    !      N1 = N2 = N
    !      B1 = B2 = B3 = B
    !      P1 = P2 = P
    !      s1 = s2 = s
    !      d1 = 0
    !      d2 = d3 = d/3
    !
    !
    If this point does not lie in a k.p region --> kpL = .FALSE.
    !
    !
    Input:
    ! i ...
    material grid point
    !
    !
    Ouput:
    !
    ! kpL .TRUE.
    if k.p is done in this region.
    ! parV
    contains k.p parameters according to following scheme:
    !
    ! parV(1)  = L1
    ! parV(2)  = L2
    ! parV(3)  = M1
    ! parV(4)  = M2
    ! parV(5)  = M3
    ! parV(6)  = N1
    ! parV(7)  = N2
    ! parV(8)  = B1
    ! parV(9)  = B2
    ! parV(10) = B3
    ! parV(11) = P1
    ! parV(12) = P2
    ! parV(13) = s1
    ! parV(14) = s2
    ! parV(15) = E_c
    (without any potential but including strain)
    ! parV(16) = E_v
    (without any potential)
    ! parV(17) = d1
    ! parV(18) = d2
    ! parV(19) = d3
    !
    !
    Units: [eV] and [Angstrom]
    !
    !---------------------------------------------------------------------

    Please note: This subroutine is valid for wurtzite and for zincblende (see script for meaning of  parameters). Same routine for 2D and 3D.

 

  • par_minV --> material parameters between i-1 and i
    par_andV -->
    material parameters between i and i+1
    parV     -->
    material parameters on i

    !-------------------------------------------------------------------------
     SUBROUTINE Get_kp_MaterialParameters(i_phys_min,i_phys,i_phys_and    , &
                                          j_phys_min,j_phys,j_phys_and    , &
                                          k_phys_min,k_phys,k_phys_and    , &
                                          par_i_minV,par_j_minV,par_k_minV, &
                                          par_i_andV,par_j_andV,par_k_andV, &
                                          parV,par_octM)
    !-------------------------------------------------------------------------
    !
    Input:
    !
    ! 1D: i_phys_min,i_phys,i_phys_and
    are the three neighboring points
    ! 2D: i_phys_min,i_phys,i_phys_and,
    !     j_phys_min,j_phys,j_phys_and
    are the five neighboring points.
    ! 3D: i_phys_min,i_phys,i_phys_and,
    !     j_phys_min,j_phys,j_phys_and,
    !     k_phys_min,k_phys,k_phys_and
    are the seven neighboring points.
    !
    If (i_phys_min = i_phys = i_phys_and) just parV is valid.
    !
    !
    Ouput:

    ! par_i_minV ->
    between i_phys_min,i_phys
    ! par_i_andV ->
    between i_phys    ,i_phys_and
    ! par_j_minV ->
    between j_phys_min,j_phys
    ! par_j_andV ->
    between j_phys    ,j_phys_and
    ! par_k_minV ->
    between k_phys_min,k_phys
    ! par_k_andV ->
    between k_phys    ,k_phys_and
    ! parV       ->
    on (i_phys,j_phys)
    !
    ! par_andV(1)  = L1
    ! par_andV(2)  = L2
    ! par_andV(3)  = M1
    ! par_andV(4)  = M2
    ! par_andV(5)  = M3
    ! par_andV(6)  = N1
    ! par_andV(7)  = N2
    ! par_andV(8)  = B1
    ! par_andV(9)  = B2
    ! par_andV(10) = B3
    ! par_andV(11) = P1
    ! par_andV(12) = P2
    ! par_andV(13) = s1
    ! par_andV(14) = s2
    !
    ! only for parV
    !
    ! parV(15) = E_c
    (including electrostatic potential)
    ! parV(16) = E_v
    (including electrostatic potential)
    ! parV(17) = d1
    ! parV(18) = d2
    ! parV(19) = d3
    !
    !
    Units: [eV] and [Angstrom]
    !
    !
    Note: The electrostatic potential phiV is contained in MODULE potentials.
    !
    !----------------------------------------------------------------------

 

  • !---------------------------------------------------------------------
     SUBROUTINE get_which_kp(num_qr,kpL,kind_kpC,chargeC)
    !---------------------------------------------------------------------
    !
    Input:
    ! num_qr ...
    number of quantum region
    !
    !
    Output:
    ! kpL,kind_kpC,chargeC
    !
    !---------------------------------------------------
    !
    !
    Checks, if k.p is done in quantum_region num_qr.
    !
    If so --> kpL = .TRUE. ( .FALSE. otherwise)
    ! kind_kpC = '8x8','6x6'
    ! ('---'
    if kpL=.FALSE.)
    ! chargeC = 'el','hl','bo'
    ! 'bo' -->
    k.p done for both carriers
    ! '--' -->
    if no k.p done (kpL=.false.)
    !
    !---------------------------------------------------------------------

 

 

3. Rotation of k.p Hamiltonian

3.1 Storage of rotation / Rotation of k.p Hamiltonian

The rotation is carried out from the crystal to the calculation system as described in 'Rotations'. The rotated k.p-Hamiltonian has the form as specified in MODULE rotate_kp.

                                  / r11 r12 r13 \
  rotation matrix : R =  |  r21 r22  r23 |
                                  \ r31 r32 r33 /

For definiton of k.p wurtzite matrix, follow this link.

REAL(8)  ::  r11,r12,r13, &
             r21,r22,r23, &
             r31,r32,r33

 

Valence band

1. Rotation of Hvv

(3x3)-matrix with L,M,N in basis |x>, |y>, |z>.

The rotation of Hvv is carried out as follows:
   H'(k') = RH(RTk')RT

k': k vector in new coordinate system which is the simulation system.
H': Hamiltonian in new coordinate system which is the simulation system.
H: Hamiltonian in crystal system.
RTk' = k: k vector in crystal system.

Since the basis states |x>, |y>, |z> are functions in the crystal sytem, they must be transferred into the simulation system.


H'vv is a (3x3) matrix which has 9 entries which are functions of 6 variables k'x², k'y², k'z², k'xk'y=k'yk'x, k'xk'z=k'zk'x, k'yk'z=k'zk'y.
                                                                                            9 variables k'x², k'y², k'z², k'xk'y, k'xk'z, k'yk'z,k'yk'x, k'zk'x, k'zk'y

We are writing the rotated (3x3) H'vv matrix as the sum of several (3x3) matrices ordered by k'x², k'y², k'z², k'xk'y, k'xk'z, k'yk'z:
                                                                                                                    ordered by k'x², k'y², k'z², k'xk'y, k'xk'z, k'yk'z, k'yk'x, k'zk'x, k'zk'y:

H'vv = ( 3x3 ) k'x² + ( 3x3 ) k'y² + ( 3x3 ) k'z² + ( 3x3 ) k'xk'y + ( 3x3 ) k'xk'z + ( 3x3 ) k'yk'z
          
 ( 3x3 ) k'x² + ( 3x3 ) k'y² + ( 3x3 ) k'z² + ( 3x3 ) k'xk'y + ( 3x3 ) k'xk'z + ( 3x3 ) k'yk'z + ( 3x3 ) k'yk'x + ( 3x3 ) k'zk'x + ( 3x3 ) k'zk'y

or

H'vv = RM1.k'x² + RM2.k'y² + RM3.k'z² + RM4.k'xk'y + RM5.k'xk'z + RM6.k'yk'z
         
  RM1.k'x² + RM2.k'y² + RM3.k'z² + RM4.k'xk'y + RM5.k'xk'z + RM6.k'yk'z + RM7.k'yk'x + RM8.k'zk'x + RM9.k'zk'y

          RM  (i=1...6)
                      (i=1...9)

      i=1...6
      i=1...9
      i=1 <--> k'x²
      i=2 <--> k'y²
      i=3 <--> k'z²
      i=4 <--> k'xk'y
      i=5 <--> k'xk'z
      i=6 <--> k'yk'z
      i=7 <--> k'yk'x
      i=8 <--> k'zk'x
      i=9 <--> k'zk'y

 

 


RMi is a (3x3) matrix which has 9 entries which are functions of 7 variables L1, L2, M1, M2, M3, N1, N2.

Again, we are writing the rotated (3x3) RMi matrix as the sum of several (3x3) matrices ordered by L1, L2, M1, M2, M3, N1, N2:

RMi = ( 3x3 ) L1 + ( 3x3 ) L2 + ( 3x3 ) M1 + ( 3x3 ) M2 + ( 3x3 ) M3 + ( 3x3 ) N1 + ( 3x3 ) N2

or

RMi = RMiL1.L1 + RMiL2.L2 + RMiM1.M1 + RMiM2.M2 + RMiM3.M3 + RMiN1.N1 + RMiN2.N2

(L,M,N Dresselhaus parameters)

RMij    (i=1...6, j=1...7)
             (i=1...9, j=1...7)

      j=1...7
      j=1 <--> L1
      j=2 <--> L2
      j=3 <--> M1
      j=4 <--> M2
      j=5 <--> M3
      j=6 <--> N1
      j=7 <--> N2

 

rotationCoeff_wurM(1:6,1:7)%matrixM(1:3,1:3) = rotationCoeffM(i,j)%matrixM(3x3) = RMij
rotationCoeff_wurM(1:9,1:7)%matrixM(1:3,1:3) = rotationCoeffM(i,j)%matrixM(3x3) = RMij

REAL(8),DIMENSION(6,7,3,3) :: rotationCoeff_wurM

 

Representation for wurtzite is also used for zincblende:
L=L1=L2
M=M1=M2=M3
N=N1=N2

The diagonal elements Ev+hbar²/(2m0) k² do not change under rotation.

 


Conduction band:

2. Rotation of Hcc

For definiton of Hcc wurtzite matrix, follow this link.

Hcc = (Hcc)ij

H'11 = H11 + Ec + hbar²/(2m0).s2.(k'x²+k'y²+k'z²) +
                      + hbar²/(2m0).(s1-s2).(r13²k'x² + r23²k'y² + r33²k'z² + 2 r13r23²k'xk'y + 2 r13r33²k'xk'z + 2 r23r33k'yk'z )

 


3. Rotation of Hcv

Follow this link for the structure of the matrix Hcv.

H'cv is a (2x6) matrix which can be split into four (1x3) matrices. Two of these are zero, the other two are identical and have these three components Hcvsx, Hcvsy, Hcvsz which are functions of 6 variables k'x², k'y², k'z², k'xk'y=k'yk'x, k'xk'z=k'zk'x, k'yk'z=k'zk'y.
                                                                             6 variables k'xk'y=k'yk'x, k'xk'z=k'zk'x, k'yk'z=k'zk'y, k'x, k'y, k'z.  (???)

Hcvsx = B ky kz + i P kx

Hcvsy = B kx kz + i P ky

Hcvsz = B kx ky + i P kz

The B terms are functions in second order of k, the P terms are functions in first order of k, thus we divide the matrix components into 2 parts, one for B and one for P.

H'cvsx  = H'cvsx (only B part) + H'cvsx (only P part)

 

B Part

x' = R . x

 

This is how the rotated H'cvsn should look like:

H'cvsx (only B part) = < s | H | x' >  =  r11 B1 ky kz + r12 B2 kx kz + r13 B3 kx ky

H'cvsy (only B part) = < s | H | y' >  =  r21 B1 ky kz + r22 B2 kx kz + r23 B3 kx ky

H'cvsz (only B part) = < s | H | z' >  =  r31 B1 ky kz + r32 B2 kx kz + r33 B3 kx ky

 

However, we are writing the rotated Hcvsn (n=x,y,z) component (=scalar) as the sum of several scalar components ordered by k'x², k'y², k'z², k'xk'y, k'xk'z, k'yk'z:

 

H'cvsx (only B part) = < s | H | x' > = RMx1.k'x² + RMx2.k'y² + RMx3.k'z² + RMx4.k'xk'y + RMx5.k'xk'z + RMx6.k'yk'z

H'cvsy (only B part) = < s | H | y' > = RMy1.k'x² + RMy2.k'y² + RMy3.k'z² + RMy4.k'xk'y + RMy5.k'xk'z + RMy6.k'yk'z

H'cvsz (only B part) = < s | H | z' > = RMz1.k'x² + RMz2.k'y² + RMz3.k'z² + RMz4.k'xk'y + RMz5.k'xk'z + RMz6.k'yk'z

H'cvsn = RMn1.k'x² + RMn2.k'y² + RMn3.k'z² + RMn4.k'xk'y + RMn5.k'xk'z + RMn6.k'yk'z

RMni  is a scalar!    (i=1...6) (n=1...3)

 

RMx1 =  RMx1B1 B1 + RMx1B2 B2 + RMx1B3 B3;   RMx2 = ...;   RMx3 = ... ;   RMx4 = ...;   RMx5 = ... ;  RMx6 = ...;

RMy1 =  RMy1B1 B1 + RMy1B2 B2 + RMy1B3 B3;   RMy2 = ...;   RMy3 = ... ;   RMy4 = ...;   RMy5 = ... ;  RMy6 = ...;

RMz1 =  RMz1B1 B1 + RMz1B2 B2 + RMz1B3 B3;   RMz2 = ...;   RMz3 = ... ;   RMz4 = ...;   RMz5 = ... ;  RMz6 = ...;

RMni = RMniB1 B1 + RMniB2 B2 + RMniB3 B2        (Bm=1...3)   (wurtzite)

RMniBm is a scalar!    (Bm=1...3)  (i=1...6) (n=1...3)

 

rotationCondM --> conduction band (for B)

A vector of dimension 3 stores this information:

[ H'cvsy (only B part), H'cvsz (only B part), H'cvsz (only B part) ]

 --> vector(n=1..3) = (Hsx Hsy Hsz)

REAL(8),DIMENSION(6,3,3)    :: rotationCond_wurM

 

Representation for wurtzite is also used for zincblende:
B = B1 = B2 =B3


P Part

x' = R . x

 

This is how the rotated H'cvsn should look like:

H'cvsx (only P part) = < s | H | x' > = r11 P2 kx + r12 P2 ky + r13 P1 kz

H'cvsy (only P part) = < s | H | y' > = r21 P2 kx + r22 P2 ky + r23 P1 kz

H'cvsz (only P part) = < s | H | z' > = r31 P2 kx + r32 P2 ky + r33 P1 kz

 

However, we are writing the rotated Hcvsn (n=x,y,z) component (=scalar) as the sum of several scalar components ordered by kx, ky, kz:

 

H'cvsx (only P part) = < s | H | x' > = RMx1.k'x + RMx2.k'y + RMx3.k'z

H'cvsy (only P part) = < s | H | y' > = RMy1.k'x + RMy2.k'y + RMy3.k'z

H'cvsz (only P part) = < s | H | z' > = RMz1.k'x + RMz2.k'y + RMz3.k'z

          H'cvsn = RMn1.k'x + RMn2.k'y + RMn3.k'z

RMni  is a scalar!    (i=1...3) (n=1...3)

 

RMx1 =  RMx1P1 P1 + RMx1P2 P2;       RMx2 = ...;   RMx3 = ... ;   RMx4 = ...;   RMx5 = ... ;  RMx6 = ...;

RMy1 =  RMy1P1 P1 + RMy1P2 P2;       RMy2 = ...;   RMy3 = ... ;   RMy4 = ...;   RMy5 = ... ;  RMy6 = ...;

RMz1 =  RMz1P1 P1 + RMz1P2 P2;       RMz2 = ...;   RMz3 = ... ;   RMz4 = ...;   RMz5 = ... ;  RMz6 = ...;

RMni = RMniP1 P1 + RMniP2 P2       (Pm=1...2)   (wurtzite)

RMniPm is a scalar!    (Pm=1...2)  (i=1...3) (n=1...3)

 

rotation_P_M --> conduction band (for P)

A vector of dimension 3 stores this information:

[ H'cvsx (only P part), H'cvsy (only P part), H'cvsz (only P part) ]

 --> vector(n=1..3) = (Hsx Hsy Hsz)

 

COMPLEX(8),DIMENSION(3,2,3) :: rotation_P_wurM

complex!!!

 

Representation for wurtzite is also used for zincblende:
P = P1 = P2


 

Crystal field splitting

This link for details.

Here, we don't need to split the rotated matrix D' into the k' components. We just store the rotated matrix after we have done the basis transformation:

D' = R.D.RT

 

Crystal field splitting: crysSplit_wurM (d1)

wurtzite:   d1
zincblende:   d1=0

 

REAL(8),DIMENSION(3,3)   :: crysSplit_wurM

 


 

Spin-orbit interaction

This link for details.

Here, we don't need to split the rotated matrix HSO' into the k' components. We just store the rotated matrix after we have done the basis transformation:

HSO' = R.HSO.RT

 

rotationSpin_wurM      rotation of spin matrix

for wurtzitze:    d2, d3
for zincblende:  d2=d/3 , d3=d/3

COMPLEX(8),DIMENSION(6,6) :: rotationSpin_wurM

 


 

Strain

This link for details.

In subroutine SetupStrainHamiltonianVB (MODULE mod_setup_rot_kp) the strain matrix Hstrain will be set up in the crystal coordinate system. Then it will be rotated into the simulation coordinate system leading to H'strain. Here we don't need to split H'strain into the k' components.

H'strain = R.Hstrain.RT

 

The strain Hamiltonian Hstrain is completely set up in the crystal system and then rotated directly to the calculation system. This is done in subroutine set_strain_mat_physgrid_point / subroutine set_strain_mat_guent_point in MODULE set_strain_mat.

 

Strain for holes: zincblende and wurtzite in same matrix.

Holes:  StrainHamiltonianVB_3x3M(3,3)

COMPLEX(8),DIMENSION(3,3) :: StrainHamiltonianVB_3x3M

 


 

NOTE: The data in this module only depend on the rotation matrix.
     --> Only calculated anew when rotation has changed.
     --> Stores for which direction last rotation has been calculated in.

         last_rot_matM(3,3)

          If rotation not defined: last_rot_matM = 0.0d0    ( = starting value)

REAL(8),DIMENSION(3,3) :: last_rot_matM=0.0d0

 


 

3.2 Setup of rotation

The rotation matrix is defined in update_rotation / update_rotation_guent in MODULE mod_update_rotation. These subroutines update the rotation matrix in MODULE rotate_kp and the arrays rotationCoeff_wurM, rotationCond_wurM, rotation_P_wurM in MODULE rotate_kp for a given point on physical/material grid.

The arrays in MODULE rotate_kp are defined with the help of the routines in MODULE mod_setup_rot_kp:

!--------------------------------------------------!
!
!  1. subroutine setup_rotationCoeffM
!
!  2. subroutine setup_rotationSpin_wurM
!
!  3. subroutine setup_rotationCondM
!
!  4. subroutine setup_rotation_P_M
!
!  5. subroutine setup_crysSplit_wurM
!
!  6. subroutine SetupStrainHamiltonianVB
!
!--------------------------------------------------!

NOTE: Before these subroutines are called, the rotation matrix in MODULE rotate_kp
           (i.e. r11,r12,r13,
         r21,r22,r23,
         r31,r32,r33
) must have been defined.

 


 

4. Information on how to assemble the bulk k.p Hamiltonian

In subroutine setup_kpbulk [MODULE kp_bulk] the rotated bulk k.p Hamiltonian is assembled. One can study in this subroutine, how the assembly works:

!-----------------------------------------------------
 subroutine setup_kpbulk(i_guent,k1,k2,k3,hamiltonM)
!-----------------------------------------------------

For a given point i_guent on material grid we are calculating th k.p matrix matM.

After that we are adding the strain matrix.
Note: k1,k2,k3 in Angstrom (AA).

It sets up the strain matrix using directly strain from material grid.

!---------------------------------------------------------------------------
 USE derived_constants     ,ONLY:h2b2m_evAA2
 USE rotate_kp             ,ONLY:rotationCond_wurM,rotationCoeff_wurM,rotation_P_wurM,  &
                                 rotationSpin_wurM,crysSplit_wurM,StrainHamiltonianVB_3x3M, &
                                 r11,r12,r13,r22,r23,r33,r21,r31,r32
 USE mod_update_rotation   ,ONLY:update_rotation_guent
 USE mod_setup_rot_kp      ,ONLY:setup_rotationCoeffM,setup_rotationSpin_wurM,   &
                                 setup_rotationCondM,setup_rotation_P_M,setup_crysSplit_wurM
 USE gitter                ,ONLY:addressM,PointInfoM
 USE set_strain_mat        ,ONLY:set_strain_mat_guent_point
 USE structure_functions   ,ONLY:convert_guenther_to_phys2D
 

 REAL(8),intent(in)           :: k1,k2,k3
 LOGICAL                      :: kpL
!-----------------------------------------------------------

 REAL(8)                      :: ind1,ind
 INTEGER                      :: i,j,adr1,adr2
 INTEGER ,DIMENSION(4,3)      :: coordM         ! 2D
 


Here we are retrieving the k.p parameters for the points i_guent from MODULE extract_kp_parameters.

USE extract_kp_parameters,ONLY: get_kp_guenther_parameters
integer,intent(in)              :: i_guent
real(8),dimension(19)           :: parV
real(8)                         :: E_c,E_v,s1,s2,d1,d2,d3,l1,l2,
                                   m1,m2,m3,n1,n2,b1,b2,b3,p1,p2,pot
 

CALL get_kp_guenther_parameters(i_guent,parV,kpL)

Note: The definition of parV is the same for wurtzite and zincblende.

 l1  = parV(1)  !  Dresselhaus parameters
 l2  = parV(2)
 m1  = parV(3)
 m2  = parV(4)
 m3  = parV(5)
 n1  = parV(6)
 n2  = parV(7)

 b1  = parV(8)  !
 Inversion symmetry parameters
 b2  = parV(9)
 b3  = parV(10)

 p1  = parV(11) !
 Matrix elements, mixing conduction and valence bands
 p2  = parV(12)

 s1  = parV(13) !
 Inverse effective masses of electrons
 s2  = parV(14)

 E_c = parV(15) !
 Conduction band energy (without potential but including strain)
 E_v = parV(16) !
 Valence band energy (without potential)

 d1  = parV(17) !
 Crystal field splitting
 d2  = parV(18) !
 Spin-orbit interaction
 d3  = parV(19) !
 Spin-orbit interaction


The electrostatic potential is taken from phiV.

USE define_geometry,ONLY :  n_material_points
USE gitter         ,ONLY : 
addressM

 

 IF (domain_dimension==1) THEN

  if (i_guent==1) then
     adr1=
addressM(1,1,1)
     adr2=adr1
   else if (i_guent==n_material_points) then
     adr1=
addressM(n_material_points+1,1,1)
     adr2=adr1
   else
     if (
PointInfoM(i_guent,1,1)==0) then
        adr1=
addressM(i_guent,1,1)
     else
        adr1=
addressM(i_guent,1,1)+1
     end if
        adr2=
addressM(i_guent+1,1,1)
   end if
   pot = 0.5d0  * (phiV(adr1)        + phiV(adr2))

 ELSE IF (domain_dimension==2) THEN
SetupStrainHamiltonianVB   CALL convert_guenther_to_phys2D(i_guent,coordM)
   pot = 0.25d0 * (phiV(coordM(1,3)) + phiV(coordM(2,3)) + &
                   phiV(coordM(3,3)) + phiV(coordM(4,3)) )
 END IF

  E_c=E_c-pot
  E_v=E_v-pot

Now E_c and E_v include the potential.

Conduction band energy (including potential and including strain)
Valence     band energy (including potential)


Here we define the rotation matrix and the rotated k.p Hamiltonian.

CALL update_rotation_guent(i_guent)

(Here the three routines
    call setup_rotationCoeffM
    call setup_rotationCondM
    call setup_rotation_P_M
are called.)

CALL setup_rotationSpin_wurM(d2,d3)
CALL setup_crysSplit_wurM(d1)

CALL set_strain_mat_guent_point(i_guent)
 
Here we are retrieving the deformation potentials.
   call get_deformation_pot_vb(i_guent,l1,l2,m1,m2,m3,n1,n2,a_abs)
 
    a_abs is ac but it isn't used as it is already included in E_c which is defined in the k.p parameters described above.
      Here we are extracting the strain tensor for grid point
i_guent (specified in crystal coordinate system).
   call get_strain_voigt_notation_cxyz(i_guent,strainM)

   call SetupStrainHamiltonianVB(epsM,l1,l2,n1,n2,m1,m2,m3)

    Sets up strain matrix H'strain for H'vv part [ (6*6) = two 3*3 blocks ] and
        carries out rotation numerically.


For
  
rotationCoeff_wurM(1:6,1:7,1:3,1:3) = rotationCoeffM(i,,j,3x3) = RMij

   RM  (i=1...6)  RMij    (i=1...6, j=1...7)
   
  i=1...6
      i=1 <--> k'x²
      i=2 <--> k'y²
      i=3 <--> k'z²
      i=4 <--> k'xk'y
      i=5 <--> k'xk'z
      i=6 <--> k'yk'z
      j=1...7
      j=1 <--> L1
      j=2 <--> L2
      j=3 <--> M1
      j=4 <--> M2
      j=5 <--> M3
      j=6 <--> N1
      j=7 <--> N2

(for details, see above!)

we are looping over i and j.

do i=1,6

   select case(i)
   case(1)
    ind=k1**2
   case(2)
    ind=k2**2
   case(3)
    ind=k3**2
   case(4)
    ind=k1*k2
   case(5)
    ind=k1*k3
   case(6)
    ind=k2*k3
   end select

   do j=1,7
    select case(j)
    case(1)
     ind1=l1
    case(2)
     ind1=l2
    case(3)
     ind1=m1
    case(4)
     ind1=m2
    case(5)
     ind1=m3
    case(6)
     ind1=n1
    case(7)
     ind1=n2
    end select

The Hamiltonian matrix is always 8x8, even in the case for 6x6 kp. In the latter case, the first two rows and first two columns are not used. Here we calculate H'vv and H'vv which are identical.

complex(8),dimension(8,8),intent(out) :: hamiltonM

hamiltonM = cmplx(0d0)

   hamiltonM(3:5,3:5) = hamiltonM(3:5,3:5) +
                        dcmplx(ind*ind1*rotationCoeff_wurM(i,j,:,:)) !
H'vv
   hamiltonM(6:8,6:8) = hamiltonM(6:8,6:8) +
                        dcmplx(ind*ind1*rotationCoeff_wurM(i,j,:,:)) !
H'vv
 end do


Now we consider the inversion symmetry parameters B leading to H'cv and H'cvT

   do j=1,3

     select case(j)
     case(1)
      ind1=b1
     case(2)
      ind1=b2
     case(3)
      ind1=b3
     end select

       H'cv:

       hamiltonM(1,3)=hamiltonM(1,3)+dcmplx(ind*ind1*rotationCond_wurM(i,j,1))
       hamiltonM(1,4)=hamiltonM(1,4)+dcmplx(ind*ind1*rotationCond_wurM(i,j,2))
       hamiltonM(1,5)=hamiltonM(1,5)+dcmplx(ind*ind1*rotationCond_wurM(i,j,3))

       hamiltonM(2,6)=hamiltonM(2,6)+dcmplx(ind*ind1*rotationCond_wurM(i,j,1))
       hamiltonM(2,7)=hamiltonM(2,7)+dcmplx(ind*ind1*rotationCond_wurM(i,j,2))
       hamiltonM(2,8)=hamiltonM(2,8)+dcmplx(ind*ind1*rotationCond_wurM(i,j,3))

       H'cvT:

       hamiltonM(3,1)=hamiltonM(3,1)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,1)))
       hamiltonM(4,1)=hamiltonM(4,1)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,2)))
       hamiltonM(5,1)=hamiltonM(5,1)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,3)))

       hamiltonM(6,2)=hamiltonM(6,2)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,1)))
       hamiltonM(7,2)=hamiltonM(7,2)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,2)))
       hamiltonM(8,2)=hamiltonM(8,2)+ &
                      dconjg(dcmplx(ind*ind1*rotationCond_wurM(i,j,3)))

     end do
end do


Now we consider the P matrix elements that are mixing the conduction and valence bands.

     do i=1,3

      select case(i)
      case(1)
       ind=k1
      case(2)
       ind=k2
      case(3)
       ind=k3
      end select

      do j=1,2

       select case(j)
       case(1)
        ind1=p1
       case(2)
        ind1=p2
       end select

        H'cv:

                hamiltonM(1,3)=hamiltonM(1,3)+ind*ind1*rotation_P_wurM(i,j,1)
        hamiltonM(1,4)=hamiltonM(1,4)+ind*ind1*rotation_P_wurM(i,j,2)
        hamiltonM(1,5)=hamiltonM(1,5)+ind*ind1*rotation_P_wurM(i,j,3)

        hamiltonM(2,6)=hamiltonM(2,6)+ind*ind1*rotation_P_wurM(i,j,1)
        hamiltonM(2,7)=hamiltonM(2,7)+ind*ind1*rotation_P_wurM(i,j,2)
        hamiltonM(2,8)=hamiltonM(2,8)+ind*ind1*rotation_P_wurM(i,j,3)

                H'cvT:

        hamiltonM(3,1)=hamiltonM(3,1)+dconjg(ind*ind1*rotation_P_wurM(i,j,1))
        hamiltonM(4,1)=hamiltonM(4,1)+dconjg(ind*ind1*rotation_P_wurM(i,j,2))
        hamiltonM(5,1)=hamiltonM(5,1)+dconjg(ind*ind1*rotation_P_wurM(i,j,3))

        hamiltonM(6,2)=hamiltonM(6,2)+dconjg(ind*ind1*rotation_P_wurM(i,j,1))
        hamiltonM(7,2)=hamiltonM(7,2)+dconjg(ind*ind1*rotation_P_wurM(i,j,2))
        hamiltonM(8,2)=hamiltonM(8,2)+dconjg(ind*ind1*rotation_P_wurM(i,j,3))

     end do

   end do


Now we consider the spin-orbit interaction and the crystal field splitting (crystal field splitting = 0  for zincblende).

H'vv: hamiltonM(3:8,3:8) = hamiltonM(3:8,3:8) + rotationSpin_wurM
H'vv: hamiltonM(3:5,3:5) = hamiltonM(3:5,3:5) + crysSplit_wurM
H'vv: hamiltonM(6:8,6:8) = hamiltonM(6:8,6:8) + crysSplit_wurM


Inverse effective masses of electrons (S parameters).

H'cc: hamiltonM(1,1)=hamiltonM(1,1)+ &
                dcmplx(E_c+h2b2m_evAA2*s2*(k1**2+k2**2+k3**2))+
                dcmplx(h2b2m_evAA2*(s1-s2)*(r13**2*k1**2+r23**2*k2**2+
                r33**2*k3**2+2*r13*r23*k1*k2+2*r13*r33*k1*k3+2*r23*r33*k2*k3))

H'cc: hamiltonM(2,2)=hamiltonM(2,2)+ &
                dcmplx(E_c+h2b2m_evAA2*s2*(k1**2+k2**2+k3**2))+
                dcmplx(h2b2m_evAA2*(s1-s2)*(r13**2*k1**2+r23**2*k2**2+
                r33**2*k3**2+2*r13*r23*k1*k2+2*r13*r33*k1*k3+2*r23*r33*k2*k3))

H'vv: hamiltonM(3,3)=hamiltonM(3,3)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))
H'vv: hamiltonM(4,4)=hamiltonM(4,4)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))
H'vv: hamiltonM(5,5)=hamiltonM(5,5)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))
H'vv: hamiltonM(6,6)=hamiltonM(6,6)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))
H'vv: hamiltonM(7,7)=hamiltonM(7,7)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))
H'vv: hamiltonM(8,8)=hamiltonM(8,8)+dcmplx(E_v+h2b2m_evAA2*(k1**2+k2**2+k3**2))


Now strain:


H'vv: hamiltonM(3:5,3:5) = hamiltonM(3:5,3:5) + StrainHamiltonianVB_3x3M
H'vv: hamiltonM(6:8,6:8) = hamiltonM(6:8,6:8) + StrainHamiltonianVB_3x3M


Final matrix must be hermitian.

end subroutine setup_kpbulk


 

 

5. Setup of k.p Hamiltonian

The setup of the k.p Hamiltonian is done in subroutine setup_kp_mat (setup_kp_mat1D).

!---------------------------------------------
 subroutine setup_kp_mat(num_qr,k_z,bndC)
!---------------------------------------------
!
Creates k.p matrix for given region num_qr.
!
Arrays in MODULE sparse_matrix must be deallocated.
!
! num_qr =
number of quantum region
! bndC = 'per','dir','neu'
(Periodic/Dirichlet/Neumann boundary conditions)
!
!--------------------------------------------

Note: If Arnoldi is used the symmetry is disturbed by adding a number disturb_arnoldi [MODULE: numeric_control].

 

The discretized Hamiltonian can be written in the form D.H, where D is a diagonal matrix and H is a hermitian one. It can be shown (see e.g. in the glossary) that the eigenvalues of the matrix D.H are the same as those of the matrix D1/2.H.D1/2. The matrix H is stored as a sparse matrix in MODULE sparse_matrix (or in 1D alternatively in MODULE lapack_mat). The diagonal matrix D1/2 is stored as vector diag_kpV in MODULE normalization_factors_sg. The dimension is 6xdim_qr or  8xdim_qr for 6x6 or 8x8 k.p theory, respectively (dim_qr is the dimension of the quantum region, i.e. the number of grid points on the physical grid contained in the quantum region).

 

3D

The program loops through all grid points of the quantum region (1..dim_qr) and gets the discretization of the differential operator for the corresponding point via subroutine get_differentialkp3D. The output (for grid point (i,j,k) on the physical grid) consists of the following components:

  1. differentialM(n,8,8)  , n=1..19 is specified as follows:
      1  <-->  (i  , j  , k-1)
      2  <-->  (i  , j-1, k  )
      3  <-->  (i-1, j  , k  )
      4  <-->  (i  , j  , k  )
      5  <-->  (i+1, j  , k  )
      6  <-->  (i  , j+1, k  )
      7  <-->  (i  , j  , k+1)
      8  <-->  (i-1, j-1, k  )
      9  <-->  (i+1, j-1, k  )
     10  <-->  (i-1, j+1, k  )
     11  <-->  (i+1, j+1, k  )
     12  <-->  (i-1, j  , k-1)
     13  <-->  (i+1, j  , k-1)
     14  <-->  (i-1, j  , k+1)
     15  <-->  (i+1, j  , k+1)
     16  <-->  (i  , j-1, k-1)
     17  <-->  (i  , j+1, k-1)
     18  <-->  (i  , j-1, k+1)
     19  <-->  (i  , j+1, k+1)

    So for each of the 19 adjoining grid points on the Physical mesh an 8x8 block is provided which contains the discretized Hamiltonian on this point. For 6x6 k.p theory only the (3:8,3:8) submatrix is valid.
  2. In order to write these elements to the matrix, one must know the position of the points n=1..19 within the numbering in the quantum region (1..dim_qr). This information is provided in coord_in_qrM(1:19).
  3. The component in the vector diag_kpV for the regarded grid point is provided by diag_kp.

In principle the setup is done twice. The first time only the nonvanishing matrix elements are counted in order to be able to allocate the storage space for the sparse matrix. Then the real setup is done.
See the source code for further information (setup_kp_mat).

In 3D,  the subroutine get_differentialkp3D first gets the k.p parameters and then discretizes the differentials.

  • !---------------------------------------------------------------------
     subroutine get_differentialkp3D(iqr,num_qr,bndC,differentialM,coord_in_qrM,diag_kp)
    !---------------------------------------------------------------------
    !
    !
    Input:
    ! iqr -->
    number of point within quantum region num_qr
    !
    ! bndC = 'neu'
    or 'dir'
    !
    !
    Output:
    !
    ! differentialM(1..19,8,8)
    differential
    !
    each point contains a (8x8)-block
    !
    ! coord_in_qrM(1:19)
    contains for each point in differentialM
    !
    the position within the quantum region and the matrix.
    !
    ! coord_in_qrM = 0
    if the corresponding point does not lie within quantum region.
    !
    !
    Note: For 6x6 k.p theory only the (3:8,3:8) submatrix is valid.
    ! diag_kp
    contains the component of diag_kpV for the point iqr.
    !
    !----------------------------------------------------------------------


    First, the routine gets the k.p parameters: call Get_kp_MaterialParameters. (More details here.)
    Then the dicretization routine is called:      call discretize_kp3D
    Note: The hermitian matrix D^{1/2} H D^{1/2} is assembled. D is returned in diag_kp.
  • The differential operator is set up in
    !----------------------------------------------------------------------
    subroutine
    discretize_kp3D(i_phys,j_phys,k_phys,adr,hV,parV, &
                               par_betweenM,neuVL,differentialM)
    !----------------------------------------------------------------------
    !
    ! par_betweenM(1:6,1:14)
    :
    ! -----------------------
    ! par_betweenM(n_par,1:14) <--> n_par=1 (i  ,j  ,k-1)
    !                               n_par=2 (i  ,j-1,k  )
    !                               n_par=3 (i-1,j  ,k  )
    !                               n_par=4 (i+1,j  ,k  )
    !                               n_par=5 (i  ,j+1,k  )
    !                               n_par=6 (i  ,j  ,k+1)
    ! parV(1:19)
    !
    !
    For definiton of parV, par_minV and par_andV see: Information on how to extract information on electronic structure
    !--------------------------------------------
    !
    !
    differential:
    ! --------
    ! differentialM(1..19,8,8)
    !
    each point contains a (8x8)-block
    !
    !--------------------------------------------

    !  neuVL (1) <--> i-
    direction
    !        (2) <--> i+
    direction
    !        (3) <--> j-
    direction
    !        (4) <--> j+
    direction
    !        (5) <--> k-
    direction
    !        (6) <--> k+
    direction
    !
    !  = .TRUE.
    , if Neumann boundary condition in corresponding direction
    !
    !-----------------------------------------------------------------

 

2D

2D is analogous to 3D. See also source code for further information (setup_kp_mat).
Note: Discretization is done in x- and y-plane. kz must be provided via input (1/AA).

In 2D,  the subroutine get_differentialkp2D first gets the k.p parameters and then discretizes the differentials.

The program loops through all grid points of the quantum region (1..dim_qr) and gets the discretization for the corresponding point via subroutine get_differentialkp2D. The output (for grid point (i,j) on the physical grid) consists of the following components (same scheme as in 3D but k-component discarded):

  1. differentialM(n,8,8)  , n=1..19 is specified as follows:
      1  <-->  ---------
      2  <-->  (i  , j-1)
      3  <-->  (i-1, j  )
      4  <-->  (i  , j  )
      5  <-->  (i+1, j  )
      6  <-->  (i  , j+1)
      7  <-->  ---------
      8  <-->  (i-1, j-1)
      9  <-->  (i+1, j-1)
     10  <-->  (i-1, j+1)
     11  <-->  (i+1, j+1)
     12  <-->  ---------
     13  <-->  ---------
     14  <-->  ---------
     15  <-->  ---------
     16  <-->  ---------
     17  <-->  ---------
     18  <-->  ---------
     19  <-->  ---------

    So for each of the 9 (3D: 19) adjoining grid points on the physical mesh an 8x8 block is provided which contains the discretized Hamiltonian on this point. For 6x6 k.p theory only the (3:8,3:8) submatrix is valid.
  • !---------------------------------------------------------------------
     subroutine get_differentialkp2D(iqr,num_qr,k_z,bndC,differentialM, coord_in_qrM,diag_kp)
    !---------------------------------------------------------------------
    !
    !
    discretizes in (i,j) plane, 
    !
    scheme the same as in 3D, k-component discarded
    !
    !--------------------
    ! k_z
    in [1/AA]
    !---------------------------------------------------------------------
  • !---------------------------------------------------------------------
    subroutine discretize_kp2D(i_phys,j_phys,adr,k_z,hV,parV, &
                               par_betweenM,neuVL,differentialM)
    !---------------------------------------------------------------------
    !!
    ! par_betweenM(1:6,1:14)
    :
    !------------------------
    ! par_betweenM(n_par,1:14) <--> n_par=1 ---------
    !                               n_par=2 (i  ,j-1)
    !                               n_par=3 (i-1,j  )
    !                               n_par=4 (i+1,j  )
    !                               n_par=5 (i  ,j+1)
    !                               n_par=6 ---------
    !                 5
    !               3   4
    !                 2
    !
    ! parV(1:19)
    !
    For definiton of parV, par_minV and par_andV see: Information on how to extract information on electronic structure
    !------------------------------------------------------
    !
    !
    differential:
    !--------------
    ! differentialM(1..19,8,8)
    !
    !--------------------------------------------

    ! neuVL (1) <--> i-
    direction
    !       (2) <--> i+
    direction
    !       (3) <--> j-
    direction
    !       (4) <--> j+
    direction
    !       (5) <--> ------------
    !       (6) <--> ------------
    !
    ! = .TRUE.
    , if Neumann boundary conditions in corresponding direction
    !
    !-----------------------------------------------------------------

 

1D

In 1D there is the additional option to assemble a full matrix (instead of a sparse matrix) which can be diagonalized by LAPACK.

The k.p matrix is assembled in the following steps:

  1. Loop through all points in quantum region (1..dim_qr).
  2. Get the k.p parameters for this point via subroutine Get_kp_MaterialParameters.
  3. Get the discretization of the Hamiltonian (differential star): subroutine discretize_kp1D. In 1D, only the two neighboring points are involved in discretization --> so the discretization on point iqr(=1..dim_qr) can be expressed with three 8x8 matrices leftM, onM, rightM. Since the matrix is hermitian, only onM and rightM are provided.
    - Counts off-diagonal elements (upper triangle matrix) different from 0
    - Allocates matrices: either for LAPACK (i.e. full matrix) or for iterative method (i.e. sparse matrix)
    - Does the same loop as under i., ii. and iii. and defines k.p matrix under iv.
  4. Write the nonvanishing matrix elements of onM and rightM to the k.p matrix.

See also source code for further information (setup_kp_mat1D).

  • !-------------------------------------------------------------
     subroutine setup_kp_mat1D(num_qr,bndC,kx,ky,K_z,matrix_formC)
    !-------------------------------------------------------------
    !
    !
    Creates k.p matrix for given region num_reg.
    !
    Arrays in MODULE sparse_matrix must be deallocated.
    !
    !
    Allocates ija, sa in MODULE sparse_matrix if matrix_formC = 'iterat'
    !      kp_lapackM
    in MODULE lapack_mat    if matrix_formC = 'lapack'.
    !
    !
    Note: These must be deallocated afterwards.
    !
    !
    !
    Input:
    ! num_qr =
    number of quantum region
    ! bndC = 'per','dir','neu'
    (Periodic/Dirichlet/Neumann boundary conditions)
    !
    ! kx,ky,K_z
    in AA
    !
    ! matrix_formC = 'lapack'   --> 
    sets up full matrix in MODULE lapack_mat
    !                'iterat'   --> 
    sparse matrix
    !
    !
    Discretization is done in z-direction.
    ! kx,ky
    define the k|| dispersion.
    ! K_z
    is a superlattice vector (which is zero in this version).
    !
    ! norm_kpV (MODULE normalization_factors_sg)
    is used to normalize the wavefunctions.
    !
    !-------------------------------------------------------------

Note: If Arnoldi is used the symmetry is disturbed by adding a number disturb_arnoldi [MODULE numeric_control].

 

The differential operator in discretized form for a given point on the physical grid is provided by subroutine discretize_kp1D in MODULE discretize_kp_1D.

Note: The hermitian matrix D^{1/2} H D^{1/2} is assembled.

First the rotation is updated (see Rotation of k.p Hamiltonian). Then the rotation matrices for crystal-splitting and spin-orbit-coupling are defined. Finally the strain k.p matrix for the considered point is set up:

call update_rotation(adr)
d1=parV(17)
d2=parV(18)
d3=parV(19)
call setup_crysSplit_wurM(d1)
call setup_rotationSpin_wurM(d2,d3)
call set_strain_mat_physgrid_point(i_phys,1,1)

  • !------------------------------------------------------------------------
     subroutine discretize_kp1D(K_z,i_phys,adr,h_imin,h_i,kx,ky,par_minV,parV, &

                                  par_andV,onM,rightM)
    !------------------------------------------------------------------------
    !
    !
    This subroutine discretizes the k.p Hamiltonian for the point i=i_phys.
    ! In any case, the 8x8-Hamiltonian is discretized.
    ! If only 6x6-kp is done, only the part (3:8,3:8) is relevant.
    ! In this version, the superlattice vector K_z is also taken into account.
    ! If no superlattice --> K_z must be 0.
    ! The discretization provides a diagonal (8x8) block [called onM here]
    ! and two off-diagonal (8x8)-blocks.
    ! Since total matrix is hermitian, only the off-diagonal block to the right
    ! [called rightM here] is calculated:
    !
    !             i-1 --- --  hi-1   ----- i ----- hi   -----  i+1
    !             i-1 --- -- h_imin ----- i ----- h_i -----  i+1
    !                       par_minV    parV   par_andV
    !
    !                                      onM            rightM
    !                                     (8x8)            (8x8)
    !
    !
    The Hamiltonian is principally given in the form:
    !
    ! H = RM1 kx2 + RM2 ky2 + RM3 kz2 + RM4 kxky + RM5 kxkz +  RM6 kykz
    !
    !
    The coefficient matrices are assembled in the routines
    !
    ! 1) subroutine put_together(num_diff,coeff_lM,coeff_rM)
    !
    !       num_diff =  1  -->  kx2
    !                =  2  -->  ky2
    !                =  3  -->  kz2
    !                =  4  -->  kxky
    !                =  5  -->  kxkz
    !                =  6  -->  kykz
    !                =  7  -->  constant
    !                =  8  -->  kx
    !                =  9  -->  ky
    !                = 10  -->  kz
    !

    ! Creates corresponding coefficient matrix out of par_andV and par_minV for the specified differential.
    !
    For coeff_lM par_minV is valid, for coeff_rM par_andV.
    !
    ! 2) subroutine put_together_on(num_diff,coeff_M)
    !
    !      num_diff = as above
    !
    !
    Creates corresponding coefficient matrix out of parV for the specified differential.
    !
    !
    Note: put_together and put_together_on are in principal identical.
    !     
    They create the prefactors of the differentials 1..10.
    !     
    For the setup of differentials sometimes the values on the
    !      grid point is used, sometimes the value between two points.
    !      So the values on and between the points are set up
    !      separately and delivered to the subroutines that setup the differentials.
    !
    !     
    It holds the relation (with coeff_iM from put_together_on(i,coeff_iM)
    !     
    and num_diff_i as defined above):
    !
    !      H = sum_i  [ coeff_iM num_diff_i ]    (i
    from 1 to 10)
    !
    !------------------------------------------------------------------------
    !
    !
    For kx,ky the values from input are directly used.
    !
    ! kz
    and kz^2 are discretized:
    !
    ! 1) subroutine set_kzkz(h_imin,h_i,mat_onM,mat_lM,mat_rM,on_1M,right_1M)
    !
    !      
    creates matrizes for differential kzkz
    !      
    on input: mat_lM -> i-1/2  , mat_rM -> i+1/2 , mat_onM -> i
    !              kz -->  -i d/dz
    !
    !       ----------------------------------------------------
    !       (kz)^2 = -d^2/dz^2 + 2K_z kz + K_z^2     (K_z
    superlattice vector,
    !                                                 =0
    if no superlattice
    !       ----------------------------------------------------
    !       mat_onM,mat_lM,mat_rM -->
    input
    !       on_1M,right_1M        -->
    discretized differential
    !
    !      
    Note: The discretization is done for every point of the (8x8) block.
    !
    !2)  subroutine set_kz(h_imin,h_i,mat_onM,mat_lM,mat_rM,on_2M,right_2M)
    !
    !      
    creates matrizes for differential kz
    !      
    on input: mat_lM -> i-1/2  , mat_rM -> i+1/2 , mat_onM -> i
    !              kz    --> -i d/dz
    !
    !       -----------------------------------------------------
    !       kz = -i d/dz + K_z
    !       -----------------------------------------------------
    !
    !       mat_onM,mat_lM,mat_rM -->
    input
    !       on_2M,right_2M        -->
    discretized differential
    !
    !------------------------------------------------------------------------------
    !
    ! onM    -->
    diagonal block
    ! rightM -->
    right block
    !
    ! i_phys
    is point number on physical grid (1..n_1)
    ! adr
    is point number on grid (1..lengvecV).
    !   --> 
    There rotation is checked and strain is updated.
    ! h_imin
    and h_i distance to neighboring point [in AA] !!!!!!
    ! kx,ky,K_z
    in 1/AA   !!!!
    !
    ! par_minV -->
    material parameters between i-1 and i
    ! par_andV -->
    material parameters between i and i+1
    ! parV     -->
    material parameters on i
    !
    ! par_andV( 1) = L1
    ! par_andV( 2) = L2
    ! par_andV( 3) = M1
    ! par_andV( 4) = M2
    ! par_andV( 5) = M3
    ! par_andV( 6) = N1
    ! par_andV( 7) = N2
    ! par_andV( 8) = B1
    ! par_andV( 9) = B2
    ! par_andV(10) = B3
    ! par_andV(11) = P1
    ! par_andV(12) = P2
    ! par_andV(13) = s1
    ! par_andV(14) = s2
    !
    !
    only for parV
    !
    ! parV(15) = E_c
    ! parV(16) = E_v
    ! parV(17) = d1
    ! parV(18) = d2
    ! parV(19) = d3
    !
    !
    For definiton of parV, par_minV and par_andV see: Information on how to extract information on electronic structure
    !
    !
    Units: eV , AA
    !
    ! neumann : flag --> = 0
    (normal: inner point or periodic or Dirichlet boundary)
    !                    = 1
    Neumann boundary condition and left boundary
    !                    = 2
    Neumann boundary condition and right boundary
    !
    !------------------------------------------------------------------------------

 

 

6. Calculate new k.p-states

The subroutine update_kp calculates the new k.p states for the potential phiV, which is provided as input. Additional input is whether to calculate it for electrons (el) or holes (hl) and the number of the quantum region (num_qr). The following steps are carried out:

 

Store potential for k.p parameters

Since the k.p parameters Ec and Ev must include the electrostatic potential, the array phiV in MODULE potentials is taken into account (see subroutine Get_kp_MaterialParameters in How to extract information on electronic structure).

 

Determine energy where to search for electron/hole states

The internal subroutine prepare_det_edge determines the values E_cond and E_val. E_cond is the lowest conduction band value (of Gamma band which is assumed to have band number 1) throughout the quantum region and E_val is the highest valence band value (of band numbers 1 to 3 = hh, lh, split-off hole). From E_cond and E_val the energy level E_separate is calculated.

NOTE: This model treats electrons and holes as strictly separated. This allows to combine different models for electrons and holes. This model fails, however, if E_cond<E_val. Then the program stops. In order to handle such a situation within 8x8 k.p theory a model must be implemented that does not distinguish between electron and hole states.

If n eigenvalues are requested for the conduction band, the subroutine calculate_kp_eigenvalues looks for the n eigenvalues above E_separate (below E_separate for the valence band respectively).

 

Solve eigenvalue problem for k|| = 0

This is done in two steps:

  1. Setup k.p matrix in subroutine setup_kp_mat1D (see Setup of k.p Hamiltonian).
  2. Calculate the specified eigenvalues of that matrix in subroutine calculate_kp_eigenvalues (see Calculate k.p eigenvalues).

 

Determine separation energy (SeparationEnergyEdgeV)  quantum-mechanical <--> classical density anew

Now the separation energy is determined anew in subroutine determine_edge_qm. This subroutine uses pot_condM / pot_valM (el/hl), which are generated by the internal subroutine prepare_det_edge, as input (see Separation models: Eigenvalues <--> Thomas-Fermi).

 

Problem of defining k||

The range of k|| is determined in subroutine determine_range_for_kpar. Input is the energy of the ground eigenstate for k||=0. Furthermore, the maximum energy which separates classical and quantum mechanical treatment (which is known from subroutine determine_edge_qm (which was described in the paragraph above) is provided.

This subroutine is structured as follows:

  1. The Fermi level in the quantum region is retrieved for electrons or holes, respectively.
  2. The energy is calculated up to which the eigenstates contribute to the density. This energy depends on the Fermi level.
  3. The minimum/maximum value (el/hl) of this energy and the separation energy (input) is finally the energy which must be reached by k|| states.
  4. In the following the program employs the bulk dispersion (in directions 100, 010 and 110. Note: Quantization in the rotated system is always in z direction) in order to determine k||. In this algorithm electrons/holes are supposed to have an upward/downward dispersion. If this is not the case due to the k.p parameters, the algorithm fails.

 

Discretize Brillouin zone

The k points are provided by subroutine get_k_par1D. Here the k points are retrieved by calling subroutine get_dim_2D_BZ. Details on it can be found on the documentation of the Brillouin zone integration.
Note: Since subroutine get_dim_2D_BZ discretizes a Brillouin zone with kx and ky varying from 0 to 1.0/AA, the k vectors must be scaled to k_parallel_max accordingly.

 

Redefine number of eigenvalues to be calculated

It may turn out that fewer eigenvalues are necessary for the density calculation than the specified number. Then the number of eigenvalues to be calculated is reduced.

 

Calculate eigenstates for nonvanishing k vectors

This is done in the internal subroutine calculate_kp_par.

 

 

7. Setup of single-band Schrödinger equation including magnetic field

The subroutine setup_sg defines the Schrödinger matrix H' in MODULE sparse_matrix_real (i.e. as a sparse matrix) as well as the diagonal matrix D in diag_sgV (which is stored as a vector) in MODULE normalization_factors_sg. Note that the whole eigenvalue problem is the solution of H:  H=D.H'

The subroutine setup_sg (num_qr,num_sg,num_deg,chargeC,phiV,max_edge) sets up the Schrödinger equation, specified by:

Input:
 num_qr  ...  Number of quantum cluster
 num_sg  ...  In a quantum cluster there are several Schrödinger equations to be solved.
                    First this set of equations can be distinguished with respect to the energy edge
                    which is valid for the corresponding equation.
                     (Gamma, X, L, heavy hole, light hole, split-off hole; including splitting of them due to strain.)
                     -> num_sg  ... specifies the band edge.
 num_deg ... For the same energy edge, there are several Schrödinger equations to be solved
                     with different masses (-> different mass tensor, e.g. due to anisotropic mass tensor) or boundary conditions.
                     -> num_deg ... specifies one of them.

 (See MODULE quantum_models for further explanation.)

 chargeC = 'el' , 'hl'   (electrons or holes)

 phiV    ... Electrostatic potential which is valid for the setup.

As a result, the matrix in MODULE sparse_matrix_real is allocated and contains the Schrödinger matrix.
MODULE normalization_factors_sg: diag_sgV are allocated and defined.

Output:
 max_edge : Minimum (maximum) band edge energy that enters the Schrödinger equation for electrons (holes).
                     --> Eigenvalues lie above (below) max_edge for electrons (holes).

 

The routine basically consists of the following steps:

  1. Loop through all points of the quantum cluster (1..dim_qr).
  2. For each point extract the parameters needed for the setup of the differential at that point (effective masses and band edge):
    CALL get_parameters_for_setup
  3. With these parameters define computational molecule (finite difference scheme):
    CALL set_differentials_sg
    Here:
    valV(1..4)  Matrix elements for the following points (of computational molecule):
    With (i,j,k) the coordinates on the physical grid of the point under consideration, it holds for the components of valV:
      1 --> diagonal (i,j,k)
      2 --> off-diagonal for i+1
      3 --> off-diagonal for j+1
      4 --> off-diagonal for k+1
    coordM(1..4,2) --> Coordinates (row, column) in the matrix for the corresponding matrix elements (1..4) defined above.
  4. Write nonvanishing elements to the matrix.

 

Internal details:

The routine uses several internal auxiliary subroutines:

  1. SUBROUTINE neighbor_points_sg3D:
    Routine that provides grid information on neighboring points.
  2. SUBROUTINE write_sg_masses:
    Effective masses are stored on material grid, for setup of Hamiltonian matrix they are used very often.
    --> Store them on an auxiliary variable:
    mass_sgM(1..dim_qr,1..8,1..3) contains for each point within the quantum cluster (on the physical grid) [1..dim_qr] for all 8 adjoining octants (which can be ascribed to one of material grid points) [1..8] the effective masses in x-,y- and z-direction.
  3. SUBROUTINE get_mass_qr:
    Provides mass for Schrödinger equation between the points num1 and num2.
  4. SUBROUTINE get_parameter_sg:
    Auxiliary to extract parameters (energy edge, effective mass).
  5. FUNCTION octant_in_qr:
    Auxiliary subroutine to decide if adjoining points are within/outside quantum cluster. It is used to handle boundary conditions.
  6. SUBROUTINE set_differentials_sg:
    Only the part of the differential for the upper offdiagonal matrix is assembled since the Hamiltonian is symmetric.

 

1D

In 1D, the Schrödinger equation is solved with LAPACK. Consequently, a full matrix is defined and it is solved immediately. This is done in subroutine SG_1D. The parameters effective mass, energy edge and grid spacing must be provided as input. Eigenvalues and eigenfunctions are provided as output.

 

Details:

Setup of Schrödinger equation

General hints on Schrödinger equation

The data which enter the Schrödinger equation are just from grid points which lie within quantum clusters.
Quantum clusters are primarily defined on material grid.
So on boundary points one has to decide which octants are valid and which are not.
This is done in the internal subroutine octants_in_qr.

 

!==============================================================

The Schrödinger matrix is defined in SUBROUTINE setup_sg.  [In 1D it is defined and solved in SUBROUTINE SG_1D].
The matrix is written to MODULE sparse_matrix_rout_real.

 1) First the masses for the Schrödinger equation are extracted and written to mass_sgM.
     mass_sgM(1..dim_qr,1..8,1..3) contains for each point of the quantum cluster (1..dim_qr) and
     each octant(1..8) the masses in x-,y- and z-direction (1..3).

     This is done in internal SUBROUTINE write_sg_masses:

 dim_qr=dim_qrV(num_qr)                       ! dim_qr Dimension of quantum region
 allocate(mass_sgM(dim_qr,8,3),STAT=istatus)
 if (istatus/=0) stop 'error write_sg_masses: alloc. failed'
 mass_sgM=0.0d0

 num_guent=map_qr_guentherV(num_qr)%number_of_points                 ! num_guent number of  material grid points which defined this quantum region
                                                                  ! note: quantum regions originally defined on material grid
 do ncount=1,num_guent                                                      ! noe loop through all the points

  i_j_k=map_qr_guentherV(num_qr)%guenther_grid_points(ncount)        ! i_j_k position on material grid
  call convert_guenther_to_phys3D(i_j_k,coordM)                                     ! coordM(8,3) contains in (n,1:3) the coordinates (i,j,k) of the 8 (n=1..8) adjoining points
                                                                                                           ! on the Physical grid.
  i=coordM(1,1)
  j=coordM(1,2)                                                                                      ! now treat first of these 8 points: (i,j,k)
  k=coordM(1,3)
  iqr=quantumreg_numM(i,j,k)                                                              ! iqr is position of (i,j,k) in quantum region (1..dim_qr)
  nqr=quantum_gridM(i,j,k)                                                                   ! nqr is number of quantum region, in which point (i,j,k) lies.

  if (nqr==num_qr) then                                                                            ! if this point lies within this quantum region, the corresponding octant (1..8) OF THIS POINT
  select case(chargeC)                                                                            ! is ascribed the corresponding mass
  case('el')
       mass_sgM(iqr,8,:)=def_quantum_modelsM(num_qr)%              &
                               data_sg_elM(num_sg)%mV(num_deg,ncount,:)
  case('hl')
       mass_sgM(iqr,8,:)=def_quantum_modelsM(num_qr)%              &
                               data_sg_hlM(num_sg)%mV(num_deg,ncount,:)
  end select
  end if

   .........            the same for the remaining 7 points

 end do

 !-----------------------------------------------------------------

  do i=1,dim_qr

   if (all(mass_sgM(i,:,:)==0.0d0)) stop 'error write_sg_masses:     &                !  check: if all zero --> internal error
                      & grid point without mass within quantum region'

   if (any(mass_sgM(i,:,:)==0.0d0)) then                                                       ! note: on boundaries there may be octants which do not lie within the quantum region any more
   massV=0.0d0                                                                                        !         then they are ascribed the average masses of the other octants
   su=0
   do n=1,8
    if (any(mass_sgM(i,n,:)/=0.0d0)) then
     su=su+1
     massV=massV+mass_sgM(i,n,:)
    end if
   end do
   massV=massV/dble(su)
   do n=1,8
    if (any(mass_sgM(i,n,:)==0.0d0)) mass_sgM(i,n,:)=massV
   end do
   end if

  end do

===============================================================

2) There are several auxiliary internal subroutines:

2.1)  internal subroutine neighbor_points_sg3D:

!------------------------------------------------------------------------------
 subroutine neighbor_points_sg3D(num_qr,num_sg,num_deg,chargeC,num,num_max, &
                                   i1,i2,j1,j2,k1,k2,hi1,hi2,hj1,hj2,hk1,hk2)
!-----------------------------------------------------------------------------
input:
    num is the position of the mesh point in the whole vector
        of the schroedinger equation,i.e. in 1..dim_qrV
    num_max is the number of mesh points=dim_qrV(num_qr)

    num_sg ... number of schroedinger equation with different energy levels
    num_deg ... number of schroedinger equation which belongs to that
                energy level
    chargeC = 'el' or 'hl'

output:
    positions of the neighboring points in the whole vector (1..dim_qr)
    - one step back in x-direction  : i1 , distance = hi1
    - one step forward in x-direction  : i2 , distance = hi2
    - one step back in y-direction  : j1 , distance = hj1
    - one step forward in y-direction  : j2 , distance = hj2
    - one step back in z-direction  : k1 , distance = hk1
    - one step forward in z-direction  : k2 , distance = hk2

   for boundary points
    - periodic: number of point to which it is connected
    - Neumann   <==> value=0  , then distance =0
    - Dirichlet <==> value=-1 , then distance =0

   it must be:
    num,i1,i2,j1,j2,k1,k2 =< num_max (=dim_qr)
    num >= 1

   problem must be symmetric, i.e.
   if  num=a --> i1=b  then also  num=b --> i2=a    and so on

!-----------------------------------------------------------------

  units: lengths in AA
!------------------------------------------------------------------------

   So the subroutine provides the positions of the adjoining points in the Schrödinger matrix.

2.2) internal subroutine get_mass_qr(num_qr,num1,num2,dirC,mass)

 !------------------------------------------------------------------
 subroutine get_mass_qr(num_qr,num1,num2,dirC,mass)
 !------------------------------------------------------------------
 ! provides mass for schroedinger equation between the points
 ! num1 and num2
 !
 ! input:
 !
 ! num_qr   ...  number of quantum region
 ! dirC     ...  'x','y','z'
 !
 ! output:
 !
 ! mass     ... effective mass in specified direction
 !                  for electrons positive,
 !                  for holes positive
 !
 !-------------------------------------------------------------------

 i1=inv_quantumreg_numM(num_qr,num1,1)
 j1=inv_quantumreg_numM(num_qr,num1,2)
 k1=inv_quantumreg_numM(num_qr,num1,3)

 i2=inv_quantumreg_numM(num_qr,num2,1)
 j2=inv_quantumreg_numM(num_qr,num2,2)                                          !  (i1,j1,k1),(i2,j2,k2) coordinates of corresponding points
 k2=inv_quantumreg_numM(num_qr,num2,3)                                         !   on Physical grid

 indV(1)=i2-i1
 indV(2)=j2-j1
 indV(3)=k2-k1

 if (.not.((any(abs(indV)==1)).and.(sum(abs(indV))==1)))                     &
     stop 'error in subroutine get_mass_qr: in this version just adjoining  points are allowed'

 locV = maxloc(abs(indV))                                          !  locV(1)= 1   -->  num1 --- x-direction --- num2

 select case (locV(1))
  case (1)

        if (dirC/='x') stop 'error get_mass_qr: internal inconsistency'

        l_octV(1)=2                                                                                   ! get corresponding octants
        l_octV(2)=4
        l_octV(3)=6
        l_octV(4)=8

        r_octV(1)=1
        r_octV(2)=3
        r_octV(3)=5
        r_octV(4)=7

        if (indV(1)==-1) then                                                                       ! if i2<i1 swap
            z_octV=r_octV
            r_octV=l_octV
            l_octV=z_octV
        end if

   l_massV = (/ (mass_sgM(num1,l_octV(l),1) , l=1,4 ) /)                              ! get left and right mass
   r_massV = (/ (mass_sgM(num2,r_octV(l),1) , l=1,4 ) /)

   case (2)                                                                                             !  locV(1)= 2   -->  num1 --- y-direction --- num2
                                                                                                           !  locV(1)= 3   -->  num1 --- z-direction --- num2

  ... end select

 !--------------------------------------------

 mass = (sum(l_massV)+sum(r_massV))/8d0                                              ! take the average

 end subroutine get_mass_qr
 !------------------------------------------------------------------

 2.3) internal function octant_in_qr

 !------------------------------------------------------------------
  function octant_in_qr(i,j,k,oct,num_qr) result(inL)
 !------------------------------------------------------------------
 ! --> true if all adjoining points on physical grid are within
 !      quantum region num_qr
 !------------------------------------------------------------------

Note: for point (i,j,k) and oct=1 (i.e. octant i-,j-,k-) the adjoining points are:
         (i-1,j-1,k-1),(i,j-1,k-1),(i-1,j,k-1),(i,j,k-1),(i-1,j-1,k),(i,j-1,k),(i-1,j,k),(i,j,k)

 2.4) internal subroutine get_parameter_sg:

 !-------------------------------------------------------------------
  subroutine get_parameter_sg(iwhich,whereC,num1,num2,val,num_qr, &
                                                     num_sg,chargeC)
 !-------------------------------------------------------------------
 ! input:
 !  iwhich    = 1     ==>   E_edge
 !               = 2     ==>   m_eff_x
 !               = 3     ==>   m_eff_y
 !               = 4     ==>   m_eff_z
 !
 !  whereC    = 'on'  ==> val = value on point with number num1
 !                          in this case num2 is of no importance
 !
 !                 = 'be'  ==> val = value between points
 !                          with number num1 and num2
 !
 ! output:
 !    val    value returned
 !
 ! note:
 !  it always must be :  1 =< num1 , num2 =< n_sg3D
 !
 ! num_qr    ... number of quantum region
 ! chargeC   ... = 'el' or 'hl'
 ! mass is negative for holes !!!
 !
 ! units : eV and electron_mass
 !--------------------------------------------------------------------

   select case(whereC)

   case('on')

   i=inv_quantumreg_numM(num_qr,num1,1)
   j=inv_quantumreg_numM(num_qr,num1,2)
   k=inv_quantumreg_numM(num_qr,num1,3)                            !  (i,j,k) of the point on Physical grid

   select case(chargeC)
   case('el')

    select case(iwhich)
    case(1)
     inVL=.false.
     call get_addresses_from_phys3D(i,j,k,oct1V)                                            ! positions of the eight octants on Stefan's grid
     do n=1,8
      mat=mat_numberV(oct1V(n))                                                            !  material number in numbering on whole device
      num_mat=map_qr_to_materialV(mat,num_qr)                                     ! material number in numbering in quantum region
      num_band=def_quantum_modelsM(num_qr)%data_sg_elM(num_sg)%bandnumV(num_mat)      ! get corresponding band parameter
      vec1V(n)=E_cM(num_band,oct1V(n))/abs(electron_charge)-phiV(oct1V(n))
      inVL(n)=octant_in_qr(i,j,k,n,num_qr)
     end do

     val=0.0d0
     su=0
     do n=1,8                                                                                     ! take the average of all bandedges which are within quantum region
      if (inVL(n)) then
        su=su+1
        val=val+(vec1V(n))
      end if
     end do
     if (su==0) stop 'error  get_parameter_sg: internal error'
     val=val/dble(su)
    case(2,3,4)
     stop 'error in get_parameter_sg: masses just between points implemented'
    case default
     stop 'error in get_parameter_sg: iwhichC wrong'
    end select

    .................
 

  case('be')

  select case(chargeC)
  case('el')

    select case(iwhich)
    case(1)
     stop 'error in get_parameter_sg: energies just on points implemented'
    case(2)
     call get_mass_qr(num_qr,num1,num2,'x',val)
    case(3)
     call get_mass_qr(num_qr,num1,num2,'y',val)
    case(4)
     call get_mass_qr(num_qr,num1,num2,'z',val)
    case default
     stop 'error in get_parameter_sg: iwhichC wrong'
    end select

  ...........        note : mass negative for holes

 2.5) internal subroutine get_parameters_for_setup

  !-----------------------------------------------------------------
  subroutine get_parameters_for_setup(num_qr,num_sg,num_deg,dim_qr,&
                 iqr,chargeC,neighborpointsV,dist_neighborV,kind_of_pointV,&
                                                                                 E_edge,m_effV)
  !------------------------------------------------------------------
  !
  ! input:
  !  num_qr,num_sg,num_deg ... see above
  !  dim_qr ... dimension of quantum region
  !  iqr ...  number of point in quantum region
  !  chargeC='el' , 'hl'
  !
  ! output:
  !  neighborpointsV(1:6) = (i1,i2,j1,j2,k1,k2)
  !  dist_neighborV(1:6) = (hi1,hi2,hj1,hj2,hk1,hk2)  in [AA]
  !  kind_of_pointV(1:3) = 'i' --> inner point
  !                        'n' --> neumann point
  !                        'd' --> dirichlet point
  !                      note: for periodic boundary conditions
  !                            inner point must be specified
  !    note: 1 --> x-direction (i+1)
  !             2 --> y-direction (j+1)
  !             3 --> z-direction (k+1)
  !
  !  E_edge ...  energy in [eV] on (i,j,k)
  !  m_effV(1..6)  effective masses in [electron_mass]
  !                (mi1,mi2,mj1,mj2,mk1,mk2)
  !
  !---------------------------------------------------------
  !
  !                           i1
  !
  !                            |
  !                           hi1 , mi1
  !                            |
  !
  !               j1 -hj1-  (i,j,k)  -hj2- j2
  !                   mj1             mj2 'i'
  !                            |
  !                           hi2 , mi2
  !                            |
  !
  !                            i2 'i'
  !
  !------------------------------------------------------------------

================================================================

 3) internal subroutine set_differentials_sg:

  provides differential (difference star) for Schrödinger equation:
  Note: Matrix is symmetric, so only off-diagonal elements for upper half are calculated

  !------------------------------------------------------------------
  subroutine set_differentials_sg(iqr,neighborpointsV,dist_neighborV,&
                kind_of_pointV,E_edge,m_effV,valV,coordM)
  !------------------------------------------------------------------
  !
  ! input:
  !  iqr .. = row in matrix
  !  neighborpointsV(1:6) = (i1,i2,j1,j2,k1,k2)
  !  dist_neighborV(1:6)   = (hi1,hi2,hj1,hj2,hk1,hk2)  in [AA]
  !  kind_of_pointV(1:3)   = 'i' --> inner point
  !                                   'n' --> neumann point
  !                                    'd' --> dirichlet point
  !    note: for periodic boundary conditions  inner point must be specified
  !    note: 1 --> x-direction (i+1)
  !             2 --> y-direction (j+1)
  !             3 --> z-direction (k+1)
  !
  !  E_edge ...  energy in [eV] on (i,j,k)
  !  m_effV(1..6)  effective masses in [electron_mass]
  !                      (mi1,mi2,mj1,mj2,mk1,mk2)
  !
  ! output:
  !    valV(1..4)
  !                  1 -->  diagonal
  !                  2 -->  off-diagonal for i+1
  !                  3 -->  off-diagonal for j+1
  !                  4 -->  off-diagonal for k+1
  !
  ! coordM(1..4,2) --> (row,column)   of corresponding element
  !------------------------------------------------------------------

 4)  main subroutine:

!-------- define matrix --------------------------------

 call setup_sparse_matrix_real(dim_qr,num_points)

 do iqr=1,dim_qr                                                                                        ! loops through all points

  call get_parameters_for_setup(num_qr,num_sg,num_deg,dim_qr,&              ! gets parameters

          iqr,chargeC,neighborpointsV,dist_neighborV,kind_of_pointV,&
                                                                            E_edge,m_effV)

  call set_differentials_sg(iqr,neighborpointsV,dist_neighborV,&                    ! calculates differentials
                          kind_of_pointV,E_edge,m_effV,valV,coordM)

  do n=1,4                                                                                                ! write matrix elements
     if (valV(n)/=0.0d0) then
      row=coordM(n,1)
      column=coordM(n,2)
      value=valV(n)
      call add_element_sparse_real(row,column,value,.TRUE.)
    end if
  end do

 end do

  !--------- allocate normalization and trafovector ---------------

   allocates and defines diag_sgV in MODULE normalization_factors_sg.

 

 

8. Separation models: Eigenvalues <--> Thomas-Fermi

Separation between quantum and continuum states

The separation energy is the energy level that separates quantized and continuum states. See the glossary for more details.
The information about these models is internally stored in MODULE quantum_solutions.

The separation energy is determined in SUBROUTINE determine_edge_qm.

 

SUBROUTINE determine_edge_qm(dim_qr,potM,num_ev,eigenvalV,chargeC,SeparationEnergyEdgeV,SeparationModelC)

For given potential
- potM(1..dim_qr,1:2) (1D)
- potM(1..dim_qr,1:4) (2D)
- potM(1..dim_qr,1:8) (3D)
and for given eigenvalues eigenvalV(1..num_ev) [where eigenvalV(1..num_ev) are used for the density calculation)], SeparationEnergyEdgeV (i.e. the energy which separates classical and quantum mechanical density) is determined.

chargeC = 'el' --> eigenvaluesV must be given in ascending   order
        = 'hl' --> eigenvaluesV must be given in descending order

potM contains for each point 1..dim_qr the potental value for each octant [potM(.,1:8)] (i.e. the conduction (or valence) band edge minus the electrostatic potential).

   1    2        12   13   14           25    26 ... dim_qr

   *    *   ...  *    *    *     ...    *     *  ... [grid points]


 ----------------|                      |----------- energy = 10.0
                 |                      |
                 |                      |
                 |                      |
                 |----------------------|            energy = 0.0

 potM(1,1)   potM(12,1)              potM(25,1)
  = 10.0        = 10.0                  = 0.0

 potM(1,2)   potM(12,2)              potM(25,2)
  = 10.0         = 0.0                  = 10.0

                potM(13,1)
                   = 0.0

                potM(13,2)
                   = 0.0

Note: Ordering of eigenvalues
         electrons: ev(1) = lowest  eigenvalue
         holes:      ev(1) = highest eigenvalue

SeparationModelC
       ... 'eigenvalue' --> SeparationEnergyEdgeV =
topmost eigenvalue (num_ev)

       ... 'max_energy' --> SeparationEnergyEdgeV = lowest energy point i this region
                                     + SeparationEnergyEdgeV [Here SeparationEnergyEdgeV as input and output.]
                            (for electrons)
       ... 'edge_model' --> SeparationEnergyEdgeV is the highest eigenvalue that is below minimum of conduction band edges
                            on left and right boundary (1D) (for electrons)

For holes accordingly.

More details on these models can be found in the Glossary.

All potentials must have the same units (they are just compared relative to each other).

 

 

9. Calculate eigenvalues of k.p Schrödinger equation

After the matrix has been defined, the k.p eigenvalues are calculated in subroutine calculate_kp_eigenvalues (MODULE mod_calculate_kp_eigenvalues).

Up to now three methods are implemented:

  1. Conjugate gradient method of J. Majewski --> Can only be used for extreme eigenvalues.
    Iterative solver using conjugate gradient method (up to now only for holes in 6x6 k.p).
  2. Arnoldi (ARPACK package)
  3. Jacobi-Davidson (default method)

In 1D, there is additionally LAPACK (which is the default method). The method, which is used, is specified in SchroedingerkpEvSolv1D in MODULE control_numeric.

The eigenstates from the previous calculation are taken as starting values. After calculation the eigenstates are backtransformed and normalized. Finally the sparse matrices are deallocated. In 2D and 3D the situation is analogous but there is no LAPACK any more.

See the source code and the documentation on the eigenvalues for detailed information.

!------------------------------------------------------------------------------------------------------------------------
 subroutine calculate_kp_eigenvalues(dim_qr,num_qr,num_states,E_separate,   &
                                                            chargeC,kind_kpC,eigenvalueV,spinorM)
 !-----------------------------------------------------------------------------------------------------------------------
 !
 ! calculates num_states kp-eigenstates
 ! dim_qr : dimension of quantum region
 ! num_qr ... number of quantum region
 ! kind_kpC = '8x8' or '6x6'
 ! chargeC = 'el' or 'hl'
 !
 ! kind_kpC = '8x8' and chargeC = 'el' : the num_states from E_separate
 !                                       upwards are calculated
 !
 ! kind_kpC = '8x8' and chargeC = 'hl' : the num_states from E_separate
 !                                       downwards are calculated
 !
 ! kind_kpC = '6x6' and chargeC = 'hl' : the num_states maximum states
 !                                       are calculated
 !
 !--------------------------------------------------------------------
 !
 ! uses method specified in  control_numeric: SchroedingerkpEvSolv1D:
 !
 ! - 'lapack'     diagonalizes matrix with lapack
 ! - 'it_jam'     uses diagonlization routine of Jacek:
 !
 !                init=init_itjam
 !                itere_max=itere_stagesV(stage)
 !                eps_itere=epsilon_it_jam
 !                itsub_max=itsub_stagesV(stage)
 !                eps_itsub=epsilon_it_jam
 !                iwrite=iwtite_it_jam
 !
 !                stage and epsilon_it_jam
 !                must be defined in the upper level:
 !                --> level of precision
 !
 !               (thus far only for 6x6 - holes)
 !
 ! - 'arnoldi'   precison = SchroedingerkpResidual1D
 !
 !
 !  SchroedingerkpIterations1D --> number of iterations
 !                                      set to a default value, does not change during
 !                                      simulation
 !  SchroedingerkpResidual1D --> precision for arnoldi method
 !                                               is defined according to precision of outer loop
 !
 !  note: for extremous eigenvalues --> arnoldi with option 'LR' or 'SR'
 !
 !        for inner eigenvalues --> arnoldi with option 'SM'
 !        NOTE: all eigenvalues around E_separate are calculated
 !                   due to H --> H-E_separate[eV]
 !                   This shift is carried out here explicitly.
 !                   For electrons, however, one only needs eigenvalues above this
 !                   edge (holes: below)
 !                   --> number of eigenvalues is multiplied with a factor
 !                        mult_num_ev_arnoldi
 !                        and only the relevant eigenvalues are taken
 !                        mult_num_ev_arnoldi here is just a default value, it
 !                        is specified in each kp-region
 !                        If the calculated eigenvalues are not sufficient,
 !                  -->  mult_num_ev_arnoldi (in the kp-region) is
 !                        increased by add_num_ev_arnoldi, otherwise
 !                        decreased by dec_num_ev_arnoldi
 !                        If the calculated eigenvalues are not sufficient,
 !                        the calculation is started anew
 !                        and again the specified number of eigenvalues is
 !                        calculated ...
 !
 !  note: arnoldi seems to have problems with degeneracies:
 !           -->  disturb_arnoldi is added to diagonal elements of the upper block
 !                 in order to destroy the degeneracies
 !
 !------------------------------------------------------------------------------------------------------------------------

 

 

10. Calculate eigenvalues of single-band Schrödinger equation

1D

In 1D they are calculated immediately after the setup of the Schrödinger matrix in subroutine SG_1D for the given potential phiV (INPUT).

 

2D/3D

The eigenvalues are calculated in subroutine calculate_sg for the given potential phiV (INPUT).
First the sparse matrix is defined: CALL setup_sg).
Then the eigenvalues are calculated.
Up to now three methods are implemented:

  1. Conjugate gradient method of J. Majewski --> Can only be used for extreme eigenvalues.
  2. Arnoldi (ARPACK package)
  3. Jacobi-Davidson (default method)

At the end of this subroutine, the sparse matrices are deallocated.

 

subroutine calculate_sg (num_qr,num_sg,num_deg,chargeC,phiV,num_ev, &
                                  dim_qr,eigenvaluesV,eigenfunctionsM)
!--------------------------------------------------------------------------------------------------------
! solves schroedinger equation in specified region and band
!
! input:
! num_qr ...   number of quantum region
! num_sg ...  number of schroedinger equation 
!                  (refers to those with different bandedges)
! num_deg ... for the specified class of schroedinger equations
!                   (num_deg refers to different masses and boundary conditions 
! chargeC = 'el' or 'hl'
! phiV (1..lengvecV) ... potential
! num_ev ... number of eigenvalues
! dim_qr ... dimension of the quantum region
!
! output:
! eigenvaluesV(1..num_ev) ... eigenvalues [in eV]
! eigenfunctionsM(1..num_ev,1..dim_qr) ... eigenfunctions
! --> in [1/sqrt(AA)]
! scaling factor is taken into account.i.e. wavefunction is scaled with this factor
!
!------------------------------------------------------------------------
!
! note:
!
! - for energy edge on boundaries of quantum region, it holds:
!       if multiple point, only the octant which is within quantum region
!       is valid
!       to decide this the program only accesses quantum_grid3DV
!       but no information of guenther
!      (see setup_sg for detailed information)
!----------------------------------------------------------------------------------------------------------

1)  setup_sg: Setup of Schrödinger matrix.

     Note: The Schroedinger matrix H can be transformed to a symmetric matrix H via
              H = DH ,where D is a positive definite diagonal matrix.
              In setup_sg the matrix H is defined and written as a real, symmetric sparse matrix 
              in MODULE sparse_matrix_rout_real. The diagonal matrix D is stored in diag_sgV in
              MODULE normalization_factors_sg.
              (see also glossary)

 2) The eigenvalue problem D^{1/2} H D^{1/2} is solved : eigenfunctions F
     Up to now, only ARNOLDI method is implemented: eigensolver_real_arpack
     The eigensolver needs a subroutine, which carries out the matrix vector product:
     subroutine matvec_sg_arpack
     The precision and number of iterations are obtained from epsilon_it_arnoldi and
     max_iter_arnoldi 
     in MODULE numeric_control.

 3) The eigenfunctions are backtransformed via Y = D^{1/2} F and are finally normalized.

 

 

11. Environment where these routines are called

All eigenvalues are calculated anew for given potential (phiV as input) in subroutine calculate_eigenstates.

The subroutine carries out following steps:

  1. Loop through all quantum regions.
  2. For each quantum region: Check if k.p is done: get_which_kp.
    If so, calculate k.p states: CALL update_kp.
  3. Now treat single-band Schrödinger equation:
    In each quantum region, the different Schrödinger equations can be split into different sets, for which the same band edge is valid, e.g. the degenerate L-valleys.
    For each set, there are several Schrödinger equations, which share the same band edge, but differ in effective masses and/or boundary conditions.
    --> There are two inner loops: 
              do num_sg=1,num_sg_el
                 do n_deg=1,num_deg
    Now the specified Schrödinger equation is solved: call calculate_sg (calculate_sg1D).
    Finally, the solution is stored in the pointer sgChargeV(1)%qcV(...)%...  in MODULE quantum_solutions.
  4. The energy edge, as described in "8. Separation models: eigenvalues <--> Thomas-Fermi", is determined for single-band states. For k.p states this is done in update_kp.

 

 

   
Last modified: 25-Aug-2009   -   optimised for Microsoft Internet Explorer 7®