lattice_mod.F90 Source File


Contents

Source Code


Source Code

#include "macros.h"

module lattice_mod
    ! this will be the module to mimick oop in fortran and in the
    ! lattice excitation generation implementation in neci.
    ! my plan is to create classes of lattice implementations which
    ! all come from a same base class "lattice"
    ! and maybe also do the same for sites.. where in the AIM then eg.
    ! the bath and impurity sites extend the base site class

    ! for now disable the OneEInts usage, due to circular dependencies over
    ! the sym_mod!
    use constants, only: dp, pi, EPS
    use SystemData, only: twisted_bc, nbasis, basisfn, t_trans_corr_2body, &
                          symmetry, brr, t_input_order, orbital_order, &
                          t_k_space_hubbard, t_trans_corr_hop, &
                          t_new_real_space_hubbard, nEl, tStoquastize
    use input_parser_mod, only: ManagingFileReader_t, TokenIterator_t
    use fortran_strings, only: to_upper, to_lower, to_int, to_realdp
    use util_mod, only: stop_all

    implicit none
    private
    public :: lattice, lattice_deconstructor, aim, aim_deconstructor, sort_unique, &
              lat, determine_optimal_time_step, get_helement_lattice, inside_bz_2d, &
              on_line_2d, epsilon_kvec, init_dispersion_rel_cache, setup_lattice_symmetry

    integer, parameter :: NAME_LEN = 13
    integer, parameter :: sdim = 3

    real(dp), allocatable :: dispersion_rel_cached(:)

    type :: site
        ! the basic site type for my lattice
        ! i guess here i need to store my neighbors and provide functionality
        ! how to get them
        ! and i think i want to store this data contigous in memory to
        ! speed up cache, access
        private
        ! to i want to have an index, which gives me the number?
        ! i might not even need that?
        ! in the mean-time use an index..
        integer :: ind = -1
        integer :: n_neighbors = -1

        ! i think i also want to store the k-vectors of the sites here..
        ! since i will need them if I totally want to switch to my new
        ! implementation and also if i want to deal with the new type of
        ! periodic boundary conditions.
        integer :: k_vec(3) = 0
        ! i also need the real-space coordinates for the hopping
        ! transcorrelation!
        integer :: r_vec(3) = 0

        ! also use one integer to differentiate between the k-vectors!
        ! this makes it easier to access arrays..
        integer :: k_sym = -1

        ! do i also need the inverse k-vec in here??
        integer :: k_inv(3) = 0
        integer :: sym_inv = -1

        ! ah.. here it is important: neighbors are also sites.. is this
        ! possible? and i have to be flexible to allow different types of
        ! site neighbors
        ! and i am not even sure if i want pointers here.. maybe a vector
        ! of neighbor indices is enough and even better..
        ! i cant really make it this way without another type since
        ! fortran does not like arrays of pointers or atleast does not
        ! intepret is as that from the beginning..
        ! but i think this would be perfect or?
        ! recursive types are only allowed in the fortran 2008 standard..
        ! so i have to go over an intermediate type
        ! (which has the advantage to have an array of pointers..
        ! ok i realize this whole shabang is too much..
        ! so just make a list of indices of the neighbors
        integer, allocatable :: neighbors(:)
        ! or can i point to the other sites here? hm..
        ! and maybe i want to store on-site repulsion here too.. lets see

        ! i could just hack into that i have a flag for impurities and
        ! bath sites.. which i do not need for "normal" calculations but
        ! for the AIM..
        logical :: t_impurity = .false.
        logical :: t_bath = .false.

    contains
        private

        procedure :: allocate_neighbors
        procedure :: deallocate_neighbors

        procedure :: get_neighbors => get_neighbors_site
        ! maybe i do not need a initializer?
        ! or maybe yes i do.. i would like to have something like a
        ! with the lattice.. but now i want something optional..
        procedure :: initialize => init_site

        procedure :: set_index
        procedure :: get_index
        procedure :: set_num_neighbors
        procedure :: get_num_neighbors => get_num_neighbors_site
        procedure :: set_neighbors

        procedure :: set_impurity
        procedure :: is_impurity
        procedure :: set_bath
        procedure :: is_bath
        procedure :: set_k_vec
        procedure :: set_r_vec
        ! i could also use finalization routines instead of manually
        ! deallocating everything..
        ! i need atleast gcc4.9.. which i am to lazy to update now..
        ! but will in the future!

    end type site

    ! can i do something like:
    type, extends(site) :: impurity
        private

    contains
        private

    end type impurity

    type, extends(site) :: bath
        private

    contains
        private

    end type bath

    ! maybe make this abstract.. what are the benefits?
    type, abstract :: lattice
        private
        ! base class of lattice
        ! i think i want to try to store all this contigous in memory to
        ! speed up chache access
        integer :: n_sites = -1
        integer :: n_connect_max = -1
        integer :: n_dim = -1
        ! Lookup table for momentum -> site index conversion
        integer, allocatable :: lu_table(:, :, :)
        ! Lookup table for first BZ (contains all sums of up to three momenta)
        logical, allocatable :: bz_table(:, :, :)
        ! size of the lookup tables
        integer :: kmin(sdim) = 0
        integer :: kmax(sdim) = 0
        ! also store an indexing for the real-space vectors
        integer :: r_min(sdim) = 0
        integer :: r_max(sdim) = 0

        ! actually i want to have more flexibility: maybe periodic in x
        ! but not y..
        logical :: t_periodic_x = .true.
        logical :: t_periodic_y = .true.
        logical :: t_periodic(3) = .true.

        logical :: t_bipartite_order = .false.

        ! i want to do a lattice type member also, do easier check, which
        ! lattice we are looking at.. but i need to make this nice
        ! having a defered lenght character component would be the best
        ! option:
        !         character(:), allocatable, public :: type
        ! but this needs gfortran version >= 4.9, which i guess is not
        ! available everywhere yet.. so do smth else for now:
        character(NAME_LEN) :: name = ''
        ! and "just" use trim and align in the comparisons for lattice names!

        ! and also add a flag, if we want a momentum space lattice
        ! representation.
        logical :: t_momentum_space = .false.

        integer :: lat_vec(3, 3) = 0

        ! also store k_vec ..
        integer :: k_vec(3, 3) = 0

        ! initialize the possible applicable basis vectors in the
        ! mapping to the BZ
        integer, allocatable :: basis_vecs(:, :)

        ! i also need a matrix mapping from the k-vectors to the
        ! k-symbols to quickly access them!
        integer, allocatable, public :: k_to_sym(:, :, :)

        ! and vice versa a mapping from the symbol to the k-vector
        ! or i could just use the orbital index? does this work with
        ! neci though?
        ! just use a matrix here and take the rows
        integer, allocatable, public :: sym_to_k(:, :)

        ! and also store a multiplication table in the lattice class..
        ! to make it consistend and store everything necessary in here..
        ! this just make use of the symbols!
        integer, allocatable, public :: mult_table(:, :)

        ! and also use an inverse table, which also just uses the
        ! symbols!
        integer, allocatable, public :: inv_table(:)

        ! and i think additionally i want to store which type of lattice
        ! this is in a string or? so i do not always have to
        ! use the select type functionality
        ! i would need constant expression.. so stick with select type!

        ! and in the end a lattice is a collection of sites
        ! and all the topology could be stored in the connection of the
        ! sites
        ! and i just realized that if i want to use class(lattice)
        ! generally in the whole program, i have to provide all the
        ! functionality already for the lattice.. atleast in a dummy
        ! way.. hm.. maybe there is a better way to do it..
        ! maybe i have to use pointer attribute below to make it possible
        ! to call an constructor of class(type) ..
        ! well this also does not work as i like to have it..
        ! since it is not interpreted as an array of pointer, but as a
        ! pointer to an array of class(sites), so redo this in the end!
        type(site), allocatable :: sites(:)

        ! this is just a small test if we can bring classic procedure
        ! pointers into the game.. but will be removed soon
        procedure(test), pointer :: a => null()

    contains
        private

        ! i think i need some general interface here at the top of the
        ! type definition, so all of those function can get called
        ! but i need specifice ones then for each sub-class
        ! how do i do that?
        procedure :: initialize => init_lattice
        procedure, public :: get_nsites
        procedure, public :: get_ndim
        procedure, public :: get_nconnect_max
        procedure, public :: is_periodic_x
        procedure, public :: is_periodic_y
        procedure(is_periodic_t), public, deferred :: is_periodic
        procedure(get_length_t), public, deferred :: get_length
        procedure, public :: get_site_index
        ! make the get neighbors function public on the lattice level
        procedure, public :: get_neighbors => get_neighbors_lattice
        procedure, public :: get_num_neighbors => get_num_neighbors_lattice
        procedure, public :: get_spinorb_neighbors => get_spinorb_neighbors_lat

        procedure, public :: is_k_space
        ! i definetly also want to have a print function!
        procedure, public :: print_lat
        procedure, public :: add_k_vec
        procedure :: add_k_vec_symbol
        procedure, public :: inv_k_vec
        procedure :: inv_k_vec_symbol
        procedure, public :: get_sym
        procedure, public :: subtract_k_vec
        procedure, public :: get_sym_from_k
        procedure, public :: set_sym

        procedure :: set_name

        procedure, public :: get_name

        ! maybe i want set routines too?
        ! but i guess i want the private, because there is no need of
        ! them being used outside of this module
        ! but this would make it flexible to make these function public
        procedure :: set_nsites
        procedure :: set_ndim
        procedure :: set_nconnect_max
        procedure :: set_periodic
        procedure(set_length_t), deferred :: set_length
        procedure(calc_nsites_t), deferred :: calc_nsites
        procedure :: allocate_sites
        procedure(initialize_sites_t), deferred :: initialize_sites

        procedure :: deallocate_sites

        ! for the k-space implementations also implement a lattice
        ! dependent dispersion relation function
        procedure, public :: dispersion_rel => dispersion_rel_not_implemented
        procedure, public :: dispersion_rel_orb
        procedure, public :: dispersion_rel_spin_orb

        procedure, public :: dot_prod => dot_prod_not_implemented
        procedure, public :: get_k_vec
        procedure, public :: get_r_vec
        procedure, public :: round_sym

        procedure, public :: map_k_vec
        procedure :: inside_bz
        procedure :: inside_bz_explicit
        procedure :: apply_basis_vector

        procedure, public :: get_orb_from_k_vec
        ! and procedures to initialize the site index lookup table and the
        ! matrix element lookup table
        procedure :: initialize_lu_table
        procedure :: fill_bz_table
        procedure :: fill_lu_table
        procedure :: get_lu_table_size
        procedure :: deallocate_caches
        ! actually i should make i deferred: todo
        procedure :: init_basis_vecs
        procedure, public :: init_hop_cache_bounds

    end type lattice

    ! and the plan is than to extend this with the specific lattices

    ! i think it is better to extend lattice directly for the aim
    type, abstract, extends(lattice) :: aim
        private

        integer :: n_imps = -1
        integer :: n_bath = -1

        ! i think i want to store the bath and impurity indices..
        ! thats more efficient!
        integer, allocatable :: impurity_sites(:)
        integer, allocatable :: bath_sites(:)

    contains
        private

        procedure :: set_n_imps
        procedure :: set_n_bath
        procedure :: calc_nsites => calc_nsites_aim

        procedure, public :: is_periodic => is_periodic_aim

        procedure, public :: get_n_imps
        procedure, public :: get_n_bath
        procedure, public :: is_impurity_site
        procedure, public :: is_bath_site

        procedure, public :: get_impurities
        procedure, public :: get_bath

    end type aim

    type, extends(lattice) :: chain
        private

        ! now.. the chain has a concept of length how will i generalize
        ! this.. and can i use this
        integer :: length = -1

    contains
        private

        procedure, public :: get_length => get_length_chain
        procedure, public :: is_periodic => is_periodic_chain

        procedure :: set_length => set_length_chain
        procedure :: calc_nsites => calc_nsites_chain
        procedure :: initialize_sites => init_sites_chain

        procedure, public :: dispersion_rel => dispersion_rel_chain_k
        procedure :: init_basis_vecs => init_basis_vecs_chain

        procedure, public :: dot_prod => dot_prod_chain

    end type chain

    type, extends(lattice) :: cube
        private

        integer :: length(3) = -1

    contains
        private

        procedure, public :: get_length => get_length_cube
        procedure, public :: is_periodic => is_periodic_cube
        procedure, public :: dispersion_rel => dispersion_rel_cube

        procedure :: set_length => set_length_cube
        procedure :: calc_nsites => calc_nsites_cube
        procedure :: initialize_sites => init_sites_cube

    end type cube

    type, extends(lattice) :: rectangle
        private

        ! how to encode the length?
        integer :: length(2) = -1
    contains
        private

        procedure :: init_basis_vecs_rect_base

        procedure, public :: get_length => get_length_rect
        procedure, public :: is_periodic => is_periodic_rect
        procedure, public :: dispersion_rel => dispersion_rel_rect

        procedure :: set_length => set_length_rect
        procedure :: calc_nsites => calc_nsites_rect
        procedure :: initialize_sites => init_sites_rect
        procedure :: init_basis_vecs => init_basis_vecs_rect
        procedure, public :: dot_prod => dot_prod_rect

    end type rectangle

    type, extends(rectangle) :: kagome
        private
    contains
        private

        procedure :: calc_nsites => calc_nsites_kagome
        procedure :: initialize_sites => init_sites_kagome

    end type kagome

    type, extends(rectangle) :: hexagonal
        ! i found a unit cell for the hexagonal lattice. but this is
        ! unfortunately 8 sites big alread.. anyway try it
        private

    contains
        private

        procedure :: calc_nsites => calc_nsites_hexagonal
        procedure :: initialize_sites => init_sites_hexagonal
    end type hexagonal

    type, extends(rectangle) :: triangular
        ! use the length quantitiy and periodicity of the rectangle
        private

    contains
        private

        ! number of sites is also the same! atleast in this definition of
        ! the triangular lattice
        ! so only init_sites must be made new
        procedure :: initialize_sites => init_sites_triangular

    end type triangular

    type, extends(rectangle) :: tilted
        ! can i just extend rectangle and change the boundary conditions?
        private

    contains
        private

        procedure, public :: dispersion_rel => dispersion_rel_tilted
        procedure :: calc_nsites => calc_nsites_tilted
        procedure :: initialize_sites => init_sites_tilted
        procedure :: init_basis_vecs => init_basis_vecs_tilted
        procedure, public :: dot_prod => dot_prod_tilted
    end type tilted

    type, extends(rectangle) :: sujun
        private

    contains
        private
        procedure :: calc_nsites => calc_nsites_sujun
        procedure :: initialize_sites => init_sites_sujun

    end type sujun

    type, extends(rectangle) :: ext_input
        private

    contains
        private

        procedure :: calc_nsites => read_lattice_n_sites
        procedure :: initialize_sites => read_sites

    end type ext_input

    type, extends(rectangle) :: ole
        private

    contains
        private
        procedure, public :: dispersion_rel => dispersion_rel_ole
        procedure :: calc_nsites => calc_nsites_ole
        procedure :: initialize_sites => init_sites_ole
        procedure :: find_periodic_neighbors => find_periodic_neighbors_ole

        procedure :: inside_bz => inside_bz_ole

    end type ole

    ! can i just extend the chain class to make an impurity chain?
    ! argh this is annoying without multiple inheritance..
    type, extends(aim) :: aim_chain
        private

        integer :: length = -1

    contains
        private

        procedure, public :: get_length => get_length_aim_chain

        procedure :: set_length => set_length_aim_chain

        procedure :: initialize_sites => init_sites_aim_chain

    end type aim_chain

    ! also implement a 'star' geometry, especially to deal with the AIM models
    type, extends(lattice) :: star
        private

    contains
        private

        procedure :: calc_nsites => calc_nsites_star
        procedure :: set_length => set_length_star
        procedure :: initialize_sites => init_sites_star

        procedure, public :: get_length => get_length_star
        procedure, public :: is_periodic => is_periodic_star

    end type star

    type, extends(aim) :: aim_star
        private

    contains
        private

        procedure :: set_length => set_length_aim_star
        procedure :: calc_nsites => calc_nsites_aim_star
        procedure :: initialize_sites => init_sites_aim_star

        procedure, public :: is_periodic => is_periodic_aim_star
        procedure, public :: get_length => get_length_aim_star

    end type aim_star

    ! i also want to have a rectangle..
    ! can i store every possibility in the rectangle type?
    ! like also the tilted square lattice ..
    ! the tilted essentially is only different boundary conditions..
    ! the rectangle type would be the basic 2D lattice with 4 nearest
    ! neighbors..
    ! a square would be a special case of a rectangle with lx = ly
    ! and a tilted would be a special case of a rectangle
    ! and a tilted square? is it a special case of tilted or square?
    ! but this is not of concern now.. actually i should finally
    ! implement the rest of this code to actually work in neci!

    ! create the abstract interfaces for the deferred function in the base
    ! abstract type: lattice
    abstract interface

        pure function get_length_t(this, dimen) result(length)
            import :: lattice
            class(lattice), intent(in) :: this
            integer, intent(in), optional :: dimen
            ! fix our self to 3 dimensions.. thats not good i know, but
            ! atleast it gives us more flexibility
            integer :: length
        end function get_length_t

        subroutine set_length_t(this, length_x, length_y, length_z)
            import :: lattice
            class(lattice) :: this
            integer, intent(in) :: length_x, length_y
            integer, intent(in), optional :: length_z
        end subroutine set_length_t

        function calc_nsites_t(this, length_x, length_y, length_z) result(n_sites)
            import :: lattice
            class(lattice) :: this
            integer, intent(in) :: length_x, length_y
            integer, intent(in), optional :: length_z
            integer :: n_sites
        end function calc_nsites_t

        logical pure function is_periodic_t(this, dimen)
            import :: lattice
            class(lattice), intent(in) :: this
            integer, intent(in), optional :: dimen
        end function is_periodic_t

        subroutine initialize_sites_t(this)
            import :: lattice
            class(lattice) :: this
        end subroutine initialize_sites_t

        function dispersion_rel_t(this, k_vec) result(disp)
            use constants, only: dp
            import :: lattice
            class(lattice) :: this
            integer, intent(in) :: k_vec(3)
            real(dp) :: disp
        end function dispersion_rel_t

    end interface

    interface lattice
        procedure lattice_constructor
    end interface

    interface aim
        procedure aim_lattice_constructor
    end interface

    ! can i make an abstract interface for the dummy procedures above?
    ! no not really.. since i would need class(lattice) in the interface
    ! definition, but need the interface above already..or?
    ! maybe i do not have to declare the this argument here..
    ! since it is a proc ptr..
    ! ok.. this works now.. but i dont know for what yet..

    abstract interface
        real function test(this)
            import :: lattice
            class(lattice) :: this
        end function test
    end interface

    interface site
        procedure site_constructor
    end interface

    integer, parameter :: DIM_CHAIN = 1, &
                          DIM_STAR = 1, &
                          N_CONNECT_MAX_CHAIN = 2, &
                          STAR_LENGTH = 1, &
                          DIM_RECT = 2, &
                          DIM_CUBE = 3

    ! define the global lattice class here or? this makes more sense
    class(lattice), pointer :: lat => null()

    abstract interface
        function get_helement_lattice_ex_mat_t(nI, ic, ex, tpar) result(hel)
            import :: dp, nEl
            implicit none
            integer, intent(in) :: nI(nel), ic, ex(2, ic)
            logical, intent(in) :: tpar
            HElement_t(dp) :: hel
        end function get_helement_lattice_ex_mat_t

        function get_helement_lattice_general_t(nI, nJ, ic_ret) result(hel)
            import :: dp, nEl
            implicit none
            integer, intent(in) :: nI(nel), nJ(nel)
            integer, intent(inout), optional :: ic_ret
            HElement_t(dp) :: hel
        end function get_helement_lattice_general_t
    end interface

    procedure(get_helement_lattice_ex_mat_t), pointer, public :: get_helement_lattice_ex_mat => null()
    procedure(get_helement_lattice_general_t), pointer, public :: get_helement_lattice_general => null()

    interface get_helement_lattice
        ! These wrapper functions just exist because of this bug
        ! https://github.com/Fortran-FOSS-Programmers/ford/issues/477
        ! call the pointers in the future directly.
        procedure get_helement_lattice_ex_mat_wrapper
        procedure get_helement_lattice_general_wrapper
    end interface get_helement_lattice

    interface epsilon_kvec
        module procedure epsilon_kvec_vector
        module procedure epsilon_kvec_symmetry
        module procedure epsilon_kvec_orbital
    end interface epsilon_kvec

contains

    subroutine setup_lattice_symmetry
        ! since i need it also in the real-space lattice for the
        ! hopping transcorrelation move the symmetry setup for the
        ! k-spae hubbard model into the lattice_mod
#ifdef DEBUG_
        character(*), parameter :: this_routine = "setup_lattice_symmetry"
#endif
        integer :: i, kmin(3), kmax(3), j, k_i(3), k, l, ind
        ASSERT(associated(lat))

        if (allocated(lat%k_to_sym)) deallocate(lat%k_to_sym)
        if (allocated(lat%sym_to_k)) deallocate(lat%sym_to_k)
        if (allocated(lat%mult_table)) deallocate(lat%mult_table)
        if (allocated(lat%inv_table)) deallocate(lat%inv_table)

        allocate(lat%sym_to_k(lat%get_nsites(), 3))
        allocate(lat%mult_table(lat%get_nsites(), lat%get_nsites()))
        allocate(lat%inv_table(lat%get_nsites()))

        ! i have to setup the symlabels first ofc..
        do i = 1, lat%get_nsites()
            ! and also just encode the symmetry labels as integers, instead of
            ! 2^(k-1), to be able to treat more than 64 orbitals (in the old
            ! implementation, an integer overflow happened in this case!)
            ind = get_spatial(brr(2 * i))

            call lat%set_sym(ind, i)

        end do

        kmin = 0
        kmax = 0
        do i = 1, lat%get_nsites()
            k_i = lat%get_k_vec(i)
            do j = 1, lat%get_ndim()
                if (k_i(j) < kmin(j)) kmin(j) = k_i(j)
                if (k_i(j) > kmax(j)) kmax(j) = k_i(j)
            end do
        end do

        allocate(lat%k_to_sym(kmin(1):kmax(1), kmin(2):kmax(2), kmin(3):kmax(3)))

        lat%k_to_sym = 0

        ! now find the inverses:
        do i = 1, lat%get_nsites()

            ! find the orbital of -k
            j = lat%get_orb_from_k_vec(-lat%get_k_vec(i))

            lat%inv_table(lat%get_sym(i)) = lat%get_sym(j)

            lat%sym_to_k(lat%get_sym(i), :) = lat%get_k_vec(i)

            k_i = lat%get_k_vec(i)

            lat%k_to_sym(k_i(1), k_i(2), k_i(3)) = lat%get_sym(i)

            ! and create the symmetry product of (i) with every other symmetry
            do k = 1, lat%get_nsites()
                ! i just have to add the momenta and map it to the first BZ
                l = lat%get_orb_from_k_vec(lat%get_k_vec(i) + lat%get_k_vec(k))

                lat%mult_table(lat%get_sym(i), lat%get_sym(k)) = lat%get_sym(l)

            end do
        end do

    end subroutine setup_lattice_symmetry

    real(dp) function epsilon_kvec_vector(k_vec)
        ! and actually this function has to be defined differently for
        ! different type of lattices! TODO!
        ! actually i could get rid of this function and directly call
        ! the dispersion relation of the lattice..
        integer, intent(in) :: k_vec(3)
#ifdef DEBUG_
        character(*), parameter :: this_routine = "epsilon_kvec_vector"
#endif

        ASSERT(associated(lat))

        ! i could save the basic lattice vectors for the lattice or even
        ! store the dispersion relation for each lattice type and call it
        ! with a given k-vector?
        ! change that to only access the cached result of the dispersion
        ! relation

        ! it is necessary to call this function only with k_vectors
        ! within the first BZ!!
        epsilon_kvec_vector = dispersion_rel_cached(lat%get_sym_from_k(k_vec))

    end function epsilon_kvec_vector

    real(dp) function epsilon_kvec_symmetry(sym)
        ! access the stored dispersion relation values through the symmetry
        ! symbol associated with a k-vector
        type(symmetry), intent(in) :: sym

        epsilon_kvec_symmetry = dispersion_rel_cached(sym%s)

    end function epsilon_kvec_symmetry

    real(dp) function epsilon_kvec_orbital(orb)
        ! access the stored dispersion relation values through the spatial
        ! orbital (orb)
        integer, intent(in) :: orb

        epsilon_kvec_orbital = dispersion_rel_cached(lat%get_sym(orb))

    end function epsilon_kvec_orbital

    subroutine init_dispersion_rel_cache()
        ! to avoid excessive calls to the cos() function cache the
        ! dispersion relation of the lattice and make them accessible
        ! through the symmetry label associated with the k-vectors!
        character(*), parameter :: this_routine = "init_dispersion_rel_cache"
        integer :: i, sym_min, sym_max, sym

        ASSERT(associated(lat))

        if (allocated(dispersion_rel_cached)) deallocate(dispersion_rel_cached)

        sym_min = 0
        sym_max = 0
        do i = 1, lat%get_nsites()
            sym = lat%get_sym(i)

            if (sym < sym_min) sym_min = sym
            if (sym > sym_max) sym_max = sym
        end do

        allocate(dispersion_rel_cached(sym_min:sym_max), source=0.0_dp)

        do i = 1, lat%get_nsites()
            dispersion_rel_cached(lat%get_sym(i)) = lat%dispersion_rel_orb(i)
        end do

    end subroutine init_dispersion_rel_cache

    subroutine set_sym(this, orb, sym)
        class(lattice) :: this
        integer, intent(in) :: orb, sym

        this%sites(orb)%k_sym = sym

    end subroutine set_sym

    pure function add_k_vec(this, k_1, k_2) result(k_out)
        class(lattice), intent(in) :: this
        integer, intent(in) :: k_1(3), k_2(3)
        integer :: k_out(3)
#ifdef DEBUG_
        character(*), parameter :: this_routine = "add_k_vec"
#endif

        ASSERT(allocated(this%mult_table))
        ASSERT(allocated(this%k_to_sym))

        k_out = this%sym_to_k(this%mult_table( &
                              this%k_to_sym(k_1(1), k_1(2), k_1(3)), &
                              this%k_to_sym(k_2(1), k_2(2), k_2(3))), :)

    end function add_k_vec

    function add_k_vec_symbol(this, sym_1, sym_2) result(sym_out)
        class(lattice) :: this
        integer, intent(in) :: sym_1, sym_2
        integer :: sym_out
#ifdef DEBUG_
        character(*), parameter :: this_routine = "add_k_vec_symbol"
#endif

        ASSERT(allocated(this%mult_table))

        sym_out = this%mult_table(sym_1, sym_2)

    end function add_k_vec_symbol

    function inv_k_vec(this, k) result(k_inv)
        class(lattice) :: this
        integer, intent(in) :: k(3)
        integer :: k_inv(3)
#ifdef DEBUG_
        character(*), parameter :: this_routine = "inv_k_vec"
#endif

        ASSERT(allocated(this%sym_to_k))
        ASSERT(allocated(this%inv_table))
        ASSERT(allocated(this%k_to_sym))

        k_inv = this%sym_to_k(this%inv_table(this%k_to_sym(k(1), k(2), k(3))), :)

    end function inv_k_vec

    pure function get_sym(this, orb) result(sym)
        ! gives the symmetry label associated with the k-vector of
        ! spatial orbital (orb)
        class(lattice), intent(in) ::  this
        integer, intent(in) :: orb
        integer :: sym
        unused_var(this)

        sym = this%sites(orb)%k_sym

    end function get_sym

    pure function get_sym_from_k(this, k) result(sym)
        ! the routine to get the symmetry label associated with the k-vector k
        class(lattice), intent(in) :: this
        integer, intent(in) :: k(3)
        integer :: sym

        sym = this%k_to_sym(k(1), k(2), k(3))

    end function get_sym_from_k

    ! do i also need a inv_k_vec_symbol function?
    function inv_k_vec_symbol(this, sym) result(inv_sym)
        class(lattice) :: this
        integer, intent(in) :: sym
        integer :: inv_sym
#ifdef DEBUG_
        character(*), parameter :: this_routine = "inv_k_vec_symbol"
#endif

        ASSERT(allocated(this%inv_table))

        inv_sym = this%inv_table(sym)

    end function inv_k_vec_symbol

    function subtract_k_vec(this, k_1, k_2) result(k_out)
        class(lattice) :: this
        integer, intent(in) :: k_1(3), k_2(3)
        integer :: k_out(3)
        unused_var(this)

        k_out = this%add_k_vec(k_1, this%inv_k_vec(k_2))

    end function subtract_k_vec

    function get_orb_from_k_vec(this, k_in, spin) result(orb)
        class(lattice) :: this
        integer, intent(in) :: k_in(3)
        integer, intent(in), optional :: spin
        integer :: orb
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_orb_from_k_vec"
#endif
        integer :: i

        ! checking if it is in the first is not necessary anymore
        ! as the lookup table captures more than just the BZ

        ! the naive way would be to loop over all sites and check if the
        ! k-vector fits..
        ! but that would be too effortive, so we use the lookup table

        i = this%lu_table(k_in(1), k_in(2), k_in(3))

        ! and, if required, include the spin in the index
        if (present(spin)) then
            ASSERT(spin == 1 .or. spin == 2)
            if (spin == 1) then
                orb = 2 * i - 1
            else if (spin == 2) then
                orb = 2 * i
            end if
        else
            orb = i
        end if

    end function get_orb_from_k_vec

    elemental function round_sym(this, sym_in) result(sym_out)
        ! routine to map k-vectors outside first BZ back inside
        class(lattice), intent(in) :: this
        type(basisfn), intent(in) :: sym_in
        type(basisfn) :: sym_out

        integer :: k_vec(3)

        ! write a lattice specific routine, which checks if the k-vector is
        ! inside the first BZ of this lattice (which has to be defined by
        ! the input and implemented by me!)
        if (this%inside_bz(sym_in%k)) then
            ! then i have to do nothing
            sym_out = sym_in
        else
            ! otherwise map the k-vector back..
            k_vec = this%map_k_vec(sym_in%k)
            sym_out = sym_in
            sym_out%k = k_vec
        end if

    end function round_sym

    pure function map_k_vec(this, k_in) result(k_out)
        class(lattice), intent(in) :: this
        integer, intent(in) :: k_in(3)
        integer :: k_out(3)

        integer :: i

        k_out = k_in

        if (this%inside_bz(k_in)) then
            k_out = k_in

        else
            ! here i have to do something..
            ! should i store this matrix to setup the lattice within the
            ! lattice class? so i can reuse it here..
            ! or i apply the primitive vectors to the k_vec and check if
            ! a resulting vector lies within the first BZ..
            i = 1
            k_out = k_in
            do while (.not. this%inside_bz(k_out))
                ! apply all possible basis vectors of the lattice
                k_out = this%apply_basis_vector(k_in, i)
                i = i + 1
            end do
        end if

    end function map_k_vec

    logical pure function inside_bz(this, k_vec)
        class(lattice), intent(in) :: this
        integer, intent(in) :: k_vec(3)

        ! this function should also be deferred!

        ! i think with Kais new BZ implementation we can write this function
        ! generally.

        ! I think this should be the approach for most lattices
        ! do a check if we have the bz-ishness of this vector stored
        if (all(k_vec <= this%kmax) .and. all(k_vec >= this%kmin)) then
            inside_bz = this%bz_table(k_vec(1), k_vec(2), k_vec(3))
        else
            ! if not, do the explicit check
            inside_bz = this%inside_bz_explicit(k_vec)
        end if

    end function inside_bz

    logical pure function inside_bz_explicit(this, k_vec)
        class(lattice), intent(in) :: this
        integer, intent(in) :: k_vec(sdim)

        integer :: i

        ! oles lattice is defined by four corner points and two lines
        inside_bz_explicit = .false.

        do i = 1, this%get_nsites()
            if (all(k_vec == this%get_k_vec(i))) inside_bz_explicit = .true.
        end do

    end function inside_bz_explicit

    logical pure function inside_bz_ole(this, k_vec)
        class(ole), intent(in) :: this
        integer, intent(in) :: k_vec(sdim)

        ! I think this should be the approach for most lattices
        ! do a check if we have the bz-ishness of this vector stored
        if (all(k_vec <= this%kmax) .and. all(k_vec >= this%kmin)) then
            inside_bz_ole = this%bz_table(k_vec(1), k_vec(2), k_vec(3))
        else
            ! if not, do the explicit check
            inside_bz_ole = this%inside_bz_explicit(k_vec)
        end if
    end function inside_bz_ole

    logical function inside_bz_chain(this, k_vec)
        class(chain) :: this
        integer, intent(in) :: k_vec(3)

        ! the chain goes as -Lx/2+1, +2, .. Lx/2
        inside_bz_chain = .false.

        if (k_vec(1) >= (-(this%length + 1) / 2 + 1) .and. &
            k_vec(1) <= this%length / 2) inside_bz_chain = .true.

    end function inside_bz_chain


    subroutine init_basis_vecs(this)
        class(lattice) :: this
        character(*), parameter :: this_routine = "init_basis_vecs"

        unused_var(this)
        ! K.G. 25.11.2019: Why is this a runtime check? Should be done compile-time
        call stop_all(this_routine, "this routine should always be deferred!")

    end subroutine init_basis_vecs

    subroutine init_basis_vecs_chain(this)
        class(chain) :: this

        integer :: i, j, k

        if (allocated(this%basis_vecs)) deallocate(this%basis_vecs)

        if (t_trans_corr_2body) then
            j = 2
            allocate(this%basis_vecs(5, 3))
        else
            j = 1
            allocate(this%basis_vecs(3, 3))
        end if

        this%basis_vecs = 0

        k = 0
        do i = -j, j
            k = k + 1
            this%basis_vecs(k, 1) = i * this%get_length(1)
        end do

    end subroutine init_basis_vecs_chain

    subroutine init_basis_vecs_rect(this)
        class(rectangle) :: this

        integer :: l

        if (t_trans_corr_2body) then
            l = 2
        else
            l = 1
        end if

        call this%init_basis_vecs_rect_base(l)
    end subroutine init_basis_vecs_rect

    subroutine init_basis_vecs_tilted(this)
        class(tilted) :: this

        ! Tilted lattices require more basis vectors stored (up to triple application of basis vector)
        call this%init_basis_vecs_rect_base(4)
    end subroutine init_basis_vecs_tilted

    !> Base function for setting up a the basis vector array for rectangular lattices (extracted from the previous init_basis_vecs_rect)
    !> @param[in] l  Maximal number of unit vectors to be combined into a basis vector
    subroutine init_basis_vecs_rect_base(this,l)
        class(rectangle), intent(inout) :: this
        integer, intent(in) :: l

        integer :: i,j,k

        if (allocated(this%basis_vecs)) deallocate(this%basis_vecs)
        allocate(this%basis_vecs((2*l+1)**2,3))
        this%basis_vecs = 0
        k = 0
        do i = -l, l
            do j = -l, l
                k = k + 1
                this%basis_vecs(k, :) = i * this%k_vec(:, 1) + j * this%k_vec(:, 2)
            end do
        end do

    end subroutine init_basis_vecs_rect_base

    pure function apply_basis_vector(this, k_in, ind) result(k_out)
        ! i have to specifically write this for every lattice type..
        class(lattice), intent(in) :: this
        integer, intent(in) :: k_in(3)
        integer, intent(in), optional :: ind
        integer :: k_out(3)
        character(*), parameter :: this_routine = "apply_basis_vector"

        ! i should only make this an abstract interface, since this function
        ! must be deffered!
        ASSERT(ind >= 0)
        ASSERT(ind <= size(this%basis_vecs, 1))

        k_out = k_in + this%basis_vecs(ind, :)

    end function apply_basis_vector

    integer pure function get_length_aim_star(this, dimen)
        class(aim_star), intent(in) :: this
        integer, intent(in), optional :: dimen
        unused_var(this)
        if (present(dimen)) then
            unused_var(dimen)
        end if

        unused_var(this)
        unused_var(dimen)

        get_length_aim_star = STAR_LENGTH

    end function get_length_aim_star

    subroutine set_impurity(this, flag)
        class(site) :: this
        logical, intent(in) :: flag

        this%t_impurity = flag

    end subroutine set_impurity

    subroutine set_bath(this, flag)
        class(site) :: this
        logical, intent(in) :: flag

        this%t_bath = flag

    end subroutine set_bath

    logical function is_impurity(this)
        class(site) :: this

        is_impurity = this%t_impurity

    end function is_impurity

    logical function is_bath(this)
        class(site) :: this

        is_bath = this%t_bath

    end function is_bath

    subroutine init_sites_aim_chain(this)
        class(aim_chain) :: this
        character(*), parameter :: this_routine = "init_sites_aim_chain"
        ! now this is the important routine..

        integer :: i

        if (this%get_nsites() < 2) then
            call stop_all(this_routine, &
                          "less than 2 sites!")
        end if
        ! for the chain we should assert that we only have one impurity!
        if (this%get_n_imps() > 1) then
            call stop_all(this_routine, "more than one impurity!")
        end if

        ! the first site is the impurity!
        this%sites(1) = site(1, 1, [2], site_type='impurity')

        ! the bath sites are connected within each other, but not periodic!
        do i = 1, this%get_n_bath() - 1
            this%sites(i + 1) = site(i + 1, 2, [i, i + 2], site_type='bath')
        end do

        ! and the last bath site only has one neighbor
        this%sites(this%get_nsites()) = site(this%get_nsites(), 1, &
                                             [this%get_nsites() - 1], site_type='bath')

    end subroutine init_sites_aim_chain

    function calc_nsites_aim(this, length_x, length_y, length_z) result(n_sites)
        class(aim) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_aim"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(this)
        unused_var(length_z)

        ! for AIM systems assume first length input is number of impurity
        ! sites and bath sites are number of path sites per impurity!!
        if (length_x <= 0) then
            call stop_all(this_routine, &
                          "incorrect aim_sites input <= 0!")
        end if
        if (length_y <= 0) then
            call stop_all(this_routine, &
                          "incorrect bath_sites input <= 0!")
        end if

        n_sites = length_x * length_y + length_x

    end function calc_nsites_aim

    logical function is_bath_site(this, ind)
        class(aim) :: this
        integer, intent(in) :: ind
        character(*), parameter :: this_routine = "is_bath_site"

        ASSERT(ind > 0)
        ASSERT(ind <= this%get_nsites())

        is_bath_site = this%sites(ind)%is_bath()

    end function is_bath_site

    logical function is_impurity_site(this, ind)
        class(aim) :: this
        integer, intent(in) :: ind
        character(*), parameter :: this_routine = "is_impurity_site"

        ASSERT(ind > 0)
        ASSERT(ind <= this%get_nsites())

        is_impurity_site = this%sites(ind)%is_impurity()

    end function is_impurity_site

    function get_bath(this) result(bath_sites)
        class(aim) :: this
        integer :: bath_sites(this%n_bath)

        bath_sites = this%bath_sites

    end function get_bath

    function get_impurities(this) result(imp_sites)
        class(aim) :: this
        integer :: imp_sites(this%n_imps)

        ! i guees it is better to store the impurity and the bath
        ! indices
        imp_sites = this%impurity_sites
    end function get_impurities

    subroutine set_n_imps(this, n_imps)
        class(aim) :: this
        integer, intent(in) :: n_imps
        character(*), parameter :: this_routine = "set_n_imps"

        ASSERT(n_imps > 0)

        this%n_imps = n_imps

    end subroutine set_n_imps

    integer function get_n_imps(this)
        class(aim) :: this

        get_n_imps = this%n_imps

    end function get_n_imps

    subroutine set_n_bath(this, n_bath)
        class(aim) :: this
        integer, intent(in) :: n_bath
        character(*), parameter :: this_routine = "set_n_bath"

        ASSERT(n_bath > 0)

        this%n_bath = n_bath

    end subroutine set_n_bath

    integer function get_n_bath(this)
        class(aim) :: this

        get_n_bath = this%n_bath

    end function get_n_bath

    logical function is_k_space(this)
        class(lattice) :: this

        is_k_space = this%t_momentum_space

    end function is_k_space

    ! for the beginning set the aim periodicity to false all the time!
    logical pure function is_periodic_aim(this, dimen)
        class(aim), intent(in) :: this
        integer, intent(in), optional :: dimen
        unused_var(this)
        if (present(dimen)) then
            unused_var(dimen)
        end if

        unused_var(this)
        unused_var(dimen)
        ! this function should never get called with dimension input or?
        is_periodic_aim = .false.

    end function is_periodic_aim

    subroutine set_num_neighbors(this, n_neighbors)
        class(site) :: this
        integer, intent(in) :: n_neighbors

        this%n_neighbors = n_neighbors

    end subroutine set_num_neighbors

    pure integer function get_num_neighbors_site(this)
        class(site), intent(in) :: this

        get_num_neighbors_site = this%n_neighbors

    end function get_num_neighbors_site

    integer function get_site_index(this, ind)
        ! for now.. since i have not checked how efficient this whole
        ! data-structure is to it in the nicest, safest way.. until i profile!
        class(lattice) :: this
        integer, intent(in) :: ind
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_site_index"
#endif

        ASSERT(ind <= this%get_nsites())
        ASSERT(allocated(this%sites))

        get_site_index = this%sites(ind)%get_index()

    end function get_site_index

    subroutine init_sites_aim_star(this)
        class(aim_star) :: this
        character(*), parameter :: this_routine = "init_sites_aim_star"

        integer :: i

        if (this%get_nsites() < 2) then
            call stop_all(this_routine, &
                          "something went wrong: less than 2 sites!")
        end if

        if (this%get_n_imps() > 1) then
            call stop_all(this_routine, "more than one impurity not yet implemented!")
        end if

        ! do the first impurity
        ! the impurity is connected to all the bath sites!
        this%sites(1) = site(1, this%get_n_bath(), [(i, i=2, this%get_nsites())], &
                             site_type='impurity')

        ! and all the bath sites are just connected to the impurity
        do i = 2, this%get_nsites()
            this%sites(i) = site(i, 1, [1], site_type='bath')
        end do

    end subroutine init_sites_aim_star

    subroutine init_sites_star(this)
        ! create the lattice structure of the star geometry..
        ! with one special pivot site with index 1, which is connected
        ! to all the other sites and the other sites are just connected to
        ! the pivot site!
        class(star) :: this
        character(*), parameter :: this_routine = "init_sites_star"

        integer :: i

        if (this%get_nsites() <= 0) then
            call stop_all(this_routine, &
                          "something went wrong: negative or 0 number of sites!")
        else if (this%get_nsites() == 1) then
            this%sites(1) = site(ind=1, n_neighbors=0, neighbors=[integer ::])

        else
            ! first to the special pivot site in the middle of the star
            this%sites(1) = site(ind=1, n_neighbors=this%get_nconnect_max(), &
                                 neighbors=[(i, i=2, this%get_nsites())])

            ! and all the others are just connected to the pivot
            do i = 2, this%get_nsites()
                this%sites(i) = site(ind=i, n_neighbors=1, neighbors=[1])
            end do
        end if

    end subroutine init_sites_star

    subroutine init_sites_chain(this)
        class(chain) :: this

        integer :: i, vec(3), j, n
        ! ok what exactly do i do here??
        ! and i think i want to write a constructor for the sites.. to do
        ! nice initialization for AIM sites etc.

        ! do i insist that other stuff is already set in the lattice type?
        ! or do i take it as input here?
        ! i think i inisist that it is set already!
        ! and i have to deal with the edge case of a one-sited lattice

        ! but how to i deal with the geometry here..
        ! i know it is a chain and i know how many sites and i know if it
        ! is periodic or not..
        ! so just create the "lattice"
        ! 1 - 2 - 3 - 4 - 5 - ...
        ! maybe us an associate structure here to not always type so much.

        ! deal with the first and last sites specifically!
        ! this initializes the site class with the index dummy variable:
        ! ok.. fortran does not really like arrays of pointers..
        ! i could make a workaround with yet another type or do i more
        ! explicit here ..

        if (this%t_bipartite_order) then
            if (t_input_order) then
                n = this%get_nsites()
                if (this%is_periodic()) then
                    vec = [-(this%length + 1) / 2 + orbital_order(1), 0, 0]
                    this%sites(orbital_order(1)) = site(orbital_order(1), 2, &
                        [orbital_order(n), orbital_order(2)], vec, vec)

                    vec = [this%length / 2, 0, 0]
                    this%sites(orbital_order(n)) = site(orbital_order(n), 2, &
                        [orbital_order(n-1), orbital_order(1)], vec, vec)


                else
                    vec = [-(this%length + 1) / 2 + orbital_order(1), 0, 0]
                    this%sites(orbital_order(1)) = site(orbital_order(1), 1, &
                        [orbital_order(2)], vec, vec)

                    vec = [this%length / 2, 0, 0]
                    this%sites(orbital_order(n)) = site(orbital_order(n), 1, &
                        [orbital_order(n-1)], vec, vec)

                end if

                ! and do the rest inbetween which is always the same
                do i = 2, this%get_nsites() - 1

                    vec = [-(this%length + 1) / 2 + orbital_order(i), 0, 0]
                    ! if periodic and first or last: already dealt with above
                    this%sites(orbital_order(i)) = site(orbital_order(i), &
                        N_CONNECT_MAX_CHAIN, [orbital_order(i-1),orbital_order(i + 1)], vec, vec)

                end do


            else
                if (this%is_periodic()) then

                    ! use more concise site contructors!
                    ! also encode k- and real-space vectors.. i have to get this right!
                    vec = [-(this%length + 1) / 2 + 1, 0, 0]

                    this%sites(1) = site(1, 2, &
                        [this%get_nsites(), this%get_nsites()/2 + 1], vec, vec)

                    vec = [this%length / 2, 0, 0]
                    this%sites(this%get_nsites()) = site(this%get_nsites(), 2, &
                                             [this%get_nsites()/2, 1], vec, vec)

                else
                    ! open boundary conditions:
                    ! first site:
                    this%sites(1) = site(1, 1, [this%get_nsites()/2 + 1], &
                                         [-(this%length + 1) / 2 + 1, 0, 0])

                    ! last site:
                    this%sites(this%get_nsites()) = site(this%get_nsites(), 1, &
                                         [this%get_nsites()/2], [this%length / 2, 0, 0])

                end if

                ! and do the rest inbetween which is always the same
                do i = 2, this%get_nsites()/2

                    vec = [-(this%length + 1) / 2 + 2 * i - 1, 0, 0]
                    ! if periodic and first or last: already dealt with above
                    this%sites(i) = site(i, N_CONNECT_MAX_CHAIN, &
                        [this%get_nsites()/2 + i - 1, this%get_nsites()/2 + i], vec, vec)

                end do

                j = 1
                do i = this%get_nsites()/2 + 1, this%get_nsites() - 1
                    vec = [-(this%length + 1) / 2 + 2*j , 0, 0]
                    ! if periodic and first or last: already dealt with above
                    this%sites(i) = site(i, N_CONNECT_MAX_CHAIN, &
                        [j, j + 1], vec, vec)

                    j = j + 1
                end do
            end if
        else
            if (this%get_nsites() == 1) then
                this%sites(1) = site(ind=1, n_neighbors=0, neighbors=[integer ::], &
                                     k_vec=[0, 0, 0], r_vec=[0, 0, 0])
                return
            end if

            if (this%is_periodic()) then
                ! use more concise site contructors!
                ! also encode k- and real-space vectors.. i have to get this right!
                vec = [-(this%length + 1) / 2 + 1, 0, 0]

                this%sites(1) = site(1, 2, [this%get_nsites(), 2], vec, vec)

                vec = [this%length / 2, 0, 0]
                this%sites(this%get_nsites()) = site(this%get_nsites(), 2, &
                                         [this%get_nsites() - 1, 1], vec, vec)

            else
                ! open boundary conditions:
                ! first site:
                this%sites(1) = site(1, 1, [2], &
                                     [-(this%length + 1) / 2 + 1, 0, 0])

                ! last site:
                this%sites(this%get_nsites()) = site(this%get_nsites(), 1, &
                             [this%get_nsites() - 1], [this%length / 2, 0, 0])

            end if

            ! and do the rest inbetween which is always the same
            do i = 2, this%get_nsites() - 1

                vec = [-(this%length + 1) / 2 + i, 0, 0]
                ! if periodic and first or last: already dealt with above
                this%sites(i) = site(i, N_CONNECT_MAX_CHAIN, [i - 1, i + 1], vec, vec)

            end do
        end if

    end subroutine init_sites_chain

    subroutine init_sites_cube(this)
        class(cube) :: this
        character(*), parameter :: this_routine = "init_sites_cube"
        integer :: temp_array(this%length(1), this%length(2), this%length(3))
        integer :: i, x, y, z, temp_neigh(6)
        integer :: up(this%length(1), this%length(2), this%length(3))
        integer :: down(this%length(1), this%length(2), this%length(3))
        integer :: left(this%length(1), this%length(2), this%length(3))
        integer :: right(this%length(1), this%length(2), this%length(3))
        integer :: in(this%length(1), this%length(2), this%length(3))
        integer :: out(this%length(1), this%length(2), this%length(3))
        integer, allocatable :: neigh(:)

        ! minumum cube size:
        ASSERT(this%get_nsites() >= 8)

        ! encode the lattice with the fortran intrinsic ordering of matrices
        temp_array = reshape([(i, i=1, this%get_nsites())], &
                             this%length)

        up = cshift(temp_array, -1, 1)
        down = cshift(temp_array, 1, 1)
        right = cshift(temp_array, 1, 2)
        left = cshift(temp_array, -1, 2)
        in = cshift(temp_array, -1, 3)
        out = cshift(temp_array, 1, 3)

        if (this%is_periodic()) then
            do i = 1, this%get_nsites()
                ! find conversion to 3D matrix indices..
                ! i am not yet sure about that: but seems to work
                x = mod(i - 1, this%length(1)) + 1
                z = (i - 1) / (this%length(1) * this%length(2)) + 1
                y = mod((i - 1) / this%length(1), this%length(2)) + 1

                temp_neigh = [up(x, y, z), down(x, y, z), left(x, y, z), right(x, y, z), &
                              in(x, y, z), out(x, y, z)]

                neigh = sort_unique(temp_neigh)

                this%sites(i) = site(i, size(neigh), neigh)

                deallocate(neigh)

            end do
        else
            call stop_all(this_routine, &
                          "closed boundary conditions not yet implemented for cubic lattice!")
        end if

    end subroutine init_sites_cube

    subroutine init_sites_kagome(this)
        ! the unit cell of the kagome i use is:
        ! ___
        ! \./  which is encoded as 1 - 4
        ! /_\                      2/- 5
        !    \                     3/  6
        !
        ! see below how this is implemented specifically!
        class(kagome) :: this
        integer, allocatable :: order(:)
        integer :: i
        character(*), parameter :: this_routine = "init_sites_kagome"

        allocate(order(this%get_nsites()), source = 0)

        if (t_input_order .and. this%t_bipartite_order) then
            order = orbital_order
        else
            order = [(i, i = 1, this%get_nsites())]
        end if


        ! and i think i will for the 6-site, 1x2 and 2x1 12 site and 2x2 24-site
        ! hard encode the neighbors and stuff because this seems to be a pain
        ! in the ass!

        if (this%is_periodic()) then
            if (this%length(1) == 1 .and. this%length(2) == 1) then
                ! the smallest cluster
                this%sites(order(1)) = site(order(1), 3, order([2, 4, 6]))
                this%sites(order(2)) = site(order(2), 4, order([1, 3, 4, 5]))
                this%sites(order(3)) = site(order(3), 3, order([2, 5, 6]))
                this%sites(order(4)) = site(order(4), 3, order([1, 2, 6]))
                this%sites(order(5)) = site(order(5), 3, order([2, 3, 6]))
                this%sites(order(6)) = site(order(6), 4, order([1, 3, 4, 5]))

            else if (this%length(1) == 1 .and. this%length(2) == 2) then
                ! the 1x2 cluster with 12-sites:
                this%sites(1) = site(1, 4, [2, 4, 10, 12])
                this%sites(2) = site(2, 4, [1, 3, 4, 5])
                this%sites(3) = site(3, 4, [2, 5, 11, 12])
                this%sites(4) = site(4, 4, [1, 2, 6, 7])
                this%sites(5) = site(5, 4, [2, 3, 6, 9])
                this%sites(6) = site(6, 4, [4, 5, 7, 9])
                this%sites(7) = site(7, 4, [4, 6, 8, 10])
                this%sites(8) = site(8, 4, [7, 9, 10, 11])
                this%sites(9) = site(9, 4, [5, 6, 8, 11])
                this%sites(10) = site(10, 4, [1, 7, 8, 12])
                this%sites(11) = site(11, 4, [3, 8, 9, 12])
                this%sites(12) = site(12, 4, [1, 3, 10, 11])

            else if (this%length(1) == 2 .and. this%length(2) == 1) then
                ! the 2x1 12-site cluster:
                this%sites(1) = site(1, 3, [2, 4, 12])
                this%sites(2) = site(2, 4, [1, 3, 4, 5])
                this%sites(3) = site(3, 3, [2, 5, 6])
                this%sites(4) = site(4, 3, [1, 2, 12])
                this%sites(5) = site(5, 3, [2, 3, 6])
                this%sites(6) = site(6, 4, [3, 5, 7, 10])
                this%sites(7) = site(7, 3, [6, 8, 10])
                this%sites(8) = site(8, 4, [7, 9, 10, 11])
                this%sites(9) = site(9, 3, [8, 11, 12])
                this%sites(10) = site(10, 3, [6, 7, 8])
                this%sites(11) = site(11, 3, [8, 9, 12])
                this%sites(12) = site(12, 4, [1, 4, 9, 11])

            else if (this%length(1) == 2 .and. this%length(2) == 2) then
                ! the 2x2 24-site cluster.. puh.. thats a lot to do..
                this%sites(1) = site(1, 4,   [2, 4, 10, 24])
                this%sites(2) = site(2, 4,   [1, 3,  4,  5])
                this%sites(3) = site(3, 4,   [2, 5, 11, 12])
                this%sites(4) = site(4, 4,   [1, 2,  7, 18])
                this%sites(5) = site(5, 4,   [2, 3,  6,  9])
                this%sites(6) = site(6, 4,   [5, 9, 16, 19])
                this%sites(7) = site(7, 4,   [4, 8, 10, 18])
                this%sites(8) = site(8, 4,   [7, 9, 10, 11])
                this%sites(9) = site(9, 4,   [5, 6,  8, 11])
                this%sites(10) = site(10, 4, [1,  7, 8, 24])
                this%sites(11) = site(11, 4, [3,  8, 9, 12])
                this%sites(12) = site(12, 4, [3, 11,13, 22])
                this%sites(13) = site(13, 4, [12,14,16, 22])
                this%sites(14) = site(14, 4, [13,15,16, 17])
                this%sites(15) = site(15, 4, [14,17,23, 24])
                this%sites(16) = site(16, 4, [6, 13,14, 19])
                this%sites(17) = site(17, 4, [14,15,18, 21])
                this%sites(18) = site(18, 4, [4, 7, 17, 21])
                this%sites(19) = site(19, 4, [6, 16,20, 22])
                this%sites(20) = site(20, 4, [19,21,22, 23])
                this%sites(21) = site(21, 4, [17,18,20, 23])
                this%sites(22) = site(22, 4, [12,13,19, 20])
                this%sites(23) = site(23, 4, [15,20,21, 24])
                this%sites(24) = site(24, 4, [1, 10,15, 23])

            else if (this%length(1) == 3 .and. this%length(2) == 2) then
                ! the 3x2x6 36-site cluster
                this%sites( 1) = site( 1, 4, [ 2, 4,16,36])
                this%sites( 2) = site( 2, 4, [ 1, 3, 4, 5])
                this%sites( 3) = site( 3, 4, [ 2, 5,17,18])
                this%sites( 4) = site( 4, 4, [ 1, 2, 7,24])
                this%sites( 5) = site( 5, 4, [ 2, 3, 6, 9])
                this%sites( 6) = site( 6, 4, [ 5, 9,22,25])
                this%sites( 7) = site( 7, 4, [ 4, 8,10,24])
                this%sites( 8) = site( 8, 4, [ 7, 9,10,11])
                this%sites( 9) = site( 9, 4, [ 5, 6, 8,11])
                this%sites(10) = site(10, 4, [ 7, 8,13,30])
                this%sites(11) = site(11, 4, [ 8, 9,12,15])
                this%sites(12) = site(12, 4, [11,15,28,31])
                this%sites(13) = site(13, 4, [10,14,16,36])
                this%sites(14) = site(14, 4, [13,15,16,17])
                this%sites(15) = site(15, 4, [11,12,14,17])
                this%sites(16) = site(16, 4, [ 1,13,14,36])
                this%sites(17) = site(17, 4, [ 3,14,15,18])
                this%sites(18) = site(18, 4, [ 3,17,19,34])
                this%sites(19) = site(19, 4, [18,20,22,34])
                this%sites(20) = site(20, 4, [19,21,22,23])
                this%sites(21) = site(21, 4, [20,23,35,36])
                this%sites(22) = site(22, 4, [ 6,19,20,25])
                this%sites(23) = site(23, 4, [20,21,24,27])
                this%sites(24) = site(24, 4, [ 4, 7,23,27])
                this%sites(25) = site(25, 4, [ 6,22,26,28])
                this%sites(26) = site(26, 4, [25,27,28,29])
                this%sites(27) = site(27, 4, [23,24,26,29])
                this%sites(28) = site(28, 4, [12,25,26,31])
                this%sites(29) = site(29, 4, [26,27,30,33])
                this%sites(30) = site(30, 4, [10,13,29,33])
                this%sites(31) = site(31, 4, [12,28,32,34])
                this%sites(32) = site(32, 4, [31,33,34,35])
                this%sites(33) = site(33, 4, [29,30,32,35])
                this%sites(34) = site(34, 4, [18,19,31,32])
                this%sites(35) = site(35, 4, [21,32,33,36])
                this%sites(36) = site(36, 4, [ 1,16,21,35])

            else if (this%length(1) == 4 .and. this%length(2) == 2) then
                ! the 4x2x6 48-site cluster
                this%sites( 1) = site( 1, 4, [ 2, 4,22,48])
                this%sites( 2) = site( 2, 4, [ 1, 3, 4, 5])
                this%sites( 3) = site( 3, 4, [ 2, 5,23,24])
                this%sites( 4) = site( 4, 4, [ 1, 2, 7,30])
                this%sites( 5) = site( 5, 4, [ 2, 3, 6, 9])
                this%sites( 6) = site( 6, 4, [ 5, 9,28,31])
                this%sites( 7) = site( 7, 4, [ 4, 8,10,30])
                this%sites( 8) = site( 8, 4, [ 7, 9,10,11])
                this%sites( 9) = site( 9, 4, [ 5, 6, 8,11])
                this%sites(10) = site(10, 4, [ 7, 8,13,36])
                this%sites(11) = site(11, 4, [ 8, 9,12,15])
                this%sites(12) = site(12, 4, [11,15,34,37])
                this%sites(13) = site(13, 4, [10,14,16,36])
                this%sites(14) = site(14, 4, [13,15,16,17])
                this%sites(15) = site(15, 4, [11,12,14,17])
                this%sites(16) = site(16, 4, [13,14,19,42])
                this%sites(17) = site(17, 4, [14,15,18,21])
                this%sites(18) = site(18, 4, [17,21,40,43])
                this%sites(19) = site(19, 4, [16,20,22,42])
                this%sites(20) = site(20, 4, [19,21,22,23])
                this%sites(21) = site(21, 4, [17,18,20,23])
                this%sites(22) = site(22, 4, [ 1,19,20,48])
                this%sites(23) = site(23, 4, [ 3,20,21,24])
                this%sites(24) = site(24, 4, [ 3,23,25,46])
                this%sites(25) = site(25, 4, [24,26,28,46])
                this%sites(26) = site(26, 4, [25,27,28,29])
                this%sites(27) = site(27, 4, [26,29,47,48])
                this%sites(28) = site(28, 4, [ 6,25,26,31])
                this%sites(29) = site(29, 4, [26,27,30,33])
                this%sites(30) = site(30, 4, [ 4, 7,29,33])
                this%sites(31) = site(31, 4, [ 6,28,32,34])
                this%sites(32) = site(32, 4, [31,33,34,35])
                this%sites(33) = site(33, 4, [29,30,32,35])
                this%sites(34) = site(34, 4, [12,31,32,37])
                this%sites(35) = site(35, 4, [32,33,36,39])
                this%sites(36) = site(36, 4, [10,13,35,39])
                this%sites(37) = site(37, 4, [12,34,38,40])
                this%sites(38) = site(38, 4, [37,39,40,41])
                this%sites(39) = site(39, 4, [35,36,38,41])
                this%sites(40) = site(40, 4, [18,37,38,43])
                this%sites(41) = site(41, 4, [38,39,42,45])
                this%sites(42) = site(42, 4, [16,19,41,45])
                this%sites(43) = site(43, 4, [18,40,44,46])
                this%sites(44) = site(44, 4, [43,45,46,47])
                this%sites(45) = site(45, 4, [41,42,44,47])
                this%sites(46) = site(46, 4, [24,25,43,44])
                this%sites(47) = site(47, 4, [27,44,45,48])
                this%sites(48) = site(48, 4, [ 1,22,27,47])

            else
                call stop_all(this_routine, &
                              "only 1x1,1x2,2x1, 2x2, 3x2 and 4x2 clusters implemented for kagome yet!")
            end if

        else
            call stop_all(this_routine, &
                          "closed boundary conditions not yet implemented for kagome lattice!")

        end if

    end subroutine init_sites_kagome

    subroutine init_sites_hexagonal(this)
        ! the way to create the neighboring indices is based on the convention
        ! that the lattice is interpreted in such a way: the unit cell is:
        !  __
        ! /  \   with encoding: 1 5
        ! \__/                  2 6
        ! /  \                  3 7
        !                       4 8

        ! which leads to an lattice:
        ! __/  \__/
        !   \__/  \_
        ! _ /  \__/
        !   \__/  \_
        ! __/  \__/
        !   \__/  \_

        ! so the up and down neighbors are easy to do again.
        ! just the left and right are different: now each site only either
        ! has left or right, not both and it alternates
        ! but this also depends on the colum of encoding one is in:
        !   1 - 5    9 - 13
        ! - 2   6 - 10   14 -
        !   3 - 7   11 - 15
        ! - 4   8 - 12   16 -
        class(hexagonal) :: this
        integer :: temp_array(4 * this%length(1), 2 * this%length(2))
        integer :: up(4 * this%length(1), 2 * this%length(2))
        integer :: down(4 * this%length(1), 2 * this%length(2))
        integer :: right(4 * this%length(1), 2 * this%length(2))
        integer :: left(4 * this%length(1), 2 * this%length(2))

        integer :: i, temp_neigh(3), x, y, special
        integer, allocatable :: neigh(:), order(:)
        character(*), parameter :: this_routine = "init_sites_hexagonal"

        allocate(order(this%get_nsites()), source = 0)

        if (t_input_order .and. this%t_bipartite_order) then
            order = orbital_order
        else
            order = [(i, i = 1, this%get_nsites())]
        end if

        temp_array = reshape([(order(i), i=1, this%get_nsites())], &
                             [4 * this%length(1), 2 * this%length(2)])

        up = cshift(temp_array, -1, 1)
        down = cshift(temp_array, 1, 1)
        left = cshift(temp_array, -1, 2)
        right = cshift(temp_array, 1, 2)

        if (this%is_periodic()) then
            do i = 1, this%get_nsites()
                ! columns and rows:
                x = mod(i - 1, 4 * this%length(1)) + 1
                y = (i - 1) / (4 * this%length(1)) + 1

                if (is_odd(y)) then
                    if (is_odd(i)) then
                        ! every odd number in a odd column has a right neighbor
                        special = right(x, y)
                    else
                        ! otherwise left
                        special = left(x, y)
                    end if
                else
                    ! for even columns it is the other way around
                    if (is_odd(i)) then
                        special = left(x, y)
                    else
                        special = right(x, y)
                    end if
                end if
                temp_neigh = [up(x, y), down(x, y), special]

                neigh = sort_unique(temp_neigh)

                this%sites(order(i)) = site(order(i), size(neigh), neigh)
            end do
        else
            call stop_all(this_routine, &
                          "closed boundary conditions not yet implemented for hexagonal lattice!")

        end if

    end subroutine init_sites_hexagonal

    subroutine init_sites_triangular(this)
        class(triangular) :: this
        integer :: temp_array(this%length(1), this%length(2))
        integer :: down(this%length(1), this%length(2))
        integer :: up(this%length(1), this%length(2))
        integer :: left(this%length(1), this%length(2))
        integer :: right(this%length(1), this%length(2))
        integer :: lu(this%length(1), this%length(2))
        integer :: rd(this%length(1), this%length(2))
        integer :: i, temp_neigh(6), x, y
        integer, allocatable :: neigh(:), order(:)
        character(*), parameter :: this_routine = "init_sites_triangular"

        ASSERT(this%get_nsites() >= 4)

        allocate(order(this%get_nsites()), source = 0)
        if (t_input_order .and. this%t_bipartite_order) then
            order = orbital_order
        else
            order = [(i, i = 1, this%get_nsites())]
        end if

        temp_array = reshape([(order(i), i=1, this%get_nsites())], this%length)

        up = cshift(temp_array, -1, 1)
        down = cshift(temp_array, 1, 1)
        right = cshift(temp_array, 1, 2)
        left = cshift(temp_array, -1, 2)
        lu = cshift(up, -1, 2)
        rd = cshift(down, 1, 2)

        if (this%is_periodic()) then
            do i = 1, this%get_nsites()

                x = mod(i - 1, this%length(1)) + 1
                y = (i - 1) / this%length(1) + 1

                temp_neigh = [up(x, y), down(x, y), left(x, y), right(x, y), lu(x, y), rd(x, y)]

                neigh = sort_unique(temp_neigh)

                this%sites(order(i)) = site(order(i), size(neigh), neigh)

                deallocate(neigh)

            end do
        else
            call stop_all(this_routine, &
                          "closed boundary conditions not yet implemented for triangular lattice!")
        end if

    end subroutine init_sites_triangular

    subroutine init_sites_rect(this)
        class(rectangle) :: this
        character(*), parameter :: this_routine = "init_sites_rect"

        integer :: i, temp_neigh(4), x, y
        integer :: temp_array(this%length(1), this%length(2))
        integer :: down(this%length(1), this%length(2))
        integer :: up(this%length(1), this%length(2))
        integer :: left(this%length(1), this%length(2))
        integer :: right(this%length(1), this%length(2))
        integer, allocatable :: neigh(:)
        integer :: k_vec(3), r_vec(3)
        integer, allocatable :: order(:)

        ! this is the important routine..
        ! store lattice like that:
        ! 1 4 7
        ! 2 5 8
        ! 3 6 9

        ASSERT(this%get_nsites() >= 4)

        ! use cshift intrinsic of fortran..
        ! how do i efficiently set that up?
        if (this%t_bipartite_order) then
            if (this%length(1) /= this%length(2)) then
                if (this%length(2) /= 2) then
                    call stop_all(this_routine, &
                        "ladder bipartite ordering is implemented with Ly == 2)")
                end if

                allocate(order(this%get_nsites()), source = 0)
                if (t_input_order) then
                    order = orbital_order
                else
                    do i = 1, this%length(1)/2
                        order(2*i-1) = i
                        order(2*i) = this%length(1) + i
                    end do
                    do i = this%length(1)/2 + 1, this%length(1)
                        order(2*i-1) = this%length(1) + i
                        order(2*i) = i
                    end do
                end if
            else
                if (this%get_nsites() == 16) then
                    allocate(order(16), source = 0)
                    if (t_input_order) then
                        order = orbital_order
                    else
                        order = [ 1,  9,  2, 10, &
                                 11,  3, 12,  4, &
                                  5, 13,  6, 14, &
                                 15,  7, 16,  8]
                    end if
                else if (this%get_nsites() == 36) then

                    allocate(order(36), source = 0)
                    if (t_input_order) then
                        order = orbital_order
                    else
                        order = [ 1, 19,  2, 20,  3, 21, &
                                 22,  4, 23,  5, 24,  6, &
                                  7, 25,  8, 26,  9, 27, &
                                 28, 10, 29, 11, 30, 12, &
                                 13, 31, 14, 32, 15, 33, &
                                 34, 16, 35, 17, 36, 18]
                    end if
                else
                    if (t_input_order) then
                        order = orbital_order
                    else
                        call stop_all(this_routine, &
                            "bipartite order for square only implemented for 4x4! and 6x6 for now!")
                    end if
                end if
            end if
        else
            allocate(order(this%get_nsites()), source = [(i, i = 1, this%get_nsites())])
        end if

        temp_array = reshape( order, this%length)

        up = cshift(temp_array, -1, 1)
        down = cshift(temp_array, 1, 1)
        right = cshift(temp_array, 1, 2)
        left = cshift(temp_array, -1, 2)

        if (this%is_periodic()) then

            do i = 1, this%get_nsites()
                ! create the neighbor list
                x = mod(i - 1, this%length(1)) + 1
                y = (i - 1) / this%length(1) + 1

                temp_neigh = [up(x, y), down(x, y), left(x, y), right(x, y)]

                neigh = sort_unique(temp_neigh)

                k_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]
                r_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]
                this%sites(order(i)) = site(order(i), size(neigh), neigh, k_vec, r_vec)

                deallocate(neigh)

            end do

        else if (this%is_periodic(1)) then
            ! only periodic in x-direction
            do i = 1, this%get_nsites()

                x = mod(i - 1, this%length(1)) + 1
                y = (i - 1) / this%length(1) + 1

                ! now definetly always take the left and right neighbors
                ! but up and down if we are not jumping boundaries
                if (x == 1) then
                    ! dont take upper neighbor -> just repeat an neighbor,
                    ! which will get removed from unique
                    temp_neigh = [down(x, y), down(x, y), left(x, y), right(x, y)]
                else if (x == this%length(1)) then
                    temp_neigh = [up(x, y), up(x, y), left(x, y), right(x, y)]

                else
                    ! take all
                    temp_neigh = [up(x, y), down(x, y), left(x, y), right(x, y)]
                end if

                neigh = sort_unique(temp_neigh)

                k_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]
                r_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]

                this%sites(order(i)) = site(order(i), size(neigh), neigh, k_vec, r_vec)

                deallocate(neigh)
            end do

        else if (this%is_periodic(2)) then
            ! only periodic in the y-direction
            do i = 1, this%get_nsites()

                x = mod(i - 1, this%length(1)) + 1
                y = (i - 1) / this%length(1) + 1

                ! now definetly always take the left and right neighbors
                ! but up and down if we are not jumping boundaries
                if (y == 1) then
                    ! dont take upper neighbor -> just repeat an neighbor,
                    ! which will get removed from unique
                    temp_neigh = [up(x, y), down(x, y), right(x, y), right(x, y)]
                else if (y == this%length(2)) then
                    temp_neigh = [up(x, y), down(x, y), left(x, y), left(x, y)]

                else
                    ! take all
                    temp_neigh = [up(x, y), down(x, y), left(x, y), right(x, y)]
                end if

                neigh = sort_unique(temp_neigh)

                k_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]
                r_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]

                this%sites(order(i)) = site(order(i), size(neigh), neigh, k_vec, r_vec)

                deallocate(neigh)
            end do

        else
            ! non-periodic
            do i = 1, this%get_nsites()

                x = mod(i - 1, this%length(1)) + 1
                y = (i - 1) / this%length(1) + 1

                ! now definetly always take the left and right neighbors
                ! but up and down if we are not jumping boundaries
                if (x == 1 .and. y == 1) then
                    ! dont take upper neighbor -> just repeat an neighbor,
                    ! which will get removed from unique
                    temp_neigh = [down(x, y), down(x, y), right(x, y), right(x, y)]
                else if (y == this%length(2) .and. x == this%length(1)) then
                    temp_neigh = [up(x, y), up(x, y), left(x, y), left(x, y)]

                else if (x == this%length(1) .and. y == 1) then
                    temp_neigh = [up(x, y), up(x, y), right(x, y), right(x, y)]
                else if (y == this%length(2) .and. x == 1) then
                    temp_neigh = [down(x, y), down(x, y), left(x, y), left(x, y)]

                else if (x == 1) then
                    temp_neigh = [left(x, y), right(x, y), down(x, y), down(x, y)]

                else if (x == this%length(1)) then
                    temp_neigh = [left(x, y), right(x, y), up(x, y), up(x, y)]

                else if (y == 1) then
                    temp_neigh = [right(x, y), right(x, y), up(x, y), down(x, y)]

                else if (y == this%length(2)) then
                    temp_neigh = [left(x, y), left(x, y), up(x, y), down(x, y)]

                else
                    ! take all
                    temp_neigh = [up(x, y), down(x, y), left(x, y), right(x, y)]
                end if

                neigh = sort_unique(temp_neigh)

                k_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]
                r_vec = [x - (this%length(1) + 1) / 2, y - (this%length(2) + 1) / 2, 0]

                this%sites(order(i)) = site(order(i), size(neigh), neigh, k_vec, r_vec)

                deallocate(neigh)
            end do

        end if

    end subroutine init_sites_rect

    subroutine read_sites(this)

        class(ext_input):: this

        integer:: i, n_site, n_neighbors
        integer, allocatable :: neighs(:)

        CHARACTER(LEN=100) w
        type(ManagingFileReader_t) :: file_reader
        type(TokenIterator_t) :: tokens

        file_reader = ManagingFileReader_t("lattice.file")

        readsites: do while (file_reader%nextline(tokens, skip_empty=.true.))
            w = to_upper(tokens%next())
            select case (w)
            case ('SITE')
                n_site = to_int(tokens%next())

                n_neighbors = to_int(tokens%next())
                if (allocated(neighs)) deallocate(neighs)
                allocate(neighs(n_neighbors), source=0)
                do i = 1, size(neighs)
                    neighs(i) = to_int(tokens%next())
                end do
                this%sites(n_site) = site(n_site, n_neighbors, neighs)
            end select
        end do readsites

        call file_reader%close()
    end subroutine read_sites

    subroutine init_sites_sujun(this)
        ! order of the lattice sites
        !   4 7 10
        !   3 6 9
        ! 1 2 5 8
        !
        class(sujun) :: this
        this%sites(1) = site(1, 4, [2,4,8,10])
        this%sites(2) = site(2, 4, [1,3,5,7])
        this%sites(3) = site(3, 4, [2,4,6,8])
        this%sites(4) = site(4, 4, [1,3,7,9])
        this%sites(5) = site(5, 4, [2,6,8,10])
        this%sites(6) = site(6, 4, [3,5,7,9])
        this%sites(7) = site(7, 4, [2,4,6,10])
        this%sites(8) = site(8, 4, [1,3,5,9])
        this%sites(9) = site(9, 4, [4,6,8,10])
        this%sites(10) = site(10,4, [1,5,7,9])

    end subroutine init_sites_sujun

    subroutine init_sites_ole(this)
        class(ole) :: this

        ! i think i can index an array in fortran in reversed order
        integer :: ind_array(-this%length(1):(this%length(1) + 1), &
                             -this%length(2):this%length(1))

        integer :: i, j, k, mat_ind(this%n_sites, 2), up, down, left, right, &
                   k_vec(3), A(2), B(2), C(2), D(2), k_vec_prep(24, 3)
        integer, allocatable :: neigh(:)

        ! how do i set up Ole cluster..
        ! in real and k-space.. this will be a pain i guess..

        ! i could make the same neighboring matrices as for the tilted
        k = 1
        ind_array = 0
        ! define the vertices of the parallelogram
        A = [-this%length(2), 0]
        B = [0, -this%length(1)]
        C = [this%length(1), 0]
        D = [this%length(1) - this%length(2), this%length(1)]

        ! i do not need to loop over the edge values, which belong to a
        ! different unit cell!
        do j = -this%length(2) + 1, this%length(1) - 1
            do i = this%length(1) - 1, -(this%length(1) + 1), -1

                ! i have to take 2 special points into account ! which are
                ! by definition on the edge of the (3,3) boundary
                if (inside_bz_2d(j, i, A, B, C, D) .and. .not. on_line_2d([j, i], A, D) &
                    .and. .not. on_line_2d([j, i], C, D)) then

                    ind_array(-i, j) = k
                    mat_ind(k, :) = [-i, j]

                    k = k + 1

                end if
            end do
        end do

        k_vec_prep(1, :) = [1, -3, 0]
        k_vec_prep(2, :) = [-2, 1, 0]
        k_vec_prep(3, :) = [-2, 0, 0]

        k_vec_prep(4, :) = [-1, 2, 0]
        k_vec_prep(5, :) = [-1, 1, 0]
        k_vec_prep(6, :) = [-1, 0, 0]
        k_vec_prep(7, :) = [-1, -1, 0]
        k_vec_prep(8, :) = [-1, -2, 0]

        k_vec_prep(9, :) = [0, 3, 0]
        k_vec_prep(10, :) = [0, 2, 0]
        k_vec_prep(11, :) = [0, 1, 0]
        k_vec_prep(12, :) = [0, 0, 0]
        k_vec_prep(13, :) = [0, -1, 0]
        k_vec_prep(14, :) = [0, -2, 0]
        k_vec_prep(15, :) = [0, -3, 0]

        k_vec_prep(16, :) = [1, 2, 0]
        k_vec_prep(17, :) = [1, 1, 0]
        k_vec_prep(18, :) = [1, 0, 0]
        k_vec_prep(19, :) = [1, -1, 0]
        k_vec_prep(20, :) = [1, -2, 0]

        k_vec_prep(21, :) = [2, 0, 0]
        k_vec_prep(22, :) = [2, -1, 0]
        k_vec_prep(23, :) = [2, -2, 0]

        k_vec_prep(24, :) = [3, -1, 0]

        ! now i want to get the neigbhors
        do i = 1, this%get_nsites()
            ! how to efficiently do this, and in a general way?
            ! better than in the other cases
            ! i am not going over the boundaries, due to the way i set up
            ! the matrix above.. hopefully
            up = ind_array(mat_ind(i, 1) - 1, mat_ind(i, 2))
            if (up == 0) then
                up = this%find_periodic_neighbors([mat_ind(i, 1) - 1, mat_ind(i, 2)], &
                                                  ind_array)
            end if

            down = ind_array(mat_ind(i, 1) + 1, mat_ind(i, 2))
            if (down == 0) then
                down = this%find_periodic_neighbors([mat_ind(i, 1) + 1, mat_ind(i, 2)], &
                                                    ind_array)
            end if

            left = ind_array(mat_ind(i, 1), mat_ind(i, 2) - 1)
            if (left == 0) then
                left = this%find_periodic_neighbors([mat_ind(i, 1), mat_ind(i, 2) - 1], &
                                                    ind_array)
            end if

            right = ind_array(mat_ind(i, 1), mat_ind(i, 2) + 1)
            if (right == 0) then
                right = this%find_periodic_neighbors([mat_ind(i, 1), mat_ind(i, 2) + 1], &
                                                     ind_array)
            end if

            neigh = sort_unique([up, down, left, right])

            ! i have to get the matrix indiced again, with the correct
            ! sign..
            if (this%get_nsites() == 24) then
                k_vec = k_vec_prep(i, :)
            else
                k_vec = [mat_ind(i, 2), -mat_ind(i, 1), 0]
            end if

            this%sites(i) = site(i, size(neigh), neigh, k_vec)

        end do

    end subroutine init_sites_ole

    integer function find_periodic_neighbors_ole(this, ind, A)
        ! function to give me a periodic neighbor of a specific lattice
        class(ole) :: this
        integer, intent(in) :: ind(2), A(:, :)
        integer :: temp(-this%length(1):(this%length(1) + 1), &
                        -this%length(2):this%length(1))

        integer :: unique, shift(4, 2), i
        ! i am not sure, if i have to specify the indices and size of the
        ! matrix inputted..

        ! get the lattice vectors:
        associate(r1 => this%lat_vec(1:2, 1), r2 => this%lat_vec(1:2, 2), &
                   x => ind(1), y => ind(2))

            shift = transpose(reshape([r1, r2, r1 + r2, r1 - r2], [2, 4]))

            find_periodic_neighbors_ole = -1

            do i = 1, 4
                ! apply all the periodic vectors one after the other
                ! negative and positive..
                temp = apply_pbc(A, shift(i, :))

                if (temp(x, y) /= 0) then
                    find_periodic_neighbors_ole = temp(x, y)
                    return
                end if

                temp = apply_pbc(A, -shift(i, :))

                if (temp(x, y) /= 0) then
                    find_periodic_neighbors_ole = temp(x, y)
                    return
                end if
            end do

        end associate

        find_periodic_neighbors_ole = unique

    end function find_periodic_neighbors_ole

    function apply_pbc(array, shift) result(s_array)
        integer, intent(in) :: array(:, :), shift(2)
        integer :: s_array(size(array, 1), size(array, 2))

        ! i have to be sure about the sign conventions here..
        s_array = eoshift(array, shift(1), dim=2)
        s_array = eoshift(s_array, -shift(2), dim=1)

    end function apply_pbc

    logical function inside_bz_2d(x, y, A, B, C, D)
        ! function to check if a point (x,y) is inside our outside the
        ! rectangle indicated by the 3 points A,B,C,D
        ! the definition is to provide the points in this fashion:
        !  A -- D
        !  |    |
        !  B -- C
        ! in a circular fashion, otherwise it does not work, since it would
        ! be a different rectangle
        ! idea from:
        ! https://stackoverflow.com/questions/2752725/finding-whether-a-point-lies-inside-a-rectangle-or-not
        integer, intent(in) :: x, y, A(2), B(2), C(2), D(2)

        integer :: vertex(4, 2), edges(4, 2), R(4), S(4), T(4), U(4)
        inside_bz_2d = .false.

        vertex = transpose(reshape([A, B, C, D], [2, 4]))

        edges(1, :) = A - B
        edges(2, :) = B - C
        edges(3, :) = C - D
        edges(4, :) = D - A

        R = edges(:, 2)
        S = -edges(:, 1)
        T = -(R * vertex(:, 1) + S * vertex(:, 2))

        U = R * x + S * y + T

        if (all(U >= 0)) inside_bz_2d = .true.

    end function inside_bz_2d

    logical function on_line_2d(P, A, B)
        integer, intent(in) :: P(2), A(2), B(2)
        ! function to check if a point is on a line(for integers now only!)

        integer :: AB(2), AP(2)

        AB = B - A
        AP = P - A

        on_line_2d = .false.

        if (AB(1) * AP(2) - AB(2) * AP(1) == 0) on_line_2d = .true.

    end function on_line_2d

    subroutine init_sites_tilted(this)
        class(tilted) :: this
        character(*), parameter :: this_routine = "init_sites_tilted"

        integer :: temp_array(-this%length(1):this%length(2), &
                              -this%length(1):this%length(2) + 1)
        integer :: up(-this%length(1):this%length(2), &
                      -this%length(1):this%length(2) + 1)
        integer :: down(-this%length(1):this%length(2), &
                        -this%length(1):this%length(2) + 1)
        integer :: left(-this%length(1):this%length(2), &
                        -this%length(1):this%length(2) + 1)
        integer :: right(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: right_ul(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: right_ur(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: right_dl(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: right_dr(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: right_rr(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: right_ll(-this%length(1):this%length(2), &
                            -this%length(1):this%length(2) + 1)
        integer :: up_ul(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: up_ur(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: up_dl(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: up_dr(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: up_rr(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: up_ll(-this%length(1):this%length(2), &
                         -this%length(1):this%length(2) + 1)
        integer :: down_ul(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: down_ur(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: down_dl(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: down_dr(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: down_rr(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: down_ll(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_ul(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_ur(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_dl(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_dr(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_rr(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: left_ll(-this%length(1):this%length(2), &
                           -this%length(1):this%length(2) + 1)
        integer :: i, j, k, l, pbc, temp_neigh(4), k_min, k_max, offset, k_vec(3), m
        integer :: right_nn, left_nn, up_nn, down_nn, pbc_1(2), pbc_2(2), r_vec(3)
        integer, allocatable :: neigh(:)
        integer, allocatable :: order(:)
        ! convention of lattice storage:
        !
        !   2 5
        ! 1 3 6 8
        !   4 7
        ! update: we also want to have non-square tilted clusters.. how would
        ! that work. eg a 10-site 2x3 cluster:
        !   2 5
        ! 1 3 6 9
        !   4 7 10
        !     8
        ! can i also do a 1x2 4-site tilted? like:
        ! 1 2
        !   3 4
        ! and also a 2x1:
        !
        ! 1 3

        ASSERT(this%get_nsites() >= 4)

        ! set up the lattice indices, via the use of "k-vectors"
        temp_array(:, :) = 0
        if (this%t_bipartite_order) then
            if ( .not. (this%get_nsites() == 18 .or. this%get_nsites() == 8)) then
                call stop_all(this_routine, &
                    "bipartite only for 8 or 18 tilted sites for now")
            end if
            allocate(order(this%get_nsites()), source = 0)

            if (t_input_order) then
                order = orbital_order
            else
                if (this%get_nsites() == 18) then
                    order = [ 1, 2, 10, 3, 4, 11, 5, 12, 6, 13, 7, 14, 8, 15, 16, 9, 17, 18]
                else if (this%get_nsites() == 8) then
                    order = [1,2,5,3,6,4,7,8]
                end if
            end if
        else
            allocate(order(this%get_nsites()), source = [(i, i = 1, this%get_nsites())])
        end if


        k = 0
        l = 1
        m = this%get_nsites() / 2 + 1
        do i = -this%length(1) + 1, 0
            do j = -k, k

                temp_array(j, i) = order(l)
                l = l + 1

            end do
            k = k + 1
        end do

        ! here i need to change the k-vectors, differently, if it is a
        ! rectangular tilted lattice..
        ! and for now, until i have implemented it better over
        ! lattice vectors assume lx - ly <= 1! only one difference
        k = k - 1
        ! or should i do an inbetween-step if lx /= ly? this is also
        ! possible

        offset = abs(this%length(1) - this%length(2))
        k_min = -this%length(1) + 1
        k_max = this%length(2) - offset
        do i = 1, offset

            do j = k_min, k_max
                temp_array(j, i) = l
                l = l + 1
            end do

            ! shift the y indication by 1 up or down
            k_min = k_min + 1
            k_max = k_max + 1
        end do

        if (this%length(1) < this%length(2)) then
            k_min = k_min
            k_max = k_max - 1
        else if (this%length(1) > this%length(2)) then
            k_min = k_min + 1
            k_max = k_max
        else
            ! otherwise k_min and k_max where never defined
            k_min = -k
            k_max = k
        end if

        do i = offset + 1, this%length(2)


            ! if (this%t_bipartite_order) then
            !     do j = k_min, k_max, 2
            !
            !         temp_array(j, i) = m
            !
            !         m = m + 1
            !
            !     end do
            !     do j = k_min + 1, k_max - 1, 2
            !         temp_array(j, i) = l
            !         l = l + 1
            !     end do
            ! else
                do j = k_min, k_max

                    temp_array(j, i) = order(l)

                    l = l + 1

                end do
            ! end if

            ! k_min is negative
            k_min = k_min + 1
            k_max = k_max - 1
        end do

        up = cshift(temp_array, -1, 1)
        down = cshift(temp_array, 1, 1)
        right = cshift(temp_array, 1, 2)
        left = cshift(temp_array, -1, 2)

        ! apply the periodic boundary conditions to the neighbors
        pbc = this%length(1)
        ! for rectangular tilted lattices this is different of course

        if (this%length(1) == 1) then
            pbc_1 = [2, 0]
            pbc_2 = [this%length(2), -this%length(2)]
        else if (this%length(2) == 1) then
            pbc_1 = [this%length(1), this%length(1)]
            pbc_2 = [2, 0]
        else
            pbc_1 = [this%length(1), this%length(1)]
            pbc_2 = [this%length(2), -this%length(2)]
        end if

        ! do something like and do this generally maybe..
        call apply_pbc_tilted(up, pbc_1, pbc_2, up_ur, up_dr, up_ul, up_dl, up_rr, up_ll)
        call apply_pbc_tilted(down, pbc_1, pbc_2, down_ur, down_dr, down_ul, &
            down_dl, down_rr, down_ll)
        call apply_pbc_tilted(right, pbc_1, pbc_2, right_ur, right_dr, right_ul, &
            right_dl, right_rr, right_ll)
        call apply_pbc_tilted(left, pbc_1, pbc_2, left_ur, left_dr, left_ul, &
            left_dl, left_rr, left_ll)

        k = 0
        l = 1
        ! now get the neighbors
        if (this%is_periodic()) then
            ! fully periodic case
            do i = -this%length(1) + 1, 0
                do j = -k, k
                    ! make the neigbors list
                    up_nn = maxval([up(j, i), up_ur(j, i), up_dr(j, i), up_ul(j, i), &
                                    up_dl(j, i)])

                    if (up_nn == 0) then
                        up_nn = maxval([up_rr(j, i), up_ll(j, i)])
                        if (up_nn == 0) then
                            print *, " up: smth wrong!"
                        end if
                    end if

                    down_nn = maxval([down(j, i), down_ur(j, i), down_dr(j, i), &
                        down_ul(j, i), down_dl(j, i)])

                    if (down_nn == 0) then
                        down_nn = maxval([down_rr(j, i), down_ll(j, i)])

                        if (down_nn == 0) then
                            print *, "down: smth wrong!"
                        end if
                    end if

                    right_nn = maxval([right(j, i), right_ur(j, i), right_dr(j, i), &
                        right_ul(j, i), right_dl(j, i)])

                    if (right_nn == 0) then
                        right_nn = right_ll(j, i)
                        if (right_nn == 0) then
                            print *, "right: smth wrong!"
                        end if
                    end if

                    left_nn = maxval([left(j, i), left_ur(j, i), left_dr(j, i), &
                        left_ul(j, i), left_dl(j, i)])

                    if (left_nn == 0) then
                        left_nn = left_rr(j, i)
                        if (left_nn == 0) then
                            print *, "left: smth wrong!"
                        end if
                    end if

                    neigh = sort_unique([up_nn, down_nn, left_nn, right_nn])
                    ! also start to store the k-vector here!
                    ! have to be sure that i make it correct
                    k_vec = [i, j, 0]
                    r_vec = [j, i, 0]
                    this%sites(order(l)) = site(order(l), size(neigh), neigh, k_vec, r_vec)

                    l = l + 1

                    deallocate(neigh)

                end do
                k = k + 1
            end do

            k = k - 1

            k_min = -this%length(1) + 1
            k_max = this%length(2) - offset
            do i = 1, offset
                do j = k_min, k_max
                    ! make the neigbors list
                    up_nn = maxval([up(j, i), up_ur(j, i), up_dr(j, i), up_ul(j, i), &
                                    up_dl(j, i)])

                    if (up_nn == 0) then
                        up_nn = maxval([up_rr(j, i), up_ll(j, i)])
                        if (up_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    down_nn = maxval([down(j, i), down_ur(j, i), down_dr(j, i), &
                        down_ul(j, i), down_dl(j, i)])

                    if (down_nn == 0) then
                        down_nn = maxval([down_rr(j, i), down_ll(j, i)])

                        if (down_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    right_nn = maxval([right(j, i), right_ur(j, i), right_dr(j, i),&
                        right_ul(j, i), right_dl(j, i)])

                    if (right_nn == 0) then
                        right_nn = right_ll(j, i)
                        if (right_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    left_nn = maxval([left(j, i), left_ur(j, i), left_dr(j, i), &
                        left_ul(j, i), left_dl(j, i)])

                    if (left_nn == 0) then
                        left_nn = left_rr(j, i)
                        if (left_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    neigh = sort_unique([up_nn, down_nn, left_nn, right_nn])

                    k_vec = [i, j, 0]
                    r_vec = [j, 1, 0]
                    this%sites(order(l)) = site(order(l), size(neigh), neigh, k_vec, r_vec)

                    l = l + 1

                    deallocate(neigh)

                end do
                k_min = k_min + 1
                k_max = k_max + 1
            end do

            if (this%length(1) < this%length(2)) then
                k_min = k_min
                k_max = k_max - 1
            else if (this%length(1) > this%length(2)) then
                k_min = k_min + 1
                k_max = k_max
            else
                k_min = -k
                k_max = k
            end if

            do i = offset + 1, this%length(2)
                do j = k_min, k_max
                    ! make the neigbors list
                    up_nn = maxval([up(j, i), up_ur(j, i), up_dr(j, i), up_ul(j, i), &
                                    up_dl(j, i)])

                    if (up_nn == 0) then
                        up_nn = maxval([up_rr(j, i), up_ll(j, i)])
                        if (up_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    down_nn = maxval([down(j, i), down_ur(j, i), down_dr(j, i), &
                        down_ul(j, i), down_dl(j, i)])

                    if (down_nn == 0) then
                        down_nn = maxval([down_rr(j, i), down_ll(j, i)])

                        if (down_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    right_nn = maxval([right(j, i), right_ur(j, i), right_dr(j, i), &
                        right_ul(j, i), right_dl(j, i)])

                    if (right_nn == 0) then
                        right_nn = right_ll(j, i)
                        if (right_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    left_nn = maxval([left(j, i), left_ur(j, i), left_dr(j, i), &
                        left_ul(j, i), left_dl(j, i)])

                    if (left_nn == 0) then
                        left_nn = left_rr(j, i)
                        if (left_nn == 0) then
                            print *, "smth wrong!"
                        end if
                    end if

                    neigh = sort_unique([up_nn, down_nn, left_nn, right_nn])

                    k_vec = [i, j, 0]
                    r_vec = [j, i, 0]
                    this%sites(order(l)) = site(order(l), size(neigh), neigh, k_vec, r_vec)

                    l = l + 1

                    deallocate(neigh)

                end do
                k_min = k_min + 1
                k_max = k_max - 1

            end do
        else if (this%is_periodic(1)) then
            ! only apply (x,x) periodicity
            do i = -this%length(1) + 1, 0
                do j = -k, k

                    up_nn = maxval([up(j, i), up_ur(j, i), up_dl(j, i)])
                    down_nn = maxval([down(j, i), down_ur(j, i), down_dl(j, i)])
                    left_nn = maxval([left(j, i), left_ur(j, i), left_dl(j, i)])
                    right_nn = maxval([right(j, i), right_ur(j, i), right_dl(j, i)])

                    temp_neigh = [up_nn, down_nn, left_nn, right_nn]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)
                end do
                k = k + 1
            end do
            k = k - 1

            do i = 1, this%length(1)
                do j = -k, k

                    up_nn = maxval([up(j, i), up_ur(j, i), up_dl(j, i)])
                    down_nn = maxval([down(j, i), down_ur(j, i), down_dl(j, i)])
                    left_nn = maxval([left(j, i), left_ur(j, i), left_dl(j, i)])
                    right_nn = maxval([right(j, i), right_ur(j, i), right_dl(j, i)])

                    temp_neigh = [up_nn, down_nn, left_nn, right_nn]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)
                end do
                k = k - 1
            end do

        else if (this%is_periodic(2)) then
            ! only apply (x,-x) periodicity
            do i = -this%length(1) + 1, 0
                do j = -k, k

                    up_nn = maxval([up(j, i), up_ul(j, i), up_dr(j, i)])
                    down_nn = maxval([down(j, i), down_ul(j, i), down_dr(j, i)])
                    left_nn = maxval([left(j, i), left_ul(j, i), left_dr(j, i)])
                    right_nn = maxval([right(j, i), right_ul(j, i), right_dr(j, i)])

                    temp_neigh = [up_nn, down_nn, left_nn, right_nn]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)
                end do
                k = k + 1
            end do
            k = k - 1

            do i = 1, this%length(1)
                do j = -k, k

                    up_nn = maxval([up(j, i), up_ul(j, i), up_dr(j, i)])
                    down_nn = maxval([down(j, i), down_ul(j, i), down_dr(j, i)])
                    left_nn = maxval([left(j, i), left_ul(j, i), left_dr(j, i)])
                    right_nn = maxval([right(j, i), right_ul(j, i), right_dr(j, i)])

                    temp_neigh = [up_nn, down_nn, left_nn, right_nn]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)
                end do
                k = k - 1
            end do

        else
            ! non-periodic case
            do i = -this%length(1) + 1, 0
                do j = -k, k
                    ! only a neighbor if the index is non-zero!
                    temp_neigh = [up(j, i), down(j, i), left(j, i), right(j, i)]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)

                end do
                k = k + 1
            end do

            k = k - 1
            do i = 1, this%length(1)
                do j = -k, k
                    ! only a neighbor if the index is non-zero!
                    temp_neigh = [up(j, i), down(j, i), left(j, i), right(j, i)]

                    neigh = sort_unique(pack(temp_neigh, temp_neigh > 0))

                    this%sites(order(l)) = site(order(l), size(neigh), neigh)

                    l = l + 1

                    deallocate(neigh)

                end do
                k = k - 1
            end do
        end if

    end subroutine init_sites_tilted

    subroutine apply_pbc_tilted(array, pbc_1, pbc_2, ur, dr, ul, dl, rr, ll)
        ! intermediate routine to apply the periodic boundary conditions for
        ! the rectangular tilted lattice
        integer, intent(in) :: array(:, :), pbc_1(2), pbc_2(2)
        integer, intent(out) :: ur(:, :), dr(:, :), ul(:, :), dl(:, :), rr(:, :), ll(:, :)

        integer :: plus(2)

        plus = pbc_1 + pbc_2
        ! i have to do this properly, so it still works with the old
        ! tilted implementation:
        ! cshfting along the 2nd dimension is the x-axis shift on my
        ! derivation
        ! and along the 1-direction i should shift with -pbc to move it
        ! actually up..
        ! i think something is wrong with this routine still!
        rr = cshift(cshift(array, -plus(2), 1), plus(1), 2)
        ll = cshift(cshift(array, plus(2), 1), -plus(1), 2)

        ur = cshift(cshift(array, -pbc_1(2), 1), pbc_1(1), 2)
        dr = cshift(cshift(array, -pbc_2(2), 1), pbc_2(1), 2)

        ul = cshift(cshift(array, pbc_2(2), 1), -pbc_2(1), 2)
        dl = cshift(cshift(array, pbc_1(2), 1), -pbc_1(1), 2)

    end subroutine apply_pbc_tilted

    pure function get_k_vec(this, orb) result(k_vec)
        class(lattice), intent(in) :: this
        integer, intent(in) :: orb
        integer :: k_vec(3)

        k_vec = this%sites(orb)%k_vec

    end function get_k_vec

    function get_r_vec(this, orb) result(r_vec)
        class(lattice) :: this
        integer, intent(in) :: orb
        integer :: r_vec(3)

        r_vec = this%sites(orb)%r_vec

    end function get_r_vec

    function dispersion_rel_chain_k(this, k_vec) result(disp)
        class(chain) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
#ifdef DEBUG_
        character(*), parameter :: this_routine = "dispersion_rel_chain"
#endif

        ! for now only do it for periodic boundary conditions..
        ASSERT(this%is_periodic())

        ! and for now only for nearest neighbor interaction!
        ! although this is just the nearest neighbor band..
        ! for next nearest and additional function should be implemented!

        ! i need to bring in the length of the chain and stuff..
        ! and i should consider twisted boundary conditions and nearest
        ! neigbhors here too..? i think so..
        disp = 2.0_dp * cos(2.0_dp * pi * (k_vec(1) + twisted_bc(1)) / this%length)

    end function dispersion_rel_chain_k

    function dispersion_rel_rect(this, k_vec) result(disp)
        class(rectangle) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
#ifdef DEBUG_
        character(*), parameter :: this_routine = "dispersion_rel_rect"
#endif

        ASSERT(this%is_periodic())

        disp = 2.0_dp * (cos(2 * pi * (k_vec(1) + twisted_bc(1)) / this%length(1)) &
                         + cos(2 * pi * (k_vec(2) + twisted_bc(2)) / this%length(2)))

    end function dispersion_rel_rect

    function dispersion_rel_cube(this, k_vec) result(disp)
        class(cube) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
#ifdef DEBUG_
        character(*), parameter :: this_routine = "dispersion_rel_cube"
#endif

        ASSERT(this%is_periodic())

        disp = 2.0_dp * (cos(2 * pi * (k_vec(1) + twisted_bc(1)) / this%length(1)) &
                         + cos(2 * pi * (k_vec(2) + twisted_bc(2)) / this%length(2)) &
                         + cos(2 * pi * (k_vec(3) + twisted_bc(3)) / this%length(3)))

    end function dispersion_rel_cube

    function dispersion_rel_tilted(this, k_vec) result(disp)
        class(tilted) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
#ifdef DEBUG_
        character(*), parameter :: this_routine = "dispersion_rel_tilted"
#endif

        ASSERT(this%is_periodic())

        ! todo: i have to check if this also still holds for the
        ! rectangular tilted lattice!

        ! after some more consideration i believe this is the correct:
        ! although now i am not sure about the twist anymore... check that!
        disp = 2.0_dp * (cos(pi * ((k_vec(1) + twisted_bc(1)) / this%length(1) &
                                   + (k_vec(2) + twisted_bc(2)) / this%length(2))) &
                         + cos(pi * ((k_vec(1) + twisted_bc(1)) / this%length(1) &
                                     - (k_vec(2) + twisted_bc(2)) / this%length(2))))

    end function dispersion_rel_tilted

    function dispersion_rel_ole(this, k_vec) result(disp)
        class(ole) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
#ifdef DEBUG_
        character(*), parameter :: this_routine = "dispersion_rel_ole"
#endif

        ASSERT(this%is_periodic())

        ! i finally figured out how to do the non-orthogonal
        ! boundary conditions:
        ! although i am not sure about the twisted BC in this case!
        disp = 2.0_dp * (cos(2 * pi / real(sum(this%length(1:2)), dp) * &
                             ((k_vec(1) + twisted_bc(1)) - (k_vec(2) + twisted_bc(2)))) &
                         + cos(2 * pi / real(sum(this%length(1:2)), dp) * &
                               (this%length(2) / real(this%length(1), dp) * (k_vec(1) + twisted_bc(1)) &
                                + (k_vec(2) + twisted_bc(2)))))

    end function dispersion_rel_ole

    function dispersion_rel_not_implemented(this, k_vec) result(disp)
        class(lattice) :: this
        integer, intent(in) :: k_vec(3)
        real(dp) :: disp
        character(*), parameter :: this_routine = "dispersion_rel"

        unused_var(this)
        unused_var(k_vec)

        call stop_all(this_routine, &
                      "dispersion relation not yet implemented for this lattice type!")
#ifdef WARNING_WORKAROUND_
        disp = 0.0_dp
        unused_var(this)
        unused_var(k_vec)
#endif

    end function dispersion_rel_not_implemented

    function dispersion_rel_orb(this, orb) result(disp)
        class(lattice) :: this
        integer, intent(in) :: orb
        real(dp) :: disp

        disp = this%dispersion_rel(this%get_k_vec(orb))
    end function dispersion_rel_orb

    function dispersion_rel_spin_orb(this, orb) result(disp)
        class(lattice) :: this
        integer, intent(in) :: orb
        real(dp) :: disp

        disp = this%dispersion_rel(this%get_k_vec(get_spatial(orb)))

    end function dispersion_rel_spin_orb

    function dot_prod_not_implemented(this, k_vec, r_vec) result(dot)
        ! for the "fourier transform" implement the correct
        ! dot-product with all the factors of pi and n_sites implemented
        ! for each lattice
        class(lattice) :: this
        integer, intent(in) :: k_vec(3), r_vec(3)
        real(dp) :: dot
        character(*), parameter :: this_routine = "dot_prod_not_implemented"

        unused_var(this)
        unused_var(k_vec)
        unused_var(r_vec)

        call stop_all(this_routine, "not yet implemented for this lattice type!")
#ifdef WARNING_WORKAROUND_
        dot = 0.0_dp
        unused_var(this)
        unused_var(k_vec)
        unused_var(r_vec)
#endif

        dot = 0.0_dp

    end function dot_prod_not_implemented

    function dot_prod_chain(this, k_vec, r_vec) result(dot)
        class(chain) :: this
        integer, intent(in) :: k_vec(3), r_vec(3)
        real(dp) :: dot

        dot = 2.0_dp * PI / real(this%get_nsites(), dp) * &
              (k_vec(1) + twisted_bc(1)) * r_vec(1)

    end function dot_prod_chain

    function dot_prod_rect(this, k_vec, r_vec) result(dot)
        class(rectangle) :: this
        integer, intent(in) :: k_vec(3), r_vec(3)
        real(dp) :: dot

        dot = 2.0_dp * PI * ((k_vec(1) + twisted_bc(1)) * r_vec(1) / this%length(1) &
                             + (k_vec(2) + twisted_bc(2)) * r_vec(2) / this%length(2))

    end function dot_prod_rect

    function dot_prod_tilted(this, k_vec, r_vec) result(dot)
        class(tilted) :: this
        integer, intent(in) :: k_vec(3), r_vec(3)
        real(dp) :: dot

        dot = PI * (((k_vec(1) + twisted_bc(1)) / this%length(1) + &
                     (k_vec(2) + twisted_bc(2)) / this%length(2)) * r_vec(1) + &
                    ((k_vec(1) + twisted_bc(1)) / this%length(1) - &
                     (k_vec(2) + twisted_bc(2)) / this%length(2)) * r_vec(2))

    end function dot_prod_tilted

    function sort_unique(list) result(output)
        integer, intent(in) :: list(:)
        integer, allocatable :: output(:)

        integer :: i, min_val, max_val, unique(size(list))

        unique = 0
        i = 0
        min_val = minval(list) - 1
        max_val = maxval(list)

        do while (min_val < max_val)
            i = i + 1
            min_val = minval(list, mask=list > min_val)
            unique(i) = min_val
        end do
        allocate(output(i), source=unique(1:i))

    end function sort_unique

    subroutine init_lattice(this, length_x, length_y, length_z, &
                            t_periodic_x, t_periodic_y, t_periodic_z, t_bipartite_order)
        ! and write the first dummy initialize
        class(lattice) :: this
        integer, intent(in) :: length_x, length_y, length_z
        logical, intent(in) :: t_periodic_x, t_periodic_y, t_periodic_z
        logical, intent(in), optional :: t_bipartite_order
        character(*), parameter :: this_routine = "init_lattice"

        integer :: n_sites, i
        logical :: t_bipartite_order_
        def_default(t_bipartite_order_, t_bipartite_order, .false.)

        n_sites = this%calc_nsites(length_x, length_y, length_z)

        ! and for the rest i can call general routines:
        call this%set_nsites(n_sites)
        call this%set_periodic(t_periodic_x, t_periodic_y, t_periodic_z)

        select type (this)

            ! well i cannot init type is (lattice) if i choose to make it
            ! abstract. since it is not allowed to ever be intantiated..

        class is (chain)
            ! set some defaults for the chain lattice type
            call this%set_ndim(DIM_CHAIN)

            call this%set_length(length_x, length_y)
            ! if incorrect length input it is caught in the calc_nsites above..
            if (this%get_length() == 1) then
                call this%set_nconnect_max(0)
            else if (this%get_length() == 2 .and. (.not. this%is_periodic())) then
                call this%set_nconnect_max(1)
            else
                call this%set_nconnect_max(N_CONNECT_MAX_CHAIN)
            end if

            this%lat_vec(1, 1) = length_x
            ! the type specific routine deal with the check of the
            ! length!

            ! i should not call this set_nsites since this really should
            ! just imply that it set the variable
            ! introduce a second routine, which first determines the
            ! number of sites depending on the lattice type

            ! how should i define the lattice k_vectors..
            this%k_vec(1, 1) = length_x

            this%t_bipartite_order = t_bipartite_order_


        class is (rectangle)

            call this%set_ndim(DIM_RECT)
            call this%set_length(length_x, length_y)

            if (this%get_length(1) == 2 .and. this%get_length(2) == 2) then
                if (.not. this%is_periodic(1) .and. this%is_periodic(2)) then
                    call this%set_nconnect_max(3)
                else if (.not. this%is_periodic(2) .and. this%is_periodic(1)) then
                    call this%set_nconnect_max(3)
                else if (this%is_periodic()) then
                    call this%set_nconnect_max(4)
                else if (.not. this%is_periodic()) then
                    call this%set_nconnect_max(2)
                end if
            else
                call this%set_nconnect_max(4)
            end if

            this%lat_vec(1, 1) = this%length(1)
            this%lat_vec(2, 2) = this%length(2)

            ! i also need to assign the lattice k-vectors..
            ! and i need to do it correctly..
            this%k_vec(1, 1) = this%length(1)
            this%k_vec(2, 2) = this%length(2)

            this%t_bipartite_order = t_bipartite_order_


        class is (tilted)

            call this%set_ndim(DIM_RECT)
            ! for the tilted we deal internally always with x as the
            ! lower of the two inputs. due to symmetry this does not
            ! make a difference
            ! and do not allow a 1xY or Yx1 lattice, since this implementation
            ! annoys me too much!
            if (length_x == 1 .or. length_y == 1) then
                call stop_all(this_routine, "incorrect size for tilted lattice!")
            end if
            call this%set_length(min(length_x, length_y), max(length_x, length_y))
            call this%set_nconnect_max(4)

            this%lat_vec(1:2, 1) = [this%length(1), this%length(1)]
            this%lat_vec(1:2, 2) = [-this%length(2), this%length(2)]

            this%k_vec(1:2, 1) = [this%length(1), this%length(1)]
            this%k_vec(1:2, 2) = [-this%length(2), this%length(2)]

            this%t_bipartite_order = t_bipartite_order_

        class is (ole)
            call this%set_ndim(DIM_RECT)

            if (length_x < 2 .or. length_y < 2 .or. length_x == length_y) then
                call stop_all(this_routine, "incorrect size for Oles Cluster")
            end if
            call this%set_length(min(length_x, length_y), max(length_x, length_y))
            call this%set_nconnect_max(4)

            this%lat_vec(1:2, 1) = [this%length(1), this%length(1)]
            this%lat_vec(1:2, 2) = [-this%length(2), this%length(1)]

            this%k_vec(1:2, 1) = [this%length(1), this%length(1)]
            this%k_vec(1:2, 2) = [-this%length(1), this%length(2)]

            if (t_bipartite_order_) then
                call stop_all(this_routine, &
                    "bipartite order not yet implemented for Ole lattice")
            end if

        class is (sujun)
            call this%set_ndim(DIM_RECT)

            if (length_x /= 1 .or. length_y /= 3) then
                call stop_all(this_routine, "incorrect size for Sujun cluster")
            end if

            call this%set_length(1,3)
            call this%set_nconnect_max(4)

            this%lat_vec(1:2, 1) = [1,3]
            this%lat_vec(1:2, 2) = [-3,1]

            ! k-vec todo..

        class is (ext_input)

            call read_lattice_struct(this)

        class is (cube)
            call this%set_ndim(DIM_CUBE)
            call this%set_length(length_x, length_y, length_z)
            call this%set_nconnect_max(6)

            this%lat_vec(1, 1) = this%length(1)
            this%lat_vec(2, 2) = this%length(2)
            this%lat_vec(3, 3) = this%length(3)

            this%k_vec(1, 1) = this%length(1)
            this%k_vec(2, 2) = this%length(2)
            this%k_vec(3, 3) = this%length(3)

            if (t_bipartite_order_) then
                call stop_all(this_routine, &
                    "bipartite order not yet implemented for cubic lattice")
            end if

        class is (triangular)
            call this%set_ndim(DIM_RECT)
            call this%set_length(length_x, length_y)
            ! for a filling with triangles the maximum connection is 6!

            call this%set_nconnect_max(6)


            ! todo: set lattice vector! and figure that out correctly!
            ! and write a more general routine to set the lattice
            ! vectors for all types of lattices!
        class is (hexagonal)

            call this%set_ndim(DIM_RECT)
            call this%set_length(length_x, length_y, length_z)
            call this%set_nconnect_max(3)

        class is (kagome)
            call this%set_ndim(DIM_RECT)
            call this%set_length(length_x, length_y, length_z)
            call this%set_nconnect_max(4)

        class is (star)
            call this%set_ndim(DIM_STAR)
            call this%set_nconnect_max(n_sites - 1)

            ! for the 'star' geometry the special point in the middle
            ! is connected to all the others.. so i need to calc n_sites here.
            ! also check here if something went wrong in the input:
            if (t_periodic_x .or. t_periodic_y) then
                call stop_all(this_routine, &
                              "incorrect initialization info: requested periodic 'star' geometry!")
            end if

        class is (aim_chain)

            ! do stuff
            call this%set_ndim(DIM_CHAIN)
            call this%set_length(length_x, length_y)
            ! the neighbors is a bit complicated in this case..
            ! although it is a chain.. so it should not have more than
            ! one impurity!
            if (length_x > 1) then
                call stop_all(this_routine, &
                              "more than 1 impurity taken in tha aim_chain setup!")
            end if
            if (length_y == 1) then
                call this%set_nconnect_max(1)
            else
                call this%set_nconnect_max(N_CONNECT_MAX_CHAIN)
            end if

            ! i can use class specific routine in this block
            call this%set_n_imps(length_x)
            call this%set_n_bath(length_y)

            allocate(this%impurity_sites(length_x))
            allocate(this%bath_sites(length_y))

            this%impurity_sites = [(i, i=1, length_x)]
            this%bath_sites = [(length_x + i, i=1, length_y)]

        class is (aim_star)
            ! this is the star with only 1 impurity for now..
            ! i still have to think how to efficiently setup up a
            ! cluster impurity..
            ! i guess i have to decide on a lattice and a ab-initio
            ! cluster impurity! thats good yeah

            call this%set_ndim(DIM_STAR)
            ! number of bath sites is the maximal connectivity
            call this%set_nconnect_max(length_y)

            ! also the one-site impurity can't be periodic!
            if (t_periodic_x .or. t_periodic_y) then
                call stop_all(this_routine, &
                              "incorrect initialization info: requested periodic 'star' geometry!")
            end if

            if (length_x > 1) then
                call stop_all(this_routine, &
                              "aim_star only implemented for one impurity!")
            end if

            call this%set_n_imps(length_x)
            call this%set_n_bath(length_y)

            allocate(this%impurity_sites(length_x))
            allocate(this%bath_sites(length_y))

            this%impurity_sites = [(i, i=1, length_x)]
            this%bath_sites = [(length_x + i, i=1, length_y)]

        class default
            call stop_all(this_routine, "unexpected lattice type!")

        end select
        ! do i want to allocate sites here or in the initializer?
        ! well the specific site initializer will be different for all the
        ! types of lattices.. so best would be to do everything which is
        ! common to all routine here!
        call this%allocate_sites(n_sites)

        call this%initialize_sites()

        ! and fill the lookup table for the site index determination from k vectors
        if (t_k_space_hubbard .or. (t_trans_corr_hop .and. t_new_real_space_hubbard)) then
            call this%initialize_lu_table()
        end if

    end subroutine init_lattice

    subroutine init_hop_cache_bounds(this, r_min, r_max)
        ! initialize the lower and upper bounds of the cached
        ! hopping-transcorr factor, indexed by the involved r-vector
        ! maybe move this function to lattice_mod?!
        class(lattice) :: this
        integer, intent(out), optional :: r_min(3), r_max(3)
        integer :: n_sites, i, j, ri(3), rj(3), r_diff(3)

        if (.not. (all(this%r_min == this%r_max) .and. all(this%r_min == 0))) then

            if (present(r_min) .and. present(r_max)) then
                r_min = this%r_min
                r_max = this%r_max
            end if

            return
        end if

        n_sites = this%get_nsites()

        do i = 1, n_sites
            ri = this%get_r_vec(i)
            do j = 1, n_sites
                rj = this%get_r_vec(j)

                r_diff = ri - rj

                where (r_diff < this%r_min) this%r_min = r_diff
                where (r_diff > this%r_max) this%r_max = r_diff

            end do
        end do

        if (present(r_min) .and. present(r_max)) then
            r_min = this%r_min
            r_max = this%r_max
        end if

    end subroutine init_hop_cache_bounds

    subroutine initialize_lu_table(this)
        implicit none
        class(lattice) :: this

        ! first, get the dimension of the lookup tables
        call this%get_lu_table_size()
        call this%init_basis_vecs()
        ! and allocate the lookup tables accordingly
        allocate(this%lu_table(this%kmin(1):this%kmax(1), this%kmin(2):this%kmax(2) &
                                , this%kmin(3):this%kmax(3)))
        allocate(this%bz_table(this%kmin(1):this%kmax(1), this%kmin(2):this%kmax(2), &
                                this%kmin(3):this%kmax(3)))
        ! write(stdout, *) "Lookup table size is ", 2 * (this%kmax(1) - this%kmin(1) + 1) &
            ! * (this%kmax(2) - this%kmin(2) + 1) * 8 / 1024, " kB"
        ! and fill thee lookup table with the bz vectors
        call this%fill_bz_table()
        ! now, fill the lookup table with the indices of the states
        call this%fill_lu_table()

    end subroutine initialize_lu_table

    subroutine get_lu_table_size(this)
        implicit none
        class(lattice) :: this
        integer :: i, j, ki(sdim), kj(sdim), ka(sdim), nsites, m, a, k_sum(sdim)
        integer :: k, b, kk(sdim), kb(sdim)

        this%kmin = 0
        this%kmax = 0
        ! determine the maximum/minimum indices that appear in the lu table
        ! the lu table shall contain all reachable momenta ki + kj - ka
        nsites = this%get_nsites()
        ! for 2-body transcorrelation we need 5 momenta in total
        if (t_trans_corr_2body .and. t_k_space_hubbard) then
            do i = 1, nsites
                ki = this%get_k_vec(i)
                do j = 1, nsites
                    kj = this%get_k_vec(j)
                    do k = 1, nsites
                        kk = this%get_k_vec(k)
                        do a = 1, nsites
                            ka = this%get_k_vec(a)
                            do b = 1, nsites
                                kb = this%get_k_vec(b)

                                k_sum = ki + kj + kk - ka - kb

                                do m = 1, sdim
                                    if (k_sum(m) < this%kmin(m)) this%kmin(m) = k_sum(m)
                                    if (k_sum(m) > this%kmax(m)) this%kmax(m) = k_sum(m)
                                end do
                            end do
                        end do
                    end do
                end do
            end do
        else
            do i = 1, nsites
                ki = this%get_k_vec(i)
                do j = 1, nsites
                    kj = this%get_k_vec(j)
                    do a = 1, nsites
                        ka = this%get_k_vec(a)
                        k_sum = ki + kj - ka
                        do m = 1, sdim
                            if (k_sum(m) < this%kmin(m)) this%kmin(m) = k_sum(m)
                            if (k_sum(m) > this%kmax(m)) this%kmax(m) = k_sum(m)
                        end do
                    end do
                end do
            end do
        end if

    end subroutine get_lu_table_size

    subroutine fill_lu_table(this)
        implicit none
        class(lattice) :: this
        integer :: i, j, m, k_check(sdim), k_sum(sdim), nsites
        integer :: k

        nsites = this%get_nsites()
        !U.Ebling:
        !The older loop took a very long to finish for any lattice that is not super tiny.
        !I tried a 21x5x1 rectangle and it did not finish after 2 days
        !It over-counts a lot.
        !Below is my optimized version, which loops directly over momenta instead of orbitals
        !There is no need to distinguish the 1-body and 2-body transcorrelation terms, because it
        !uses the result of the subroutine get_lu_table_size
        do i=this%kmin(1),this%kmax(1)
            do j=this%kmin(2),this%kmax(2)
                do k=this%kmin(3),this%kmax(3)
                    k_sum(1)=i
                    k_sum(2)=j
                    k_sum(3)=k
                    k_check=this%map_k_vec(k_sum)
                    do m=1,nsites
                        if(all(k_check == this%get_k_vec(m))) then
                            this%lu_table(k_sum(1),k_sum(2),k_sum(3)) = m
                            exit
                        end if
                    end do
                end do
            end do
        end do

    end subroutine fill_lu_table

    subroutine fill_bz_table(this)
        implicit none
        class(lattice) :: this
        integer :: kx, ky, kz, k(sdim)

        ! here, we store for a bunch of important k vectors, whether they are in
        ! the first BZ or not

        this%bz_table = .false.
        ! check all vectors in the [kmin,kmax] range
        do kx = this%kmin(1), this%kmax(1)
            do ky = this%kmin(2), this%kmax(2)
                do kz = this%kmin(3), this%kmax(3)
                    k = (/kx, ky, kz/)
                    ! write down if k is in the BZ
                    ! the check is done by looping over all sites
                    this%bz_table(k(1), k(2), k(3)) = this%inside_bz_explicit(k)
                end do
            end do
        end do

    end subroutine fill_bz_table

    subroutine deallocate_caches(this)
        implicit none
        class(lattice) :: this

        deallocate(this%lu_table)
        deallocate(this%bz_table)
    end subroutine deallocate_caches

    function aim_lattice_constructor(lat_type, length_x, length_y) result(this)
        character(*), intent(in) :: lat_type
        integer, intent(in) :: length_x, length_y
        class(aim), pointer :: this
        character(*), parameter :: this_routine = "aim_lattice_constructor"

        select case (lat_type)
        case ('chain', 'aim-chain', 'chain-aim')

            allocate(aim_chain :: this)

        case ('star', 'aim-star', 'star-aim')

            allocate(aim_star :: this)

        case default
            ! stop here because a incorrect lattice type was given
            call stop_all(this_routine, &
                          'incorrect lattice type provided in lattice_constructor!')

        end select

        ! the initializer deals with the different types then..
        call this%initialize(length_x, length_y, 1, .false., .false., .false.)

    end function aim_lattice_constructor

    function lattice_constructor(lattice_type, length_x, length_y, length_z, t_periodic_x, &
                                 t_periodic_y, t_periodic_z, space, t_bipartite_order) result(this)
        ! write a general public lattice_constructor for lattices
        ! the number of inputs are still undecided.. do we always have
        ! the same number or differing number of inputs?
        ! i guess, since we will be using global variables which are either
        ! read in or set as default to init it..
        character(*), intent(in) :: lattice_type
        integer, intent(in) :: length_x, length_y, length_z
        logical, intent(in) :: t_periodic_x, t_periodic_y, t_periodic_z
        character(*), intent(in), optional :: space
        logical, intent(in), optional :: t_bipartite_order
        class(lattice), pointer :: this
        character(*), parameter :: this_routine = "lattice_constructor"

        select case (lattice_type)
        case ('chain')

            allocate(chain :: this)

        case ('star')

            allocate(star :: this)

        case ('square')
            ! i guess i want to make a seperate case for the tilted
            ! square, although just the boundary conditions change, but also
            ! the length input changes
            if (length_x /= length_y) then
                call stop_all(this_routine, &
                              "incorrect length input for square lattice!")

            end if

            allocate(rectangle :: this)

        case ('rectangle', 'rect')

            allocate(rectangle :: this)

        case ('tilted', 'tilted-square', 'square-tilted')

            allocate(tilted :: this)

        case ('cube', 'cubic')
            ! for the sake of no better name also use "cube" even if the
            ! sides are not the same length

            if (any([length_x, length_y, length_z] < 2)) then
                call stop_all(this_routine, &
                              "too short cube side lengths < 2!")
            end if

            allocate(cube :: this)

        case ('triangular', 'triangle', 'tri')

            if (any([length_x, length_y] < 2)) then
                call stop_all(this_routine, &
                              "too short lengths for triangular lattice! < 2!")
            end if

            allocate(triangular :: this)

        case ('hexagonal', 'hex', 'hexagon', 'honeycomb')

            allocate(hexagonal :: this)

        case ('kagome')
            allocate(kagome :: this)

        case ('ole')
            allocate(ole :: this)

        case ('sujun')
            allocate(sujun :: this)

        case ('ext_input')
            allocate(ext_input :: this)

        case default
            ! stop here because a incorrect lattice type was given
            call stop_all(this_routine, &
                          'incorrect lattice type provided in lattice_constructor!')

        end select

        call this%set_name(lattice_type)

        ! depending on the string input defining lattice type
        ! initialize corresponding lattice
        if (present(space)) then
            select case (space)
            case ('k-space')
                this%t_momentum_space = .true.

            case ('real-space')
                this%t_momentum_space = .false.

            case default
                call stop_all(this_routine, "not recognized space!")

            end select
        end if

        ! the initializer deals with the different types then..
        call this%initialize(length_x, length_y, length_z, &
                             t_periodic_x, t_periodic_y, t_periodic_z, t_bipartite_order)

    end function lattice_constructor

    function site_constructor(ind, n_neighbors, neighbors, k_vec, r_vec, site_type) &
        result(this)
        ! similar to the lattice constructor i want to have a site
        ! constructor, which depending on some input constructs the
        ! specific sites on a lattice
        ! for now the index is the only necessary input..
        ! include more in this site constructor here
        integer, intent(in) :: ind
        integer, intent(in) :: n_neighbors
        integer, intent(in) :: neighbors(n_neighbors)
        integer, intent(in), optional :: k_vec(3)
        integer, intent(in), optional :: r_vec(3)
        character(*), intent(in), optional :: site_type
        ! i think i have to use pointers again..
        ! but maybe this is really bad to deal with in the rest of the code..
        type(site) :: this
        character(*), parameter :: this_routine = "site_constructor"

        if (present(site_type)) then
            ! not yet implementetd or to do.. so wait..
            select case (site_type)

            case ('impurity', 'imp')

                call this%set_impurity(.true.)

            case ('bath')

                call this%set_bath(.true.)

            case default
                call stop_all(this_routine, &
                              "incorrect site type provided")

            end select

        else
            ! this is the default case

        end if

        if (present(k_vec)) then
            if (present(r_vec)) then
                call this%initialize(ind, n_neighbors, neighbors, k_vec, r_vec)
            else
                call this%initialize(ind, n_neighbors, neighbors, k_vec)
            end if
        else
            ASSERT(.not. present(r_vec))
            call this%initialize(ind, n_neighbors, neighbors)
        end if

    end function site_constructor

    subroutine init_site(this, ind, n_neighbors, neighbors, k_vec, r_vec)
        ! for now only use the index in the initalization
        class(site) :: this
        integer, intent(in) :: ind, n_neighbors
        integer, intent(in) :: neighbors(n_neighbors)
        integer, intent(in), optional :: k_vec(3)
        integer, intent(in), optional :: r_vec(3)

        ! for the beginning i do not need more than to set the index..
        ! independent of the type
        call this%set_index(ind)
        call this%set_num_neighbors(n_neighbors)
        call this%allocate_neighbors(n_neighbors)
        call this%set_neighbors(neighbors)

        if (present(k_vec)) then
            call this%set_k_vec(k_vec)
        end if

        if (present(r_vec)) then
            call this%set_r_vec(r_vec)
        end if

    end subroutine init_site

    subroutine set_k_vec(this, k_vec)
        class(site) :: this
        integer, intent(in) :: k_vec(3)

        this%k_vec = k_vec

    end subroutine set_k_vec

    subroutine set_r_vec(this, r_vec)
        class(site) :: this
        integer, intent(in) :: r_vec(3)

        this%r_vec = r_vec

    end subroutine set_r_vec

    subroutine set_neighbors(this, neighbors)
        class(site) :: this
        integer, intent(in) :: neighbors(this%n_neighbors)

        this%neighbors = neighbors

    end subroutine set_neighbors

    subroutine allocate_neighbors(this, n_neighbors)
        class(site) :: this
        integer, intent(in) :: n_neighbors

        ! the procedure bound routine already checks if neighbors is
        ! allocated.
        call this%deallocate_neighbors()

        allocate(this%neighbors(n_neighbors))

    end subroutine allocate_neighbors

    subroutine set_index(this, ind)
        class(site) :: this
        integer, intent(in) :: ind

        this%ind = ind

    end subroutine set_index

    integer function get_index(this)
        class(site) :: this

        get_index = this%ind

    end function get_index

    subroutine allocate_sites(this, n_sites)
        ! do we want to
        class(lattice) :: this
        integer, intent(in) :: n_sites
        character(*), parameter :: this_routine = "allocate_sites"

        if (allocated(this%sites)) then
            call stop_all(this_routine, "sites are already allocated!")
        end if

        if (n_sites < 1) then
            call stop_all(this_routine, "0 or negative number of sites!")

        else
            ! for now it is fine to allocate the sites just as "normal"
            ! sites and not as bath/impurity sites..
            ! BUT: keep this in mind!

            allocate(this%sites(n_sites))

        end if

    end subroutine allocate_sites

    subroutine set_name(this, lat_type)
        class(lattice) :: this
        character(*), intent(in) :: lat_type

        this%name = lat_type

    end subroutine set_name

    function get_name(this) result(lattice_name)
        class(lattice) :: this
        character(NAME_LEN) :: lattice_name

        lattice_name = this%name

    end function get_name

    subroutine deallocate_sites(this)
        class(lattice) :: this
        integer :: i
        ! do i need an extra deallocater for the sites?
        if (allocated(this%sites)) then
            ! i have to run over all the sites and deallocate/nullify the
            ! neighbor pointers!
            do i = 1, this%get_nsites()
                call this%sites(i)%deallocate_neighbors()
            end do

            deallocate(this%sites)
        end if

    end subroutine deallocate_sites

    subroutine deallocate_neighbors(this)
        class(site) :: this

        if (allocated(this%neighbors)) deallocate(this%neighbors)

    end subroutine deallocate_neighbors

    subroutine lattice_deconstructor(this)
        ! routine to nullify the pointer to a lattice class
        class(lattice), pointer :: this

        ! first be sure that no sites are allocated
        call this%deallocate_sites()

        nullify (this)

        select type (this)
        class is (aim)
            deallocate(this%impurity_sites)
            deallocate(this%bath_sites)
        end select

    end subroutine lattice_deconstructor

    subroutine aim_deconstructor(this)
        class(aim), pointer :: this

        call this%deallocate_sites()

        nullify (this)

    end subroutine aim_deconstructor

    function get_neighbors_site(this) result(neighbors)
        ! this is a generic routine to get the neighbors of a site
        class(site) :: this
        ! i need assumed array shape and size here or? check how i do that!
        ! can i use the stored number of neighbors in the type?
        ! i can use n_neighbors directly but not the function which gets me
        ! n_neighbors.. strange and unfortunate..
        integer :: neighbors(this%n_neighbors)

        neighbors = this%neighbors

    end function get_neighbors_site

    function get_num_neighbors_lattice(this, ind) result(n_neighbors)
        class(lattice) :: this
        integer, intent(in) :: ind
        integer :: n_neighbors
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_num_neighbors_lattice"
#endif

        ! make all assert on a seperate line, so we exactly know what is
        ! going wrong..
        ASSERT(ind <= this%get_nsites())
        ASSERT(ind > 0)
        ASSERT(allocated(this%sites))
        ASSERT(allocated(this%sites(ind)%neighbors))

        n_neighbors = this%sites(ind)%get_num_neighbors()

    end function get_num_neighbors_lattice

    function get_neighbors_lattice(this, ind) result(neighbors)
        class(lattice) :: this
        integer, intent(in) :: ind
        ! i can't really use the stored information here, since it is
        ! input dependent and this can be out of bound.. exactly what i
        ! want to avoid.. so allocate it here!
        integer, allocatable :: neighbors(:)
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_neighbors_lattice"
#endif

        ! make all assert on a seperate line, so we exactly know what is
        ! going wrong..
        ASSERT(ind <= this%get_nsites())
        ASSERT(ind > 0)
        ASSERT(allocated(this%sites))
        ASSERT(allocated(this%sites(ind)%neighbors))

        ! apparently i have to access the data directly..
        if (this%get_nsites() == 1) then
            ! output -1 to indicate that there are no neighbors, since the
            ! lattice is too small and catch errors early.. or i could
            ! users not allow to initialize such a lattice.. woudl also
            ! make sense!
            allocate(neighbors(1))
            neighbors = -1
        else
            allocate(neighbors(this%sites(ind)%get_num_neighbors()))
            neighbors = this%sites(ind)%neighbors
        end if

    end function get_neighbors_lattice

    function get_spinorb_neighbors_lat(this, spinorb) result(neighbors)
        class(lattice) :: this
        integer, intent(in) :: spinorb
        integer, allocatable :: neighbors(:)
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_spinorb_neighbors_lat"
#endif

        ASSERT(spinorb <= 2 * this%get_nsites())
        ASSERT(spinorb > 0)
        ASSERT(allocated(this%sites))
        ASSERT(allocated(this%sites(get_spatial(spinorb))%neighbors))

        neighbors = this%get_neighbors(get_spatial(spinorb))

        if (is_beta(spinorb)) then
            neighbors = 2 * neighbors - 1
        else
            neighbors = 2 * neighbors
        end if

    end function get_spinorb_neighbors_lat

    function calc_nsites_aim_star(this, length_x, length_y, length_z) result(n_sites)
        ! for the star geometry with maybe
        class(aim_star) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_aim_star"

        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        if (length_x < 1) then
            call stop_all(this_routine, "n_imps < 1!")
        end if
        if (length_y < 1) then
            call stop_all(this_routine, "n_bath < 1!")
        end if

        n_sites = length_x + length_y

    end function calc_nsites_aim_star

    function calc_nsites_star(this, length_x, length_y, length_z) result(n_sites)
        ! the maximum of the input is used as the n_sites parameter!
        ! this is the same function as the one for "chain" below..
        ! but i cannot use it somehow..
        class(star) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_star"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(this)
        unused_var(length_z)

        if (max(length_x, length_y) < 1 .or. min(length_x, length_y) > 1 .or. &
            min(length_x, length_y) < 0) then
            n_sites = -1
            call stop_all(this_routine, "something went wrong in length input!")

        else
            n_sites = max(length_x, length_y)

        end if

    end function calc_nsites_star

    logical pure function is_periodic_aim_star(this, dimen)
        class(aim_star), intent(in) :: this
        integer, intent(in), optional :: dimen
        unused_var(this)
        unused_var(dimen)
        is_periodic_aim_star = .false.

    end function is_periodic_aim_star

    function calc_nsites_chain(this, length_x, length_y, length_z) result(n_sites)
        ! i acually do not want to rely on the previous calculated
        ! length object in the class, since it is easy to recalc from
        ! here and so i remove some dependencies..
        ! nah.. i can reuse this here in the set_length routine!
        ! since the length equal the number of sites in the chain!
        class(chain) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_chain"

        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        if (max(length_x, length_y) < 1 .or. min(length_x, length_y) > 1 .or. &
            min(length_x, length_y) < 0) then
            n_sites = -1
            call stop_all(this_routine, "something went wrong in length input!")

        else
            n_sites = max(length_x, length_y)

        end if

    end function calc_nsites_chain

    function calc_nsites_cube(this, length_x, length_y, length_z) result(n_sites)
        class(cube) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_cube"

        unused_var(this)
        if (max(length_x, length_y, length_z) < 2) then
            call stop_all(this_routine, "too small cube lengths specified! (< 2)")
        end if

        n_sites = length_x * length_y * length_z

    end function calc_nsites_cube

    function calc_nsites_hexagonal(this, length_x, length_y, length_z) result(n_sites)
        class(hexagonal) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_hexagonal"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        ! the length_x of the hexagonal is defined as the number of unit cells..
        ! and there are 8 sites in my hexagonal unit cell..
        ASSERT(length_x > 0)
        ASSERT(length_y > 0)

        n_sites = length_x * length_y * 8

    end function calc_nsites_hexagonal

    function calc_nsites_kagome(this, length_x, length_y, length_z) result(n_sites)
        class(kagome) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_kagome"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        ! the length_x and length_y of the kagome are defined as the number of unit cells..
        ! and there are 8 sites in my kagome unit cell..
        ASSERT(length_x > 0)
        ASSERT(length_y > 0)

        n_sites = length_x * length_y * 6

    end function calc_nsites_kagome

    function calc_nsites_rect(this, length_x, length_y, length_z) result(n_sites)
        class(rectangle) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_rect"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(this)
        unused_var(length_z)

        if (length_x < 2 .or. length_y < 2) then
            print *, "length_x: ", length_x
            print *, "length_y: ", length_y
            call stop_all(this_routine, "length input wrong for type rectangle!")

        else
            n_sites = length_x * length_y

        end if

    end function calc_nsites_rect

    function calc_nsites_tilted(this, length_x, length_y, length_z) result(n_sites)
        class(tilted) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if
        n_sites = 2 * length_x * length_y

    end function calc_nsites_tilted

    function calc_nsites_sujun(this, length_x, length_y, length_z) result(n_sites)
        class(sujun) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        unused_var(this)
        unused_var(length_x)
        unused_var(length_y)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        n_sites = 10

    end function calc_nsites_sujun

    function calc_nsites_ole(this, length_x, length_y, length_z) result(n_sites)
        class(ole) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites
        character(*), parameter :: this_routine = "calc_nsites_ole"
        unused_var(this)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        ! oles cluster we want to look at are defined by the vectors
        ! (x,x), (-y,x) and i also think  y = x + 2 is a requisite but i am
        ! not sure.. it is a non-orthogonal unit cell!
        ! check for the validity of the input
        if (length_x < 2 .or. length_y < 2 .or. length_x == length_y .or. &
            length_x > length_y) then
            ! and as a convention y > x is enforced!
            print *, "Lx: ", length_x
            print *, "Ly: ", length_y
            call stop_all(this_routine, "incorrect input for Ole Clusters")
        end if

        ! or shorter:
        n_sites = length_x * (length_x + length_y)

    end function calc_nsites_ole

    ! Setter and getter routines for the private data of the lattice types

    subroutine set_length_aim_star(this, length_x, length_y, length_z)
        class(aim_star) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        character(*), parameter :: this_routine = "set_length_aim_star"

        unused_var(length_x)
        unused_var(length_y)
        unused_var(length_z)
        unused_var(this)

        ! actually the length of a start is not really defined..
        ! maybe i should rethink if i make this part of the
        ! original lattice class then..
        call stop_all(this_routine, "length not defined for 'star' geometry!")
        unused_var(length_x)
        unused_var(length_y)
        if (present(length_z)) then
            unused_var(length_z)
        end if
        unused_var(this)
    end subroutine set_length_aim_star

    subroutine set_length_star(this, length_x, length_y, length_z)
        class(star) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        character(*), parameter :: this_routine = "set_length_star"

        ! actually the length of a start is not really defined..
        ! maybe i should rethink if i make this part of the
        ! original lattice class then..
        call stop_all(this_routine, "length not defined for 'star' geometry!")

        unused_var(length_x)
        unused_var(length_y)
        if (present(length_z)) then
            unused_var(length_z)
        end if
        unused_var(this)
    end subroutine set_length_star

    subroutine set_length_chain(this, length_x, length_y, length_z)
        class(chain) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(length_z)

        ! the input checkin is all done in the calc_nsites routine!
        this%length = this%calc_nsites(length_x, length_y)

    end subroutine set_length_chain

    subroutine set_length_cube(this, length_x, length_y, length_z)
        class(cube) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
#ifdef DEBUG_
        character(*), parameter :: this_routine = "set_length_cube"
#endif

        ASSERT(present(length_z))
        ASSERT(length_x > 1)
        ASSERT(length_y > 1)
        ASSERT(length_z > 1)

        this%length(1) = length_x
        this%length(2) = length_y
        this%length(3) = length_z

    end subroutine set_length_cube

    subroutine set_length_rect(this, length_x, length_y, length_z)
        class(rectangle) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(length_z)

        this%length(1) = length_x
        this%length(2) = length_y

    end subroutine set_length_rect

    subroutine set_nsites(this, n_sites)
        class(lattice) :: this
        integer, intent(in) :: n_sites

        this%n_sites = n_sites

    end subroutine set_nsites

    subroutine set_ndim(this, n_dim)
        class(lattice) :: this
        integer, intent(in) :: n_dim

        this%n_dim = n_dim

    end subroutine set_ndim

    subroutine set_nconnect_max(this, n_connect_max)
        class(lattice) :: this
        integer, intent(in) :: n_connect_max

        this%n_connect_max = n_connect_max

    end subroutine set_nconnect_max

    subroutine set_periodic(this, t_periodic_x, t_periodic_y, t_periodic_z)
        class(lattice) :: this
        logical, intent(in) :: t_periodic_x, t_periodic_y
        logical, intent(in), optional :: t_periodic_z

        ! how do we decide which periodic flag to take?
        ! well we just set it like that! the user has to be specific!
        ! well.. but we do not want to ask if this x or y is periodic in
        ! the 1dim case or?
        ! periodic is the default.. and we want to turn off periodic by
        ! inputting open-bc.. and in the case of the cain + open-bc this
        ! means immediately that it is NOT periodic.. so only if both inputs
        ! are true (which is the default case) the chain is set to be
        ! periodic! (or in the is_periodic procedure, we check if
        ! both are true!
        this%t_periodic_x = t_periodic_x
        this%t_periodic_y = t_periodic_y

        this%t_periodic(1) = t_periodic_x
        this%t_periodic(2) = t_periodic_y
        if (present(t_periodic_z)) then
            this%t_periodic(3) = t_periodic_z
        end if

    end subroutine set_periodic

    integer function get_nconnect_max(this)
        class(lattice) :: this

        get_nconnect_max = this%n_connect_max

    end function get_nconnect_max

    ! the star does not really have a concept of length..
    ! so always ouput STAR_LENGTH
    integer pure function get_length_star(this, dimen)
        class(star), intent(in) :: this
        integer, intent(in), optional:: dimen
        unused_var(dimen)
        unused_var(this)

        get_length_star = STAR_LENGTH

    end function get_length_star

    integer pure function get_length_chain(this, dimen)
        class(chain), intent(in) :: this
        integer, intent(in), optional :: dimen

        if (present(dimen)) then
            if (dimen > 1) then
                get_length_chain = 1
            else
                get_length_chain = this%length
            end if
        else
            get_length_chain = this%length
        end if

    end function get_length_chain

    integer pure function get_length_cube(this, dimen)
        class(cube), intent(in) :: this
        integer, intent(in), optional :: dimen
#ifdef DEBUG_
        character(*), parameter :: this_routine = "get_length_cube"
#endif

        ASSERT(present(dimen))
        ASSERT(dimen > 0)
        ASSERT(dimen <= 3)

        get_length_cube = this%length(dimen)

    end function get_length_cube

    integer pure function get_length_rect(this, dimen)
        class(rectangle), intent(in) :: this
        integer, intent(in), optional :: dimen
        character(*), parameter :: this_routine = "get_length_rect"

        ASSERT(present(dimen))
        ASSERT(dimen > 0)

        if (dimen > 2) then
            get_length_rect = 1
        else
            get_length_rect = this%length(dimen)
        end if

    end function get_length_rect

    integer pure function get_length_aim_chain(this, dimen)
        class(aim_chain), intent(in) :: this
        integer, intent(in), optional :: dimen

        if (present(dimen) .and. dimen > 1) then
            get_length_aim_chain = 1
        else
            get_length_aim_chain = this%length
        end if

    end function get_length_aim_chain

    subroutine set_length_aim_chain(this, length_x, length_y, length_z)
        class(aim_chain) :: this
        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        unused_var(length_x)
        if (present(length_z)) then
            unused_var(length_z)
        end if

        unused_var(length_x)
        unused_var(length_z)

        ! as a definition make the length, even for multiple impurity chains
        ! as bath_sites + 1
        this%length = length_y + 1

    end subroutine set_length_aim_chain

    logical pure function is_periodic_star(this, dimen)
        ! this is always false.. the star geometry can't be periodic
        class(star), intent(in) :: this
        integer, intent(in), optional :: dimen
        debug_function_name("is_periodic_star")
        unused_var(this)
        ASSERT(.not. present(dimen))

        is_periodic_star = .false.

    end function is_periodic_star

    logical pure function is_periodic_chain(this, dimen)
        class(chain), intent(in) :: this
        integer, intent(in), optional :: dimen
        debug_function_name("is_periodic_chain")
        ASSERT(.not. present(dimen))
        is_periodic_chain = this%t_periodic(1)
    end function is_periodic_chain

    logical pure function is_periodic_cube(this, dimen)
        class(cube), intent(in) :: this
        integer, intent(in), optional :: dimen
        debug_function_name("is_periodic_cube")
        if (present(dimen)) then
            ASSERT(1 <= dimen .and. dimen <= 3)
            is_periodic_cube = this%t_periodic(dimen)

        else
            is_periodic_cube = all(this%t_periodic)
        end if
    end function is_periodic_cube

    logical pure function is_periodic_rect(this, dimen)
        class(rectangle), intent(in) :: this
        integer, intent(in), optional :: dimen
        debug_function_name("is_periodic_rect")
        if (present(dimen)) then
            ASSERT(1 <= dimen .and. dimen <= 3)
            is_periodic_rect = this%t_periodic(dimen)
        else
            is_periodic_rect = all(this%t_periodic)
        end if
    end function is_periodic_rect

    logical function is_periodic_x(this)
        class(lattice) :: this

        is_periodic_x = this%t_periodic_x

    end function is_periodic_x

    logical function is_periodic_y(this)
        class(lattice) :: this

        is_periodic_y = this%t_periodic_y

    end function is_periodic_y

    integer elemental function get_ndim(this)
        class(lattice), intent(in) :: this
        get_ndim = this%n_dim
    end function get_ndim

    integer elemental function get_nsites(this)
        class(lattice), intent(in) :: this
        get_nsites = this%n_sites
    end function get_nsites

    subroutine print_lat(this)
        class(lattice) :: this

        ! depending on the type print specific lattice information
        select type (this)
        class is (lattice)

            call stop_all("lattice%print()", &
                          "lattice type should never be directly instantiated!")

        class is (chain)

            print *, "Lattice type is: 'chain' "
            print *, " number of dimensions: ", this%get_ndim()
            print *, " max-number of neighbors: ", this%get_nconnect_max()
            print *, " number of sites of chain: ", this%get_nsites()
            print *, " is the chain periodic: ", this%is_periodic()

        class is (rectangle)

            if (trim(this%get_name()) == 'tilted') then
                print *, "Lattice type is 'tilted' "
            else
                print *, "Lattice type is 'rectangle' "
            end if

            print *, " number of dimensions: ", this%get_ndim()
            print *, " max-number of neighbors: ", this%get_nconnect_max()
            print *, " number of sites in the lattice: ", this%get_nsites()
            if (this%is_periodic()) then
                print *, " is the lattice periodic: True"
            end if
            print *, "primitive lattice vectors: (", this%lat_vec(1:2, 1), "), (", this%lat_vec(1:2, 2), ")"

        class is (ole)
            print *, "Lattice type is 'Ole' ;) "
            print *, "number of dimensions: ", this%get_ndim()
            print *, "max-number of neigbors: ", this%get_nconnect_max()
            print *, "number of sites: ", this%get_nsites()
            print *, "primitive lattice vectors: (", this%lat_vec(1:2, 1), "), (", this%lat_vec(1:2, 2), ")"
            print *, "TODO: more and better output! "

        class is (ext_input)
            print *, "Lattice read from lattice.file file "
            print *, "Lattice type is :",this%get_name()
            print *, "number of sites in the lattice: ", this%get_nsites()
            print *, "max-number of neigbors: ", this%get_nconnect_max()
            if (this%is_periodic()) then
                print *, " is the lattice periodic: True"
            end if

        end select

    end subroutine print_lat

    function determine_optimal_time_step(time_step_death) result(time_step)
        use SystemData, only: nel, bhub, uhub, t_new_real_space_hubbard, &
                              t_tJ_model, t_heisenberg_model, exchange_j, &
                              nOccAlpha, nOccBeta, t_k_space_hubbard, omega, &
                              nbasis

        real(dp), optional, intent(out) :: time_step_death
        real(dp) :: time_step
        ! move this time-step determination to this routine for the real
        ! space hubbard to have it fully conained

        real(dp) :: p_elec, p_hole, mat_ele, max_diag

        ! determine the optimal hubbard time-step for an optimized
        ! hubbard excitation generation
        ! the first electron is chosen at random
        if (t_k_space_hubbard) then
            p_elec = 1.0_dp / real(nOccAlpha * nOccBeta, dp)
        else
            p_elec = 1.0_dp / real(nel, dp)
        end if

        ! and for a picked electron the possible neighbors are looked for
        ! open holes so the lowest probability is determined by the
        ! maximum numbers of connections
        ! do this in general in the same way for all types of
        ! lattice models. thats not exact, but a good enough estimate
        if (t_k_space_hubbard) then
            p_hole = 2.0_dp / real(nbasis - nel, dp)
        else
            p_hole = 1.0_dp / real(lat%get_nconnect_max(), dp)
        end if

        if (t_new_real_space_hubbard) then

            mat_ele = real(abs(bhub), dp)

        else if (t_tJ_model) then

            ! for the t-J take the maximum of hopping or exchange
            mat_ele = real(max(abs(bhub), abs(exchange_j)), dp)

        else if (t_heisenberg_model) then

            mat_ele = real(abs(exchange_j), dp)

        else if (t_k_space_hubbard) then
            mat_ele = abs(real(uhub, dp) / real(omega, dp))

        end if

        ! so the time-step is
        time_step = p_elec * p_hole / mat_ele

        if (present(time_step_death)) then
            ! the maximum death contribution is max(H_ii) - shift
            ! and the maximum diagonal matrix element we know it is
            ! the maximum possible number of doubly occupied sites times U
            ! but this might be a too hard limit, since especially for high U
            ! such an amount of double occupations is probably never reached
            ! and the shift must also be included in the calculation..
            if (t_new_real_space_hubbard) then
                max_diag = real(abs(uhub) * min(nOccAlpha, nOccBeta), dp)
            else if (t_tJ_model) then
                print *, "todo!"
            else if (t_heisenberg_model) then
                ! we have to find the maximum number of non-frustrated
                ! nearest neighbor spins!
                print *, "todo: "
            else if (t_k_space_hubbard) then
                print *, "todo"

            end if

            time_step_death = 1.0_dp / max_diag
        end if

    end function determine_optimal_time_step

    function read_lattice_n_sites(this, length_x, length_y, length_z) result(n_sites)

        class(ext_input):: this

        integer, intent(in) :: length_x, length_y
        integer, intent(in), optional :: length_z
        integer :: n_sites


        CHARACTER(LEN=100) w
        type(ManagingFileReader_t) :: file_reader
        type(TokenIterator_t) :: tokens

        unused_var(this)
        unused_var(length_x)
        unused_var(length_y)

        if (present(length_z)) then
            unused_var(length_z)
        end if

        file_reader = ManagingFileReader_t("lattice.file")

        lat: do while (file_reader%nextline(tokens, skip_empty=.true.))
            w = to_upper(tokens%next())
            select case (w)
            case ('N_SITES')
                n_sites = to_int(tokens%next())
            end select
        end do lat

        call file_reader%close()
    end function read_lattice_n_sites


    subroutine read_lattice_struct(this)

        class(ext_input):: this

        integer :: x1,y1, x2,y2

        CHARACTER(LEN=100) w
        type(ManagingFileReader_t) :: file_reader
        type(TokenIterator_t) :: tokens

        file_reader = ManagingFileReader_t("lattice.file")

        lat: do while (file_reader%nextline(tokens, skip_empty=.true.))
            w = to_upper(tokens%next())

            select case (w)
            case ('DIM')
                call this%set_ndim(to_int(tokens%next()))

            case ('LATTICE_TYPE')
                this%name = to_upper(tokens%next())

            case ('LATTICE_PARAM')
                x1 = to_int(tokens%next())
                y1 = to_int(tokens%next())
                x2 = to_int(tokens%next())
                y2 = to_int(tokens%next())

                this%lat_vec(1:2, 1) = [x1, y1]
                this%lat_vec(1:2, 2) = [x2, y2]

            case ('N_CONNECT_MAX')
                this%n_connect_max = to_int(tokens%next())
            end select
        end do lat

        call this%set_length(1, 3)

        call file_reader%close()

    end subroutine read_lattice_struct


    ! These wrapper functions just exist because of this bug
    ! https://github.com/Fortran-FOSS-Programmers/ford/issues/477
    ! call the pointers in the future directly.
    function get_helement_lattice_ex_mat_wrapper(nI, ic, ex, tpar) result(hel)
        integer, intent(in) :: nI(nel), ic, ex(2, ic)
        logical, intent(in) :: tpar
        HElement_t(dp) :: hel
        hel = get_helement_lattice_ex_mat(nI, ic, ex, tpar)
        if (tStoquastize .and. ic /= 0) hel = -abs(hel)        
    end function

    ! These wrapper functions just exist because of this bug
    ! https://github.com/Fortran-FOSS-Programmers/ford/issues/477
    ! call the pointers in the future directly.
    function get_helement_lattice_general_wrapper(nI, nJ, ic_ret) result(hel)
        integer, intent(in) :: nI(nel), nJ(nel)
        integer, intent(inout), optional :: ic_ret
        HElement_t(dp) :: hel
        hel = get_helement_lattice_general(nI, nJ, ic_ret)
        if (tStoquastize .and. .not. all(nI == nJ)) hel = -abs(hel)        
    end function
end module lattice_mod