def run(self): """Run the vibration calculations. This will calculate the forces for 6 displacements per atom +/-x, +/-y, +/-z. Only those calculations that are not already done will be started. Be aware that an interrupted calculation may produce an empty file (ending with .pckl), which must be deleted before restarting the job. Otherwise the forces will not be calculated for that displacement. Note that the calculations for the different displacements can be done simultaneously by several independent processes. This feature relies on the existence of files and the subsequent creation of the file in case it is not found. """ filename = self.name + '.eq.pckl' fd = opencew(filename) if fd is not None: self.calculate(filename, fd) p = self.atoms.positions.copy() for a in self.indices: for i in range(3): for sign in [-1, 1]: for ndis in range(1, self.nfree // 2 + 1): filename = ('%s.%d%s%s.pckl' % (self.name, a, 'xyz'[i], ndis * ' +-'[sign])) fd = opencew(filename) if fd is not None: disp = ndis * sign * self.delta self.atoms.positions[a, i] = p[a, i] + disp self.calculate(filename, fd) self.atoms.positions[a, i] = p[a, i]
def run(self): """Run the vibration calculations. This will calculate the forces for 6 displacements per atom +/-x, +/-y, +/-z. Only those calculations that are not already done will be started. Be aware that an interrupted calculation may produce an empty file (ending with .pckl), which must be deleted before restarting the job. Otherwise the forces will not be calculated for that displacement. Note that the calculations for the different displacements can be done simultaneously by several independent processes. This feature relies on the existence of files and the subsequent creation of the file in case it is not found. """ filename = self.name + '.eq.pckl' fd = opencew(filename) if fd is not None: self.calculate(filename, fd) p = self.atoms.positions.copy() for filename, a, i, disp in self.displacements(): fd = opencew(filename) if fd is not None: self.atoms.positions[a, i] = p[a, i] + disp self.calculate(filename, fd) self.atoms.positions[a, i] = p[a, i]
def numeric_forces(atoms, indices=None, axes=(0, 1, 2), d=0.001, parallel=None, name=None): """Evaluate finite-difference forces on several atoms. Returns an array of forces for each specified atomic index and each specified axis, calculated using finite difference on each atom and direction separately. Array has same shape as if returned from atoms.get_forces(); uncalculated elements are zero. Calculates all forces by default.""" if indices is None: indices = range(len(atoms)) F_ai = np.zeros_like(atoms.positions) n = len(indices) * len(axes) if parallel is None: atom_tasks = [atoms] * n master = True calc_comm = world else: calc_comm, tasks_comm, tasks_rank = distribute_cpus(parallel, world) master = calc_comm.rank == 0 calculator = atoms.get_calculator() calculator.set(communicator=calc_comm) atom_tasks = [None] * n for i in range(n): if ((i - tasks_rank) % tasks_comm.size) == 0: atom_tasks[i] = atoms for ia, a in enumerate(indices): for ii, i in enumerate(axes): atoms = atom_tasks[ia * len(axes) + ii] if atoms is not None: done = 0 if name: fname = '%s.%d%s.pckl' % (name, a, 'xyz'[i]) fd = opencew(fname, calc_comm) if fd is None: if master: try: F_ai[a, i] = pickle.load(open(fname)) print '# atom', a, 'xyz'[i], 'done' done = 1 except EOFError: pass done = calc_comm.sum(done) if not done: print '# rank', rank, 'calculating atom', a, 'xyz'[i] force = numeric_force(atoms, a, i, d) if master: F_ai[a, i] = force if name: fd = open('%s.%d%s.pckl' % (name, a, 'xyz'[i]), 'w') pickle.dump(force, fd) fd.close() if parallel is not None: world.sum(F_ai) return F_ai
def run(self, names): """Run task far all names. The task will be one of these four: * Open ASE's GUI * Write configuration to file * Write summary * Do the actual calculation """ names = self.expand(names) names = names[self.slice] names = self.exclude(names) if self.gui: for name in names: view(self.create_system(name)) return if self.write_to_file: if self.write_to_file[0] == ".": for name in names: filename = self.get_filename(name, self.write_to_file) write(filename, self.create_system(name)) else: assert len(names) == 1 write(self.write_to_file, self.create_system(names[0])) return if self.write_summary: self.read(names) self.analyse() self.summarize(names) return atoms = None for name in names: if self.use_lock_files: lockfilename = self.get_filename(name, ".json") fd = opencew(lockfilename) if fd is None: self.log("Skipping", name) continue fd.close() atoms = self.run_single(name) return atoms
def calculate_exact_exchange(self): name = self.filename + '.exx.npy' fd = opencew(name) if fd is None: print('Reading EXX contribution from file:', name, file=self.fd) with open(name) as fd: self.exx_sin = np.load(fd) assert self.exx_sin.shape == self.shape, self.exx_sin.shape return print('Calculating EXX contribution', file=self.fd) exx = EXX(self.calc, kpts=self.kpts, bands=self.bands, txt=self.filename + '.exx.txt', timer=self.timer) exx.calculate() self.exx_sin = exx.get_eigenvalue_contributions() / Hartree np.save(fd, self.exx_sin)
def calculate_ks_xc_contribution(self): name = self.filename + '.vxc.npy' fd = opencew(name) if fd is None: print('Reading Kohn-Sham XC contribution from file:', name, file=self.fd) with open(name) as fd: self.vxc_sin = np.load(fd) assert self.vxc_sin.shape == self.shape, self.vxc_sin.shape return print('Calculating Kohn-Sham XC contribution', file=self.fd) vxc_skn = vxc(self.calc, self.calc.hamiltonian.xc) / Hartree n1, n2 = self.bands self.vxc_sin = vxc_skn[:, self.kpts, n1:n2] np.save(fd, self.vxc_sin)
def run(self): """Run the calculations for the required displacements. This will do a calculation for 6 displacements per atom, +-x, +-y, and +-z. Only those calculations that are not already done will be started. Be aware that an interrupted calculation may produce an empty file (ending with .pckl), which must be deleted before restarting the job. Otherwise the calculation for that displacement will not be done. """ # Atoms in the supercell -- repeated in the lattice vector directions # beginning with the last atoms_N = self.atoms * self.N_c # Set calculator if provided assert self.calc is not None, "Provide calculator in __init__ method" atoms_N.set_calculator(self.calc) # Do calculation on equilibrium structure filename = self.name + '.eq.pckl' fd = opencew(filename) if fd is not None: # Call derived class implementation of __call__ output = self.__call__(atoms_N) # Write output to file if rank == 0: pickle.dump(output, fd) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # Positions of atoms to be displaced in the reference cell natoms = len(self.atoms) offset = natoms * self.offset pos = atoms_N.positions[offset: offset + natoms].copy() # Loop over all displacements for a in self.indices: for i in range(3): for sign in [-1, 1]: # Filename for atomic displacement filename = '%s.%d%s%s.pckl' % \ (self.name, a, 'xyz'[i], ' +-'[sign]) # Wait for ranks before checking for file # barrier() fd = opencew(filename) if fd is None: # Skip if already done continue # Update atomic positions atoms_N.positions[offset + a, i] = \ pos[a, i] + sign * self.delta # Call derived class implementation of __call__ output = self.__call__(atoms_N) # Write output to file if rank == 0: pickle.dump(output, fd) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # Return to initial positions atoms_N.positions[offset + a, i] = pos[a, i]
relativistic = "scalar" linspace = (0.98, 1.02, 5) # eos numpy's linspace linspacestr = "".join([str(t) + "x" for t in linspace])[:-1] code = "aims" + "-" + basis + "_e" + linspacestr code = code + "_k" + str(kptdensity) + "_w" + str(width) code = code + "_t" + str(basis_threshold) + "_r" + str(relativistic) collection = Collection() for name in names: # save all steps in one traj file in addition to the database # we should only used the database c.reserve, but here # traj file is used as another lock ... fd = opencew(name + "_" + code + ".traj") if fd is None: continue traj = PickleTrajectory(name + "_" + code + ".traj", "w") atoms = collection[name] cell = atoms.get_cell() kpts = tuple(kpts2mp(atoms, kptdensity, even=True)) kwargs = {} if relativistic == "scalar": kwargs.update({"relativistic": ["atomic_zora", relativistic]}) elif relativistic == "none": kwargs.update({"relativistic": "none"}) else: # e.g. 1.0e-12 kwargs.update({"relativistic": ["zora", relativistic]}) if atoms.get_initial_magnetic_moments().any(): # spin-polarization magmom = atoms.get_initial_magnetic_moments().sum() / len(atoms)
'addgrid':True, 'lwave':False, 'setups': setups, 'lscalapack':False, }, } linspace = (0.500,0.600,0.700,0.800,0.850,0.900,0.925,0.950,0.975,1.000,1.025,1.050,1.075,1.100,1.125,1.150,1.175,1.200,1.250,1.300,1.350,1.400,1.500) #linspace = (0.700,0.800,0.850,0.900,0.925,0.950,0.975,1.000,1.025,1.050,1.075,1.100,1.125,1.150,1.175,1.200) for name in elements: # save all steps in one traj file in addition to the database # we should only used the database c.reserve, but here # traj file is used as another lock ... label = code + '_k' + str(kptdensity) + '_w' + str(width) fd = opencew(name + '_' + label + '.traj') if fd is None: continue traj = PickleTrajectory(name + '_' + label + '.traj', 'w') if name in ['Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr']: cr = 1.69 else: cr = covalent_radii[atomic_numbers[name]] if structure == 'fcc': atoms = bulk(name, crystalstructure='fcc', a=structures[name][structure]) # sum of covelent radii scr = cr * 2 elif structure == 'rocksalt': atoms = bulk(name + 'O', crystalstructure='rocksalt', a=structures[name][structure])
code = code + '_k' + str(kptdensity) + '_w' + str(width) code = code + '_r' + str(relativistic) for name in D.keys() + names: # adsorbates + surfaces elements for category in ['fcc111', 'fcc']: for adsorbate in ['', 'C', 'O']: if category == 'fcc111': compound = '%s%s-%s' % (name, adsorbate, category) else: if category == 'fcc' and adsorbate != '': continue else: compound = '%s-%s' % (name, category) label = compound + '_' + code # but here traj file is used as another lock ... fd = opencew(label + '.traj') if fd is None: continue traj = PickleTrajectory(label + '.traj', 'w') a = fcc[name] if category == 'fcc': atoms = bulk(name, 'fcc', a=a) site = '' else: atoms = fcc111(name, size=(2, 2, 4), a=a, vacuum=6.0) atoms.center(axis=2) site = '' if adsorbate != '': d = D[adsorbate][name]['d'] site = D[adsorbate][name]['site'] add_adsorbate(atoms, adsorbate, d, site)
def numeric_forces(self,atoms, axes=(0, 1, 2), d=0.001, parallel=None, name=None): """Evaluate finite-difference forces on several atoms. Returns an array of forces for each specified atomic index and each specified axis, calculated using finite difference on each atom and direction separately. Array has same shape as if returned from atoms.get_forces(); uncalculated elements are zero. Calculates all forces by default.""" import numpy as np from ase.parallel import world, rank, distribute_cpus from ase.utils import opencew indices = range(len(atoms)) F_ai = np.zeros_like(atoms.positions) n = len(indices) * len(axes) total_calculation = len(indices)*len(axes) if parallel is None: atom_tasks = [atoms] * n master = True calc_comm = world else: calc_comm, tasks_comm, tasks_rank = distribute_cpus(parallel, world) master = calc_comm.rank == 0 calculator = atoms.get_calculator() calculator.set(communicator=calc_comm) atom_tasks = [None] * n for i in range(n): if ((i - tasks_rank) % tasks_comm.size) == 0: atom_tasks[i] = atoms counter = 0 for ia, a in enumerate(indices): for ii, i in enumerate(axes): atoms = atom_tasks[ia * len(axes) + ii] if atoms is not None: done = 0 if name: fname = '%s.%d%s.pckl' % (name, a, 'xyz'[i]) fd = opencew(fname, calc_comm) if fd is None: if master: try: F_ai[a, i] = pickle.load(open(fname)) print '# atom', a, 'xyz'[i], 'done' done = 1 except EOFError: pass done = calc_comm.sum(done) if not done: # print '# rank', rank, 'calculating atom', a, 'xyz'[i] force = self.__numeric_force(atoms, a, i, d) if master: F_ai[a, i] = force if name: fd = open('%s.%d%s.pckl' % (name, a, 'xyz'[i]), 'w') pickle.dump(force, fd) fd.close() counter += 1 fan = ['-', '\\', '|', '/'] sys.stderr.write("\r[%-100s]%3.1f%% %1s" % tuple([int(float(counter)/float(total_calculation)*100)*'=',float(counter)/float(total_calculation)*100,fan[counter%4]])) sys.stderr.flush() if parallel is not None: world.sum(F_ai) return F_ai
else: linspace = (0.92, 1.08, 7) # eos numpy's linspace if category == 'magnetic_moments': linspace = (1.0, 1.0, 1) # eos numpy's linspace linspacestr = ''.join([str(t) + 'x' for t in linspace])[:-1] code = category + '_gpaw' + '-' + mode + str(e) + '_c' + str(constant_basis) code = code + '_e' + linspacestr code = code + '_k' + str(kptdensity) + '_w' + str(width) code = code + '_r' + str(relativistic) for name in names: # save all steps in one traj file in addition to the database # we should only used the database c.reserve, but here # traj file is used as another lock ... fd = opencew(name + '_' + code + '.traj') if fd is None: continue traj = Trajectory(name + '_' + code + '.traj', 'w') atoms = collection[name] cell = atoms.get_cell() kpts = tuple(kpts2mp(atoms, kptdensity, even=True)) kwargs = {} if mode == 'fd': if constant_basis: # gives more smooth EOS in fd mode kwargs.update({'gpts': h2gpts(e, cell)}) else: kwargs.update({'h': e}) elif mode == 'pw': if constant_basis:
def calculate(self): calc = self.calc focc_S = self.focc_S e_S = self.e_S op_scc = calc.wfs.kd.symmetry.op_scc # Get phi_qaGp if self.mode == 'RPA': self.phi_aGp = self.get_phi_aGp() else: fd = opencew('phi_qaGp') if fd is None: self.reader = Reader('phi_qaGp') tmp = self.load_phi_aGp(self.reader, 0)[0] assert len(tmp) == self.npw self.printtxt('Finished reading phi_aGp') else: self.printtxt('Calculating phi_qaGp') self.get_phi_qaGp() world.barrier() self.reader = Reader('phi_qaGp') self.printtxt('Memory used %f M' % (maxrss() / 1024.**2)) self.printtxt('') if self.optical_limit: iq = np.where(np.sum(abs(self.ibzq_qc), axis=1) < 1e-5)[0][0] else: iq = np.where(np.sum(abs(self.ibzq_qc - self.q_c), axis=1) < 1e-5)[0][0] kc_G = np.array([self.V_qGG[iq, iG, iG] for iG in range(self.npw)]) if self.optical_limit: kc_G[0] = 0. # Get screened Coulomb kernel if self.mode == 'BSE': try: # Read data = pickle.load(open(self.kernel_file+'.pckl')) W_qGG = data['W_qGG'] assert np.shape(W_qGG) == np.shape(self.V_qGG) self.printtxt('Finished reading screening interaction kernel') except: # Calculate from scratch self.printtxt('Calculating screening interaction kernel.') W_qGG = self.full_static_screened_interaction() self.printtxt('') else: W_qGG = self.V_qGG t0 = time() self.printtxt('Calculating %s matrix elements' % self.mode) # Calculate full kernel K_SS = np.zeros((self.nS_local, self.nS), dtype=complex) self.rhoG0_S = np.zeros(self.nS, dtype=complex) #noGmap = 0 for iS in range(self.nS_start, self.nS_end): k1, n1, m1 = self.Sindex_S3[iS] rho1_G = self.density_matrix(n1,m1,k1) self.rhoG0_S[iS] = rho1_G[0] for jS in range(self.nS): k2, n2, m2 = self.Sindex_S3[jS] rho2_G = self.density_matrix(n2,m2,k2) K_SS[iS-self.nS_start, jS] = np.sum(rho1_G.conj() * rho2_G * kc_G) if not self.mode == 'RPA': rho3_G = self.density_matrix(n1,n2,k1,k2) rho4_G = self.density_matrix(m1,m2,self.kq_k[k1], self.kq_k[k2]) q_c = self.kd.bzk_kc[k2] - self.kd.bzk_kc[k1] q_c[np.where(q_c > 0.501)] -= 1. q_c[np.where(q_c < -0.499)] += 1. iq = self.kd.where_is_q(q_c, self.bzq_qc) if not self.qsymm: W_GG = W_qGG[iq] else: ibzq = self.ibzq_q[iq] W_GG_tmp = W_qGG[ibzq] iop = self.iop_q[iq] timerev = self.timerev_q[iq] diff_c = self.diff_qc[iq] invop = np.linalg.inv(op_scc[iop]) Gindex = np.zeros(self.npw, dtype=int) for iG in range(self.npw): G_c = self.Gvec_Gc[iG] if timerev: RotG_c = -np.int8(np.dot(invop, G_c+diff_c).round()) else: RotG_c = np.int8(np.dot(invop, G_c+diff_c).round()) tmp_G = np.abs(self.Gvec_Gc - RotG_c).sum(axis=1) try: Gindex[iG] = np.where(tmp_G < 1e-5)[0][0] except: #noGmap += 1 Gindex[iG] = -1 W_GG = np.zeros_like(W_GG_tmp) for iG in range(self.npw): for jG in range(self.npw): if Gindex[iG] == -1 or Gindex[jG] == -1: W_GG[iG, jG] = 0 else: W_GG[iG, jG] = W_GG_tmp[Gindex[iG], Gindex[jG]] if self.mode == 'BSE': tmp_GG = np.outer(rho3_G.conj(), rho4_G) * W_GG K_SS[iS-self.nS_start, jS] -= 0.5 * np.sum(tmp_GG) else: tmp_G = rho3_G.conj() * rho4_G * np.diag(W_GG) K_SS[iS-self.nS_start, jS] -= 0.5 * np.sum(tmp_G) self.timing(iS, t0, self.nS_local, 'pair orbital') K_SS /= self.vol world.sum(self.rhoG0_S) #self.printtxt('Number of G indices outside the Gvec_Gc: %d' % noGmap) # Get and solve Hamiltonian H_sS = np.zeros_like(K_SS) for iS in range(self.nS_start, self.nS_end): H_sS[iS-self.nS_start,iS] = e_S[iS] for jS in range(self.nS): H_sS[iS-self.nS_start,jS] += focc_S[iS] * K_SS[iS-self.nS_start,jS] # Force matrix to be Hermitian if not self.coupling: if world.size > 1: H_Ss = self.redistribute_H(H_sS) else: H_Ss = H_sS H_sS = (np.real(H_sS) + np.real(H_Ss.T)) / 2. + 1j * (np.imag(H_sS) - np.imag(H_Ss.T)) /2. # Save H_sS matrix self.par_save('H_SS','H_SS', H_sS) return H_sS
def calculate_screened_potential(self): """Calculates the screened potential for each q-point in the 1st BZ. Since many q-points are related by symmetry, the actual calculation is only done for q-points in the IBZ and the rest are obtained by symmetry transformations. Results are returned as a generator to that it is not necessary to store a huge matrix for each q-point in the memory.""" # The decorator $timer('W') doesn't work for generators, do we will # have to manually start and stop the timer here: self.timer.start('W') print('Calculating screened Coulomb potential', file=self.fd) if self.wstc: print('Using Wigner-Seitz truncated Coloumb potential', file=self.fd) if self.ppa: print('Using Godby-Needs plasmon-pole approximation:', file=self.fd) print(' Fitting energy: i*E0, E0 = %.3f Hartee' % self.E0, file=self.fd) # use small imaginary frequency to avoid dividing by zero: frequencies = [1e-10j, 1j * self.E0 * Hartree] parameters = {'eta': 0, 'hilbert': False, 'timeordered': False, 'frequencies': frequencies} else: print('Using full frequency integration:', file=self.fd) print(' domega0: {0:g}'.format(self.domega0 * Hartree), file=self.fd) print(' omega2: {0:g}'.format(self.omega2 * Hartree), file=self.fd) parameters = {'eta': self.eta * Hartree, 'hilbert': True, 'timeordered': True, 'domega0': self.domega0 * Hartree, 'omega2': self.omega2 * Hartree} chi0 = Chi0(self.calc, nbands=self.nbands, ecut=self.ecut * Hartree, intraband=False, real_space_derivatives=False, txt=self.filename + '.w.txt', timer=self.timer, keep_occupied_states=True, nblocks=self.blockcomm.size, no_optical_limit=self.wstc, **parameters) if self.wstc: wstc = WignerSeitzTruncatedCoulomb( self.calc.wfs.gd.cell_cv, self.calc.wfs.kd.N_c, chi0.fd) else: wstc = None self.omega_w = chi0.omega_w self.omegamax = chi0.omegamax htp = HilbertTransform(self.omega_w, self.eta, gw=True) htm = HilbertTransform(self.omega_w, -self.eta, gw=True) # Find maximum size of chi-0 matrices: gd = self.calc.wfs.gd nGmax = max(count_reciprocal_vectors(self.ecut, gd, q_c) for q_c in self.qd.ibzk_kc) nw = len(self.omega_w) size = self.blockcomm.size mynGmax = (nGmax + size - 1) // size mynw = (nw + size - 1) // size # Allocate memory in the beginning and use for all q: A1_x = np.empty(nw * mynGmax * nGmax, complex) A2_x = np.empty(max(mynw * nGmax, nw * mynGmax) * nGmax, complex) # Need to pause the timer in between iterations self.timer.stop('W') for iq, q_c in enumerate(self.qd.ibzk_kc): self.timer.start('W') if self.savew: wfilename = self.filename + '.w.q%d.pckl' % iq fd = opencew(wfilename) if self.savew and fd is None: # Read screened potential from file with open(wfilename) as fd: pd, W = pickle.load(fd) else: # First time calculation pd, W = self.calculate_w(chi0, q_c, htp, htm, wstc, A1_x, A2_x) if self.savew: pickle.dump((pd, W), fd, pickle.HIGHEST_PROTOCOL) self.timer.stop('W') # Loop over all k-points in the BZ and find those that are related # to the current IBZ k-point by symmetry Q1 = self.qd.ibz2bz_k[iq] done = set() for s, Q2 in enumerate(self.qd.bz2bz_ks[Q1]): if Q2 >= 0 and Q2 not in done: s = self.qd.sym_k[Q2] self.s = s self.U_cc = self.qd.symmetry.op_scc[s] time_reversal = self.qd.time_reversal_k[Q2] self.sign = 1 - 2 * time_reversal Q_c = self.qd.bzk_kc[Q2] d_c = self.sign * np.dot(self.U_cc, q_c) - Q_c assert np.allclose(d_c.round(), d_c) yield pd, W, Q_c done.add(Q2)
def run(self): """Run the calculations for the required displacements. This will do a calculation for 6 displacements per atom, +-x, +-y, and +-z. Only those calculations that are not already done will be started. Be aware that an interrupted calculation may produce an empty file (ending with .pckl), which must be deleted before restarting the job. Otherwise the calculation for that displacement will not be done. """ # Atoms in the supercell -- repeated in the lattice vector directions # beginning with the last atoms_N = self.atoms * self.N_c # Set calculator if provided assert self.calc is not None, "Provide calculator in __init__ method" atoms_N.calc = self.calc # Do calculation on equilibrium structure self.state = 'eq.pckl' filename = self.name + '.' + self.state fd = opencew(filename) if fd is not None: # Call derived class implementation of __call__ output = self.__call__(atoms_N) # Write output to file if world.rank == 0: pickle.dump(output, fd, protocol=2) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # Positions of atoms to be displaced in the reference cell natoms = len(self.atoms) offset = natoms * self.offset pos = atoms_N.positions[offset:offset + natoms].copy() # Loop over all displacements for a in self.indices: for i in range(3): for sign in [-1, 1]: # Filename for atomic displacement self.state = '%d%s%s.pckl' % (a, 'xyz'[i], ' +-'[sign]) filename = self.name + '.' + self.state # Wait for ranks before checking for file # barrier() fd = opencew(filename) if fd is None: # Skip if already done continue # Update atomic positions atoms_N.positions[offset + a, i] = \ pos[a, i] + sign * self.delta # Call derived class implementation of __call__ output = self.__call__(atoms_N) # Write output to file if world.rank == 0: pickle.dump(output, fd, protocol=2) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # Return to initial positions atoms_N.positions[offset + a, i] = pos[a, i]