def _read_castep(self, folder, sname=None): try: if sname is not None: cfile = os.path.join(folder, sname + ".castep") else: cfile = glob.glob(os.path.join(folder, "*.castep"))[0] sname = seedname(cfile) with silence_stdio(): atoms = io.read(cfile) atoms.info["name"] = sname return atoms except IndexError: raise IOError("ERROR: No .castep files found in {}.".format( os.path.abspath(folder))) except OSError as e: raise IOError("ERROR: {}".format(e)) except ( io.formats.UnknownFileTypeError, ValueError, TypeError, Exception, ) as e: raise IOError( "ERROR: Invalid file: {file}, due to error: {error}".format( file=sname + ".castep", error=e))
def read_output_castep(folder, avgprop='hyperfine'): # Read a castep file in the given folder, and then the required property cfile = glob.glob(os.path.join(folder, '*.castep'))[0] sname = seedname(cfile) a = io.read(cfile) a.info['name'] = sname # Now read properties depending on what's being measured if avgprop == 'hyperfine': m = parse_hyperfine_magres(os.path.join(folder, sname + '.magres')) a.arrays.update(m.arrays) return a
def _read_castep_gamma_phonons(self, atoms, folder, sname=None, QpointPhononModes=None): """Parse CASTEP phonon data into a casteppy object, and return eigenvalues and eigenvectors at the gamma point. """ # Parse CASTEP phonon data into casteppy object try: if sname is not None: pd = QpointPhononModes.from_castep( os.path.join(folder, sname + '.phonon')) else: pd = QpointPhononModes.from_castep( glob.glob(os.path.join(folder, '*.phonon'))[0]) sname = seedname( glob.glob(os.path.join(folder, '*.phonon'))[0]) # Convert frequencies back to cm-1 pd.frequencies_unit = '1/cm' # Get phonon frequencies+modes evals = np.array(pd.frequencies.magnitude) evecs = np.array(pd.eigenvectors) # Only grab the gamma point! gamma_i = None for i, q in enumerate(pd.qpts): if np.isclose(q, [0, 0, 0]).all(): gamma_i = i break if gamma_i is None: raise CastepError('Could not find gamma point phonons in' ' CASTEP phonon file') atoms.info['ph_evals'] = evals[gamma_i] atoms.info['ph_evecs'] = evecs[gamma_i] except IndexError: raise IOError("No .phonon files found in {}.".format( os.path.abspath(folder))) except (OSError, IOError) as e: raise IOError("ERROR: {}".format(e)) except Exception as e: raise IOError( "ERROR: Could not read {file} due to error: {e}".format( file=sname + '.phonon', e=e))
def next_job(self): """Grab the next job from folder_in""" cfile_list = glob.glob(os.path.join(self.folder_in, '*.cell')) if len(cfile_list) == 0: return None cfile = cfile_list[0] name = seedname(cfile) self.log('Starting job {0}\n'.format(name)) files = [cfile] # Check if .param file is available too if os.path.isfile(os.path.join(self.folder_in, name + '.param')): files += [os.path.join(self.folder_in, name + '.param')] job = { 'name': name, 'args': { 'files': files } } return job
def __init__(self, structures=[], info={}, cell_reduce=False, progress=False, suppress_ase_warnings=True): """ Initialize the AtomsCollection | Args: | structures (list[str] or list[ase.Atoms]): list of file names or | Atoms that will form | the collection | info (dict): dictionary of general information to attach | to this collection | cell_reduce (bool): if True, perform a Niggli cell reduction on | all loaded structures | progress (bool): visualize a progress bar for the loading process | suppress_ase_warnings (bool): suppress annoying ASE warnings when | loading files (default is True) """ # Start by parsing out the structures self.structures = [] if isinstance(structures, ase.Atoms): # Well, it's just one... structures = [structures] elif inspect.isgenerator(structures): # Let's unravel it iter_structs = structures structures = [] for s in iter_structs: structures.append(s) if progress: sys.stdout.write("Loading collection...\n") s_n = len(structures) for s_i, struct in enumerate(structures): if progress: # Progress bar sys.stdout.write("\rLoading: {0}".format(utils.progbar(s_i+1, s_n))) # Is it an Atoms object? if type(struct) is ase.Atoms: self.structures.append(ase.Atoms(struct)) # Copy all arrays for k in struct.arrays.keys(): if not self.structures[-1].has(k): self.structures[-1].new_array(k, struct.get_array(k)) if struct.calc is not None: # Prevents pointless attempts at re-calculating self.structures[-1].calc._old_atoms = self.structures[-1] # Or is it a string? elif utils.is_string(struct): with utils.silence_stdio(suppress_ase_warnings, suppress_ase_warnings): self.structures.append(ase_io.read(str(struct))) # If there's no name, give it the filename if 'name' not in self.structures[-1].info: self.structures[-1].info['name'] = utils.seedname(struct) else: raise TypeError('Structures must be Atoms objects or valid ' 'file names,' ' not {0}'.format(type(struct).__name__)) if cell_reduce: # Here we must keep the energy if it was present # We do this by hand because ASE has its good reasons # for severing the atoms-calculator connection when changing # the unit cell. try: _E = self.structures[-1].calc.results['energy'] except (KeyError, AttributeError): _E = None niggli_reduce(self.structures[-1]) if _E is not None: _calc = SinglePointCalculator(self.structures[-1], energy=_E) self.structures[-1].set_calculator(_calc) if progress: sys.stdout.write('\nLoaded {0} structures\n'.format(s_n)) self._all = _AllCaller(self.structures, ase.Atoms) self._arrays = {} # Now assign the info if type(info) is not dict: raise TypeError('Info must be dict,' ' not {0}'.format(type(info).__name__)) else: self.info = info.copy()
def airssGen(input_file, n=100, buildcell_command='buildcell', buildcell_path=None, clone_calc=True): """Generator function binding to AIRSS' Buildcell. This function searches for a buildcell executable and uses it to generate multiple new Atoms structures for a collection. | Args: | input_file (str or file): the .cell file with appropriate comments | specifying the details of buildcell's | construction work. | n (int): number of structures to generate. If set to None the | generator goes on indefinitely. | buildcell_command (str): command required to call the buildcell | executable. | buildcell_path (str): path where the buildcell executable can be | found. If not present, the buildcell command | will be invoked directly (assuming the | executable is in the system PATH). | clone_calc (bool): if True, the CASTEP calculator in the input file | will be copied and attached to the new structures. | This means that for example any additional CASTEP | keywords/blocks in the input file will be carried | on to the new structures. Default is True. | Returns: | airssGenerator (generator): an iterable object that yields structures | created by buildcell. """ # First: check that AIRSS is even installed if buildcell_path is None: buildcell_path = '' airss_cmd = [os.path.join(buildcell_path, buildcell_command)] try: stdout, stderr = sp.Popen(airss_cmd + ['-h'], stdout=sp.PIPE, stderr=sp.PIPE).communicate() except OSError: # Not even installed! raise RuntimeError('No instance of Buildcell found on this system') # Now open the given input file try: input_file = open(input_file) # If it's a string except TypeError: pass # If it's already a file template = input_file.read() # Now get the file name basename = seedname(input_file.name) input_file.close() # Calculator (if needed) calc = None if clone_calc: calc = ase_io.read(input_file.name).calc # And keep track of the count! # (at least if it's not infinite) i = 0 while True: if n is not None: if i >= n: return i += 1 # Generate a structure subproc = sp.Popen(airss_cmd, universal_newlines=True, stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE) stdout, stderr = safe_communicate(subproc, template) # Now turn it into a proper Atoms object # To do this we need to make it look like a file to ASE's io.read newcell = ase_io.read(StringIO(stdout), format='castep-cell') if clone_calc: newcell.set_calculator(copy.deepcopy(calc)) # Generate it a name, function of its properties postfix = hashlib.md5(str(newcell.get_positions() ).encode()).hexdigest() newcell.info['name'] = '{0}_{1}'.format(basename, postfix) yield newcell
def muon_vibrational_average_write(structure, method="independent", mu_index=-1, mu_symbol="H:mu", grid_n=20, sigma_n=3, avgprop="hyperfine", calculator="castep", displace_T=0, phonon_source_file=None, phonon_source_type="castep", **kwargs): """ Write input files to compute a vibrational average for a quantity on a muon in a given system. | Pars: | structure (str): Filename for input structure file | method (str): Method to use for the average. Options are | 'independent', 'montecarlo'. | Default is 'independent'. | mu_index (int): Position of the muon in the given cell file. | Default is -1. | mu_symbol (str): Use this symbol to look for the muon among | CASTEP custom species. Overrides muon_index if | present in cell. | grid_n (int): Number of configurations used for sampling. | Applies slightly | differently to different schemes. | sigma_n (int): Number of sigmas of the harmonic wavefunction used | for sampling. | avgprop (str): Property to calculate and average. Default is | 'hyperfine'. | calculator (str): Source of the property to calculate and average. | Can be 'castep' or 'dftb+'. Default is 'castep'. | phonon_source (str):Source of the phonon data. Can be 'castep' or | 'asedftbp'. Default is 'castep'. | **kwargs: Other arguments (such as specific arguments for | the given phonon method) """ # Open the structure file with silence_stdio(): cell = io.read(structure) path = os.path.split(structure)[0] sname = seedname(structure) cell.info["name"] = sname # Fetch species try: species = cell.get_array("castep_custom_species") except KeyError: species = np.array(cell.get_chemical_symbols()) mu_indices = np.where(species == mu_symbol)[0] if len(mu_indices) > 1: raise MuonAverageError("More than one muon found in the system") elif len(mu_indices) == 1: mu_index = mu_indices[0] else: species = list(species) species[mu_index] = mu_symbol species = np.array(species) cell.set_array("castep_custom_species", species) io_formats = {"castep": ReadWriteCastep, "dftb+": ReadWriteDFTB} # Load the phonons if phonon_source_file is not None: phpath, phfile = os.path.split(phonon_source_file) phfile = seedname(seedname(phfile)) # have to do twice for dftb case else: phpath = path phfile = sname try: rw = io_formats[phonon_source_type]() atoms = rw.read(phpath, phfile, read_phonons=True) ph_evals = atoms.info["ph_evals"] ph_evecs = atoms.info["ph_evecs"] except IOError: raise return except KeyError: phonon_source_file = os.path.join(phpath, phfile + ".phonon") if phonon_source_type == "dftb+": phonon_source_file = phonon_source_file + "s.pkl" raise (IOError( "Phonon file {0} could not be read.".format(phonon_source_file))) return # Fetch masses try: masses = parse_castep_masses(cell) except AttributeError: # Just fall back on ASE standard masses if not available masses = cell.get_masses() masses[mu_index] = constants.m_mu_amu cell.set_masses(masses) # Now create the distribution scheme if method == "independent": displsch = IndependentDisplacements(ph_evals, ph_evecs, masses, mu_index, sigma_n) elif method == "montecarlo": # Set seed np.random.seed(kwargs["random_seed"]) displsch = MonteCarloDisplacements(ph_evals, ph_evecs, masses) displsch.recalc_displacements(n=grid_n, T=displace_T) # Make it a collection pos = cell.get_positions() displaced_cells = [] for i, d in enumerate(displsch.displacements): dcell = cell.copy() dcell.set_positions(pos + d) if calculator == "dftb" and not kwargs["dftb_pbc"]: dcell.set_pbc(False) dcell.info["name"] = sname + "_displaced_{0}".format(i) displaced_cells.append(dcell) if kwargs["write_allconf"]: # Write a global configuration structure allconf = sum(displaced_cells, cell.copy()) with silence_stdio(): if all(allconf.get_pbc()): io.write(sname + "_allconf.cell", allconf) else: io.write(sname + "_allconf.xyz", allconf) # Get a calculator if calculator == "castep": params = { "castep_param": kwargs["castep_param"], "k_points_grid": kwargs["k_points_grid"], "mu_symbol": mu_symbol, } io_format = ReadWriteCastep(params=params, calc=cell.calc, script=kwargs["script_file"]) opt_args = {"calc_type": "MAGRES"} elif calculator == "dftb+": params = { "dftb_set": kwargs["dftb_set"], "dftb_pbc": kwargs["dftb_pbc"], "k_points_grid": kwargs["k_points_grid"] if kwargs["dftb_pbc"] else None, } io_format = ReadWriteDFTB(params=params, calc=cell.calc, script=kwargs["script_file"]) opt_args = {"calc_type": "SPINPOL"} displaced_coll = AtomsCollection(displaced_cells) displaced_coll.info["displacement_scheme"] = displsch displaced_coll.info["muon_index"] = mu_index displaced_coll.save_tree(sname + "_displaced", io_format.write, opt_args=opt_args)
def muon_vibrational_average_read(structure, calculator="castep", avgprop="hyperfine", average_T=0, average_file="averages.dat", **kwargs): # Open the structure file sname = seedname(structure) io_formats = {"castep": ReadWriteCastep, "dftb+": ReadWriteDFTB} try: displaced_coll = AtomsCollection.load_tree( sname + "_displaced", io_formats[calculator]().read, opt_args={"read_magres": True}, safety_check=2, ) except Exception: raise return mu_i = displaced_coll.info["muon_index"] displsch = displaced_coll.info["displacement_scheme"] to_avg = [] for a in displaced_coll: if avgprop == "hyperfine": to_avg.append(a.get_array("hyperfine")[mu_i]) elif avgprop == "charge": # Used mostly as test try: to_avg.append(a.get_charges()[mu_i]) except RuntimeError: raise (IOError("Could not read charges.")) to_avg = np.array(to_avg) displsch.recalc_weights(T=average_T) # New shape N = len(displaced_coll) shape = tuple([slice(N)] + [None] * (len(to_avg.shape) - 1)) weights = displsch.weights[shape] avg = np.sum(weights * to_avg, axis=0) # Print output report with open(average_file, "w") as f: avgname = { "hyperfine": "hyperfine tensor", "charge": "charge" }[avgprop] f.write(""" Quantum average of {property} calculated on {cell}. Scheme details: {scheme} Averaged value: {avg} All values, by configuration: {vals} """.format( property=avgname, cell=structure, scheme=displsch, avg=avg, vals="\n".join([ "Conf: {0} (Weight = {1})\n{2}\n".format( i, displsch.weights[i], v) for i, v in enumerate(to_avg) ]), ))
def muon_vibrational_average_read(cell_file, calculator='castep', avgprop='hyperfine', average_T=0, average_file='averages.dat', **kwargs): # Open the structure file cell = io.read(cell_file) path = os.path.split(cell_file)[0] sname = seedname(cell_file) num_atoms = len(cell) reader_function = { 'castep': read_output_castep, 'dftb+': read_output_dftbp }[calculator] displaced_coll = AtomsCollection.load_tree(sname + '_displaced', reader_function, safety_check=2, opt_args={'avgprop': avgprop}) mu_i = displaced_coll.info['muon_index'] displsch = displaced_coll.info['displacement_scheme'] to_avg = [] for a in displaced_coll: if avgprop == 'hyperfine': to_avg.append(a.get_array('hyperfine')[mu_i]) elif avgprop == 'charge': # Used mostly as test to_avg.append(a.get_charges()[mu_i]) to_avg = np.array(to_avg) displsch.recalc_weights(T=average_T) # New shape N = len(displaced_coll) shape = tuple([slice(N)] + [None] * (len(to_avg.shape) - 1)) avg = np.sum(displsch.weights[shape] * to_avg, axis=0) # Print output report with open(average_file, 'w') as f: avgname = { 'hyperfine': 'hyperfine tensor', 'charge': 'charge' }[avgprop] f.write(""" Quantum average of {property} calculated on {cell}. Scheme details: {scheme} Averaged value: {avg} All values, by configuration: {vals} """.format(property=avgname, cell=cell_file, scheme=displsch, avg=avg, vals='\n'.join([ '{0}\n{1}\n'.format(i, v) for i, v in enumerate(to_avg) ])))
def muon_vibrational_average_write(cell_file, method='independent', mu_index=-1, mu_symbol='H:mu', grid_n=20, sigma_n=3, avgprop='hyperfine', calculator='castep', displace_T=0, phonon_source_file=None, phonon_source_type='castep', **kwargs): """ Write input files to compute a vibrational average for a quantity on a muon in a given system. | Pars: | cell_file (str): Filename for input structure file | method (str): Method to use for the average. Options are 'independent', | 'montecarlo'. Default is 'independent'. | mu_index (int): Position of the muon in the given cell file. | Default is -1. | mu_symbol (str): Use this symbol to look for the muon among | CASTEP custom species. Overrides muon_index if | present in cell. | grid_n (int): Number of configurations used for sampling. Applies slightly | differently to different schemes. | sigma_n (int): Number of sigmas of the harmonic wavefunction used | for sampling. | avgprop (str): Property to calculate and average. Default is 'hyperfine'. | calculator (str): Source of the property to calculate and average. | Can be 'castep' or 'dftb+'. Default is 'castep'. | phonon_source (str):Source of the phonon data. Can be 'castep' or 'asedftbp'. | Default is 'castep'. | **kwargs: Other arguments (such as specific arguments for the given | phonon method) """ # Open the structure file cell = io.read(cell_file) path = os.path.split(cell_file)[0] sname = seedname(cell_file) num_atoms = len(cell) cell.info['name'] = sname # Fetch species try: species = cell.get_array('castep_custom_species') except KeyError: species = np.array(cell.get_chemical_symbols()) mu_indices = np.where(species == mu_symbol)[0] if len(mu_indices) > 1: raise MuonAverageError('More than one muon found in the system') elif len(mu_indices) == 1: mu_index = mu_indices[0] else: species = list(species) species[mu_index] = mu_symbol species = np.array(species) cell.set_array('castep_custom_species', species) # Load the phonons if phonon_source_type == 'castep': if phonon_source_file is not None: phpath, phfile = os.path.split(phonon_source_file) phfile = seedname(phfile) else: phpath = path phfile = sname ph_evals, ph_evecs = read_castep_gamma_phonons(phfile, phpath) elif phonon_source_type == 'dftb+': if phonon_source_file is None: phonon_source_file = os.path.join(path, sname + '.phonons.pkl') with open(phonon_source_file, 'rb') as f: phdata = pickle.load(f) # Find the gamma point gamma_i = None for i, p in enumerate(phdata.path): if (p == 0).all(): gamma_i = i break try: ph_evals = phdata.frequencies[gamma_i] ph_evecs = phdata.modes[gamma_i] except TypeError: raise RuntimeError(('Phonon file {0} does not contain gamma ' 'point data').format(phonon_source_file)) # Fetch masses try: masses = parse_castep_masses(cell) except AttributeError: # Just fall back on ASE standard masses if not available masses = cell.get_masses() masses[mu_index] = constants.m_mu_amu cell.set_masses(masses) # Now create the distribution scheme if method == 'independent': displsch = IndependentDisplacements(ph_evals, ph_evecs, masses, mu_index, sigma_n) elif method == 'montecarlo': displsch = MonteCarloDisplacements(ph_evals, ph_evecs, masses) displsch.recalc_displacements(n=grid_n, T=displace_T) # Make it a collection pos = cell.get_positions() displaced_cells = [] for i, d in enumerate(displsch.displacements): dcell = cell.copy() dcell.set_positions(pos + d) if calculator == 'dftb' and not kwargs['dftb_pbc']: dcell.set_pbc(False) dcell.info['name'] = sname + '_displaced_{0}'.format(i) displaced_cells.append(dcell) if kwargs['write_allconf']: # Write a global configuration structure allconf = sum(displaced_cells, cell.copy()) if all(allconf.get_pbc()): io.write(sname + '_allconf.cell', allconf) else: io.write(sname + '_allconf.xyz', allconf) # Get a calculator if calculator == 'castep': writer_function = castep_write_input calc = create_hfine_castep_calculator( mu_symbol=mu_symbol, calc=cell.calc, param_file=kwargs['castep_param'], kpts=kwargs['k_points_grid']) elif calculator == 'dftb+': writer_function = dftb_write_input kpts = kwargs['k_points_grid'] if kwargs['dftb_pbc'] else None calc = create_spinpol_dftbp_calculator(param_set=kwargs['dftb_set'], kpts=kpts) displaced_coll = AtomsCollection(displaced_cells) displaced_coll.info['displacement_scheme'] = displsch displaced_coll.info['muon_index'] = mu_index displaced_coll.save_tree(sname + '_displaced', writer_function, opt_args={ 'calc': calc, 'script': kwargs['script_file'] })