/* DFT++ is a density functional package developed by the research group of Professor Tomas Arias Copyright 1996-2003 Sohrab Ismail-Beigi This file is part of DFT++. DFT++ is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DFT++ is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DFT++; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Please see the file CREDITS for a list of authors. For academic users, we request that publications using results obtained with this software reference "New algebraic formulation of density functional calculation," by Sohrab Ismail-Beigi and T.A. Arias, Computer Physics Communications 128:1-2, 1-45 (June 2000). and, if using the wavelet basis, further reference "Multiresolution analysis of electronic structure: semicardinal and wavelet bases," T.A. Arias, Reviews of Modern Physics 71:1, 267-311 (January 1999). and "Robust ab initio calculation of condensed matter: transparent convergence through semicardinal multiresolution analysis,'' I.P. Daykov, T.A. Arias, and Torkel D. Engeness, Physical Review Letters, 90:21, 216402 (May 2003). For your convenience, preprints of the above articles may be obtained from http://arXiv.org/abs/cond-mat/9909130, 9805262, and 0204411, respectively. */ /* * Nikolaj Moll April 10, 1999 * * Add support for symmetry calculation. */ /* $Id: symm.cpp,v 1.16.2.21 2003/05/29 18:54:33 ivan Exp $ */ #include "header.h" #ifdef _WIN32 double rint(double x) { return (floor(x+.5)); } #endif /* * symmetry recongnition of the ionic configuration */ void symmetries(Ioninfo *ioninfo, Lattice *lattice, Symmetries *symm) { int sp, n1, n2, i; int bnrot = 0, tnrot = 0; vector3 r, tr; //zeroed by the default constructor matrix3 bsym[48], tsym[48], tmp, identity(1.0, 1.0, 1.0); symm->nrot = 0; dft_log("\nautomatic search of point group symmetries:\n"); /* find symmetries of bravais lattice */ bravais_symmetries(lattice, bnrot, bsym); /* first find symmetries without translation */ basis_symmetries(ioninfo, bnrot, bsym, r, symm->nrot, symm->sym); /* use the atomic positions (ipd: n2=n1 below) and the center of two * atoms of the same species as a symmetry point */ for (sp = 0; sp < ioninfo->nspecies; sp++) { for (n1 = 0; n1 < ioninfo->species[sp].natoms; n1++) { for (n2 = 0; n2 <= n1; n2++) { tr = (ioninfo->species[sp].atpos[n1] + ioninfo->species[sp].atpos[n2])/2.0; /* find subgroup of symmetries */ basis_symmetries(ioninfo, bnrot, bsym, tr, tnrot, tsym); //ipd: a little debugging info //dft_log("Symm center: "); //tr.print(dft_global_log,"%le "); //dft_log("Nsymm: %d\n", tnrot); if (tnrot > symm->nrot) { r = tr; symm->nrot = tnrot; for (i = 0; i < symm->nrot; i++) symm->sym[i] = tsym[i]; } } } } /* sort, so identity is the first matrix */ for (i = 0; i < symm->nrot; i++) if (matcmp(symm->sym[i], identity) < MIN_SYMM_TOL) { tmp = symm->sym[0]; symm->sym[0] = symm->sym[i]; symm->sym[i] = tmp; } /* print out the symmetries */ dft_log("reduced to %d total symmetries with basis:\n\n", symm->nrot); for (i = 0; i < symm->nrot; i++) { symm->sym[i].print(dft_global_log,"%4.0f "); dft_log("\n"); } dft_log_flush(); /* initialize the symmetry matrices for forces */ matrix3 AtA, invAtA; AtA = lattice->RTR; invAtA = lattice->invRTR; for (i = 0 ; i < symm->nrot; i++) { symm->f_sym[i] = AtA * symm->sym[i] * invAtA; } dft_log("symmetries for forces:\n\n"); for (i = 0; i < symm->nrot; i++) { symm->f_sym[i].print(dft_global_log,"%4.0f "); dft_log("\n"); } dft_log_flush(); #ifdef PLANEWAVES /* move atoms to symmetry point */ if (abs(r) > MIN_SYMM_TOL) { dft_log("moving atoms to new symmetry point: "); r.print(dft_global_log,"%g "); for (sp = 0; sp < ioninfo->nspecies; sp++) { for (n1 = 0; n1 < ioninfo->species[sp].natoms; n1++) { ioninfo->species[sp].atpos[n1] -= r; } } } #elif defined WAVELETS // save r in the symmetry class symm->Rsym=r; dft_log("Center of symmetry for the above symmetries: "); symm->Rsym.print(dft_global_log,"%le "); #endif } // // Finds the applicable symmetries of the Bravais lattice from among // all possible symmetries // INPUT: // lattice // OUTPUT: // bnrot - number of the symmetries (rotations) found // bsym - array of the symmetry matrices // void bravais_symmetries(Lattice *lattice, int &bnrot, matrix3 *bsym) { int i; matrix3 a, g, m, mgm, t(1.0, 1.0, 1.0), identity(1.0, 1.0, 1.0); /* transpose lattice vector matrix */ a = lattice->latvec; /* find reduced basis and transmission matrix t */ minimize_basis(a, t); /* print transmission matrix if it is not equal to the identity */ if (matcmp(t, identity) > MIN_SYMM_TOL) { dft_log("\ntransmission matrix:\n\n"); t.print(dft_global_log,"%4.0f "); dft_log("\nwith the corresponding reduced lattice vectors:\n\n"); a.print(dft_global_log,"%12.6f "); } /* calculate metric */ g = (~a)*a; /* test all possible symmetries */ bnrot = 0; for (m.m[0][0] = -1.0; m.m[0][0] <= 1.0; m.m[0][0]++) for (m.m[0][1] = -1.0; m.m[0][1] <= 1.0; m.m[0][1]++) for (m.m[0][2] = -1.0; m.m[0][2] <= 1.0; m.m[0][2]++) for (m.m[1][0] = -1.0; m.m[1][0] <= 1.0; m.m[1][0]++) for (m.m[1][1] = -1.0; m.m[1][1] <= 1.0; m.m[1][1]++) for (m.m[1][2] = -1.0; m.m[1][2] <= 1.0; m.m[1][2]++) for (m.m[2][0] = -1.0; m.m[2][0] <= 1.0; m.m[2][0]++) for (m.m[2][1] = -1.0; m.m[2][1] <= 1.0; m.m[2][1]++) for (m.m[2][2] = -1.0; m.m[2][2] <= 1.0; m.m[2][2]++) { /* determinant of symmetry has to be +-1 */ if (fabs(fabs(det3(m)) - 1.0) < MIN_SYMM_TOL) { /* calculate transformed metric */ mgm = (~m)*g*m; /* compare orignal and transformed metric */ if (matcmp(g, mgm) < MIN_SYMM_TOL) /* transposed symmetry matrix, because matrix a is transposed */ bsym[bnrot++] = m; } } /* transform symmetries from reduced basis to orginal basis */ dft_log("\n%d symmtries of the bravais lattice\n", bnrot); for (i = 0; i < bnrot; i++) bsym[i] = t*bsym[i]*inv3(t); dft_log_flush(); } void minimize_basis(matrix3 &a, matrix3 &t) { matrix3 d(1.0, 1.0, 1.0), new_a; int change, k1, k2, k3, i, j; do { change = FALSE; for (k1 = 0; k1 < 3; k1 ++) { k2 = (k1 + 1)%3; k3 = (k1 + 2)%3; for (i = -1; i <= 1; i++) for (j = -1; j <= 1; j++) { d.m[k1][k1] = 1.0; d.m[k1][k2] = 0.0; d.m[k1][k3] = 0.0; d.m[k2][k1] = i; d.m[k2][k2] = 1.0; d.m[k2][k3] = 0.0; d.m[k3][k1] = j; d.m[k3][k2] = 0.0; d.m[k3][k3] = 1.0; new_a = a * d; if (trace3((~new_a)*new_a) + MIN_SYMM_TOL < trace3((~a)*a)) { change = TRUE; a = new_a; t = t * d; } } } } while (change); } real matcmp(matrix3 a, matrix3 b) { matrix3 tmp; real sum = 0.0; int i, j; tmp = a - b; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) sum += fabs(tmp.m[i][j]); return(sum); } // // Finds a subgroup of symmetries satisfied by the unit cell basis // for a specific center of point symmetry // // INPUT: // ioninfo - information about the ions (unit cell basis) // bnrot - number of symemtries in the input group // bsym - array of symmetry matrix (input symmetry group) // tr - center of symmetry that we're going to check // OUTPUT: // tnrot - number of elements in the subgroup // tsym - array of symmetry matrices in the subgroup // void basis_symmetries(Ioninfo *ioninfo, int &bnrot, matrix3 *bsym, vector3 tr, int &tnrot, matrix3 *tsym) { vector3 **pos, new_pos; int bsym_basis[48], sp, natoms, n, n1, irot, found, i; /* get memory for new atom positions */ pos = (vector3**)mymalloc(sizeof(vector3*)*ioninfo->nspecies, "pos", "basis_symmetries"); // loop over the different types of atoms in the system for (sp = 0; sp < ioninfo->nspecies; sp++) { //allocate space for atomic coordinates for this spiece natoms = ioninfo->species[sp].natoms; pos[sp] = (vector3*)mymalloc(sizeof(vector3)*natoms, "pos[]", "basis_symmetries"); // loop over atom of this kind for (n = 0; n < natoms; n++) { /* move atoms to new symmetry point */ pos[sp][n] = ioninfo->species[sp].atpos[n] - tr; for (i = 0; i < 3; i++) { pos[sp][n].v[i] = fmod(pos[sp][n].v[i], 1.0); if (pos[sp][n].v[i] < 0.0) pos[sp][n].v[i]++; } } } // now check for symmetries for (irot = 0; irot < bnrot; irot++) { bsym_basis[irot] = TRUE; for (sp = 0; sp < ioninfo->nspecies && bsym_basis[irot]; sp++) { natoms = ioninfo->species[sp].natoms; for (n = 0; n < natoms && bsym_basis[irot]; n++) { new_pos = bsym[irot]*pos[sp][n]; for (i = 0; i < 3; i++) { new_pos.v[i] = fmod(new_pos.v[i], 1.0); if (new_pos.v[i] < 0.0) new_pos.v[i]++; } /* look if there is an equivalent atom */ for (found = FALSE, n1 = 0; n1 < natoms && !found; n1++) if (abs(new_pos - pos[sp][n1]) < MIN_SYMM_TOL) found = TRUE; bsym_basis[irot] = found; } } } /* copy symmetries which are symmetries of the basis */ tnrot = 0; for(irot = 0; irot < bnrot; irot++) if (bsym_basis[irot]) tsym[tnrot++] = bsym[irot]; for (sp = 0; sp < ioninfo->nspecies; sp++) myfree(pos[sp]); myfree(pos); } void check_symmetries(Ioninfo *ioninfo, Symmetries *symm) { int irot, sp, natoms, n, i, n1, found; vector3 new_pos; for (irot = 0; irot < symm->nrot; irot++) { for (sp = 0; sp < ioninfo->nspecies; sp++) { natoms = ioninfo->species[sp].natoms; for (n = 0; n < natoms; n++) { new_pos = symm->sym[irot]*ioninfo->species[sp].atpos[n]; for (i = 0; i < 3; i++) { new_pos.v[i] = fmod(new_pos.v[i], 1.0); if (new_pos.v[i] < 0.0) new_pos.v[i]++; } /* look if there is an equivalent atom */ for (found = FALSE, n1 = 0; n1 < natoms && !found; n1++) if (abs(new_pos - ioninfo->species[sp].atpos[n1]) < MIN_SYMM_TOL) found = TRUE; if (!found) { dft_log(DFT_SILENCE, "symmetry: %d species: %d atom: %d\n", irot, sp, n); die("Symmetries do not agree with atomic positions!\n"); } } } } } /* * fold_kpoints * * Fold the kpoints according to symmetries. * * Ref: * H.J.Monkhorst, J.D.Pack, PRB 13, 5188, 1976 * */ int fold_kpoints(vector3 *old_kvec, vector3 **new_kvec, real *old_w, real **new_w, const int *kpt_fold, int nkpts, int &new_nkpts) { int i[3], j, k; vector3 *kvec1, *kvec0 = old_kvec; real *w1, *w0 = old_w; int total_fold = kpt_fold[0] * kpt_fold[1] * kpt_fold[2]; // Ok, let's go. if (total_fold <= 0) { dft_log(DFT_SILENCE, "Why would you want fold to be 0? %d %d %d\n", kpt_fold[0], kpt_fold[1], kpt_fold[2]); // die("You are nuts. I will quit.\n"); return FALSE; } else if (total_fold == 1) // do nothing return FALSE; // move all components of kpoints to between 0 and 1 for (k = 0; k < nkpts; k++) { for (j = 0; j < 3; j++) { kvec0[k].v[j] = fmod(kvec0[k].v[j], 1.0); if (kvec0[k].v[j] < 0.0) kvec0[k].v[j] += 1.0; } } // allocate temporary storages. kvec1 = (vector3 *)mymalloc(sizeof(vector3)*total_fold*nkpts, "kvec","fold_kpoints"); w1 = (real *) mymalloc(sizeof(real)*total_fold*nkpts, "w","setup_elecinfo"); int new_n = 0; for (i[0] = 0; i[0] < kpt_fold[0]; i[0]++) for (i[1] = 0; i[1] < kpt_fold[1]; i[1]++) for (i[2] = 0; i[2] < kpt_fold[2]; i[2]++) for (k = 0; k < nkpts; k++) { for (j = 0; j < 3; j++) { if (kpt_fold[j] > 1) { kvec1[new_n].v[j] = (kvec0[k].v[j] + i[j])/kpt_fold[j]; } else { kvec1[new_n].v[j] = kvec0[k].v[j]; } } w1[new_n] = w0[k]/total_fold; new_n++; } // modify the number of kpoints. new_nkpts = nkpts * total_fold; // get the kvec and w to point to the newly folded lists. *new_kvec = kvec1; *new_w = w1; // output the folded kpoint coordinates dft_log("kpoint folding with mesh: %d x %d x %d\n", kpt_fold[0], kpt_fold[1], kpt_fold[2]); for (k = 0; k < nkpts; k++) { dft_log("%5d\t[ %4.2f %4.2f %4.2f ] %4.2f\n",k, kvec1[k].v[0], kvec1[k].v[1], kvec1[k].v[2], w1[k]); } dft_log("\n"); return TRUE; } /* * reduce_kpoints * * Reduce the kpoints according to system symmetries. * * Requirement: all components of kvec[] must be between 0 and 1. */ void reduce_kpoints(const vector3 *old_kvec, real *old_w, vector3 **new_kvec, real **new_w, int nkpts, int &new_nkpts, const Symmetries &symm, Elecinfo &elecinfo, const Lattice &lattice, int reduce_kpts_flag) { const matrix3 &GGT = lattice.GGT; matrix3 invGGT = lattice.invGGT; const matrix3 identity(1.0,1.0,1.0); matrix3 sym[48]; vector3 k_new, k_tmp; int i, ii, j, k, *found = NULL, nrot = symm.nrot; real diff1, diff2, total_w; // check that first one is identity if ( identity != symm.sym[0] ) { die("reduce_kpoints: first symmetry operation is not identity"); } /* * if not reduce_kpts_flag, then set symmetry numbers to 0. * (i.e. not used) * so that the rest of the subroutine just allocates and copy * over the kpoint information to elecinfo. */ if ( !reduce_kpts_flag ) nrot = 0; // get the symmetry matrix for k space /* * Sk = (~A A) Sr (G ~G) / (2pi)^2 * note: (~A A / (2pi)^2) = inv ( G ~G ) * */ for (i = 0; i < nrot; i++) { sym[i] = invGGT * symm.sym[i] * GGT; } // initialize found array to false. found = (int*)mymalloc(sizeof(int)*nkpts,"found","reduce_kpoints"); for (i = 1; i < nkpts; i++) found[i] = FALSE; /* for each original k-point, * if not found yet, put it in new k-point list, * for each symmetry, if transformed k-point matches another * original k-point, list that one as found, add its weight * to the present k-point. */ new_nkpts = 0; for (i = 0; i < nkpts; i++) { // for each original k-point, if ( !found[i] ) { // if not yet found. new_nkpts++; // add to new-kpoint list for (j = 0; j < nrot; j++) { // for each symmetry k_new = sym[j] * old_kvec[i]; // produce transformed k. // get k_new in betweenn 0..1; for (ii = 0; ii < 3; ii++) { k_new.v[ii] = fmod(k_new.v[ii], 1.0); if (k_new.v[ii] < 0.0) k_new.v[ii] += 1.0; } // compare with remaining kpoints for (k = i+1; k < nkpts; k++) { // get k_tmp in betweenn 0..1 to compare to k_new; for (ii = 0; ii < 3; ii++) { k_tmp.v[ii] = fmod(old_kvec[k].v[ii], 1.0); if (k_tmp.v[ii] < 0.0) k_tmp.v[ii] += 1.0; } diff1 = abs2(k_new - k_tmp); // for now, only use inversion symmetry for NOSPIN case. if (elecinfo.spintype == NOSPIN) { // check also for inversion symmetry. for (ii = 0; ii < 3; ii++) { if (k_tmp.v[ii] > 0.0) k_tmp.v[ii] -= 1.0; } diff2 = abs2(k_new + k_tmp); } else { diff2 = MIN_KPT_DISTANCE * 10; } if ( ((diff1 < MIN_KPT_DISTANCE) || (diff2 < MIN_KPT_DISTANCE)) && (! found[k]) ) { found[k] = TRUE; old_w[i] += old_w[k]; } } } } } if (new_nkpts == nkpts) dft_log("reduce_kpoints: No reducable k-point discovered.\n"); else dft_log("reduce_kpoints: number of k-points reduced to %d.\n", new_nkpts); // calculate weight renormalization in case error has been accumulated. for (i = 0, total_w = 0.0; i < nkpts; i++) if ( !found[i] ) total_w += old_w[i]; *new_kvec = (vector3 *)mymalloc(sizeof(vector3)*new_nkpts, "new_kvec", "reduce_kpoints"); *new_w = (real *) mymalloc(sizeof(real)*new_nkpts, "new_w","reduce_kpoints"); for (i = j = 0; i < nkpts; i++) { if ( !found[i] ) { (*new_kvec)[j] = old_kvec[i]; (*new_w)[j] = old_w[i] / total_w; j++; } } myfree(found); // output the reduced kpoint coordinates for (k = 0; k < new_nkpts; k++) { dft_log("%5d\t[ %4.2f %4.2f %4.2f ] %4.2f \n",k, (*new_kvec)[k].v[0], (*new_kvec)[k].v[1], (*new_kvec)[k].v[2], (*new_w)[k]); } dft_log("\n"); return; } /* * map_symm_atom * * Map atoms to symmetry related ones if needs to calculate force. */ int map_symm_atom(Ioninfo &ioninfo, const Symmetries &symm) { int nrot = symm.nrot, irot, sp, nat, nat1, i; int found; Speciesinfo *species = ioninfo.species; vector3 new_pos, new_pos2; for (sp = 0; sp < ioninfo.nspecies; sp++) for (nat = 0; nat < species[sp].natoms; nat++) { for (irot = 0; irot < nrot; irot++) { new_pos = symm.sym[irot]*species[sp].atpos[nat]; for (found = FALSE, nat1 = 0; (nat1 < species[sp].natoms) && (!found); nat1++) { new_pos2 = new_pos - species[sp].atpos[nat1]; for (i = 0; i < 3; i++) { new_pos2.v[i] = fmod(new_pos2.v[i], 1.0); if (fabs(new_pos2.v[i]) > fabs(new_pos2.v[i]+1.0)) new_pos2.v[i]++; else if (fabs(new_pos2.v[i]) > fabs(new_pos2.v[i]-1.0)) new_pos2.v[i]--; } if (abs(new_pos2) < MIN_SYMM_TOL) { symm.maps[sp][irot][nat] = nat1; found = TRUE; } } if (!found) die("Species %d, atom %d, symm %d not found!!\n", sp, nat, irot); } } if (nrot <= 1) return 1; dft_log("Mapping of atoms according to symmetries:\n"); for (sp = 0; sp < ioninfo.nspecies; sp++) { for (nat = 0; nat < species[sp].natoms; nat++) { dft_log("%3d %3d : ",sp,nat); for (irot = 0; irot < nrot; irot++) { dft_log(" %3d",symm.maps[sp][irot][nat]); } dft_log("\n"); } } dft_log_flush(); return 1; }