Ejemplo n.º 1
0
    def testDFTB(self):
        try:
            yaml_file = os.path.join(_TESTDATA_DIR, "Si2-muairss-dftb.yaml")
            cell_file = os.path.join(_TESTDATA_DIR, "Si2.cell")
            input_params = load_input_file(yaml_file, MuAirssSchema)
            with silence_stdio(True, True):
                input_atoms = io.read(cell_file)

            # Run Muairss write:
            sys.argv[1:] = ["-tw", cell_file, yaml_file]
            os.chdir(_TESTDATA_DIR)
            run_muairss()
            # Check all folders contain a dftb_in.hsd and geo_end.gen
            for rootDir, subDirs, files in os.walk(
                os.path.abspath("muon-airss-out-dftb/dftb+")
            ):
                expected_files = ["geo_end.gen", "dftb_in.hsd"]

                for s in subDirs:
                    count = 0
                    for f in expected_files:
                        f = os.path.join("muon-airss-out-dftb/dftb+/" + s, f)
                        self.assertTrue(os.path.exists(f))
                        if count == 0:
                            with silence_stdio(True, True):
                                atoms = io.read(f)
                            equal = atoms.cell == input_atoms.cell
                            self.assertTrue(equal.all())
                        count += 1

            # Run DFTB
            if _RUN_DFTB:
                subprocess.call(os.path.join(_TESTDATA_DIR, "script-dftb"))
            else:
                yaml_file = os.path.join(_TESTDATA_DIR, "Si2-muairss-dftb-read.yaml")

            input_params = load_input_file(yaml_file, MuAirssSchema)

            sys.argv[1:] = [cell_file, yaml_file]
            run_muairss()
            clust_folder = "Si2_clusters/dftb+"
            self.assertTrue(os.path.exists(clust_folder))
            self.assertTrue(
                len(
                    glob.glob(
                        os.path.join(
                            clust_folder,
                            "*.{0}".format(input_params["clustering_save_format"]),
                        )
                    )
                )
                > 0
            )

            self.assertTrue(os.path.exists("Si2_clusters.txt"))
            self.assertTrue(os.path.exists("Si2_Si2_dftb+_clusters.dat"))
        finally:
            #  Remove all created files and folders
            _clean_testdata_dir()
Ejemplo n.º 2
0
def main(task=None):
    parser = ap.ArgumentParser()
    parser.add_argument('structures',
                        type=str,
                        default=None,
                        help="A structure file or a folder of files in an ASE "
                        "readable format")
    parser.add_argument('parameter_file',
                        type=str,
                        default=None,
                        help="""YAML
                        formatted file with generation parameters. The
                        arguments can be overridden by structure-specific YAML
                        files if a folder is passed as the first argument.""")
    parser.add_argument('-t',
                        type=str,
                        default='r',
                        choices=['r', 'w'],
                        dest='task',
                        help="""Task to be run by muairss. Can be either 'w'
                        (=generate and WRITE structures) or 'r' (=READ and
                        cluster results). Default is READ.""")

    args = parser.parse_args()
    params = load_input_file(args.parameter_file, MuAirssSchema)

    if task is None:
        task = args.task

    if task == 'w':
        if os.path.isdir(args.structures):
            muairss_batch_io(args, params, True)
        elif os.path.isfile(args.structures):
            with silence_stdio():
                struct = io.read(args.structures)
            save_muairss_collection(struct, params)
        else:
            raise RuntimeError("{} is neither a file or a directory".format(
                args.structures))
    elif task == 'r':
        if os.path.isdir(args.structures):
            all_coll = muairss_batch_io(args, params)
            clusters = {}
            for name, data in all_coll.items():
                clusters[name] = muairss_cluster(data['struct'],
                                                 data['collection'], params)
        elif os.path.isfile(args.structures):
            with silence_stdio():
                struct = io.read(args.structures)
            collection = load_muairss_collection(struct, params)
            clusters = {
                params['name']: muairss_cluster(struct, collection, params)
            }
        else:
            raise RuntimeError("{} is neither a file or a directory".format(
                args.structures))
        write_cluster_report(args, params, clusters)
Ejemplo n.º 3
0
    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))
Ejemplo n.º 4
0
def main():

    parser = ap.ArgumentParser()
    parser.add_argument(
        "structure",
        type=str,
        default=None,
        help="A structure file in an ASE readable format",
    )
    parser.add_argument(
        "-sp",
        "--symprec",
        type=float,
        default=1e-3,
        help="Symmetry precision to use in spglib",
    )
    args = parser.parse_args()

    with silence_stdio():
        a = io.read(args.structure)

    symdata = SymmetryDataset.get(a, symprec=args.symprec)
    wpoints = WyckoffPoints.get(a, symprec=args.symprec)
    fpos = a.get_scaled_positions()

    write_symmetry_report(args, symdata, wpoints, fpos)
