Beispiel #1
0
    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')
Beispiel #2
0
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')
Beispiel #3
0
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)
Beispiel #4
0
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']
                             })