def H(self): """Returns the Hessian matrix extracted from the frozen phonon calculations. """ if self._H is not None: return self._H #Otherwise, we need to calculate it from scratch. This depends on #whether we are using DFPT or frozen phonons. if not self.dfpt: self.calc_forcesets() dim = ' '.join(map(str, self.supercell)) xargs = ["phonopy", '--dim="{}"'.format(dim), "--writefc"] execute(xargs, self.phonodir, venv=True) if not path.isfile(path.join(self.phonodir, "FORCE_CONSTANTS")): msg.err("Cannot convert FORCE_SETS to FORCE_CONSTANTS") msg.err(''.join(xargs["output"])) else: self.calc_fc() with chdir(self.phonodir): result = file_IO.parse_FORCE_CONSTANTS() self._H = unroll_fc(result) self.save_pkl(self._H, self.H_file) return self._H
def sbatch(jobfile, folder, kwargs): """Submits jobs to a supercomputer with sbatch. Args: jobfile (str): the name of jobfile that needs to be submitted. folder (str): the path to the jobfile. kwargs (dict): other arguments to pass to the execute function. """ execute(["sbatch", jobfile], **kwargs)
def vasp_to_xyz(folder, outfile="output.xyz", recalc=0, properties=["species", "pos", "z", "dft_force"], parameters=["dft_energy", "dft_virial"], config_type=None): """Creates an extended XYZ file for the calculated structure in OUTCAR for the given folder. Args: folder (str): path to the folder to convert. outfile (str): name of the XYZ file to create. The file will be created in the same folder as the original if no absolute path is given. recalc (bool): when True, re-convert the OUTCAR file, even if the target XYZ file already exists. """ from matdb.atoms import Atoms if not path.isabs(outfile): #Convert to absolute path if one wasn't given. outfile = path.join(folder, outfile) if (path.isfile(outfile) and stat(outfile).st_size > 100 and recalc <= 0): return True p = ','.join(properties) P = ','.join(parameters) renames = [("energy", "vasp_energy"), ("force", "vasp_force"), ("virial", "vasp_virial")] sargs = ["convert.py", "-I", "vasprun.xml", "-p", p, "-P", P, "-f", "xyz"] for s, d in renames: sargs.append("-n") sargs.append(s) sargs.append(d) sargs.extend(["-o", outfile, "vasprun.xml"]) from matdb.utility import execute execute(sargs, folder, errignore="OMP_STACKSIZE") if config_type is not None: #We need to load the XYZ file, add the config_type parameter and then #save it again. The -e parameter of convert.py should save this, but in #our experiments, it doesn't :(. a = Atoms(outfile) a.params["config_type"] = config_type a.write(outfile) return path.isfile(outfile) and stat(outfile).st_size > 100
def test_execute(): """Tests missing lines of execute. """ from matdb.utility import execute # test early breaking on stderr and stdout. temp=execute(('which','python'),'.',nlines=0) temp=execute(('python','enum.x'),'.',nlines=0) temp=execute(('which','python'),'.',env_vars={"POTENTIALS_DIR":'1'}) assert "bin/python\n" in temp['output'][0] assert temp['error'] == []
def test_execution(): """Tests the execution via shell subprocess in a different folder. """ from matdb.utility import execute, reporoot sargs = ["pwd"] target = path.join(reporoot, "matdb") xres = execute(sargs, target, nlines=1, env_vars={"VASP_PP_PATH":"~/."}) assert xres["output"][0].strip() == target sargs = ["cat dummy-file"] target = path.join(reporoot, "matdb") xres = execute(sargs, target, nlines=1) assert len(xres["error"]) > 0
def calc_DOS(self, recalc=False): """Calculates the *total* density of states. Args: recalc (bool): when True, recalculate the DOS, even if the file already exists. """ dosfile = path.join(self.phonodir, "mesh.yaml") if not recalc and path.isfile(dosfile): return #Make sure we have calculated the force sets already. self.calc_forcesets(recalc) settings = { "ATOM_NAME": ' '.join(self.database.parent.species), "DIM": ' '.join(map(str, self.supercell)), "MP": ' '.join(map(str, self.dosmesh)) } with open(path.join(self.phonodir, "dos.conf"), 'w') as f: for k, v in settings.items(): f.write("{} = {}\n".format(k, v)) sargs = ["phonopy", "-p", "dos.conf", "-s"] xres = execute(sargs, self.phonodir, venv=True) #Make sure that phonopy actually produced files; otherwise show the output #(phonopy doesn't write to stderr, only stdout). if not path.isfile(dosfile): #pragma: no cover msg.std(''.join(xres["error"])) msg.err("could not calculate the DOS; see errors.")
def test_cat(tmpdir): """Tests concatenation of multiple files. """ from matdb.utility import cat, execute, reporoot files = [path.join(reporoot, "tests", "files", f) for f in ["A.txt", "B.txt"]] outfile = str(tmpdir.join("cat_C.txt")) cat(files, outfile) sargs = ["diff", "C.txt", outfile] xres = execute(sargs, path.join(reporoot, "tests/files")) assert len(xres["output"]) == 0
def _setup_configs(self, rerun): """Displaces the seed configuration preparatory to calculating the force sets for phonon spectra. .. note:: This method *appears* to be VASP-specific. However, the configurations that are generated by `phonopy` as VASP `POSCAR` files are turned into :class:`~matdb.atoms.Atoms` objects before they are passed to the super class that sets up the actual calculations. So, it is quite general. Args: rerun (int): when > 1, recreate the folders even if they already exist. If > 0, recreate the jobfile. """ #We also don't want to setup again if we have the results already. if self.ready() and rerun == 0: return if not self.is_setup() or rerun > 1: from ase.io import write write(path.join(self.phonodir, "POSCAR"), self.atoms, "vasp") scell = ' '.join(map(str, self.supercell)) sargs = ["phonopy", "-d", '--dim="{}"'.format(scell)] pres = execute(sargs, self.phonodir, venv=True) #Make sure that phonopy produced the supercell. If it didn't, it #should have printed an error to *stdout* because it doesn't know #about stderr... if not path.isfile(path.join(self.phonodir, "SPOSCAR")): msg.err('\n'.join(pres["output"])) from matdb.atoms import Atoms if not self.dfpt: #Frozen phonons, create a config execution folder for each of #the displacements. from glob import glob with chdir(self.phonodir): for dposcar in glob("POSCAR-*"): dind = int(dposcar.split('-')[1]) datoms = Atoms(dposcar, format="vasp") self.create(datoms) else: #Pull the perfect supercell and set it up for executing with #DFPT parameters. with chdir(self.phonodir): datoms = Atoms("SPOSCAR", format="vasp") self.create(datoms) # Last of all, create the job file to execute the job array. self.jobfile(rerun)
def execute(self, dryrun=False): """Submits the job script for the currently configured potential training. Args: dryrun (bool): when True, simulate the submission without actually submitting. Returns: bool: True if the submission generated a job id (considered successful). """ if self.ready(): msg.info( "Trainer {} is already done;".format(self.root) + "skipping execute step.", 2) return if not path.isfile(self._jobfile): return False if not path.isfile(self._trainfile): msg.std("train.h5 missing in {}; can't execute.".format(self.root)) return False # We must have what we need to execute. Compile the command and submit. shell_command = self.controller.db.shell_command # We suport 'bash' and 'sbatch' shell commands, if it's neighter one # of them, default to 'bash' if shell_command not in ['bash', 'sbatch']: shell_command = 'bash' cargs = [shell_command, self._jobfile] if dryrun: msg.okay("Executed {} in {}".format(' '.join(cargs), self.root)) return True else: xres = execute(cargs, self.root) # supercompute will return "Submitted" if len(xres["output"]) > 0 and "Submitted" in xres["output"][0]: msg.okay("{}: {}".format(self.root, xres["output"][0].strip())) return True # local computer elif len(xres["error"]) == 0: return True else: return False
def modulate_atoms(db): """Generates modulated configurations using the dynamical matrix of the :class:`DynMatrix` instance. Args: db (Group): database with parameters needed to module the atoms, (Calibration or Modulation databases). """ #Generating the modulation file. We need to sample the DOS in order to #compute that correctly. dosfile = path.join(db.base.phonodir, "mesh.yaml") qvecs = sample_dos(dosfile, sampling=db.sampling, nfreqs=db.nconfigs) conffile = path.join(db.base.phonodir, db.confname) modstr = [' '.join(map(str, db.base.supercell))] for iq, (bandi, qvec) in enumerate(qvecs): mstr = "{0:.7f} {1:.7f} {2:.7f} {3:d} {4:.7f} {5:.7f}" if hasattr(db, "_amplitudes"): A = db._amplitudes[iq] else: A = np.random.normal(1, 0.25) * db.amplitude phi = np.random.uniform(0, 180) args = qvec + [bandi, A, phi] modstr.append(mstr.format(*args)) phondict = db.phonons.copy() phondict["dim"] = db.base.supercell phondict["atom_name"] = ' '.join(db.base.parent.species) phondict["modulation"] = ', '.join(modstr) with open(conffile, 'w') as f: for k, v in phondict.items(): if isinstance(v, (list, tuple, set)): value = ' '.join(map(str, v)) f.write("{} = {}\n".format(k.upper(), value)) else: f.write("{} = {}\n".format(k.upper(), v)) from matdb.utility import execute sargs = ["phonopy", db.confname] xres = execute(sargs, db.base.phonodir, venv=True) #Make sure that phonopy actually produced files; otherwise show the output #(phonopy doesn't write to stderr, only stdout). testmod = path.join(db.base.phonodir, "MPOSCAR-001") if not path.isfile(testmod): #pragma: no cover msg.err(''.join(xres["output"]))
def execute(self, dryrun=False, recovery=False, env_vars=None): """Submits the job script for each of the folders in this database if they are ready to run. Args: dryrun (bool): when True, simulate the submission without actually submitting. recovery (bool): when True, submit the script for running recovery jobs. env_vars (dict): `dict` of environment variables to set before calling the execution. The variables will be set back after execution. Returns: bool: True if the submission generated a job id (considered successful). """ self._expand_sequence() if len(self.sequence) == 0: jobfile = "recovery.sh" if recovery else "jobfile.sh" if not path.isfile(path.join(self.root, jobfile)): return False if not recovery: if not all( a.calc.can_execute(self.configs[i]) for i, a in self.config_atoms.items()): return False #We also need to check that we haven't already submitted this #job. Check to see if it is executing. if any( a.calc.is_executing(self.configs[i]) for i, a in self.config_atoms.items()): return False #Make sure that the calculation isn't complete. if self.last_config_atoms is not None: if any( a.calc.can_extract(self.last_iteration[i]) for i, a in self.last_config_atoms.items()): return False # We must have what we need to execute. Compile the command and # submit. from matdb.utility import execute cargs = [self.database.parent.shell_command, jobfile] if dryrun: from matdb.msg import okay okay("Executed {} in {}".format(' '.join(cargs), self.root)) return True else: xres = execute(cargs, self.root, env_vars=env_vars) if len(xres["output"]) > 0 and "Submitted" in xres["output"][0]: from matdb.msg import okay okay("{}: {}".format(self.root, xres["output"][0].strip())) return True else: #pragma: no cover return False else: #pragma: no cover, enumerated database shouldn't take seeds already_executed = [] for group in self.sequence.values(): already_executed.append( group.execute(dryrun=dryrun, recovery=recovery, env_vars=env_vars)) return all(already_executed)
def command(self): """Returns the command that is needed to train the GAP potentials specified by this object. .. note:: This method also configures the directory that the command will run in so that it has the relevant files. """ self._set_root() if not path.isfile(path.join(self.root, "status.txt")): self.iter_status = "train" self.iter_count = 1 self.cell_iter = 0 else: with open(path.join(self.root, "status.txt"), "r") as f: for line in f: old_status = line.strip().split() if old_status[0] == "done": self.iter_status = "train" # iter_count is the total iteration number for the whole process, not limited to one cell self.iter_count = int(old_status[1]) + 1 self.cell_iter = int(old_status[2]) if int(old_status[3]) <= self.next_cell_threshold: self.cell_iter = int(old_status[2]) + 1 if len(self.cell_sizes) > self.cell_iter: self.relax_max_atoms = self.cell_sizes[self.cell_iter] else: #pragma: no cover msg.err( "The MTP process has finished for the cell sizes " "specified in the YML file.") target1 = path.join(self.root, "unrelaxed.cfg") target2 = path.join(self.root, "to-relax.cfg") if path.isfile(target1): remove(target1) if path.isfile(target2): remove(target2) if self.iter_count >= self.iter_threshold: #pragma: no cover msg.err( "The number of iterations has exceeded " "the allowed number of {0}. To increase this limit " "use the iteration_threshold aption in the YML " "file.".format(self.iter_threshold)) else: self.iter_status = old_status[0] self.iter_count = int(old_status[1]) self.cell_iter = int(old_status[2]) #if we're at the start of a training iteration use the command to train the potential if self.iter_status == "train": self._make_train_cfg(self.iter_count) #remove the selected.cfg and rexaed.cfg from the last iteration if they exist if path.isfile(path.join(self.root, "new_training.cfg")): remove(path.join(self.root, "new_training.cfg")) if path.isfile(path.join(self.root, "relaxed.cfg")): remove(path.join(self.root, "relaxed.cfg")) if not path.isfile(path.join(self.root, "relax.ini")): self._make_relax_ini() if not path.isfile(path.join(self.root, "pot.mtp")): self._make_pot_initial() template = self._train_template() with open(path.join(self.root, "status.txt"), "w+") as f: f.write("relax_setup {0} {1}".format(self.iter_count, self.cell_iter)) if self.iter_status == "relax_setup": # If pot has been trained if path.isfile(path.join(self.root, "Trained.mtp_")): rename(path.join(self.root, "Trained.mtp_"), path.join(self.root, "pot.mtp")) # Calculate the grad of the training configurations. calc_grade = self._calc_grade_template() execute(calc_grade.split(), self.root) if path.isfile(path.join(self.root, "temp1.cfg")): remove(path.join(self.root, "temp1.cfg")) if not path.isfile(path.join(self.root, "state.mvs")): #pragma: no cover raise MlpError( "mlp failed to produce the 'state.mvs` file with command " "'mlp calc-grade pot.mtp train.cfg train.cfg temp1.cfg'") # if the unrelaxed.cfg file exists we need to move it to # replace the existing 'to-relax.cfg' otherwise we need to # create the 'to-relax.cfg' file. if path.isfile(path.join(self.root, "unrelaxed.cfg")) and self.use_unrelaxed: rename(path.join(self.root, "unrelaxed.cfg"), path.join(self.root, "to-relax.cfg")) self.iter_status = "relax" elif not path.isfile(path.join(self.root, "to-relax.cfg")): self._setup_to_relax_cfg() template = "matdb_mtp_to_relax.py > create-to-relax.txt" with open(path.join(self.root, "status.txt"), "w+") as f: f.write("relax {0} {1}".format(self.iter_count, self.cell_iter)) else: self.iter_status = "relax" if self.iter_status == "relax": # command to relax structures if path.isfile(path.join(self.root, "candidate.cfg")): remove(path.join(self.root, "candidate.cfg")) template = self._relax_template() with open(path.join(self.root, "status.txt"), "w+") as f: f.write("select {0} {1}".format(self.iter_count, self.cell_iter)) # if relaxation is done if self.iter_status == "select": cand_files = glob(path.join(self.root, "candidate.cfg_*")) cat(cand_files, path.join(self.root, "candidate.cfg")) for cfile in cand_files: remove(cfile) # command to select next training set. template = self._select_template() with open(path.join(self.root, "status.txt"), "w+") as f: f.write("add {0} {1}".format(self.iter_count, self.cell_iter)) if self.iter_status == "add": with chdir(self.root): # Now add the selected atoms to the Active database. # input filename sould always be entered before output filename execute([ "mlp", "convert-cfg", "new_training.cfg", "POSCAR", "--output-format=vasp-poscar" ], self.root) if path.isfile("new_training.cfg"): rename("new_training.cfg", "new_training.cfg_iter_{}".format(self.iter_count)) if path.isfile("relaxed.cfg"): rename("relaxed.cfg", "relaxed.cfg_iter_{}".format(self.iter_count)) new_configs = [] new_POSCARS = glob("POSCAR*") # Here We need to re-write the POSCARs so that they # have the correct species. for POSCAR in new_POSCARS: # We need to put the correct species into the # title of the POSCAR so that ASE can read it pos_file = [] with open(POSCAR, 'r') as f: for line in f: pos_file.append(line) pos_file[0] = "{0} : {1}".format(" ".join(self.species), pos_file[0]) with open(POSCAR, 'w+') as f: for line in pos_file: f.write(line) new_configs.append(Atoms(POSCAR, format="vasp")) remove(POSCAR) self.active.add_configs(new_configs, self.iter_count) self.active.setup() if len(self.active.configs ) != self.active.nconfigs: #pragma: no cover raise LogicError( "The active database failed to setup the calculations " "for iteration {0}".format(self.iter_count)) self.active.execute() with open(path.join(self.root, "status.txt"), "w+") as f: f.write("done {0} {1} {2}".format(self.iter_count, self.cell_iter, len(new_configs))) template = '' return template
def _calc_bands(atoms, hessian, supercell=(1, 1, 1), outfile=None, grid=None): """Calculates the band structure for the given Hessian matrix. Args: atoms (matdb.atoms.Atoms): atoms object corresponding to the *primitive* cell. The specified supercell matrix should result in a number of atoms that matches the dimensionality of the Hessian. supercell (tuple): tuple of `int` supercell matrix components; can have either 3 or 9 components. hessian (numpy.ndarray): with shape `(natoms*3, natoms*3)`. grid (list): list of `int` specifying the number of divisions in k-space along each reciprocal unit vector. outfile (str): path to the output `band.yaml` file that should be created by this function. Returns: If `outfile` is None, then this method returns a dictionary that has the same format as :func:`from_yaml`. """ #Create a temporary directory in which to work. target = mkdtemp() bandfile = path.join(target, "band.yaml") if grid is None: grid = [13, 13, 13] if isinstance(supercell, np.ndarray): supercell = supercell.flatten() #First, roll up the Hessian and write it as a FORCE_CONSTANTS file. with chdir(target): HR = roll(hessian) write_FORCE_CONSTANTS(HR) atoms.write("POSCAR", format="vasp") #We need to create the band.conf file and write the special #paths in k-space at which the phonons should be calculated. atom_types = _ordered_unique(atoms.get_chemical_symbols()) settings = [("FORCE_CONSTANTS", "READ"), ("ATOM_NAME", ' '.join(atom_types)), ("DIM", ' '.join(map(str, supercell))), ("MP", ' '.join(map(str, grid)))] labels, bands = parsed_kpath(atoms) bandfmt = "{0:.3f} {1:.3f} {2:.3f}" sband = [] for Q in bands: sband.append(bandfmt.format(*Q)) settings.append(("BAND", " ".join(sband))) settings.append(("BAND_LABELS", ' '.join(labels))) with open("band.conf", 'w') as f: for k, v in settings: f.write("{} = {}\n".format(k, v)) sargs = ["phonopy", "band.conf"] xres = execute(sargs, target, venv=True) if not path.isfile(bandfile): #pragma: no cover msg.err("could not calculate phonon bands; see errors.") msg.std(''.join(xres["output"])) result = None if outfile is not None: #Move the band.yaml file to the new target location. from shutil import move move(bandfile, outfile) else: result = from_yaml(bandfile) #Remove the temporary directory that we created and return the result. rmtree(target) return result