Ejemplo n.º 5
0
def print_symmetry_report():

    parser = ap.ArgumentParser()
    parser.add_argument('structure',
                        type=str,
                        default=None,
                        help="A structure file in an ASE readable format")
    args = parser.parse_args()

    with silence_stdio():
        a = io.read(args.structure)

    symdata = SymmetryDataset.get(a)
    wpoints = WyckoffPoints.get(a)
    fpos = a.get_scaled_positions()

    print("Wyckoff points symmetry report for {0}".format(args.structure))
    print("Space Group International Symbol: "
          "{0}".format(symdata['international']))
    print("Space Group Hall Number: " "{0}".format(symdata['hall_number']))
    print("Absolute\t\t\tFractional\t\tHessian constraints")

    # List any Wyckoff point that does not already have an atom in it
    for wp in wpoints:
        if np.any(np.isclose(np.linalg.norm(fpos - wp.fpos, axis=1), 0)):
            continue
        print("{0}\t{1}\t{2}".format(wp.pos, wp.fpos, wp.hessian))
    def test_write(self):
        # read in cell file to get atom
        try:
            input_folder = _TESTDATA_DIR + "/Si2"
            os.chdir(input_folder)

            output_folder = "test_save"
            os.mkdir(output_folder)

            with silence_stdio():
                atoms = io.read("Si2.cell")

            # test writing geom_opt output
            param_file = "Si2-muairss-uep.yaml"
            params = load_input_file(param_file, MuAirssSchema)

            reader = ReadWriteUEP(params=params)

            reader.write(atoms, output_folder)

            self.assertTrue(
                os.path.exists(os.path.join(output_folder, "test_save.yaml")))

            params["charged"] = False

            reader = ReadWriteUEP(params=params)

            reader.write(atoms, output_folder)

        except Exception as e:
            print(e)
        finally:
            shutil.rmtree("test_save")
Ejemplo n.º 7
0
    def _create_calculator(self, calc_type=None):
        with silence_stdio():
            if self._calc is not None and isinstance(self._calc, Castep):
                calc = deepcopy(self._calc)
            else:
                calc = Castep()

        mu_symbol = self.params.get('mu_symbol', 'H:mu')

        # Start by ensuring that the muon mass and gyromagnetic ratios are
        # included
        gamma_block = calc.cell.species_gamma.value
        if gamma_block is None:
            calc.cell.species_gamma = add_to_castep_block(
                gamma_block, mu_symbol, constants.m_gamma, 'gamma')

            mass_block = calc.cell.species_mass.value
            calc.cell.species_mass = add_to_castep_block(
                mass_block, mu_symbol, constants.m_mu_amu, 'mass')

        # Now assign the k-points
        k_points_param = self.params.get('k_points_grid')

        if k_points_param is not None:
            calc.cell.kpoint_mp_grid = list_to_string(k_points_param)
        else:
            if calc.cell.kpoint_mp_grid is None:
                calc.cell.kpoint_mp_grid = list_to_string([1, 1, 1])

        # Read the parameters
        pfile = self.params.get('castep_param', None)
        if pfile is not None:
            with silence_stdio():
                calc.param = read_param(self.params['castep_param']).param

        self._calc = calc

        if calc_type == "MAGRES":
            calc = self._create_hfine_castep_calculator()
        elif calc_type == "GEOM_OPT":
            calc = self._create_geom_opt_castep_calculator()

        return self._calc
Ejemplo n.º 8
0
def save_muairss_collection(struct, params, batch_path=""):
    """Generate input files for a single structure and configuration file"""

    dc = generate_muairss_collection(struct, params)
    # Just to keep track, add the parameters used to the collection
    dc.info["muairss_params"] = dict(params)

    # Output folder
    out_path = safe_create_folder(os.path.join(batch_path, params["out_folder"]))

    if not out_path:
        raise RuntimeError("Could not create folder {0}")

    io_formats = {
        "castep": ReadWriteCastep,
        "dftb+": ReadWriteDFTB,
        "uep": ReadWriteUEP,
    }

    calcs = [s.strip().lower() for s in params["calculator"].split(",")]
    if "all" in calcs:
        calcs = io_formats.keys()

    # Save LICENSE file for DFTB+ parameters
    if "dftb+" in calcs:
        from pymuonsuite.data.dftb_pars import get_license

        with open(os.path.join(out_path, "dftb.LICENSE"), "w") as f:
            f.write(get_license())

    for cname in calcs:
        rw = io_formats[cname](params, script=params.get("script_file"))
        calc_path = os.path.join(out_path, cname)
        dc.save_tree(
            calc_path,
            rw.write,
            name_root=params["name"],
            opt_args={"calc_type": "GEOM_OPT"},
            safety_check=2,
        )

    # Do we also save a collective structure?
    allf = params["allpos_filename"]
    if allf is not None:
        alls = struct.copy()
        csp = struct.get_chemical_symbols()
        for atoms in dc:
            # We rely on the fact the muon is always put at the end
            mu = atoms[-1]
            alls.append(mu)
            csp += [params["mu_symbol"]]

        alls.set_array("castep_custom_species", np.array(csp))
        with silence_stdio(True, True):
            io.write(allf, alls)
