def structure_from_file(structure_file): """ Attempts to reconstruct a PyChemia Structure from the contents of any given file. Valid entries :param structure_file: The path to a file where the structure can be reconstructed :type structure_file: str :return: PyChemia Structure if succeed, None otherwise """ st = None basename = os.path.basename(structure_file) if not os.path.isfile(structure_file): raise ValueError("ERROR: Could not open file '%s'" % structure_file) if basename[-4:].lower() == 'json': st = Structure.load_json(structure_file) elif basename[-3:].lower() == 'cif' and HAS_PYMATGEN: import pychemia.external.pymatgen st = pychemia.external.pymatgen.cif2structure(structure_file)[0] elif 'poscar' in basename.lower(): st = read_poscar(structure_file) elif 'contcar' in basename.lower(): st = read_poscar(structure_file) elif 'abinit' in basename.lower(): av = AbinitInput(structure_file) st = av.get_structure() else: try: st = read_poscar(structure_file) except ValueError: raise ValueError('Ćould not convert file as POSCAR') if st is None: pcm_log.debug("ERROR: Could not extract structure from file '%s'" % structure_file) return st
def structure_from_file(structure_file): st = None if not os.path.isfile(structure_file): print("ERROR: Could not open file '%s'" % structure_file) sys.exit(1) if structure_file[-4:].lower() == 'json': st = Structure.load_json(structure_file) elif structure_file[-3:].lower() == 'cif' and HAS_PYMATGEN: import pychemia.external.pymatgen st = pychemia.external.pymatgen.cif2structure(structure_file)[0] elif structure_file[-6:].lower() == 'poscar': st = read_poscar(structure_file) elif structure_file[-6:].lower() == 'contcar': st = read_poscar(structure_file) elif structure_file[-6:].lower() == 'abinit.in': av = InputVariables(structure_file) av.get_structure() else: try: st = read_poscar(structure_file) except ValueError: print("ERROR: Could not extract structure from file '%s'" % structure_file) exit(1) return st
def __init__(self, name, source_dir='.', mag_atoms=None, magmom_magnitude=2.0, distance_tolerance=0.1, incar_extra=None, debug=False): """ This class provides a population of Magnetic Moment vectors for the same structure and was created to be used on VASP. The magnetic moment is set in cartesian coordinates with 3 numbers for each atom in the unit cell. This population provides methods to manipulate the magnetic moments between different candidates in order to optimize the magnetic orientations using the global-search methods implemented on PyChemia. :param name: The name of the database to be created or directly the PyChemiaDB database object. The name is used when the database can be created without username, password and no encryption. Otherwise the database must be created first and its object be 'name' argument. :param source_dir: Directory that contains the basic 4 files for VASP: 'POSCAR', 'POTCAR', 'KPOINTS' and 'INCAR' Except for 'INCAR' the files are linked symbolically on each directory that will run VASP The input variables on 'INCAR' changing only MAGMOM and I_CONSTRAINED_M. The 'INCAR' file could contain generic variables and some other variables could be directly specified using the dictionary 'incar_extra'. :param mag_atoms: List of atoms for which the Magnetic Moments are changed. If the variable is None, the list is inferred from the original INCAR file. The numbering of atoms start with 0. :param magmom_magnitude: Fix value for the magnitude of the magnetic moment imposed for all the atoms in 'mag_atoms' list :param distance_tolerance: Maximal distance in magnetic moments to consider two candidates as equivalent. :param incar_extra: Extra variables for INCAR file that are added or replaced from the INCAR read with 'source_dir' :param debug: If True produce a verbose output during the different calls to the methods. """ Population.__init__(self, name, 'global', distance_tolerance=distance_tolerance) if not os.path.isfile(source_dir + os.sep + 'INCAR'): raise ValueError("INCAR not found") if not os.path.isfile(source_dir + os.sep + 'POSCAR'): raise ValueError("POSCAR not found") if not os.path.isfile(source_dir + os.sep + 'POTCAR'): raise ValueError("POTCAR not found") if not os.path.isfile(source_dir + os.sep + 'KPOINTS'): raise ValueError("KPOINTS not found") self.input = read_incar(source_dir + os.sep + 'INCAR') self.source_dir = source_dir if 'MAGMOM' not in self.input: raise ValueError('INCAR should define the MAGMOM variable') magmom = np.array(self.input.MAGMOM).reshape((-1, 3)) self.structure = read_poscar(source_dir + os.sep + 'POSCAR') if mag_atoms is None: self.mag_atoms = list(np.where(np.apply_along_axis(np.linalg.norm, 1, magmom) > 0.0)[0]) self.mag_atoms = [int(x) for x in self.mag_atoms] else: self.mag_atoms = mag_atoms self.magmom_magnitude = magmom_magnitude if incar_extra is None: self.incar_extra = {'IBRION': -1, 'LWAVE': True, 'LAMBDA': 10, 'NSW': 0, 'I_CONSTRAINED_M': 1} else: self.incar_extra = incar_extra self.debug = debug
def structure_from_file(structure_file): st = None if not os.path.isfile(structure_file): print("ERROR: Could not open file '%s'" % structure_file) sys.exit(1) if structure_file[-4:].lower() == 'json': st = Structure.load_json(structure_file) elif structure_file[-3:].lower() == 'cif' and HAS_PYMATGEN: import pychemia.external.pymatgen st = pychemia.external.pymatgen.cif2structure(structure_file)[0] elif structure_file[-6:].lower() == 'poscar': st = read_poscar(structure_file) elif structure_file[-6:].lower() == 'contcar': st = read_poscar(structure_file) elif structure_file[-6:].lower() == 'abinit.in': av = AbinitInput(structure_file) av.get_structure() else: try: st = read_poscar(structure_file) except ValueError: print("ERROR: Could not extract structure from file '%s'" % structure_file) exit(1) return st
def __init__(self, name, source_dir='.', mag_atoms=None, magmom_magnitude=2.0, distance_tolerance=0.1): Population.__init__(self, name, 'global') if not os.path.isfile(source_dir + os.sep + 'INCAR'): raise ValueError("INCAR not found") if not os.path.isfile(source_dir + os.sep + 'POSCAR'): raise ValueError("POSCAR not found") self.input = read_incar(source_dir + os.sep + 'INCAR') magmom = np.array(self.input.get_value('MAGMOM')).reshape((-1, 3)) self.structure = read_poscar(source_dir + os.sep + 'POSCAR') if mag_atoms is None: self.mag_atoms = list(np.where(np.apply_along_axis(np.linalg.norm, 1, magmom) > 0.0)[0]) self.mag_atoms = [int(x) for x in self.mag_atoms] else: self.mag_atoms = mag_atoms self.magmom_magnitude = magmom_magnitude self.distance_tolerance = distance_tolerance
def update(self, workdir): """ This routine determines how to proceed with the relaxation for one specific work directory :param workdir: (str) String representation of the id in the mongodb :return: """ # workdir = self.basedir + os.sep + entry_id entry_id = os.path.basename(workdir) vj = self.vasp_jobs[entry_id] runj = self.runs[entry_id] if os.path.isfile(workdir + os.sep + 'OUTCAR'): vj.get_outputs() self.update_history(entry_id) if os.path.isfile(workdir + os.sep + 'RELAXED'): self.add_status(entry_id, 'RELAXED') elif not os.path.isfile(workdir + os.sep + 'PROCAR'): self.add_status(entry_id, 'NOPROCAR') else: self.del_status(entry_id, 'NOPROCAR') if not os.path.isfile(workdir + os.sep + 'OUTCAR'): self.add_status(entry_id, 'NOOUTCAR') else: self.del_status(entry_id, 'NOOUTCAR') print('-') vo = VaspOutput(workdir + os.sep + 'OUTCAR') relaxation_info = vo.relaxation_info() if len(relaxation_info) != 3: print('[' + str(entry_id) + ']' + ' Missing some data in OUTCAR (forces or stress)') self.add_status(entry_id, 'NOOUTCAR') print('[' + str(entry_id) + ']' + 'Results:') for i in relaxation_info: print('[' + str(entry_id) + '] %20s %12.5e' % (i, relaxation_info[i])) # Conditions to consider the structure relaxed if relaxation_info['avg_force'] < self.target_force: if relaxation_info['avg_stress_diag'] < self.target_stress: if relaxation_info[ 'avg_stress_non_diag'] < self.target_stress: wf = open(workdir + os.sep + 'RELAXED', 'w') for i in relaxation_info: wf.write("%15s %12.3f" % (i, relaxation_info[i])) wf.close() wf = open(workdir + os.sep + 'COMPLETE', 'w') for i in relaxation_info: wf.write("%15s %12.3f" % (i, relaxation_info[i])) wf.close() self.add_status(entry_id, 'RELAXED') if self.modify_input(entry_id): # How to change ISIF if relaxation_info['avg_force'] < 0.1: if relaxation_info['avg_stress_diag'] < 0.1: if relaxation_info['avg_stress_non_diag'] < 0.1: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 2 # How to change IBRION # if info['avg_force'] < 0.1 and info['avg_stress_diag'] < 0.1 and info['avg_stress_non_diag'] < 0.1: # vj.input_variables.variables['IBRION'] = 1 # elif info['avg_force'] < 1 and info['avg_stress_diag'] < 1 and info['avg_stress_non_diag'] < 1: # vj.input_variables.variables['IBRION'] = 2 # else: # vj.input_variables.variables['IBRION'] = 3 # How to change EDIFF if vj.input_variables.variables['EDIFF'] > 2 * 1E-4: vj.input_variables.variables['EDIFF'] = round_small( vj.input_variables.variables['EDIFF'] / 2) else: vj.input_variables.variables['EDIFF'] = 1E-4 # How to change EDIFFG if vj.input_variables.variables['EDIFFG'] < -2 * self.target_force: vj.input_variables.variables['EDIFFG'] = round_small( vj.input_variables.variables['EDIFFG'] / 2) else: vj.input_variables.variables['EDIFFG'] = -self.target_force # Print new values print('[' + str(entry_id) + ']' + 'New Values:') for i in ['ISIF', 'IBRION', 'EDIFF', 'EDIFFG']: print('[' + str(entry_id) + ']' + i + ' : ', vj.input_variables.variables[i]) print('-') for i in ['OUTCAR']: if not os.path.exists(workdir + os.sep + i): wf = open(workdir + os.sep + i, 'w') wf.write('') wf.close() log = logging.handlers.RotatingFileHandler(workdir + os.sep + i, maxBytes=1, backupCount=1000) log.doRollover() try: vj.structure = read_poscar(workdir + os.sep + 'CONTCAR') except ValueError: print('Error reading CONTCAR') vj.set_inputs() properties = vj.outcar status = self.status[entry_id] newentry = self.population.db.update(entry_id, structure=vj.structure, properties=properties, status=status) vj.save_json(workdir + os.sep + 'vaspjob.json') wf = open(workdir + os.sep + 'entry.json', 'w') json.dump(newentry, wf, sort_keys=True, indent=4, separators=(',', ': ')) wf.close() return True else: vj.set_inputs() status = self.status[entry_id] newentry = self.population.db.update(entry_id, structure=vj.structure, status=status) vj.save_json(workdir + os.sep + 'vaspjob.json') wf = open(workdir + os.sep + 'entry.json', 'w') json.dump(newentry, wf, sort_keys=True, indent=4, separators=(',', ': ')) wf.close() return True
def update(self, workdir): """ This routine determines how to proceed with the relaxation for one specific work directory :param workdir: (str) String representation of the id in the mongodb :return: """ # workdir = self.basedir + os.sep + entry_id entry_id = os.path.basename(workdir) vj = self.vasp_jobs[entry_id] runj = self.runs[entry_id] if os.path.isfile(workdir + os.sep + 'OUTCAR'): vj.get_outputs() self.update_history(entry_id) if os.path.isfile(workdir + os.sep + 'RELAXED'): self.add_status(entry_id, 'RELAXED') elif not os.path.isfile(workdir + os.sep + 'PROCAR'): self.add_status(entry_id, 'NOPROCAR') else: self.del_status(entry_id, 'NOPROCAR') if not os.path.isfile(workdir + os.sep + 'OUTCAR'): self.add_status(entry_id, 'NOOUTCAR') else: self.del_status(entry_id, 'NOOUTCAR') print('-') vo = VaspOutput(workdir + os.sep + 'OUTCAR') relaxation_info = vo.relaxation_info() if len(relaxation_info) != 3: print('[' + str(entry_id) + ']' + ' Missing some data in OUTCAR (forces or stress)') self.add_status(entry_id, 'NOOUTCAR') print('[' + str(entry_id) + ']' + 'Results:') for i in relaxation_info: print('[' + str(entry_id) + '] %20s %12.5e' % (i, relaxation_info[i])) # Conditions to consider the structure relaxed if relaxation_info['avg_force'] < self.target_force: if relaxation_info['avg_stress_diag'] < self.target_stress: if relaxation_info['avg_stress_non_diag'] < self.target_stress: wf = open(workdir + os.sep + 'RELAXED', 'w') for i in relaxation_info: wf.write("%15s %12.3f" % (i, relaxation_info[i])) wf.close() wf = open(workdir + os.sep + 'COMPLETE', 'w') for i in relaxation_info: wf.write("%15s %12.3f" % (i, relaxation_info[i])) wf.close() self.add_status(entry_id, 'RELAXED') if self.modify_input(entry_id): # How to change ISIF if relaxation_info['avg_force'] < 0.1: if relaxation_info['avg_stress_diag'] < 0.1: if relaxation_info['avg_stress_non_diag'] < 0.1: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 3 else: vj.input_variables.variables['ISIF'] = 2 # How to change IBRION # if info['avg_force'] < 0.1 and info['avg_stress_diag'] < 0.1 and info['avg_stress_non_diag'] < 0.1: # vj.input_variables.variables['IBRION'] = 1 # elif info['avg_force'] < 1 and info['avg_stress_diag'] < 1 and info['avg_stress_non_diag'] < 1: # vj.input_variables.variables['IBRION'] = 2 # else: # vj.input_variables.variables['IBRION'] = 3 # How to change EDIFF if vj.input_variables.variables['EDIFF'] > 2 * 1E-4: vj.input_variables.variables['EDIFF'] = round_small(vj.input_variables.variables['EDIFF'] / 2) else: vj.input_variables.variables['EDIFF'] = 1E-4 # How to change EDIFFG if vj.input_variables.variables['EDIFFG'] < - 2 * self.target_force: vj.input_variables.variables['EDIFFG'] = round_small(vj.input_variables.variables['EDIFFG'] / 2) else: vj.input_variables.variables['EDIFFG'] = - self.target_force # Print new values print('[' + str(entry_id) + ']' + 'New Values:') for i in ['ISIF', 'IBRION', 'EDIFF', 'EDIFFG']: print('[' + str(entry_id) + ']' + i + ' : ', vj.input_variables.variables[i]) print('-') for i in ['OUTCAR']: if not os.path.exists(workdir + os.sep + i): wf = open(workdir + os.sep + i, 'w') wf.write('') wf.close() log = logging.handlers.RotatingFileHandler(workdir + os.sep + i, maxBytes=1, backupCount=1000) log.doRollover() try: vj.structure = read_poscar(workdir + os.sep + 'CONTCAR') except ValueError: print('Error reading CONTCAR') vj.set_inputs() properties = vj.outcar status = self.status[entry_id] newentry = self.population.db.update(entry_id, structure=vj.structure, properties=properties, status=status) vj.save_json(workdir + os.sep + 'vaspjob.json') wf = open(workdir + os.sep + 'entry.json', 'w') json.dump(newentry, wf, sort_keys=True, indent=4, separators=(',', ': ')) wf.close() return True else: vj.set_inputs() status = self.status[entry_id] newentry = self.population.db.update(entry_id, structure=vj.structure, status=status) vj.save_json(workdir + os.sep + 'vaspjob.json') wf = open(workdir + os.sep + 'entry.json', 'w') json.dump(newentry, wf, sort_keys=True, indent=4, separators=(',', ': ')) wf.close() return True
def __init__(self, name, source_dir='.', mag_atoms=None, magmom_magnitude=2.0, distance_tolerance=0.1, incar_extra=None, debug=False): """ This class provides a population of Magnetic Moment vectors for the same structure and was created to be used on VASP. The magnetic moment is set in cartesian coordinates with 3 numbers for each atom in the unit cell. This population provides methods to manipulate the magnetic moments between different candidates in order to optimize the magnetic orientations using the global-search methods implemented on PyChemia. :param name: The name of the database to be created or directly the PyChemiaDB database object. The name is used when the database can be created without username, password and no encryption. Otherwise the database must be created first and its object be 'name' argument. :param source_dir: Directory that contains the basic 4 files for VASP: 'POSCAR', 'POTCAR', 'KPOINTS' and 'INCAR' Except for 'INCAR' the files are linked symbolically on each directory that will run VASP The input variables on 'INCAR' changing only MAGMOM and I_CONSTRAINED_M. The 'INCAR' file could contain generic variables and some other variables could be directly specified using the dictionary 'incar_extra'. :param mag_atoms: List of atoms for which the Magnetic Moments are changed. If the variable is None, the list is inferred from the original INCAR file. The numbering of atoms start with 0. :param magmom_magnitude: Fix value for the magnitude of the magnetic moment imposed for all the atoms in 'mag_atoms' list :param distance_tolerance: Maximal distance in magnetic moments to consider two candidates as equivalent. :param incar_extra: Extra variables for INCAR file that are added or replaced from the INCAR read with 'source_dir' :param debug: If True produce a verbose output during the different calls to the methods. """ Population.__init__(self, name, 'global', distance_tolerance=distance_tolerance) if not os.path.isfile(source_dir + os.sep + 'INCAR'): raise ValueError("INCAR not found") if not os.path.isfile(source_dir + os.sep + 'POSCAR'): raise ValueError("POSCAR not found") if not os.path.isfile(source_dir + os.sep + 'POTCAR'): raise ValueError("POTCAR not found") if not os.path.isfile(source_dir + os.sep + 'KPOINTS'): raise ValueError("KPOINTS not found") self.input = read_incar(source_dir + os.sep + 'INCAR') self.source_dir = source_dir if 'MAGMOM' not in self.input: raise ValueError('INCAR should define the MAGMOM variable') magmom = np.array(self.input.MAGMOM).reshape((-1, 3)) self.structure = read_poscar(source_dir + os.sep + 'POSCAR') if mag_atoms is None: self.mag_atoms = list( np.where( np.apply_along_axis(np.linalg.norm, 1, magmom) > 0.0)[0]) self.mag_atoms = [int(x) for x in self.mag_atoms] else: self.mag_atoms = mag_atoms self.magmom_magnitude = magmom_magnitude if incar_extra is None: self.incar_extra = { 'IBRION': -1, 'LWAVE': True, 'LAMBDA': 10, 'NSW': 0, 'I_CONSTRAINED_M': 1 } else: self.incar_extra = incar_extra self.debug = debug
def worker_maise(db_settings, entry_id, workdir, relaxator_params): """ Relax and return evaluate the energy of the structure stored with identifier 'entry_id' using the MAISE code :param db_settings: (dict) Dictionary of DB parameters needed to create a PyChemiaDB object :param entry_id: MongoDB identifier of one entry of the database created from db_settings :param workdir: (str) Working directory where input and output from MAISE is written :param relaxator_params: (dict) Arguments needed to control the relaxation using MAISE Arguments are store as keys and they include: 'target_forces' : Used to defined the tolerance to consider one candidate as relaxed. 'source_dir': Directory with executable maise and directory INI :return: """ max_ncalls = 6 pcdb = get_database(db_settings) target_forces = relaxator_params['target_forces'] source_dir = relaxator_params['source_dir'] pcm_log.info('[%s]: Starting relaxation. Target forces: %7.3e' % (str(entry_id), target_forces)) if pcdb.is_locked(entry_id): return else: pcdb.lock(entry_id) structure = pcdb.get_structure(entry_id) status = pcdb.get_dicts(entry_id)[2] if 'ncalls' in status and status['ncalls'] > 0: ncalls = status['ncalls'] + 1 print('ncalls = ', status['ncalls']) else: ncalls = 1 print('Verifing initial structure...') while np.min(structure.distance_matrix()+(np.eye(structure.natom)*5)) < 1.9: print('ERROR: Bad initial guess, two atoms are to close. Creating new random structure for id: %s' % str(entry_id)) write_poscar(structure, workdir + os.sep + 'Fail_initial_POSCAR') # WIH structure = Structure.random_cell(structure.composition) write_poscar(structure, workdir + os.sep + 'POSCAR') if not os.path.exists(workdir + os.sep + 'setup') and ncalls == 1: # WIH print('First run.') # WIH # print('Verifying that everything runs smoothly') # WIH print(workdir + os.sep + 'setup') shutil.copy2(source_dir + os.sep + 'setup_1', workdir + os.sep + 'setup') # WIH elif ncalls > 1: # WIH shutil.copy2(source_dir + os.sep + 'setup_2', workdir + os.sep + 'setup') # WIH if not os.path.exists(workdir + os.sep + 'INI'): os.symlink(source_dir + os.sep + 'INI', workdir + os.sep + 'INI') if not os.path.exists(workdir + os.sep + 'maise'): os.symlink(source_dir + os.sep + 'maise', workdir + os.sep + 'maise') # Get the Current Working Directory # cwd = os.getcwd() # Move to the actual directory where maise will run os.chdir(workdir) wf = open('maise.stdout', 'w') subprocess.call(['./maise'], stdout=wf) wf.close() if os.path.isfile('OSZICAR'): energies = np.loadtxt('OSZICAR') else: energies = None forces = None stress = None stress_kb = None if os.path.isfile('OUTCAR'): rf = open('OUTCAR', 'r') data = rf.read() pos_forces = re.findall(r'TOTAL-FORCE \(eV/Angst\)\s*-*\s*([-.\d\s]+)\s+-{2}', data) pos_forces = np.array([x.split() for x in pos_forces], dtype=float) if len(pos_forces) > 0 and len(pos_forces[-1]) % 7 == 0: pos_forces.shape = (len(pos_forces), -1, 7) forces = pos_forces[:, :, 3:6] # positions = pos_forces[:, :, :3] else: print('Forces and Positions could not be parsed : ', pos_forces.shape) print('pos_forces =\n%s ' % pos_forces) str_stress = re.findall('Total([.\d\s-]*)in', data) if len(str_stress) == 2: stress = np.array([[float(y) for y in x.split()] for x in str_stress]) str_stress = re.findall('in kB([.\d\s-]*)energy', data) if len(str_stress) == 2: stress_kb = np.array([[float(y) for y in x.split()] for x in str_stress]) create_new = False if not os.path.isfile('CONTCAR') or os.path.getsize("CONTCAR") == 0: create_new = True print('CONTCAR not found in entry: %s' % str(entry_id)) i = 1 while True: if not os.path.isfile('POSCAR-failed-%03s' % str(i)): os.rename('POSCAR', 'POSCAR-failed-%03s' % str(i)) break else: i += 1 else: new_structure = read_poscar('CONTCAR') # min_dist = np.min(new_structure.distance_matrix+np.ones((new_structure.natom,new_structure.natom))) min_dist = np.min(new_structure.distance_matrix()+(np.eye(new_structure.natom)*5)) # WIH print('Minimal distance= %8.7f' % min_dist) # WIH if min_dist < 2.0: print('ERROR: MAISE finished with and structure with distances too close:', entry_id) # WIH write_poscar(new_structure, workdir + os.sep + 'Collapsed_CONTCAR') # WIH create_new = True # WIH if create_new: new_structure = Structure.random_cell(structure.composition) ncalls = 0 # WIH if ncalls > max_ncalls: print('WARNING: Too many calls to MAISE and no relaxation succeeded, replacing structure: ', entry_id) # WIH new_structure = Structure.random_cell(structure.composition) pcdb.entries.update({'_id': entry_id}, {'$set': {'status.ncalls': 0}}) create_new = True else: pcdb.entries.update({'_id': entry_id}, {'$set': {'status.ncalls': ncalls}}) pcdb.update(entry_id, structure=new_structure, properties={}) # if not create_new and energies is not None and forces is not None and stress is not None: if energies is not None and forces is not None and stress is not None: te = energies[1] pcdb.entries.update({'_id': entry_id}, {'$set': {'status.relaxation': 'succeed', 'status.target_forces': target_forces, 'properties.initial_forces': generic_serializer(forces[0]), 'properties.initial_stress': generic_serializer(stress[0]), 'properties.initial_stress_kB': generic_serializer(stress_kb[0]), 'properties.forces': generic_serializer(forces[1]), 'properties.stress': generic_serializer(stress[1]), 'properties.stress_kB': generic_serializer(stress_kb[1]), 'properties.energy': te, 'properties.energy_pa': te / new_structure.natom, 'properties.energy_pf': te / new_structure.get_composition().gcd}}) for ifile in ['POSCAR', 'CONTCAR', 'setup', 'OUTCAR', 'maise.stdout', 'list.dat']: if not os.path.exists(ifile): wf = open(ifile, 'w') wf.write('') wf.close() n = 1 while True: if os.path.exists(ifile + ('_%03d' % n)): n += 1 else: break os.rename(ifile, ifile+('_%03d' % n)) pcm_log.info('[%s]: Unlocking the entry' % str(entry_id)) pcdb.unlock(entry_id)
def worker_maise(db_settings, entry_id, workdir, target_forces, relaxator_params): max_ncalls = 6 pcdb = get_database(db_settings) pcm_log.info('[%s]: Starting relaxation. Target forces: %7.3e' % (str(entry_id), target_forces)) if pcdb.is_locked(entry_id): return else: pcdb.lock(entry_id) structure = pcdb.get_structure(entry_id) status = pcdb.get_dicts(entry_id)[2] if 'ncalls' in status: ncalls = status['ncalls'] + 1 else: ncalls = 1 #print('Current directory: '+os.getcwd() ) #print('Working directory: '+workdir) write_poscar(structure,workdir+os.sep+'POSCAR') if not os.path.exists(workdir+os.sep+'setup'): shutil.copy2('setup', workdir) if not os.path.exists(workdir+os.sep+'INI'): os.symlink(os.getcwd()+os.sep+'INI', workdir+os.sep+'INI') if not os.path.exists(workdir+os.sep+'maise'): os.symlink(os.getcwd()+os.sep+'maise', workdir+os.sep+'maise') cwd=os.getcwd() os.chdir(workdir) wf=open('maise.stdout','w') subprocess.call(['./maise'], stdout=wf) wf.close() if os.path.isfile('OSZICAR'): energies=np.loadtxt('OSZICAR') else: energies=None if os.path.isfile('OUTCAR'): rf = open('OUTCAR', 'r') data = rf.read() pos_forces = re.findall(r'TOTAL-FORCE \(eV/Angst\)\s*-*\s*([-.\d\s]+)\s+-{2}', data) pos_forces = np.array([x.split() for x in pos_forces], dtype=float) if len(pos_forces) > 0 and len(pos_forces[-1]) % 7 == 0: pos_forces.shape = (len(pos_forces), -1, 7) forces = pos_forces[:, :, 3:6] positions = pos_forces[:, :, :3] else: print('Forces and Positions could not be parsed : ', pos_forces.shape) print('pos_forces =\n%s ' % pos_forces) str_stress=re.findall('Total([\.\d\s-]*)in',data) if len(str_stress)==2: stress = np.array([[float(y) for y in x.split()] for x in str_stress]) str_stress=re.findall('in kB([\.\d\s-]*)energy',data) if len(str_stress)==2: stress_kB = np.array([[float(y) for y in x.split()] for x in str_stress]) else: forces=None stress=None stress_kB=None new_structure=read_poscar('CONTCAR') if np.min(new_structure.distance_matrix()+np.eye(new_structure.natom))<0.23: print('WARNING: Structure collapse 2 atoms, creating a new random structure') new_structure=Structure.random_cell(new_structure.composition) if ncalls > max_ncalls: print('WARNING: Too many calls to MAISE and no relaxation succeeded, replacing structure') new_structure=Structure.random_cell(new_structure.composition) pcdb.entries.update({'_id': entry_id}, {'$set': {'status.ncalls': 0}}) else: pcdb.entries.update({'_id': entry_id}, {'$set': {'status.ncalls': ncalls}}) pcdb.update(entry_id, structure=new_structure) if energies is not None and forces is not None and stress is not None: te = energies[1] pcdb.entries.update({'_id': entry_id}, {'$set': {'status.relaxation': 'succeed', 'status.target_forces': target_forces, 'properties.initial_forces': generic_serializer(forces[0]), 'properties.initial_stress': generic_serializer(stress[0]), 'properties.initial_stress_kB': generic_serializer(stress_kB[0]), 'properties.forces': generic_serializer(forces[1]), 'properties.stress': generic_serializer(stress[1]), 'properties.stress_kB': generic_serializer(stress_kB[1]), 'properties.energy': te, 'properties.energy_pa': te / new_structure.natom, 'properties.energy_pf': te / new_structure.get_composition().gcd}}) for ifile in ['POSCAR', 'CONTCAR', 'setup', 'OUTCAR', 'maise.stdout', 'list.dat']: if not os.path.exists(ifile): wf = open(ifile, 'w') wf.write('') wf.close() n=1 while True: if os.path.exists(ifile+ ('_%03d' % n)): n+=1 else: break os.rename(ifile,ifile+('_%03d' % n)) pcm_log.info('[%s]: Unlocking the entry' % str(entry_id)) pcdb.unlock(entry_id)
def worker_maise(db_settings, entry_id, workdir, relaxator_params): """ Relax and return evaluate the energy of the structure stored with identifier 'entry_id' using the MAISE code :param db_settings: (dict) Dictionary of DB parameters needed to create a PyChemiaDB object :param entry_id: MongoDB identifier of one entry of the database created from db_settings :param workdir: (str) Working directory where input and output from MAISE is written :param relaxator_params: (dict) Arguments needed to control the relaxation using MAISE Arguments are store as keys and they include: 'target_forces' : Used to defined the tolerance to consider one candidate as relaxed. 'source_dir': Directory with executable maise and directory INI :return: """ max_ncalls = 6 pcdb = get_database(db_settings) target_forces = relaxator_params['target_forces'] source_dir = relaxator_params['source_dir'] pcm_log.info('[%s]: Starting relaxation. Target forces: %7.3e' % (str(entry_id), target_forces)) if pcdb.is_locked(entry_id): return else: pcdb.lock(entry_id) structure = pcdb.get_structure(entry_id) status = pcdb.get_dicts(entry_id)[2] if 'ncalls' in status and status['ncalls'] > 0: ncalls = status['ncalls'] + 1 print('ncalls = ', status['ncalls']) else: ncalls = 1 print('Verifing initial structure...') while np.min(structure.distance_matrix() + (np.eye(structure.natom) * 5)) < 1.9: print( 'ERROR: Bad initial guess, two atoms are to close. Creating new random structure for id: %s' % str(entry_id)) write_poscar(structure, workdir + os.sep + 'Fail_initial_POSCAR') # WIH structure = Structure.random_cell(structure.composition) write_poscar(structure, workdir + os.sep + 'POSCAR') if not os.path.exists(workdir + os.sep + 'setup') and ncalls == 1: # WIH print('First run.') # WIH # print('Verifying that everything runs smoothly') # WIH print(workdir + os.sep + 'setup') shutil.copy2(source_dir + os.sep + 'setup_1', workdir + os.sep + 'setup') # WIH elif ncalls > 1: # WIH shutil.copy2(source_dir + os.sep + 'setup_2', workdir + os.sep + 'setup') # WIH if not os.path.exists(workdir + os.sep + 'INI'): os.symlink(source_dir + os.sep + 'INI', workdir + os.sep + 'INI') if not os.path.exists(workdir + os.sep + 'maise'): os.symlink(source_dir + os.sep + 'maise', workdir + os.sep + 'maise') # Get the Current Working Directory # cwd = os.getcwd() # Move to the actual directory where maise will run os.chdir(workdir) wf = open('maise.stdout', 'w') subprocess.call(['./maise'], stdout=wf) wf.close() if os.path.isfile('OSZICAR'): energies = np.loadtxt('OSZICAR') else: energies = None forces = None stress = None stress_kb = None if os.path.isfile('OUTCAR'): rf = open('OUTCAR', 'r') data = rf.read() pos_forces = re.findall( r'TOTAL-FORCE \(eV/Angst\)\s*-*\s*([-.\d\s]+)\s+-{2}', data) pos_forces = np.array([x.split() for x in pos_forces], dtype=float) if len(pos_forces) > 0 and len(pos_forces[-1]) % 7 == 0: pos_forces.shape = (len(pos_forces), -1, 7) forces = pos_forces[:, :, 3:6] # positions = pos_forces[:, :, :3] else: print('Forces and Positions could not be parsed : ', pos_forces.shape) print('pos_forces =\n%s ' % pos_forces) str_stress = re.findall('Total([.\d\s-]*)in', data) if len(str_stress) == 2: stress = np.array([[float(y) for y in x.split()] for x in str_stress]) str_stress = re.findall('in kB([.\d\s-]*)energy', data) if len(str_stress) == 2: stress_kb = np.array([[float(y) for y in x.split()] for x in str_stress]) create_new = False if not os.path.isfile('CONTCAR') or os.path.getsize("CONTCAR") == 0: create_new = True print('CONTCAR not found in entry: %s' % str(entry_id)) i = 1 while True: if not os.path.isfile('POSCAR-failed-%03s' % str(i)): os.rename('POSCAR', 'POSCAR-failed-%03s' % str(i)) break else: i += 1 else: new_structure = read_poscar('CONTCAR') # min_dist = np.min(new_structure.distance_matrix+np.ones((new_structure.natom,new_structure.natom))) min_dist = np.min(new_structure.distance_matrix() + (np.eye(new_structure.natom) * 5)) # WIH print('Minimal distance= %8.7f' % min_dist) # WIH if min_dist < 2.0: print( 'ERROR: MAISE finished with and structure with distances too close:', entry_id) # WIH write_poscar(new_structure, workdir + os.sep + 'Collapsed_CONTCAR') # WIH create_new = True # WIH if create_new: new_structure = Structure.random_cell(structure.composition) ncalls = 0 # WIH if ncalls > max_ncalls: print( 'WARNING: Too many calls to MAISE and no relaxation succeeded, replacing structure: ', entry_id) # WIH new_structure = Structure.random_cell(structure.composition) pcdb.entries.update({'_id': entry_id}, {'$set': {'status.ncalls': 0}}) create_new = True else: pcdb.entries.update({'_id': entry_id}, {'$set': { 'status.ncalls': ncalls }}) pcdb.update(entry_id, structure=new_structure, properties={}) # if not create_new and energies is not None and forces is not None and stress is not None: if energies is not None and forces is not None and stress is not None: te = energies[1] pcdb.entries.update({'_id': entry_id}, { '$set': { 'status.relaxation': 'succeed', 'status.target_forces': target_forces, 'properties.initial_forces': generic_serializer(forces[0]), 'properties.initial_stress': generic_serializer(stress[0]), 'properties.initial_stress_kB': generic_serializer( stress_kb[0]), 'properties.forces': generic_serializer(forces[1]), 'properties.stress': generic_serializer(stress[1]), 'properties.stress_kB': generic_serializer(stress_kb[1]), 'properties.energy': te, 'properties.energy_pa': te / new_structure.natom, 'properties.energy_pf': te / new_structure.get_composition().gcd } }) for ifile in [ 'POSCAR', 'CONTCAR', 'setup', 'OUTCAR', 'maise.stdout', 'list.dat' ]: if not os.path.exists(ifile): wf = open(ifile, 'w') wf.write('') wf.close() n = 1 while True: if os.path.exists(ifile + ('_%03d' % n)): n += 1 else: break os.rename(ifile, ifile + ('_%03d' % n)) pcm_log.info('[%s]: Unlocking the entry' % str(entry_id)) pcdb.unlock(entry_id)