void llg_stt_rhs(double *dm_dt, double *m, double *h, double *h_stt, double *alpha, double beta, double u0, double gamma, int n) { #pragma omp parallel for for (int index = 0; index < n; index++) { int i = 3 * index; int j = 3 * index + 1; int k = 3 * index + 2; double coeff = -gamma / (1 + alpha[index] * alpha[index]); double mm = m[i] * m[i] + m[j] * m[j] + m[k] * m[k]; double mh = m[i] * h[i] + m[j] * h[j] + m[k] * h[k]; //hp=mm.h-mh.m=-mx(mxh) double hpi = mm*h[i] - mh*m[i]; double hpj = mm*h[j] - mh*m[j]; double hpk = mm*h[k] - mh*m[k]; double mth0 = cross_x(m[i], m[j], m[k], hpi, hpj, hpk); double mth1 = cross_y(m[i], m[j], m[k], hpi, hpj, hpk); double mth2 = cross_z(m[i], m[j], m[k], hpi, hpj, hpk); dm_dt[i] = coeff * (mth0 - hpi * alpha[index]); dm_dt[j] = coeff * (mth1 - hpj * alpha[index]); dm_dt[k] = coeff * (mth2 - hpk * alpha[index]); //the above part is standard LLG equation. double coeff_stt = u0 / (1 + alpha[index] * alpha[index]); double mht = m[i] * h_stt[i] + m[j] * h_stt[j] + m[k] * h_stt[k]; hpi = mm*h_stt[i] - mht * m[i]; hpj = mm*h_stt[j] - mht * m[j]; hpk = mm*h_stt[k] - mht * m[k]; mth0 = cross_x(m[i], m[j], m[k], hpi, hpj, hpk); mth1 = cross_y(m[i], m[j], m[k], hpi, hpj, hpk); mth2 = cross_z(m[i], m[j], m[k], hpi, hpj, hpk); dm_dt[i] += coeff_stt * ((1 + alpha[index] * beta) * hpi - (beta - alpha[index]) * mth0); dm_dt[j] += coeff_stt * ((1 + alpha[index] * beta) * hpj - (beta - alpha[index]) * mth1); dm_dt[k] += coeff_stt * ((1 + alpha[index] * beta) * hpk - (beta - alpha[index]) * mth2); double c = 6 * sqrt(dm_dt[i] * dm_dt[i] + dm_dt[j] * dm_dt[j] + dm_dt[k]* dm_dt[k] ); dm_dt[i] += c * (1 - mm) * m[i]; dm_dt[j] += c * (1 - mm) * m[j]; dm_dt[k] += c * (1 - mm) * m[k]; } }
void llg_rhs_baryakhtar(double *dm_dt, double *m, double *h, double *delta_h, double *alpha, double beta, int *pins, double gamma, int nxyz, int do_precession) { #pragma omp parallel for for (int id = 0; id < nxyz; id++) { int i = 3*id; int j = i + 1; int k = j + 1; if (pins[id]>0) { dm_dt[i] = 0; dm_dt[j] = 0; dm_dt[k] = 0; continue; } double coeff = -gamma; if (do_precession) { dm_dt[i] = coeff*cross_x(m[i],m[j],m[k],h[i],h[j],h[k]); dm_dt[j] = coeff*cross_y(m[i],m[j],m[k],h[i],h[j],h[k]); dm_dt[k] = coeff*cross_z(m[i],m[j],m[k],h[i],h[j],h[k]); } dm_dt[i] += gamma*(alpha[i]*h[i] - beta*delta_h[i]); dm_dt[j] += gamma*(alpha[i]*h[j] - beta*delta_h[j]); dm_dt[k] += gamma*(alpha[i]*h[k] - beta*delta_h[k]); } }
void llg_rhs_baryakhtar_reduced(double *dm_dt, double *m, double *hp, double *delta_hp, double *alpha, double beta, int *pins, double gamma, int nxyz, int do_precession, double default_c) { #pragma omp parallel for for (int id = 0; id < nxyz; id++) { int i = 3*id; int j = i + 1; int k = j + 1; if (pins[id]>0) { dm_dt[i] = 0; dm_dt[j] = 0; dm_dt[k] = 0; continue; } double coeff = -gamma; if (do_precession) { dm_dt[i] = coeff*cross_x(m[i],m[j],m[k],hp[i],hp[j],hp[k]); dm_dt[j] = coeff*cross_y(m[i],m[j],m[k],hp[i],hp[j],hp[k]); dm_dt[k] = coeff*cross_z(m[i],m[j],m[k],hp[i],hp[j],hp[k]); } double hpx = alpha[i]*hp[i]-beta*delta_hp[i]; double hpy = alpha[i]*hp[j]-beta*delta_hp[j]; double hpz = alpha[i]*hp[k]-beta*delta_hp[k]; double mm = m[i]*m[i] + m[j]*m[j] + m[k]*m[k]; double mh = m[i]*hpx + m[j]*hpy + m[k]*hpz; //suppose m is normalised, i.e., hp=mm.h-mh.m=-mx(mxh) dm_dt[i] += gamma*(mm*hpx - mh*m[i]); dm_dt[j] += gamma*(mm*hpy - mh*m[j]); dm_dt[k] += gamma*(mm*hpz - mh*m[k]); double c=0; if (default_c<0) { c = 6*sqrt(dm_dt[i]*dm_dt[i]+dm_dt[j]*dm_dt[j]+dm_dt[k]*dm_dt[k]); } else { c = default_c; } //printf("%0.15g %0.15g\n", c, default_c); dm_dt[i] += c*(1-mm)*m[i]; dm_dt[j] += c*(1-mm)*m[j]; dm_dt[k] += c*(1-mm)*m[k]; } }
void dmi_field_bulk(double *m, double *field, double *energy, double *Ms_inv, double *D, double dx, double dy, double dz, int n, int *ngbs) { /* In the atomic model, the effective field in the i-th spin site has the * summation: D_{ij} x S_{ij} * * where j is the index of a NN neighbour and D_{ij} = D * r_{ij}, r_{ij} * being the position vector connectin the site i with the site j * * In the continuum, the field can be written as : - nabla X M , which can * be discretised with an expression similar to the atomic model one when * doing the finite difference approximation of the derivative, thus we * only need the DMI vector as: -D * r_{ij} * * So, in the loop through neighbours we compute: * * neighbour field sum this gives * -x: (D / dx) * (+x X M) --> Components in y, z * +x: (D / dx) * (-x X M) --> % * -y: (D / dy) * (+y X M) --> Components in x, z * +y: (D / dy) * (-y X M) --> % * -z: (D / dz) * (+z X M) --> Components in x, y * +z: (D / dz) * (-z X M) --> % * * which gives: * field_x = (D / dx) * (mz[-x] - mz[+x]) * + (D / dz) * (mx[-z] - mx[+x]) * ... * * which are the first order derivatives. * * To compute the DMI for other point groups we will have to find * the vectors and how to discretise the derivative * */ /* Here we iterate through every mesh node */ #pragma omp parallel for for (int i = 0; i < n; i++) { double sign; double * dmivector = malloc(3 * sizeof(double)); double DMIc; double fx = 0, fy = 0, fz = 0; int idnm = 0; // Index for the magnetisation matrix int idn = 6 * i; // index for the neighbours /* Set a zero field for sites without magnetic material */ if (Ms_inv[i] == 0.0){ field[3 * i] = 0; field[3 * i + 1] = 0; field[3 * i + 2] = 0; energy[i] = 0; continue; } /* Here we iterate through the neighbours. Remember: * j = 0, 1, 2, 3, 4, 5 --> -x, +x, -y, +y, -z, +z */ for (int j = 0; j < 6; j++) { /* Remember that index=-1 is for sites without material, so * we skip those sites */ if (ngbs[idn + j] >= 0) { /* Since we are iterating through the 6 NN neighbours, we * set the sign for the DMI vector since, for example, * in the x directions, r_{ij} = (+-1, 0, 0) * So, for j=0, 2, 4 (i.e. -x, -y, -z) we use a negatiev sign */ if (j == 0 || j == 2 || j == 4){ sign = -1; } else{ sign = 1; } /* Now we set the DMI vectors according to the neighbour position */ if (j == 0 || j == 1) { DMIc = -D[i] / dx; dmivector[0] = sign, dmivector[1] = 0, dmivector[2] = 0; } else if (j == 2 || j == 3) { DMIc = -D[i] / dy; dmivector[0] = 0, dmivector[1] = sign, dmivector[2] = 0; } else if (j == 4 || j == 5) { DMIc = -D[i] / dz; dmivector[0] = 0, dmivector[1] = 0, dmivector[2] = sign; } /* Magnetisation array index of the neighbouring spin * since ngbs gives the neighbour's index */ idnm = 3 * ngbs[idn + j]; /* Check that the magnetisation of the neighbouring spin * is larger than zero */ if (Ms_inv[ngbs[idn + j]] > 0){ /* We do here: (D / dx_i) * ( r_{ij} X M_{j} ) * The cross_i function gives the i component of the * cross product */ /* The x component of the cross product of +-x * times anything is zero (similar for the other comps */ if (j != 0 && j != 1) { fx += DMIc * cross_x(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); } if (j != 2 && j != 3) { fy += DMIc * cross_y(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); } if (j != 4 && j != 5) { fz += DMIc * cross_z(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); } } } } /* Energy as: (-mu0 * Ms / 2) * [ H_dmi * m ] */ energy[i] = -0.5 * (fx * m[3 * i] + fy * m[3 * i + 1] + fz * m[3 * i + 2]); /* Update the field H_dmi which has the same structure than *m */ field[3 * i] = fx * Ms_inv[i] * MU0_INV; field[3 * i + 1] = fy * Ms_inv[i] * MU0_INV; field[3 * i + 2] = fz * Ms_inv[i] * MU0_INV; free(dmivector); } }
void dmi_field_interfacial(double *m, double *field, double *energy, double *Ms_inv, double *D, double dx, double dy, double dz, int n, int *ngbs) { /* In the atomic model, the effective field in the i-th spin site has the * summation: D_{ij} x S_{ij} * * For the Interfacial DMI, D_{ij} has the structure: D_{ij} = r_{ij} X z * * so the Dzyaloshinskii vectors are IN plane. This function only works * along the XY plane since we assume there is a non magnetic material below * with a different SOC which gives the DMI for neighbouring in plane spins * * The computation is similar than before, only that we no longer have * a z component. In the continuum the H_dmi field is: * H_dmi = (2 * D / (Ms a a_z)) * ( x X dM/dy - y X dM/dx ) * where "a" is the lattice spacing in the plane and "a_z" the system * thickness along z * This derivative can be obtained doing the cross product * as in the atomic model, hence when going through the neighbours * loop, we can obtain the field doing the following cross products: * * neighbour field sum * -x: (D / dx) * (+y X M) * +x: (D / dx) * (-y X M) * -y: (D / dy) * (-x X M) * +y (D / dy) * (+x X M) * * So, our Dzyaloshinskii vectors can be depicted in a square lattice as * * o +y * | * --> D * ^ | * -x o __ | __ o __ | _ o +x * | v * <-- * | * o -y * * If we start with this picture in the atomic model, we can get the * continuum expression when doing the limit a_x a_y a_z --> 0 * */ /* Here we iterate through every mesh node */ #pragma omp parallel for for (int i = 0; i < n; i++) { double sign; double DMIc; double * dmivector = malloc(3 * sizeof(double)); double fx = 0, fy = 0, fz = 0; int idnm = 0; // Index for the magnetisation matrix int idn = 6 * i; // index for the neighbours /* Set a zero field for sites without magnetic material */ if (Ms_inv[i] == 0.0){ field[3 * i] = 0; field[3 * i + 1] = 0; field[3 * i + 2] = 0; energy[i] = 0; continue; } /* Here we iterate through the neighbours in the XY plane */ for (int j = 0; j < 4; j++) { /* Remember that index=-1 is for sites without material */ if (ngbs[idn + j] >= 0) { /* Since we are iterating through 4 NN neighbours, we * set the sign for the DMI vector since, for example, * in the x directions, (r_{ij} X z) = (0, +-1, 0) * So, for j=1, 2 (i.e. x, -y) we use a negatiev sign * (see the DMI vector details in the above documentation) */ if (j == 1 || j == 2){ sign = -1; } else{ sign = 1; } /* Now we set the vectors with the mesh spacing below only * for in plane neighbours */ if (j == 0 || j == 1) { DMIc = D[i] / dx; dmivector[0] = 0, dmivector[1] = sign, dmivector[2] = 0; } else if (j == 2 || j == 3) { DMIc = D[i] / dy; dmivector[0] = sign, dmivector[1] = 0, dmivector[2] = 0; } /* Magnetisation array index of the neighbouring spin * since ngbs gives the neighbour's index */ idnm = 3 * ngbs[idn + j]; /* Check that the magnetisation of the neighbouring spin * is larger than zero */ if (Ms_inv[ngbs[idn + j]] > 0){ fx += DMIc * cross_x(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); fy += DMIc * cross_y(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); fz += DMIc * cross_z(dmivector[0], dmivector[1], dmivector[2], m[idnm], m[idnm + 1], m[idnm + 2]); } } } /* Energy as: (-mu0 * Ms / 2) * [ H_dmi * m ] */ energy[i] = -0.5 * (fx * m[3 * i] + fy * m[3 * i + 1] + fz * m[3 * i + 2]); /* Update the field H_dmi which has the same structure than *m */ field[3 * i] = fx * Ms_inv[i] * MU0_INV; field[3 * i + 1] = fy * Ms_inv[i] * MU0_INV; field[3 * i + 2] = fz * Ms_inv[i] * MU0_INV; free(dmivector); } }
void dmi_field_bulk(double *m, double *field, double *energy, double *Ms_inv, double *D, double dx, double dy, double dz, int n, int *ngbs) { /* In the atomic model, the effective field in the i-th spin site has the * summation: D_{ij} x S_{ij} * * where j is the index of a NN neighbour and D_{ij} = D * r_{ij}, r_{ij} * being the position vector connectin the site i with the site j * * In the continuum, the field can be written as : - nabla X M , which can * be discretised with an expression similar to the atomic model one when * doing the finite difference approximation of the derivative, thus we * only need the DMI vector as: -D * r_{ij} * * So, in the loop through neighbours we compute: * * neighbour field sum this gives * -x: (D / dx) * (+x X M) --> Components in y, z * +x: (D / dx) * (-x X M) --> % * -y: (D / dy) * (+y X M) --> Components in x, z * +y: (D / dy) * (-y X M) --> % * -z: (D / dz) * (+z X M) --> Components in x, y * +z: (D / dz) * (-z X M) --> % * * which gives: * field_x = (D / dx) * (mz[-x] - mz[+x]) * + (D / dz) * (mx[-z] - mx[+x]) * ... * * which are the first order derivatives. * * The DMI vector norms are given by the *D array. If our simulation has * n mesh nodes, then the D array is (6 * n) long, i.e. the DMI vector norm * per every neighbour per every mesh node. The order is the same than the NNs * array, i.e. * * D = [D(x)_0, D(-x)_0, D(y)_0, D(-y)_0, D(z)_0, D(-z)_0, D(x)_1, ...] * * where D(j)_i means the DMI vector norm of the NN in the j-direction at * the i-th mesh node. Remember that the DMI vector points in a single * direction towards the NN site, e.g. the DMI vector of the NN in the * +y direction for the 0th spin, is DMI_vector = D(y)_0 * (0, 1, 0) * * NOTE: * To compute the DMI for other point groups we will have to find * the vectors and how to discretise the derivative * */ /* The DMI vector directions are the same according to the neighbours * positions. Thus we set them here to avoid compute them * every time in the loop . So, if we have the j-th NN, * the DMI vector will be dmivector[3 * j] */ double dmivector[18] = {-1, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1 }; /* These are for the DMI prefactor or coefficient */ double dxs[6] = {dx, dx, dy, dy, dz, dz}; /* Here we iterate through every mesh node */ #pragma omp parallel for shared(dmivector, dxs) for (int i = 0; i < n; i++) { double DMIc; double fx = 0, fy = 0, fz = 0; int idnm = 0; // Index for the magnetisation matrix int idn = 6 * i; // index for the neighbours /* Set a zero field for sites without magnetic material */ if (Ms_inv[i] == 0.0){ field[3 * i] = 0; field[3 * i + 1] = 0; field[3 * i + 2] = 0; energy[i] = 0; continue; } /* Here we iterate through the neighbours. Remember: * j = 0, 1, 2, 3, 4, 5 --> -x, +x, -y, +y, -z, +z */ for (int j = 0; j < 6; j++) { /* Remember that index=-1 is for sites without material, so * we skip those sites */ if (ngbs[idn + j] >= 0) { /* Magnetisation array index of the neighbouring spin * since ngbs gives the neighbour's index */ idnm = 3 * ngbs[idn + j]; /* Check that the magnetisation of the neighbouring spin * is larger than zero */ if (Ms_inv[ngbs[idn + j]] > 0){ /* We do here: (D / dx_i) * ( r_{ij} X M_{j} ) * The cross_i function gives the i component of the * cross product. The coefficient is computed according * to the DMI strength of the current lattice site. * For the denominator, for example, if j=2 or 3, then * dxs[j] = dy * The D vector is 6 * n which is specified for every neighbour */ DMIc = -D[idn + j] / dxs[j]; /* Compute only for DMI vectors largr than zero */ if (abs(DMIc) > 0) { /* The x component of the cross product of +-x * times anything is zero (similar for the other comps) */ if (j != 0 && j != 1) { fx += DMIc * cross_x(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); } if (j != 2 && j != 3) { fy += DMIc * cross_y(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); } if (j != 4 && j != 5) { fz += DMIc * cross_z(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); } } } } } /* Energy as: (-mu0 * Ms / 2) * [ H_dmi * m ] */ energy[i] = -0.5 * (fx * m[3 * i] + fy * m[3 * i + 1] + fz * m[3 * i + 2]); /* Update the field H_dmi which has the same structure than *m */ field[3 * i] = fx * Ms_inv[i] * MU0_INV; field[3 * i + 1] = fy * Ms_inv[i] * MU0_INV; field[3 * i + 2] = fz * Ms_inv[i] * MU0_INV; } }
void dmi_field_interfacial(double *m, double *field, double *energy, double *Ms_inv, double *D, double dx, double dy, double dz, int n, int *ngbs) { /* In the atomic model, the effective field in the i-th spin site has the * summation: D_{ij} x S_{ij} * * For the Interfacial DMI, D_{ij} has the structure: D_{ij} = r_{ij} X z * See Rohart et al. Phys. Rev. B 88, 184422) * * but [Yang et al. Phys. Rev. Lett. 115, 267210] uses the opposite sign (?) * * so the Dzyaloshinskii vectors are IN plane. This function only works * along the XY plane since we assume there is a non magnetic material below * with a different SOC which gives the DMI for neighbouring in plane spins * * The computation is similar than before, only that we no longer have * a z component. In the continuum the H_dmi field is: * H_dmi = (2 * D / (Ms a a_z)) * ( x X dM/dy - y X dM/dx ) * where "a" is the lattice spacing in the plane and "a_z" the system * thickness along z * This derivative can be obtained doing the cross product * as in the atomic model, hence when going through the neighbours * loop, we can obtain the field doing the following cross products: * * neighbour field sum * -x: (D / dx) * (+y X M) * +x: (D / dx) * (-y X M) * -y: (D / dy) * (-x X M) * +y (D / dy) * (+x X M) * * So, our Dzyaloshinskii vectors can be depicted in a square lattice as * * o +y * | * --> D * ^ | * -x o __ | __ o __ | _ o +x * | v * <-- * | * o -y * * If we start with this picture in the atomic model, we can get the * continuum expression when doing the limit a_x a_y a_z --> 0 * * The DMI vector norms are given by the *D array. If our simulation has * n mesh nodes, then the D array is (4 * n) long, i.e. the DMI vector norm * per every neighbour per every mesh node. The order is the same than the NNs * array, i.e. * * D = [D(x)_0, D(-x)_0, D(y)_0, D(-y)_0, D(x)_1, ...] * * where D(j)_i means the DMI vector norm of the NN in the j-direction at * the i-th mesh node. Remember that the DMI vector points in a single * direction towards the NN site, e.g. the DMI vector of the NN in the * +y direction for the 0th spin, is DMI_vector = D(y)_0 * (0, 1, 0) */ /* We set the DMi vectors here. For the j-th NN, the DMI * vector starts at dmivector[3 * j] * (NNs are in the order: -x, +x, -y, +y) * For interfacial DMI we only compute the DMI in 2D * */ double dmivector[12] = { 0, 1, 0, 0, -1, 0, -1, 0, 0, 1, 0, 0, }; double dxs[4] = {dx, dx, dy, dy}; /* Here we iterate through every mesh node */ #pragma omp parallel for shared(dmivector, dxs) for (int i = 0; i < n; i++) { double sign; double DMIc; double fx = 0, fy = 0, fz = 0; int idnm = 0; // Index for the magnetisation matrix int idn = 6 * i; // index for the neighbours int idn_DMI = 4 * i; // index for the neighbours for the DMI array /* Set a zero field for sites without magnetic material */ if (Ms_inv[i] == 0.0){ field[3 * i] = 0; field[3 * i + 1] = 0; field[3 * i + 2] = 0; energy[i] = 0; continue; } /* Here we iterate through the neighbours in the XY plane */ for (int j = 0; j < 4; j++) { /* Remember that index=-1 is for sites without material */ if (ngbs[idn + j] >= 0) { /* DMI coefficient according to the neighbour position */ DMIc = D[idn_DMI + j] / dxs[j]; /* Magnetisation array index of the neighbouring spin * since ngbs gives the neighbour's index */ idnm = 3 * ngbs[idn + j]; /* Check that the magnetisation of the neighbouring spin * is larger than zero, as well as the DMI coefficient */ if (Ms_inv[ngbs[idn + j]] > 0 && abs(DMIc) > 0) { fx += DMIc * cross_x(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); fy += DMIc * cross_y(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); fz += DMIc * cross_z(dmivector[3 * j], dmivector[3 * j + 1], dmivector[3 * j + 2], m[idnm], m[idnm + 1], m[idnm + 2]); } } } /* Energy as: (-mu0 * Ms / 2) * [ H_dmi * m ] */ energy[i] = -0.5 * (fx * m[3 * i] + fy * m[3 * i + 1] + fz * m[3 * i + 2]); /* Update the field H_dmi which has the same structure than *m */ field[3 * i] = fx * Ms_inv[i] * MU0_INV; field[3 * i + 1] = fy * Ms_inv[i] * MU0_INV; field[3 * i + 2] = fz * Ms_inv[i] * MU0_INV; } }
void dmi_field_bulk(double *spin, double *field, double *energy, double D, int *ngbs, int nxyz) { /* Bulk DMI field and energy computation * * ngbs[] contains the *indexes* of the neighbours * in the following order: * -x, +x, -y, +y, -z, +z * * for every spin. It is -1 for boundaries. * The array is like: * | 0-x, 0+x, 0-y, 0+y, 0-z, 0+z, 1-x, 1+x, 1-y, ... | * i=0 i=1 ... * * where 0-y is the index of the neighbour of the 0th spin, * in the -y direction, for example * * Thus, for every neighbour ( ngbs[i + j], j=0,1,...5 ) * we compute the field contribution. * * The value of ngbs[] at sites with Ms = 0, is negative * * The ngbs array also gives the correct indexes for the spins * at periodic boundaries * * The Bulk DMI Dzyaloshinskii vectors are in the same direction than the * r_ij vectors which connect two neighbouring sites, pointing from the * lattice site *i* towards *j* * * The bulk DMI is defined in 3 dimensions * * Then the DMI field is computed as : * * Sum_j ( D_ij X S_j ) = Sum_j ( r_ij X S_j ) * * for every spin *i* * * --- Check this: can we use a SC lattice for this bulk DMI expression? * Since, for example, MnSi crystal structure is more complex */ #pragma omp parallel for for (int i = 0; i < nxyz; i++) { int id = 0; int idv = 6 * i; // index for the neighbours double fx = 0, fy = 0, fz = 0; if (ngbs[idv]>=0) { // neighbour at x-1 id = 3*ngbs[idv]; fx += D*cross_x(-1,0,0,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(-1,0,0,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(-1,0,0,spin[id],spin[id+1],spin[id+2]); } if (ngbs[idv+1]>=0) { // neighbour x+1 id = 3*ngbs[idv+1]; fx += D*cross_x(1,0,0,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(1,0,0,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(1,0,0,spin[id],spin[id+1],spin[id+2]); } if (ngbs[idv+2]>=0) { // neighbour at y-1 id = 3*ngbs[idv+2]; fx += D*cross_x(0,-1,0,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(0,-1,0,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(0,-1,0,spin[id],spin[id+1],spin[id+2]); } if (ngbs[idv+3]>=0) { // neighbour at y+1 id = 3*ngbs[idv+3]; fx += D*cross_x(0,1,0,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(0,1,0,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(0,1,0,spin[id],spin[id+1],spin[id+2]); } if (ngbs[idv+4]>=0) { // neighbour at z-1 id = 3*ngbs[idv+4]; fx += D*cross_x(0,0,-1,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(0,0,-1,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(0,0,-1,spin[id],spin[id+1],spin[id+2]); } if (ngbs[idv+5]>=0) { // neighbour at z+1 id = 3*ngbs[idv+5]; fx += D*cross_x(0,0,1,spin[id],spin[id+1],spin[id+2]); fy += D*cross_y(0,0,1,spin[id],spin[id+1],spin[id+2]); fz += D*cross_z(0,0,1,spin[id],spin[id+1],spin[id+2]); } field[3 * i] = fx; field[3 * i + 1] = fy; field[3 * i + 2] = fz; energy[i] = -0.5 * (fx * spin[3 * i] + fy * spin[3 * i + 1] + fz * spin[3 * i + 2] ); } }
void dmi_field_interfacial_atomistic(double *spin, double *field, double *energy, double D, int *ngbs, int n, int nneighbours, double *DMI_vec) { /* Interfacial DMI field and energy computation * * ngbs[] contains the *indexes* of the neighbours * for different lattices. The number of neighbours is specified with the * nneighbours integer * * For example, in a simple cubic (SC) lattice, * the array is like: * | 0-x, 0+x, 0-y, 0+y, 0-z, 0+z, 1-x, 1+x, 1-y, ... | * i=0 i=1 ... * * where 0-y is the index of the neighbour of the 0th spin, * in the -y direction, for example * * Thus, for every neighbour of the i-th spin ( ngbs[i + j], j=0,1,...5 ) * we compute the field contribution * The value of ngbs[] at sites with Ms = 0, is negative (-1) * * The ngbs array also gives the correct indexes for the spins * at periodic boundaries * * The Interfacial DMI needs the direction of the Dzyaloshinskii vectors, * which are obtained as * * D_ij = r_ij X +z * * where r_ij is the vector connecting two neighbouring sites. * This vectors are computed in Python and passed here as the DMI_vec array, * whose entries are according to the neighbours matrix order, i.e. * the (DMI_vec[3 * j], DMI_vec[3 * j + 1], DMI_vec[3 * j + 2]) vector * are the Dij vector components between the i-th site and the j-th neighbour * (we assume the vectors are the same between NNs for every lattice site) * * * For instance, in a SC 2D lattice, the X -th spin has the DMI vectors: * [ lattice site X, neighbours O ] * * O * . * ---> D_ij * . * ^ . ^ y * O . | . . X . . | . O | * . v |---> x * . * <--- * . * O * * so DMI_vec = {0, 1, 0, 0, -1, 0, -1, 0, 0, 1, 0, 0} * NN: j=0 j=1 j=2 j=3 * * This DMI is only defined in two dimensions--> interfaces, thin films * Then the DMI field is computed as : Sum_j ( D_ij X S_j ) * for every spin *i* * * In a hexagonal lattice we have 6 neighbours * * We assume a constant DMI vector magnitude */ #pragma omp parallel for for (int i = 0; i < n; i++) { int idn = 6 * i; // index for the NNs int idnm = 0; // index for the magnetisation components of the NNs double fx = 0, fy = 0, fz = 0; /* Now we compute the field contribution from every neighbour */ for(int j = 0; j < nneighbours; j++){ /* Check that Ms != 0 */ if (ngbs[idn + j] >= 0) { /* Now we add the field contribution of the j-th * neighbour for the i-th spin: D_ij X S_j */ idnm = 3 * ngbs[idn + j]; // Magnetisation component index of the j-th NN fx += D * cross_x(DMI_vec[3 * j], DMI_vec[3 * j + 1], DMI_vec[3 * j + 2], spin[idnm], spin[idnm + 1], spin[idnm + 2]); fy += D * cross_y(DMI_vec[3 * j], DMI_vec[3 * j + 1], DMI_vec[3 * j + 2], spin[idnm], spin[idnm + 1], spin[idnm + 2]); fz += D * cross_z(DMI_vec[3 * j], DMI_vec[3 * j + 1], DMI_vec[3 * j + 2], spin[idnm], spin[idnm + 1], spin[idnm + 2]); } } field[3 * i] = fx; field[3 * i + 1] = fy; field[3 * i + 2] = fz; // TODO: check whether the energy is correct or not. /* Avoid second counting with 1/2 */ energy[i] = -0.5 * (fx * spin[3 * i] + fy * spin[3 * i + 1]+ fz * spin[3 * i + 2] ); } }