Ejemplo n.º 9
0
    def write(self, a, folder, sname=None, calc_type="GEOM_OPT"):
        """Writes input files for an Atoms object with a Castep
        calculator.

        | Args:
        |   a (ase.Atoms):          Atoms object to write. Can have a Castep
        |                           calculator attached to carry cell/param
        |                           keywords.
        |   folder (str):           Path to save the input files to.
        |   sname (str):            Seedname to save the files with. If not
        |                           given, use the name of the folder.
        |   calc_type (str):        Castep task which will be performed:
        |                           "GEOM_OPT" or "MAGRES"
        """
        if calc_type == "GEOM_OPT" or calc_type == "MAGRES":
            if sname is None:
                sname = os.path.split(folder)[-1]  # Same as folder name

            self._calc = deepcopy(self._calc)

            # We only use the calculator attached to the atoms object if a calc
            # has not been set when initialising the ReadWrite object OR we
            # have not called write() and made a calculator before.

            if self._calc is None:
                if isinstance(a.calc, Castep):
                    self._calc = deepcopy(a.calc)
                self._create_calculator(calc_type=calc_type)
            else:
                self._update_calculator(calc_type)
            a.calc = self._calc
            with silence_stdio():
                io.write(
                    os.path.join(folder, sname + ".cell"),
                    a,
                    magnetic_moments="initial",
                )
            write_param(
                os.path.join(folder, sname + ".param"),
                a.calc.param,
                force_write=True,
            )

            if self.script is not None:
                stxt = open(self.script).read()
                stxt = stxt.format(seedname=sname)
                with open(os.path.join(folder, "script.sh"), "w") as sf:
                    sf.write(stxt)
        else:
            raise (NotImplementedError(
                "Calculation type {} is not implemented."
                " Please choose 'GEOM_OPT' or 'MAGRES'".format(calc_type)))
Ejemplo n.º 10
0
def muairss_batch_io(args, global_params, save=False):
    structures_path = args.structures

    all_files = glob.glob(os.path.join(structures_path, "*"))
    structure_files = [
        path for path in all_files if not os.path.splitext(path)[1] == ".yaml"
    ]

    if save:
        global_params["out_folder"] = safe_create_folder(global_params["out_folder"])

    print(
        "Beginning {0} of {1} structures".format(
            "creation" if save else "loading", len(structure_files)
        )
    )

    bpath = global_params["out_folder"]

    loaded = {}

    for path in structure_files:
        name = parse_structure_name(path)
        parameter_file = os.path.join(structures_path, "{}.yaml".format(name))
        if not os.path.isfile(parameter_file):
            parameter_file = None
        with silence_stdio():
            struct = io.read(path)
        params = dict(global_params)  # Copy
        params["name"] = name
        if parameter_file is not None:
            params = load_input_file(parameter_file, MuAirssSchema, merge=params)
        params["out_folder"] = params["name"]

        if save:
            print("Making {} ---------------------".format(name))
            save_muairss_collection(struct, params, batch_path=bpath)
        else:
            print("Loading {} ---------------------".format(name))
            coll = load_muairss_collection(struct, params, batch_path=bpath)
            loaded[name] = {"struct": struct, "collection": coll}

    print("Done!")

    if not save:
        return loaded
    def test_create_calc(self):
        folder = os.path.join(_TESTDATA_DIR, "Si2")

        def check_geom_opt_params(calc, params):
            self.assertEqual(calc.label, params["name"])
            self.assertEqual(calc.geom_steps, params["geom_steps"])
            self.assertEqual(calc.gw_factor, params["uep_gw_factor"])
            self.assertEqual(calc.opt_tol, params["geom_force_tol"])
            self.assertEqual(calc.save_structs, params["uep_save_structs"])

        # In the case that a params dict is provided, the values for the
        # parameters should be taken from here:
        params = {
            "name": "Si2",
            "charged": True,
            "geom_steps": 300,
            "uep_gw_factor": 4.0,
            "geom_force_tol": 0.05,
            "uep_save_structs": False,
            "uep_chden": "Si2.den_fmt",
        }

        reader = ReadWriteUEP(params=params)
        with silence_stdio():
            a = io.read(os.path.join(folder, "Si2.cell"))

        calc = reader._create_calculator(a, folder, "Si2")
        check_geom_opt_params(calc, params)

        # In the case that we do not supply a params dict or a calculator,
        # the new calculator should get the default settings:
        reader = ReadWriteUEP()

        params = {
            "name": "Si2",
            "geom_steps": 30,
            "uep_gw_factor": 5.0,
            "geom_force_tol": 1e-5,
            "uep_save_structs": True,
        }

        calc = reader._create_calculator(a, folder, "Si2")
        check_geom_opt_params(calc, params)
Ejemplo n.º 12
0
    def _create_hfine_castep_calculator(self):
        """Update calculator to contain all the necessary parameters
        for a hyperfine calculation."""

        # Remove settings for geom_opt calculator:
        self._calc.param.geom_max_iter = None
        self._calc.param.geom_force_tol = None
        self._calc.param.max_scf_cycles = None
        self._calc.param.write_cell_structure = None
        self._calc.param.charge = None
        self._calc.cell.fix_all_cell = None

        pfile = self.params.get('castep_param', None)
        if pfile is not None:
            with silence_stdio():
                self._calc.param = read_param(
                    self.params['castep_param']).param

        self._calc.param.task = 'Magres'
        self._calc.param.magres_task = 'Hyperfine'

        return self._calc
