def test_tree(self): aselist = [Atoms('C'), Atoms('H'), Atoms('N'), Atoms('O')] testcoll = AtomsCollection(aselist) testcoll.save_tree('test_save', 'xyz', safety_check=2) loadcoll = AtomsCollection.load_tree('test_save', 'xyz') self.assertTrue(''.join(loadcoll.all.get_chemical_formula()) == 'CHNO') # Try a custom save format def custom_save(a, path, format_string): with open(os.path.join(path, 'struct.custom'), 'w') as f: f.write(format_string.format(''.join( a.get_chemical_formula()))) def custom_load(path, marker): with open(os.path.join(path, 'struct.custom')) as f: l = f.read().strip() return Atoms(l.split(marker)[1].strip()) testcoll.save_tree('test_save', custom_save, opt_args={'format_string': 'Formula:{0}'}, safety_check=2) loadcoll = AtomsCollection.load_tree('test_save', custom_load, opt_args={'marker': ':'}) self.assertTrue(''.join(loadcoll.all.get_chemical_formula()) == 'CHNO')
def write_cluster_report(args, params, clusters): if params['clustering_method'] == 'hier': clustinfo = """ Clustering method: Hierarchical t = {t} """.format(t=params['clustering_hier_t']) elif params['clustering_method'] == 'kmeans': clustinfo = """ Clustering method: k-Means k = {k} """.format(k=params['clustering_kmeans_k']) with open(params['name'] + '_clusters.txt', 'w') as f: f.write(""" **************************** | | | MUAIRSS | | Clustering report | | | **************************** Name: {name} Date: {date} Structure file(s): {structs} Parameter file: {param} {clustinfo} ******************* """.format(name=params['name'], date=datetime.now(), structs=args.structures, param=args.parameter_file, clustinfo=clustinfo)) for name, cdata in clusters.items(): f.write('Clusters for {0}:\n'.format(name)) if params['clustering_save_min'] is not None or \ params['clustering_save_type'] is not None: if params['clustering_save_folder'] is not None: clustering_save_path = safe_create_folder( params['clustering_save_folder']) else: clustering_save_path = safe_create_folder( '{0}_clusters'.format(params['name'])) if not clustering_save_path: raise RuntimeError('Could not create folder {0}') for calc, clusts in cdata.items(): # Computer readable fdat = open( params['name'] + '_{0}_{1}_clusters.dat'.format(name, calc), 'w') f.write('CALCULATOR: {0}\n'.format(calc)) (cinds, cgroups), ccolls, gvecs = clusts f.write('\t{0} clusters found\n'.format(max(cinds))) min_energy_structs = [] for i, g in enumerate(cgroups): f.write('\n\n\t-----------\n\tCluster ' '{0}\n\t-----------\n'.format(i + 1)) f.write('\tStructures: {0}\n'.format(len(g))) coll = ccolls[i + 1] E = gvecs[g, 0] Emin = np.amin(E) Eavg = np.average(E) Estd = np.std(E) f.write('\n\tEnergy (eV):\n') f.write('\tMinimum\t\tAverage\t\tStDev\n') f.write('\t{0:.2f}\t\t{1:.2f}\t\t{2:.2f}\n'.format( Emin, Eavg, Estd)) fdat.write('\t'.join( map(str, [ i + 1, len(g), Emin, Eavg, Estd, coll[np.argmin( E)].structures[0].positions[-1][0], coll[np.argmin(E)].structures[0].positions[-1][1], coll[np.argmin(E)].structures[0].positions[-1][2] ])) + '\n') f.write('\n\tMinimum energy structure: {0}\n'.format( coll[np.argmin(E)].structures[0].info['name'])) # Save minimum energy structure if params['clustering_save_type'] == 'structures' or \ params['clustering_save_min']: # For backwards-compatability with old pymuonsuite # versions if params['clustering_save_min']: if params['clustering_save_format'] is None: params['clustering_save_format'] = 'cif' try: calc_path = os.path.join(clustering_save_path, calc) if not os.path.exists(calc_path): os.mkdir(calc_path) fname = ('{0}_{1}_min_cluster_' '{2}.{3}'.format( params['name'], calc, i + 1, params['clustering_save_format'])) with silence_stdio(): io.write(os.path.join(calc_path, fname), coll[np.argmin(E)].structures[0]) except (io.formats.UnknownFileTypeError) as e: print("ERROR: File format '{0}' is not " "recognised. Modify 'clustering_save_format'" " and try again.".format(e)) return except ValueError as e: print("ERROR: {0}. Modify 'clustering_save_format'" "and try again.".format(e)) return min_energy_structs.append(coll[np.argmin(E)].structures[0]) f.write('\n\n\tStructure list:') for j, s in enumerate(coll): if j % 4 == 0: f.write('\n\t') f.write('{0}\t'.format(s.info['name'])) fdat.close() if params['clustering_save_type'] == 'input': calc_path = os.path.join(clustering_save_path, calc) sname = "{0}_min_cluster".format(params['name']) io_formats = { 'castep': ReadWriteCastep, 'dftb+': ReadWriteDFTB, } try: write_method = io_formats[ params['clustering_save_format']](params).write except KeyError as e: print("ERROR: Calculator type {0} is not " "recognised. Modify 'clustering_save_format'" " to be one of: {1}".format( e, list(io_formats.keys()))) return if params['clustering_save_format'] == 'dftb+': from pymuonsuite.data.dftb_pars import get_license with open( os.path.join(clustering_save_path, 'dftb.LICENSE'), 'w') as license_file: license_file.write(get_license()) min_energy_structs = AtomsCollection(min_energy_structs) # here we remove the structure's name so the original # numbering of the structs is removed: for i, a in enumerate(min_energy_structs): min_energy_structs.structures[i].info.pop('name', None) min_energy_structs.save_tree( calc_path, write_method, name_root=sname, opt_args={'calc_type': 'GEOM_OPT'}, safety_check=2) # Print distance matrix f.write('\n\n\t----------\n\n\tSimilarity (ranked):\n') centers = np.array( [np.average(gvecs[g], axis=0) for g in cgroups]) dmat = np.linalg.norm(centers[:, None] - centers[None, :], axis=-1) inds = np.triu_indices(len(cgroups), k=1) for i in np.argsort(dmat[inds]): c1 = inds[0][i] c2 = inds[1][i] d = dmat[c1, c2] f.write('\t{0} <--> {1} (distance = {2:.3f})\n'.format( c1 + 1, c2 + 1, d)) f.write('\n--------------------------\n\n') f.write('\n==========================\n\n')
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_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'] })