Ejemplo n.º 13
0
    def test_create_calc(self):
        folder = os.path.join(_TESTDATA_DIR, "Si2")

        def check_geom_opt_params(calc, params):
            self.assertEqual(calc.label, params['name'])
            self.assertEqual(calc.geom_steps, params['geom_steps'])
            self.assertEqual(calc.gw_factor, params['uep_gw_factor'])
            self.assertEqual(calc.opt_tol, params['geom_force_tol'])
            self.assertEqual(calc.save_structs, params['uep_save_structs'])

        # In the case that a params dict is provided, the values for the
        # parameters should be taken from here:
        params = {'name': 'Si2',
                  'charged': True,
                  'geom_steps': 300,
                  'uep_gw_factor': 4.0,
                  'geom_force_tol': 0.05,
                  'uep_save_structs': False,
                  'uep_chden': 'Si2.den_fmt'}

        reader = ReadWriteUEP(params=params)
        with silence_stdio():
            a = io.read(os.path.join(folder, "Si2.cell"))

        calc = reader._create_calculator(a, folder, "Si2")
        check_geom_opt_params(calc, params)

        # In the case that we do not supply a params dict or a calculator,
        # the new calculator should get the default settings:
        reader = ReadWriteUEP()

        params = {'name': 'Si2',
                  'geom_steps': 30,
                  'uep_gw_factor': 5.0,
                  'geom_force_tol': 1e-5,
                  'uep_save_structs': True}

        calc = reader._create_calculator(a, folder, 'Si2')
        check_geom_opt_params(calc, params)
Ejemplo n.º 14
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)
Ejemplo n.º 15
0
    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 asephonons_entry():

    from ase import io
    from ase.calculators.dftb import Dftb
    from pymuonsuite.data.dftb_pars import DFTBArgs

    parser = ap.ArgumentParser(
        description="Compute phonon modes with ASE and"
        " DFTB+ for reuse in quantum effects "
        "calculations."
    )
    parser.add_argument(
        "structure_file",
        type=str,
        help="Structure for which to compute the phonons",
    )
    parser.add_argument(
        "parameter_file",
        type=str,
        help="YAML file containing relevant input parameters",
    )

    args = parser.parse_args()

    # Load parameters
    params = load_input_file(args.parameter_file, AsePhononsSchema)

    fname, fext = os.path.splitext(args.structure_file)
    if params["name"] is None:
        params["name"] = fname

    # Load structure
    with silence_stdio():
        a = io.read(args.structure_file)

    # Create a Dftb calculator
    dargs = DFTBArgs(params["dftb_set"])
    # Is it periodic?
    if params["pbc"]:
        a.set_pbc(True)
        calc = Dftb(
            atoms=a, label="asephonons", kpts=params["kpoint_grid"], **dargs.args
        )
        ph_kpts = params["phonon_kpoint_grid"]
    else:
        a.set_pbc(False)
        calc = Dftb(atoms=a, label="asephonons", **dargs.args)
        ph_kpts = None
    a.calc = calc
    try:
        phdata = ase_phonon_calc(
            a,
            kpoints=ph_kpts,
            ftol=params["force_tol"],
            force_clean=params["force_clean"],
            name=params["name"],
        )
    except Exception as e:
        print(e)
        print("Error: Could not write phonons file, see asephonons.out for" " details.")
        sys.exit(1)

    # Save optimised structure
    with silence_stdio():
        io.write(params["name"] + "_opt" + fext, phdata.structure)

    # And write out the phonons
    outf = params["name"] + "_opt.phonons.pkl"
    pickle.dump(phdata, open(outf, "wb"))
    write_phonon_report(args, params, phdata)
Ejemplo n.º 17
0
    def testCASTEP(self):
        try:
            yaml_file = os.path.join(_TESTDATA_DIR, "Si2-muairss-castep.yaml")
            cell_file = os.path.join(_TESTDATA_DIR, "Si2.cell")
            param_file = os.path.join(_TESTDATA_DIR, "Si2.param")
            input_params = load_input_file(yaml_file, MuAirssSchema)
            with silence_stdio(True, True):
                castep_param = read_param(param_file).param

            # Run Muairss write:
            sys.argv[1:] = ["-tw", cell_file, yaml_file]
            os.chdir(_TESTDATA_DIR)
            run_muairss()
            # Check all folders contain a yaml file
            for (rootDir, subDirs, files) in os.walk("muon-airss-out-castep/castep/"):
                for s in subDirs:
                    expected_file = os.path.join(
                        "muon-airss-out-castep/castep/" + s, s + ".cell"
                    )
                    script_file = input_params["script_file"]
                    if script_file is not None:
                        expected_script = os.path.join(
                            "muon-airss-out-castep/castep/" + s, "script.sh"
                        )
                        self.assertTrue(os.path.exists(expected_script))

                    self.assertTrue(os.path.exists(expected_file))
                    with silence_stdio():
                        atoms = io.read(expected_file)
                    self.assertEqual(
                        atoms.calc.cell.kpoint_mp_grid.value,
                        list_to_string(input_params["k_points_grid"]),
                    )
                    expected_param_file = os.path.join(
                        "muon-airss-out-castep/castep/" + s, s + ".param"
                    )
                    self.assertTrue(os.path.exists(expected_param_file))
                    with silence_stdio():
                        output_castep_param = read_param(expected_param_file).param
                    self.assertEqual(
                        output_castep_param.cut_off_energy,
                        castep_param.cut_off_energy,
                    )
                    self.assertEqual(
                        output_castep_param.elec_energy_tol,
                        castep_param.elec_energy_tol,
                    )
                    # below test didn't work as cell positions get rounded...
                    # equal = atoms.cell == input_atoms.cell
                    # self.assertTrue(equal.all())

            yaml_file = os.path.join(_TESTDATA_DIR, "Si2-muairss-castep-read.yaml")
            sys.argv[1:] = [cell_file, yaml_file]
            run_muairss()

            self.assertTrue(os.path.exists("Si2_clusters.txt"))
            self.assertTrue(os.path.exists("Si2_Si2_castep_clusters.dat"))

            # Test clustering_write_input has produced files we expect:
            self.assertTrue(os.path.exists("Si2_clusters"))
            calc_folder = "Si2_clusters/castep/"
            for (rootDir, subDirs, files) in os.walk(calc_folder):
                for s in subDirs:
                    expected_file = os.path.join(calc_folder + s, s + ".cell")
                    self.assertTrue(os.path.exists(expected_file))
                    with silence_stdio():
                        atoms = io.read(expected_file)
                    self.assertEqual(
                        atoms.calc.cell.kpoint_mp_grid.value,
                        list_to_string(input_params["k_points_grid"]),
                    )
                    expected_param_file = os.path.join(calc_folder + s, s + ".param")
                    self.assertTrue(os.path.exists(expected_param_file))
                    with silence_stdio():
                        output_castep_param = read_param(expected_param_file).param
                    self.assertEqual(
                        output_castep_param.cut_off_energy,
                        castep_param.cut_off_energy,
                    )
                    self.assertEqual(
                        output_castep_param.elec_energy_tol,
                        castep_param.elec_energy_tol,
                    )
        finally:
            # Remove all created files and folders
            _clean_testdata_dir()
Ejemplo n.º 18
0
    def read(self, folder, sname=None, read_spinpol=False, read_phonons=False,
             **kwargs):
        ''' Read a DFTB+ output non-destructively.
        |
        |   Args:
        |   folder (str) :          path to a directory to load DFTB+ results
        |   sname (str):            name to label the atoms with and/or of the
        |                           .phonons.pkl file to be read
        |   Returns:
        |   atoms (ase.Atoms):      an atomic structure with the results
        |                           attached in a SinglePointCalculator
        '''

        try:
            with silence_stdio():
                atoms = io.read(os.path.join(folder, 'geo_end.gen'))

        except IOError:
            raise IOError("ERROR: No geo_end.gen file found in {}."
                          .format(os.path.abspath(folder)))
        except Exception as e:
            raise IOError("ERROR: Could not read {file}, due to error: {error}"
                          .format(file='geo_end.gen', error=e))
        if sname is None:
            atoms.info['name'] = os.path.split(folder)[-1]
        else:
            atoms.info['name'] = sname
        results_file = os.path.join(folder, "results.tag")
        if os.path.isfile(results_file):
            # DFTB+ was used to perform the optimisation
            temp_file = os.path.join(folder, "results.tag.bak")

            # We need to backup the results file here because
            # .read_results() will remove the results file
            with BackupFile(results_file, temp_file):
                calc = Dftb(atoms=atoms)
                calc.atoms_input = atoms
                calc.directory = folder
                calc.do_forces = True
                calc.read_results()

            energy = calc.get_potential_energy()
            forces = calc.get_forces()
            charges = calc.get_charges(atoms)

            calc = SinglePointCalculator(atoms, energy=energy,
                                         forces=forces, charges=charges)

            atoms.calc = calc

        if read_spinpol:
            try:
                pops = parse_spinpol_dftb(folder)
                hfine = []
                for i in range(len(atoms)):
                    hf = compute_hfine_mullpop(atoms, pops, self_i=i,
                                               fermi=True, fermi_neigh=True)
                    hfine.append(hf)
                atoms.set_array('hyperfine', np.array(hfine))
            except (IndexError, IOError) as e:
                raise IOError('Could not read hyperfine details due to error: '
                              '{0}'.format(e))

        if read_phonons:
            try:
                if sname is not None:
                    phonon_source_file = os.path.join(folder, sname +
                                                      '.phonons.pkl')
                else:
                    print("Phonons filename was not given, searching for any"
                          " .phonons.pkl file.")
                    phonon_source_file = glob.glob(
                        os.path.join(folder, '*.phonons.pkl'))[0]
                self._read_dftb_phonons(atoms, phonon_source_file)
            except IndexError:
                raise IOError("No .phonons.pkl files found in {}."
                              .format(os.path.abspath(folder)))
            except IOError:
                raise IOError("{} could not be found."
                              .format(phonon_source_file))
            except Exception as e:
                raise IOError('Could not read {file} due to error: {error}'
                              .format(file=phonon_source_file, error=e))

        return atoms
Ejemplo n.º 19
0
    def __init__(self, seedname, gw_fac=3, path=""):
        """Initialise a ChargeDistrbution object.

        Initialise a ChargeDistribution object with CASTEP output files.

        Arguments:
            seedname {str} -- The seedname of the CASTEP output files
                              (.den_fmt and .castep) used to load the data.

        Keyword Arguments:
            gw_fac {number} -- Gaussian width factor. The Gaussian width used
                               for each ion will be the radius of its
                               pseudopotential divided by this factor.
                               (default: {3})
            path {str} -- Path in which the CASTEP output files can be found.
                          (default: {''})

        Raises:
            RuntimeError -- CASTEP pseudopotentials were not found
        """

        # CASTEP SPECIFIC CODE - reading files

        # Load the electronic density
        seedpath = os.path.join(path, seedname)

        self._elec_den = FMTReader(seedpath + ".den_fmt")
        with silence_stdio():
            self._struct = io.read(seedpath + ".castep")

        ppots = parse_castep_ppots(seedpath + ".castep")

        # Override by also grabbing any pseudopotentials found in the .cell
        # file

        cppot = None
        try:
            with silence_stdio():
                cppot = io.read(seedpath + ".cell").calc.cell.species_pot.value
        except IOError:
            pass  # If not available, ignore this
        if cppot is not None:
            ppf = [lpp.split() for lpp in cppot.split("\n") if lpp]
            for el, pppath in ppf:
                f = os.path.join(path, pppath)
                try:
                    ppots.update(parse_castep_ppots(f))
                except IOError:
                    # File not found
                    print("WARNING: pseudopotential file "
                          "{0} not found".format(f))

        # END OF CASTEP SPECIFIC CODE

        # unit cell and FFT grid
        lattice = np.array(self._elec_den.real_lattice)  # Ang
        grid = np.array(self._elec_den.grid)

        # dx = [np.linalg.norm(lattice[i]) / grid[i] for i in range(3)]
        inv_latt = np.linalg.inv(lattice.T) * 2 * np.pi  # Ang^-1

        fft_grid = np.array(
            np.meshgrid(*[np.fft.fftfreq(grid[i]) * grid[i] for i in range(3)],
                        indexing="ij"))
        # Uses the g-vector convention in formulas used
        # this should match CASTEP's grid in reciprocal space
        self._g_grid = np.tensordot(inv_latt, fft_grid, axes=(0, 0))  # Ang^-1

        # Information for the elements, and guarantee zero net charge
        elems = self._struct.get_chemical_symbols()
        pos = self._struct.get_positions()
        try:
            # CASTEP SPECIFIC CODE (maybe) - this way of getting charges &
            # gaussian widths from pseudopotentials
            self._q = np.array([ppots[el][0] for el in elems])  # e
            self._gw = np.array([ppots[el][1] / gw_fac for el in elems])  # Ang
            # END OF CASTEP SPECIFIC CODE
        except KeyError:
            raise RuntimeError("""Some or all CASTEP pseudopotentials were not
found. UEP calculation can not go on. Please notice that at the moment only
ultrasoft pseudopotentials are supported, and if not generated automatically,
they must be possible to retrieve using the paths in the SPECIES_POT block of
the .cell file.""")

        # Here we find the Fourier components of the potential due to
        # the valence electrons
        self._rho = self._elec_den.data[:, :, :, 0]
        if not np.isclose(np.average(self._rho), sum(self._q), 1e-4):
            raise RuntimeError("Cell is not neutral")
        # Put the minus sign for electrons

        # CASTEP SPECIFIC CODE - unit conversion. Needs standardising.
        self._rho *= -sum(self._q) / np.sum(
            self._rho)  # Normalise charge. Now it's e/grid points
        # END OF CASTEP SPECIFIC CODE

        self._rhoe_G = np.fft.fftn(self._rho)
        Gnorm = np.linalg.norm(self._g_grid, axis=0)
        # remove 0-values as we will use 1/G_norm in calculations
        # (0-values are special cases and we set the results manually elsewhere)
        Gnorm_fixed = np.where(Gnorm > 0, Gnorm, np.inf)

        cell = np.array(self._elec_den.real_lattice)
        vol = abs(np.dot(np.cross(cell[:, 0], cell[:, 1]), cell[:,
                                                                2]))  # Ang^3
        self._vol = vol

        # Core formula: reciprocal space potential contribution FROM ELECTRONS
        self._Ve_G = 4 * np.pi / Gnorm_fixed**2 * (self._rhoe_G / vol)  # e/Ang

        # Now on to doing the same for ionic components
        self._rhoi_G = self._g_grid[0] * 0.0j
        for i, p in enumerate(pos):
            self._rhoi_G += self._q[i] * np.exp(
                -1.0j  # phase term corresponding to translation in direct space
                * np.sum(self._g_grid[:, :, :, :] * p[:, None, None, None],
                         axis=0) - 0.5 * (self._gw[i] * Gnorm)**2)

        # Reciprocal space potential contributions FROM IONS
        # (approximated as Gaussian charges)
        self._Vi_G = 4 * np.pi / Gnorm_fixed**2 * (self._rhoi_G / vol)

        # Is there any data on spin polarization?
        self._spinpol = False
        if self._elec_den.data.shape[-1] >= 2:
            self._spinpol = True
            self._spin = self._elec_den.data[:, :, :, 1]
            self._spin_G = np.fft.fftn(self._spin)

            # Dipolar tensor FFT
            dyad_G = self._g_grid[:, None] * self._g_grid[None, :]
            dyad_G /= Gnorm_fixed**2
            self._dip_G = (
                4.0 / 3.0 * np.pi *
                (3 * dyad_G - np.eye(3)[:, :, None, None, None] + 0j))
            self._dip_G[:, :, 0, 0, 0] = 0
            self._dip_G *= self._spin_G / (self._vol *
                                           np.prod(self._spin.shape))
            # Convert to Tesla.
            self._dip_G *= _dipT

        # Now, Thomas-Fermi energy
        tfint = np.sum(abs(self._rho / self._vol)**(5 / 3) * self._vol)
        C = (0.3 * cnst.hbar**2 / cnst.m_e * (3 * np.pi**2)**(2 / 3) / cnst.e *
             1e20)
        self._thomasFermiE = C * tfint
Ejemplo n.º 20
0
    def test_write(self):
        # read in cell file to get atom
        try:
            input_folder = _TESTDATA_DIR + "/Si2"
            output_folder = os.path.join(_TESTDATA_DIR, "test_save")
            os.mkdir(output_folder)

            os.chdir(input_folder)

            yaml_file = os.path.join(input_folder, "Si2-muairss-castep.yaml")
            cell_file = os.path.join(input_folder, "Si2.cell")
            param_file = os.path.join(input_folder, "Si2.param")
            input_params = load_input_file(yaml_file, MuAirssSchema)

            with silence_stdio():
                castep_param = read_param(param_file).param
                atoms = io.read(cell_file)

            # test writing geom_opt output
            reader = ReadWriteCastep(params=input_params)
            reader.write(
                atoms,
                output_folder,
                sname="Si2_geom_opt",
                calc_type="GEOM_OPT",
            )

            reader.write(atoms, output_folder, sname="Si2_magres", calc_type="MAGRES")

            # # read back in and check that atom locations are preserved
            with silence_stdio():
                geom_opt_atoms = io.read(
                    os.path.join(output_folder, "Si2_geom_opt.cell")
                )
                magres_atoms = io.read(os.path.join(output_folder, "Si2_magres.cell"))
            equal = atoms.positions == geom_opt_atoms.positions
            # self.assertTrue(equal.all()) # is not true due to to rounding
            equal = geom_opt_atoms.positions == magres_atoms.positions
            self.assertTrue(equal.all())
            self.assertEqual(
                geom_opt_atoms.calc.cell.kpoint_mp_grid.value,
                list_to_string(input_params["k_points_grid"]),
            )
            self.assertEqual(
                magres_atoms.calc.cell.kpoint_mp_grid.value,
                list_to_string(input_params["k_points_grid"]),
            )

            # Test if parameters file have correct tasks:
            with silence_stdio():
                geom_params = read_param(
                    os.path.join(output_folder, "Si2_geom_opt.param")
                ).param
                magres_params = read_param(
                    os.path.join(output_folder, "Si2_magres.param")
                ).param
            self.assertEqual(geom_params.task.value, "GeometryOptimization")
            self.assertEqual(magres_params.task.value, "Magres")
            self.assertEqual(magres_params.magres_task.value, "Hyperfine")
            # These are only set in the param file only so should equal
            # the value in the param file:
            self.assertEqual(geom_params.geom_max_iter, castep_param.geom_max_iter)
            self.assertEqual(geom_params.cut_off_energy, castep_param.cut_off_energy)
            self.assertEqual(geom_params.elec_energy_tol, castep_param.elec_energy_tol)
            # This is set in the input yaml and param file so should equal
            # the value in the yaml file:
            self.assertEqual(
                geom_params.geom_force_tol.value,
                str(input_params["geom_force_tol"]),
            )

            self.assertEqual(magres_params.cut_off_energy, castep_param.cut_off_energy)
            self.assertEqual(
                magres_params.elec_energy_tol, castep_param.elec_energy_tol
            )

        finally:
            shutil.rmtree(output_folder)
Ejemplo n.º 21
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')
Ejemplo n.º 22
0
    def __init__(self, seedname, gw_fac=3, path=''):
        """Initialise a ChargeDistrbution object.

        Initialise a ChargeDistribution object with CASTEP output files.

        Arguments:
            seedname {str} -- The seedname of the CASTEP output files
                              (.den_fmt and .castep) used to load the data.

        Keyword Arguments:
            gw_fac {number} -- Factor used to divide the Gaussian width used
                               for the ions. The final width will be the
                               radius of the pseudopotential divided by this.
                               (default: {3})
            path {str} -- Path in which the CASTEP output files can be found.
                          (default: {''})

        Raises:
            RuntimeError -- CASTEP pseudopotentials were not found
        """

        # Load the electronic density
        seedpath = os.path.join(path, seedname)

        self._elec_den = FMTReader(seedpath + '.den_fmt')
        with silence_stdio():
            self._struct = io.read(seedpath + '.castep')

        ppots = parse_castep_ppots(seedpath + '.castep')

        # Override by also grabbing any pseudopotentials found in the .cell
        # file

        cppot = None
        try:
            with silence_stdio():
                cppot = io.read(seedpath + '.cell').calc.cell.species_pot.value
        except IOError:
            pass  # If not available, ignore this
        if cppot is not None:
            ppf = [l.split() for l in cppot.split('\n') if l]
            for el, pppath in ppf:
                f = os.path.join(path, pppath)
                try:
                    ppots.update(parse_castep_ppots(f))
                except IOError:
                    # File not found
                    print('WARNING: pseudopotential file '
                          '{0} not found'.format(f))

        # FFT grid
        lattice = np.array(self._elec_den.real_lattice)
        grid = np.array(self._elec_den.grid)

        dx = [np.linalg.norm(lattice[i]) / grid[i] for i in range(3)]
        inv_latt = np.linalg.inv(lattice.T) * 2 * np.pi

        fft_grid = np.array(
            np.meshgrid(*[np.fft.fftfreq(grid[i]) * grid[i] for i in range(3)],
                        indexing='ij'))
        # Uses the g-vector convention in formulas used
        self._g_grid = np.tensordot(inv_latt, fft_grid, axes=(0, 0))

        # Information for the elements, and guarantee zero net charge
        elems = self._struct.get_chemical_symbols()
        pos = self._struct.get_positions()
        try:
            self._q = np.array([ppots[el][0] for el in elems])
            self._gw = np.array([ppots[el][1] / gw_fac for el in elems])
        except KeyError:
            raise RuntimeError("""Some or all CASTEP pseudopotentials were not
found. UEP calculation can not go on. Please notice that at the moment only
ultrasoft pseudopotentials are supported, and if not generated automatically,
they must be possible to retrieve using the paths in the SPECIES_POT block of
the .cell file.""")

        # Here we find the Fourier components of the potential due to
        # the valence electrons
        self._rho = self._elec_den.data[:, :, :, 0]
        if not np.isclose(np.average(self._rho), sum(self._q), 1e-4):
            raise RuntimeError('Cell is not neutral')
        # Put the minus sign for electrons
        self._rho *= -sum(self._q) / np.sum(self._rho)  # Normalise charge
        self._rhoe_G = np.fft.fftn(self._rho)
        Gnorm = np.linalg.norm(self._g_grid, axis=0)
        Gnorm_fixed = np.where(Gnorm > 0, Gnorm, np.inf)

        cell = np.array(self._elec_den.real_lattice)
        vol = abs(np.dot(np.cross(cell[:, 0], cell[:, 1]), cell[:, 2]))
        self._vol = vol

        self._Ve_G = 4 * np.pi / Gnorm_fixed**2 * (self._rhoe_G / vol)

        # Now on to doing the same for ionic components
        self._rhoi_G = self._g_grid[0] * 0.j
        for i, p in enumerate(pos):
            self._rhoi_G += (self._q[i] * np.exp(-1.0j * np.sum(
                self._g_grid[:, :, :, :] * p[:, None, None, None], axis=0) -
                                                 0.5 *
                                                 (self._gw[i] * Gnorm)**2))

        pregrid = (4 * np.pi / Gnorm_fixed**2 * 1.0 / vol)
        self._Vi_G = (pregrid * self._rhoi_G)

        # Is there any data on spin polarization?
        self._spinpol = False
        if self._elec_den.data.shape[-1] >= 2:
            self._spinpol = True
            self._spin = self._elec_den.data[:, :, :, 1]
            self._spin_G = np.fft.fftn(self._spin)

            # Dipolar tensor FFT
            dyad_G = self._g_grid[:, None] * self._g_grid[None, :]
            dyad_G /= Gnorm_fixed**2
            self._dip_G = 4.0 / 3.0 * np.pi * (
                3 * dyad_G - np.eye(3)[:, :, None, None, None] + 0j)
            self._dip_G[:, :, 0, 0, 0] = 0
            self._dip_G *= self._spin_G / (self._vol *
                                           np.prod(self._spin.shape))
            # Convert to Tesla.
            self._dip_G *= _dipT

        # Now, Thomas-Fermi energy
        tfint = np.sum(abs(self._rho / self._vol)**(5 / 3) * self._vol)
        C = 0.3 * cnst.hbar**2 / cnst.m_e * (3 * np.pi**2)**(2 /
                                                             3) / cnst.e * 1e20
        self._thomasFermiE = C * tfint