Пример #1
0
def random_strain(mutant, debug=False):
    """ Apply random strain tensor to unit cell from 6 \\epsilon_i components
    with values between -1 and 1. The cell is then scaled to the parent's volume.

    Parameters:

        mutant (dict): structure to mutate in-place.

    """
    def generate_cell_transform_matrix():
        """ Pick a random transformation matrix. """
        strain_components = 2 * np.random.rand(6) - 1
        cell_transform_matrix = np.eye(3)
        for i in range(3):
            cell_transform_matrix[i][i] += strain_components[i]
        cell_transform_matrix[0][1] += strain_components[3] / 2
        cell_transform_matrix[1][0] += strain_components[3] / 2
        cell_transform_matrix[2][0] += strain_components[4] / 2
        cell_transform_matrix[0][2] += strain_components[4] / 2
        cell_transform_matrix[1][2] += strain_components[5] / 2
        cell_transform_matrix[2][1] += strain_components[5] / 2
        return cell_transform_matrix

    valid = False
    while not valid:
        cell_transform_matrix = generate_cell_transform_matrix()
        # only accept matrices with positive determinant, then scale that det to 1
        if np.linalg.det(cell_transform_matrix) > 0:
            cell_transform_matrix /= pow(np.linalg.det(cell_transform_matrix),
                                         1 / 3)
            valid = True
        if valid:
            # assert symmetry
            assert np.allclose(cell_transform_matrix.T, cell_transform_matrix)
            assert np.linalg.det(cell_transform_matrix) > 0
            if debug:
                print(cell_transform_matrix)
            # exclude all strains that take us to sub-60 and sup-120 cell angles
            new_lattice_abc = cart2abc(
                np.matmul(cell_transform_matrix,
                          np.array(mutant["lattice_cart"])))
            for angle in new_lattice_abc[1]:
                if angle > 120 or angle < 60:
                    valid = False
            # also exclude all cells where at least one lattice vector is less than 2 A
            mean_lat_vec = np.mean(new_lattice_abc[0])
            for length in new_lattice_abc[0]:
                if length < mean_lat_vec / 2:
                    valid = False

    mutant["lattice_cart"] = np.matmul(cell_transform_matrix,
                                       np.array(
                                           mutant["lattice_cart"])).tolist()
    mutant["lattice_abc"] = cart2abc(mutant["lattice_cart"])
    if debug:
        print("lattice_abc:", mutant["lattice_abc"])
        print("lattice_cart:", mutant["lattice_cart"])
        print("cell_transform_matrix:", cell_transform_matrix.tolist())
Пример #2
0
def optimade_to_basic_cif(structure):
    """ A simple CIF creator that is enough to trick ChemDoodle. """

    cif_string = ""
    lattice_abc = cart2abc(structure.attributes.lattice_vectors)
    positions_frac = cart2frac(
        structure.attributes.lattice_vectors,
        structure.attributes.cartesian_site_positions,
    )
    cif_string += f"_cell_length_a {lattice_abc[0][0]}\n"
    cif_string += f"_cell_length_b {lattice_abc[0][1]}\n"
    cif_string += f"_cell_length_c {lattice_abc[0][2]}\n"
    cif_string += f"_cell_angle_alpha {lattice_abc[1][0]}\n"
    cif_string += f"_cell_angle_beta {lattice_abc[1][1]}\n"
    cif_string += f"_cell_angle_gamma {lattice_abc[1][2]}\n"
    cif_string += "loop_\n"
    cif_string += "_atom_site_label\n"
    cif_string += "_atom_site_symbol\n"
    cif_string += "_atom_site_fract_x\n"
    cif_string += "_atom_site_fract_y\n"
    cif_string += "_atom_site_fract_z\n"

    for atom, pos in zip(structure.attributes.species_at_sites, positions_frac):
        cif_string += f"{atom} {atom} {pos[0]} {pos[1]} {pos[2]}\n"

    return cif_string
Пример #3
0
 def test_cart2abc(self):
     castep_fname = REAL_PATH + "data/Na3Zn4-swap-ReOs-OQMD_759599.castep"
     self.assertTrue(os.path.isfile(castep_fname))
     test_doc, s = castep2dict(castep_fname, db=True, verbosity=VERBOSITY)
     try:
         self.assertTrue(
             np.allclose(test_doc["lattice_abc"],
                         cart2abc(test_doc["lattice_cart"])),
             msg="Conversion cart2abc failed.",
         )
         self.assertTrue(
             np.allclose(
                 cart2abc(test_doc["lattice_cart"]),
                 cart2abc(abc2cart(test_doc["lattice_abc"])),
             ),
             msg="Conversion abc2cart failed.",
         )
         self.assertAlmostEqual(
             test_doc["cell_volume"],
             cart2volume(test_doc["lattice_cart"]),
             msg="Failed to calculate volume from lattice vectors.",
             places=5,
         )
         self.assertIsInstance(test_doc["lattice_abc"],
                               list,
                               msg="Failed abc numpy cast to list")
         self.assertIsInstance(
             test_doc["lattice_cart"],
             list,
             msg="Failed cartesian numpy cast to list",
         )
         cart_pos = frac2cart(test_doc["lattice_cart"],
                              test_doc["positions_frac"])
         back2frac = cart2frac(test_doc["lattice_cart"], cart_pos)
         np.testing.assert_array_almost_equal(back2frac,
                                              test_doc["positions_frac"])
     except AssertionError:
         print("cart:", test_doc["lattice_cart"],
               abc2cart(test_doc["lattice_abc"]))
         print("abc:", test_doc["lattice_abc"],
               cart2abc(test_doc["lattice_cart"]))
         print(
             "volume:",
             test_doc["cell_volume"],
             cart2volume(test_doc["lattice_cart"]),
         )
         raise AssertionError
Пример #4
0
def ase2dict(atoms, as_model=False) -> Union[dict, Crystal]:
    """ Return a matador document (dictionary or :obj:`Crystal`)
    from an `ase.Atoms` object.

    Parameters:
        atoms (ase.Atoms): input structure.

    Keyword arguments:
        as_model (bool): if `True`, return a
            Crystal instead of a dictionary.

    Returns:
        Union[dict, Crystal]: matador output.

    """
    from matador.utils.cell_utils import cart2abc
    doc = {}

    # sort atoms, then their positions
    doc['atom_types'] = atoms.get_chemical_symbols()
    inds = [
        i[0] for i in sorted(enumerate(doc['atom_types']), key=lambda x: x[1])
    ]
    doc['positions_frac'] = atoms.get_scaled_positions().tolist()
    doc['positions_frac'] = [doc['positions_frac'][ind] for ind in inds]
    doc['atom_types'] = [doc['atom_types'][ind] for ind in inds]
    try:
        doc['lattice_cart'] = atoms.get_cell().tolist()
    except AttributeError:
        doc['lattice_cart'] = atoms.get_cell().array.tolist()
    doc['lattice_abc'] = cart2abc(doc['lattice_cart'])
    doc['num_atoms'] = len(doc['atom_types'])
    doc['stoichiometry'] = get_stoich(doc['atom_types'])
    doc['cell_volume'] = atoms.get_volume()
    doc['elems'] = {atom for atom in doc['atom_types']}
    doc['num_fu'] = doc['num_atoms'] / int(
        sum(doc['stoichiometry'][i][1]
            for i in range(len(doc['stoichiometry']))))
    doc['space_group'] = get_spacegroup_spg(doc, symprec=0.001)

    if atoms.info:
        doc["ase_info"] = copy.deepcopy(atoms.info)

    if as_model:
        doc = Crystal(doc)

    return doc
Пример #5
0
    def relax(self):
        from ase.optimize import LBFGS

        cached = sys.__stdout__
        try:
            optimizer = LBFGS(self.ucf)
            optimizer.logfile = None
            optimised = optimizer.run(fmax=0.05, steps=100)
        except Exception:
            optimised = False

        self.doc["optimised"] = bool(optimised)
        self.doc["positions_abs"] = self.atoms.get_positions().tolist()
        self.doc["lattice_cart"] = self.atoms.get_cell().tolist()
        self.doc["lattice_abc"] = cart2abc(self.doc["lattice_cart"])
        self.doc["positions_frac"] = cart2frac(self.doc["lattice_cart"],
                                               self.doc["positions_abs"])
        self.doc["enthalpy_per_atom"] = float(self.calc.results["energy"] /
                                              len(self.doc["atom_types"]))
        self.doc["enthalpy"] = float(self.calc.results["energy"])
        self.queue.put(self.doc)
        sys.stdout = cached
Пример #6
0
def doc2res(doc,
            path,
            overwrite=False,
            hash_dupe=False,
            info=True,
            spoof_titl=False,
            sort_atoms=True):
    """ Write .res file for single doc.

    Parameters:
        doc (dict): matador document containing structure
        path (str): desired filename for res file

    Keyword Arguments:
        info (bool): require info in res file header
        spoof_titl (bool): make up fake info for file header (for use with e.g. cryan)
        sorted (bool): if False, atoms are not sorted (this will not be a valid res file)
        overwrite (bool): whether or not to overwrite colliding files.
        hash_dupe (bool): whether or not to create a unique filename for
            any colliding files, or just skip writing them.

    """
    if spoof_titl:
        info = False
    if not info:
        space_group = 'P1' if 'space_group' not in doc else doc['space_group']
        if spoof_titl:
            titl = ('TITL {} -1 1 -1 0 0 {} ({}) n - 1').format(
                path.split('/')[-1], doc['num_atoms'], space_group)
        else:
            titl = ('TITL file generated by matador (Matthew Evans 2016)')
    else:
        try:
            titl = 'TITL '
            titl += (path.split('/')[-1] + ' ')
            if 'pressure' not in doc or isinstance(doc['pressure'], str):
                titl += '0.00 '
            else:
                titl += str(doc['pressure']) + ' '
            if 'cell_volume' not in doc:
                titl += '0.0 '
            else:
                titl += str(doc['cell_volume']) + ' '
            if 'enthalpy' in doc and not isinstance(doc['enthalpy'], str):
                titl += str(doc['enthalpy']) + ' '
            elif '0K_energy' in doc:
                titl += str(doc['0K_energy']) + ' '
            elif 'total_energy' in doc:
                titl += str(doc['total_energy']) + ' '
            else:
                raise KeyError('No energy field found.')
            titl += '0 0 '  # spin
            titl += str(doc['num_atoms']) + ' '
            if 'space_group' not in doc:
                titl += '(P1) '
            elif 'x' in doc['space_group']:
                titl += '(P1) '
            else:
                titl += '(' + str(doc['space_group']) + ')' + ' '
            titl += 'n - 1'
        except Exception:
            raise RuntimeError(
                'Failed to get info for res file, turn info off.')
    if 'encapsulated' in doc and doc['encapsulated']:
        rem = (
            "REM NTPROPS {{\'chiralN\': {}, \'chiralM\': {}, \'r\': {}, "
            "\'offset\': [0.5, 0.5, 0.5], \'date\': \'xxx\', \'eformperfu\': 12345, \'z\': {}}}"
            .format(doc['cnt_chiral'][0], doc['cnt_chiral'][1],
                    doc['cnt_radius'], doc['cnt_length']))
    flines = []
    if 'encapsulated' in doc and doc['encapsulated']:
        flines.append(rem)
    flines.append(titl)
    cell_str = 'CELL 1.0 '
    if ('lattice_abc' not in doc or len(doc['lattice_abc']) != 2
            or len(doc['lattice_abc'][0]) != 3
            or len(doc['lattice_abc'][1]) != 3):
        try:
            doc['lattice_abc'] = cart2abc(doc['lattice_cart'])
        except Exception:
            raise RuntimeError(
                'Failed to get lattice, something has gone wrong for {}'.
                format(path))
    for vec in doc['lattice_abc']:
        for coeff in vec:
            cell_str += ('{:.12f} '.format(coeff))
    flines.append(cell_str)
    flines.append('LATT -1')

    # enforce correct order by elements, sorting only the atom_types, not the positions inside them
    if len(doc['positions_frac']) != len(doc['atom_types']):
        raise RuntimeError('Atom/position array mismatch!')

    if 'site_occupancy' in doc:
        if len(doc['site_occupancy']) != len(doc['positions_frac']):
            raise RuntimeError('Occupancy/position array mismatch!')

    if 'site_occupancy' not in doc:
        occupancies = [1.0] * len(doc['positions_frac'])

    if sort_atoms:
        positions_frac, atom_types = zip(*[(pos, types) for (
            types,
            pos) in sorted(zip(doc['atom_types'], doc['positions_frac']),
                           key=lambda k: k[0])])
        if 'site_occupancy' in doc:
            occupancies, _atom_types = zip(*[(occ, types) for (
                types,
                occ) in sorted(zip(doc['atom_types'], doc['site_occupancy']),
                               key=lambda k: k[0])])

    else:
        positions_frac = doc['positions_frac']
        atom_types = doc['atom_types']

    if len(positions_frac) != len(
            doc['positions_frac']) or len(atom_types) != len(
                doc['atom_types']):
        raise RuntimeError('Site occupancy mismatch!')

    written_atoms = []
    sfac_str = 'SFAC \t'
    for elem in atom_types:
        if elem not in written_atoms:
            sfac_str += ' ' + str(elem)
            written_atoms.append(str(elem))
    flines.append(sfac_str)
    atom_labels = []
    i = 0
    j = 1
    while i < len(atom_types):
        num = atom_types.count(atom_types[i])
        atom_labels.extend(num * [j])
        i += num
        j += 1

    for atom in zip(atom_types, atom_labels, positions_frac, occupancies):
        flines.append(
            "{0:8s}{1:3d}{2[0]: 20.15f} {2[1]: 20.15f} {2[2]: 20.15f}  {3: 20.15f}"
            .format(atom[0], atom[1], atom[2], atom[3]))
    flines.append('END')
    # very important newline for compatibliy with cryan
    # flines.append('')

    return flines, 'res'
Пример #7
0
def pwout2dict(fname, **kwargs):
    """ Extract available information from pw.x .out file.

    Parameters:
        fname (str/list): filename or list of filenames to scrape as a
            QuantumEspresso pw.x output.

    """
    flines, fname = get_flines_extension_agnostic(fname, ["out", "in"])

    pwout = {}
    pwout['source'] = [fname]

    try:
        # grab file owner username
        from pwd import getpwuid
        pwout['user'] = getpwuid(stat(fname).st_uid).pw_name
    except Exception:
        pwout['user'] = '******'

    if 'CollCode' in fname:
        pwout['icsd'] = fname.split('CollCode')[-1]
    for ind, line in enumerate(reversed(flines)):
        ind = len(flines) - 1 - ind
        if 'cell_parameters' in line.lower() and 'angstrom' in line.lower(
        ) and 'lattice_cart' not in pwout:
            pwout['lattice_cart'] = []
            for j in range(3):
                line = flines[ind + j + 1].strip().split()
                pwout['lattice_cart'].append(list(map(float, line)))
            pwout['cell_volume'] = cart2volume(pwout['lattice_cart'])
        elif 'atomic_positions' in line.lower(
        ) and 'positions_frac' not in pwout:
            pwout['positions_frac'] = []
            pwout['atom_types'] = []
            j = 1
            while True:
                if 'End final coordinates' in flines[j + ind]:
                    break
                else:
                    try:
                        line = flines[j + ind].strip().split()
                        pwout['atom_types'].append(line[0])
                        pwout['positions_frac'].append(
                            list(map(float, line[1:5])))
                        j += 1
                    except Exception:
                        break
            pwout['num_atoms'] = len(pwout['atom_types'])
        elif 'final enthalpy' in line.lower() and 'enthalpy' not in pwout:
            pwout['enthalpy'] = RY_TO_EV * float(line.lower().split()[-2])
        elif 'total   stress' in line.lower() and 'pressure' not in pwout:
            pwout['pressure'] = KBAR_TO_GPA * float(line.lower().split()[-1])
        elif all(key in pwout for key in
                 ['enthalpy', 'pressure', 'lattice_cart', 'positions_frac']):
            break
    # get abc lattice
    pwout['lattice_abc'] = cart2abc(pwout['lattice_cart'])
    # calculate stoichiometry
    pwout['stoichiometry'] = defaultdict(float)
    for atom in pwout['atom_types']:
        if atom not in pwout['stoichiometry']:
            pwout['stoichiometry'][atom] = 0
        pwout['stoichiometry'][atom] += 1
    gcd_val = 0
    for atom in pwout['atom_types']:
        if gcd_val == 0:
            gcd_val = pwout['stoichiometry'][atom]
        else:
            gcd_val = gcd(pwout['stoichiometry'][atom], gcd_val)
    # convert stoichiometry to tuple for fryan
    temp_stoich = []
    for key, value in pwout['stoichiometry'].items():
        if float(value) / gcd_val % 1 != 0:
            temp_stoich.append([key, float(value) / gcd_val])
        else:
            temp_stoich.append([key, value / gcd_val])
    pwout['stoichiometry'] = temp_stoich
    atoms_per_fu = 0
    for elem in pwout['stoichiometry']:
        atoms_per_fu += elem[1]
    pwout['num_fu'] = len(pwout['atom_types']) / atoms_per_fu

    return pwout, True
Пример #8
0
def random_slice(parent_seeds,
                 standardize=True,
                 supercell=True,
                 shift=True,
                 debug=False):
    """ Simple cut-and-splice crossover of two parents.

    The overall size of the child can vary between 0.5 and 1.5 the size of the
    parent structures. Both parent structures are cut and spliced along the
    same crystallographic axis.

    Parameters:

        parents (list(dict)) : parent structures to crossover,
        standardize (bool)   : use spglib to standardize parents pre-crossover,
        supercell (bool)     : make a random supercell to rescale parents,
        shift (bool)         : randomly shift atoms in parents to unbias.

    Returns:

        dict: newborn structure from parents.

    """
    parents = deepcopy(parent_seeds)
    child = dict()
    # child_size is a number between 0.5 and 2
    child_size = 0.5 + 1.5 * np.random.rand()
    # cut_val is a number between 0.25*child_size and 0.75*child_size
    # the slice position of one parent in fractional coordinates
    # (the other is (child_size-cut_val))
    cut_val = child_size * (0.25 + (np.random.rand() / 2.0))

    parent_densities = []
    for ind, parent in enumerate(parents):
        if "cell_volume" not in parent:
            parents[ind]["cell_volume"] = cart2volume(parent["lattice_cart"])
        parent_densities.append(parent["num_atoms"] / parent["cell_volume"])
    target_density = sum(parent_densities) / len(parent_densities)

    if standardize:
        parents = [standardize_doc_cell(parent) for parent in parents]

    if supercell:
        # check ratio of num atoms in parents and grow the smaller one
        parent_extent_ratio = parents[0]["cell_volume"] / parents[1][
            "cell_volume"]
        if debug:
            print(
                parent_extent_ratio,
                parents[0]["cell_volume"],
                "vs",
                parents[1]["cell_volume"],
            )
        if parent_extent_ratio < 1:
            supercell_factor = int(round(1 / parent_extent_ratio))
            supercell_target = 0
        elif parent_extent_ratio >= 1:
            supercell_factor = int(round(parent_extent_ratio))
            supercell_target = 1
        if debug:
            print(supercell_target, supercell_factor)
        supercell_vector = [1, 1, 1]
        if supercell_factor > 1:
            for ind in range(supercell_factor):
                min_lat_vec_abs = 1e10
                min_lat_vec_ind = -1
                for i in range(3):
                    lat_vec_abs = np.sum(
                        np.asarray(
                            parents[supercell_target]["lattice_cart"][i])**2)
                    if lat_vec_abs < min_lat_vec_abs:
                        min_lat_vec_abs = lat_vec_abs
                        min_lat_vec_ind = i
                supercell_vector[min_lat_vec_ind] += 1
        if debug:
            print("Making supercell of {} with {}".format(
                parents[supercell_target]["source"][0], supercell_vector))
        if supercell_vector != [1, 1, 1]:
            parents[supercell_target] = create_simple_supercell(
                parents[supercell_target], supercell_vector, standardize=False)
    child["positions_frac"] = []
    child["atom_types"] = []
    child["lattice_cart"] = cut_val * np.asarray(
        parents[0]["lattice_cart"]) + (child_size - cut_val) * np.asarray(
            parents[1]["lattice_cart"])
    child["lattice_cart"] = child["lattice_cart"].tolist()

    # choose slice axis
    axis = np.random.randint(low=0, high=3)
    for ind, parent in enumerate(parents):
        if shift:
            # apply same random shift to all atoms in parents
            shift_vec = np.random.rand(3)
            for idx, _ in enumerate(parent["positions_frac"]):
                for k in range(3):
                    parent["positions_frac"][idx][k] += shift_vec[k]
                    if parent["positions_frac"][idx][k] >= 1:
                        parent["positions_frac"][idx][k] -= 1
                    elif parent["positions_frac"][idx][k] < 0:
                        parent["positions_frac"][idx][k] += 1
        # slice parent
        for atom, pos in zip(parent["atom_types"], parent["positions_frac"]):
            if ind == (pos[axis] <= cut_val):
                child["positions_frac"].append(pos)
                child["atom_types"].append(atom)
    # check child is sensible
    child["mutations"] = ["crossover"]
    child["stoichiometry"] = get_stoich(child["atom_types"])
    child["num_atoms"] = len(child["atom_types"])

    if "cell_volume" not in child:
        child["cell_volume"] = cart2volume(child["lattice_cart"])
    number_density = child["num_atoms"] / child["cell_volume"]

    # rescale cell based on number density of parents
    new_scale = np.cbrt(number_density / target_density)
    child["lattice_abc"] = np.asarray(cart2abc(child["lattice_cart"]))
    child["lattice_abc"][0] *= new_scale
    child["lattice_abc"] = child["lattice_abc"].tolist()
    child["lattice_cart"] = abc2cart(child["lattice_abc"])
    child["cell_volume"] = cart2volume(child["lattice_cart"])
    child["positions_abs"] = frac2cart(child["lattice_cart"],
                                       child["positions_frac"])

    return child
Пример #9
0
def check_feasible(mutant,
                   parents,
                   max_num_atoms,
                   structure_filter=None,
                   minsep_dict=None,
                   debug=False):
    """ Check if a mutated/newly-born cell is "feasible".
    Here, feasible means:

      * number density within 25% of pre-mutation/birth level,
      * no overlapping atoms, parameterised by minsep_dict,
      * cell angles between 50 and 130 degrees,
      * fewer than max_num_atoms in the cell,
      * ensure number of atomic types is maintained,
      * any custom filter is obeyed.

    Parameters:
        mutant (dict): matador doc containing new structure.
        parents (list(dict)): list of doc(s) containing parent structures.
        max_num_atoms (int): any structures with more than this many atoms will be filtered out.

    Keyword Arguments:
        structure_filter (callable): any function that takes a matador document and returns True or False.
        minsep_dict (dict): dictionary containing element-specific minimum separations, e.g.
            {('K', 'K'): 2.5, ('K', 'P'): 2.0}.

    Returns:
        bool: True if structure is feasible, else False.

    """
    # first check the structure filter
    if structure_filter is not None and not structure_filter(mutant):
        message = "Mutant with {} failed to pass the custom filter.".format(
            ", ".join(mutant["mutations"]))
        LOG.debug(message)
        if debug:
            print(message)
        return False
    # check number of atoms
    if "num_atoms" not in mutant or mutant["num_atoms"] != len(
            mutant["atom_types"]):
        mutant["num_atoms"] = len(mutant["atom_types"])
    if mutant["num_atoms"] > max_num_atoms:
        message = "Mutant with {} contained too many atoms ({} vs {}).".format(
            ", ".join(mutant["mutations"]), mutant["num_atoms"], max_num_atoms)
        LOG.debug(message)
        if debug:
            print(message)
        return False
    # check number density
    if "cell_volume" not in mutant:
        mutant["cell_volume"] = cart2volume(mutant["lattice_cart"])
    number_density = mutant["num_atoms"] / mutant["cell_volume"]
    parent_densities = []
    for ind, parent in enumerate(parents):
        if "cell_volume" not in parent:
            parents[ind]["cell_volume"] = cart2volume(parent["lattice_cart"])
        parent_densities.append(parent["num_atoms"] / parent["cell_volume"])
    target_density = sum(parent_densities) / len(parent_densities)
    if number_density > 1.5 * target_density or number_density < 0.5 * target_density:
        message = "Mutant with {} failed number density.".format(", ".join(
            mutant["mutations"]))
        LOG.debug(message)
        if debug:
            print(message)
        return False

    # now check element-agnostic minseps
    if not minseps_feasible(mutant, minsep_dict=minsep_dict, debug=debug):
        return False

    # check all cell angles are between 60 and 120.
    if "lattice_abc" not in mutant:
        mutant["lattice_abc"] = cart2abc(mutant["lattice_cart"])

    if min(mutant["lattice_abc"][1]) < 30:
        message = "Mutant with {} failed cell angle check.".format(", ".join(
            mutant["mutations"]))
        LOG.debug(message)
        if debug:
            print(message)
        return False

    if max(mutant["lattice_abc"][1]) > 120:
        message = "Mutant with {} failed cell angle check.".format(", ".join(
            mutant["mutations"]))
        LOG.debug(message)
        if debug:
            print(message)
        return False

    # check that we haven't deleted/transmuted all atoms of a certain type
    if len(set(mutant["atom_types"])) < len(set(parents[0]["atom_types"])):
        message = "Mutant with {} transmutation error.".format(", ".join(
            mutant["mutations"]))
        LOG.debug(message)
        if debug:
            print(message)
        return False
    return True
Пример #10
0
def magres2dict(fname, **kwargs):
    """ Extract available information from .magres file. Assumes units of
    Angstrom and ppm for relevant quantities.
    """
    magres = defaultdict(list)
    flines, fname = get_flines_extension_agnostic(fname, "magres")
    magres['source'] = [fname]

    # grab file owner username
    try:
        from pwd import getpwuid
        magres['user'] = getpwuid(stat(fname).st_uid).pw_name
    except Exception:
        magres['user'] = '******'

    magres['magres_units'] = dict()
    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<atoms>', '[atoms]']:
            i = 1
            while flines[line_no +
                         i].strip().lower() not in ['</atoms>', '[/atoms]']:
                split_line = flines[line_no + i].split()
                if not split_line:
                    i += 1
                    continue
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                if split_line[0] == 'units':
                    magres['magres_units'][split_line[1]] = split_line[2]
                elif 'lattice' in split_line:
                    lattice = split_line[1:]
                    for j in range(3):
                        magres['lattice_cart'].append([
                            float(elem) for elem in lattice[j * 3:(j + 1) * 3]
                        ])
                    magres['lattice_abc'] = cart2abc(magres['lattice_cart'])
                elif 'atom' in split_line:
                    atom = split_line
                    magres['atom_types'].append(atom[1])
                    magres['positions_abs'].append(
                        [float(elem) for elem in atom[-3:]])
                i += 1
            break

    if "atom_types" in magres:
        magres['num_atoms'] = len(magres['atom_types'])
        magres['positions_frac'] = cart2frac(magres['lattice_cart'],
                                             magres['positions_abs'])
        magres['stoichiometry'] = get_stoich(magres['atom_types'])

    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<magres>', '[magres]']:
            i = 1
            while flines[line_no +
                         i].strip().lower() not in ['</magres>', '[/magres]']:
                split_line = flines[line_no + i].split()
                if not split_line:
                    i += 1
                    continue
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                if split_line[0] == 'units':
                    magres['magres_units'][split_line[1]] = split_line[2]
                elif 'sus' in split_line:
                    magres["susceptibility_tensor"] = np.array(
                        [float(val) for val in split_line[1:]]).reshape(3, 3)

                elif 'ms' in split_line:
                    ms = np.array([float(val)
                                   for val in split_line[3:]]).reshape(3, 3)
                    s_iso = np.trace(ms) / 3

                    # find eigenvalues of symmetric part of shielding and order them to calc anisotropy eta
                    symmetric_shielding = _symmetrise_tensor(ms)
                    s_yy, s_xx, s_zz = _get_haeberlen_eigs(symmetric_shielding)
                    s_aniso = s_zz - (s_xx + s_yy) / 2.0
                    asymm = (s_yy - s_xx) / (s_zz - s_iso)

                    # convert from reduced anistropy to CSA
                    magres["magnetic_shielding_tensors"].append(ms)
                    magres["chemical_shielding_isos"].append(s_iso)
                    magres["chemical_shift_anisos"].append(s_aniso)
                    magres["chemical_shift_asymmetries"].append(asymm)

                elif "efg" in split_line:
                    efg = np.array([float(val)
                                    for val in split_line[3:]]).reshape(3, 3)
                    species = split_line[1]

                    eigs = _get_haeberlen_eigs(efg)
                    v_zz, eta = eigs[2], (eigs[0] - eigs[1]) / eigs[2]

                    # calculate C_Q in MHz
                    quadrupole_moment = ELECTRIC_QUADRUPOLE_MOMENTS.get(
                        species, 1.0)

                    C_Q = ((ELECTRON_CHARGE * v_zz * quadrupole_moment *
                            EFG_AU_TO_SI * BARN_TO_M2) /
                           (PLANCK_CONSTANT * 1e6))

                    magres["electric_field_gradient"].append(efg)
                    magres["quadrupolar_couplings"].append(C_Q)
                    magres["quadrupolar_asymmetries"].append(eta)

                i += 1

    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<calculation>', '[calculation]']:
            i = 1
            while flines[line_no + i].strip().lower() not in [
                    '</calculation>', '[/calculation]'
            ]:
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                # space important as it excludes other calc_code_x variables
                if 'calc_code ' in flines[line_no + i]:
                    magres['calculator'] = flines[line_no + i].split()[1]
                if 'calc_code_version' in flines[line_no + i]:
                    magres['calculator_version'] = flines[line_no +
                                                          i].split()[1]
                i += 1

    return dict(magres), True
Пример #11
0
 def lattice_cart(self, new_lattice):
     self._lattice_cart = tuple(tuple(vec) for vec in new_lattice)
     self._lattice_abc = tuple(
         tuple(elem) for elem in cell_utils.cart2abc(self._lattice_cart))
     self._volume = None
Пример #12
0
def magres2dict(fname, **kwargs):
    """ Extract available information from .magres file. Assumes units of
    Angstrom and ppm for relevant quantities.
    """
    magres = defaultdict(list)
    flines, fname = get_flines_extension_agnostic(fname, "magres")
    magres['source'] = [fname]

    # grab file owner username
    try:
        magres['user'] = getpwuid(stat(fname).st_uid).pw_name
    except Exception:
        magres['user'] = '******'

    magres['magres_units'] = dict()
    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<atoms>', '[atoms]']:
            i = 1
            while flines[line_no +
                         i].strip().lower() not in ['</atoms>', '[/atoms]']:
                split_line = flines[line_no + i].split()
                if not split_line:
                    i += 1
                    continue
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                if split_line[0] == 'units':
                    magres['magres_units'][split_line[1]] = split_line[2]
                elif 'lattice' in flines[line_no + i]:
                    lattice = flines[line_no + i].split()[1:]
                    for j in range(3):
                        magres['lattice_cart'].append([
                            float(elem) for elem in lattice[j * 3:(j + 1) * 3]
                        ])
                    magres['lattice_abc'] = cart2abc(magres['lattice_cart'])
                elif 'atom' in flines[line_no + i]:
                    atom = flines[line_no + i].split()
                    magres['atom_types'].append(atom[1])
                    magres['positions_abs'].append(
                        [float(elem) for elem in atom[-3:]])
                i += 1
            break

    magres['num_atoms'] = len(magres['atom_types'])
    magres['positions_frac'] = cart2frac(magres['lattice_cart'],
                                         magres['positions_abs'])
    magres['stoichiometry'] = get_stoich(magres['atom_types'])

    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<magres>', '[magres]']:
            i = 1
            while flines[line_no +
                         i].strip().lower() not in ['</magres>', '[/magres]']:
                split_line = flines[line_no + i].split()
                if not split_line:
                    i += 1
                    continue
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                if split_line[0] == 'units':
                    magres['magres_units'][split_line[1]] = split_line[2]
                elif 'sus' in flines[line_no + i]:
                    sus = flines[line_no + i].split()[1:]
                    for j in range(3):
                        magres['susceptibility_tensor'].append(
                            [float(val) for val in sus[3 * j:3 * (j + 1)]])
                elif 'ms' in flines[line_no + i]:
                    ms = flines[line_no + i].split()[3:]
                    magres['magnetic_shielding_tensors'].append([])
                    for j in range(3):
                        magres['magnetic_shielding_tensors'][-1].append(
                            [float(val) for val in ms[3 * j:3 * (j + 1)]])
                    magres['chemical_shielding_isos'].append(0)
                    magres['chemical_shift_anisos'].append(0)
                    magres['chemical_shift_asymmetries'].append(0)
                    for j in range(3):
                        magres['chemical_shielding_isos'][-1] += magres[
                            'magnetic_shielding_tensors'][-1][j][j] / 3

                    # find eigenvalues of symmetric part of shielding and order them to calc anisotropy eta
                    symmetric_shielding = (
                        0.5 *
                        (magres['magnetic_shielding_tensors'][-1] + np.asarray(
                            magres['magnetic_shielding_tensors'][-1]).T))
                    eig_vals, eig_vecs = np.linalg.eig(symmetric_shielding)
                    eig_vals, eig_vecs = zip(
                        *sorted(zip(eig_vals, eig_vecs),
                                key=lambda eig: abs(eig[0] - magres[
                                    'chemical_shielding_isos'][-1])))
                    # Haeberlen convention: |s_zz - s_iso| >= |s_xx - s_iso| >= |s_yy - s_iso|
                    s_yy, s_xx, s_zz = eig_vals
                    s_iso = magres['chemical_shielding_isos'][-1]
                    # convert from reduced anistropy to CSA
                    magres['chemical_shift_anisos'][-1] = s_zz - (s_xx +
                                                                  s_yy) / 2.0
                    magres['chemical_shift_asymmetries'][-1] = (
                        s_yy - s_xx) / (s_zz - s_iso)
                i += 1

    for line_no, line in enumerate(flines):
        line = line.lower().strip()
        if line in ['<calculation>', '[calculation]']:
            i = 1
            while flines[line_no + i].strip().lower() not in [
                    '</calculation>', '[/calculation]'
            ]:
                if i > len(flines):
                    raise RuntimeError("Something went wrong in reader loop")
                # space important as it excludes other calc_code_x variables
                if 'calc_code ' in flines[line_no + i]:
                    magres['calculator'] = flines[line_no + i].split()[1]
                if 'calc_code_version' in flines[line_no + i]:
                    magres['calculator_version'] = flines[line_no +
                                                          i].split()[1]
                i += 1

    return magres, True
Пример #13
0
    def preprocess(self):
        """ Decide which parts of the Workflow need to be performed,
        and set the appropriate CASTEP parameters.

        """
        # default todo
        todo = {
            'relax': True,
            'dynmat': True,
            'vdos': False,
            'dispersion': False,
            'thermodynamics': False
        }
        # definition of steps and names
        steps = {
            'relax': castep_phonon_prerelax,
            'dynmat': castep_phonon_dynmat,
            'vdos': castep_phonon_dos,
            'dispersion': castep_phonon_dispersion,
            'thermodynamics': castep_phonon_thermodynamics
        }

        exts = {
            'relax': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '-out.cell', '.*err']
            },
            'dynmat': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '.*err']
            },
            'vdos': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '.phonon', '.phonon_dos', '.*err']
            },
            'dispersion': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '.phonon', '.*err']
            },
            'thermodynamics': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '.*err']
            }
        }

        if self.calc_doc.get('task').lower() in [
                'phonon', 'thermodynamics', 'phonon+efield'
        ]:
            if ('phonon_fine_kpoint_path' in self.calc_doc
                    or 'phonon_fine_kpoint_list' in self.calc_doc
                    or 'phonon_fine_kpoint_path_spacing' in self.calc_doc):
                todo['dispersion'] = True
            if 'phonon_fine_kpoint_mp_spacing' in self.calc_doc:
                todo['vdos'] = True
            if self.calc_doc['task'].lower() == 'thermodynamics':
                todo['thermodynamics'] = True

        # prepare to do pre-relax if there's no check file
        if os.path.isfile(self.seed + '.check'):
            todo['relax'] = False
            LOG.info(
                'Restarting from {}.check, so not performing re-relaxation'.
                format(self.seed))

        for key in todo:
            if todo[key]:
                self.add_step(steps[key],
                              key,
                              input_exts=exts[key].get('input'),
                              output_exts=exts[key].get('output'))

        # always standardise the cell so that any phonon calculation can have
        # post-processing performed after the fact, unless a path has been provided
        if 'phonon_fine_kpoint_list' not in self.calc_doc and 'phonon_fine_kpoint_path' not in self.calc_doc:
            from matador.utils.cell_utils import cart2abc
            prim_doc, kpt_path = self.computer.get_seekpath_compliant_input(
                self.calc_doc,
                self.calc_doc.get('phonon_fine_kpoint_path_spacing', 0.02))
            self.calc_doc.update(prim_doc)
            self.calc_doc['lattice_abc'] = cart2abc(
                self.calc_doc['lattice_cart'])
            if todo['dispersion']:
                self.calc_doc['phonon_fine_kpoint_list'] = kpt_path

        elif todo['dispersion'] and 'phonon_fine_kpoint_path' in self.calc_doc:
            self._user_defined_kpt_path = True
            LOG.warning('Using user-defined k-point path for all structures.')
            self.calc_doc['phonon_fine_kpoint_spacing'] = self.calc_doc.get(
                'phonon_fine_kpoint_path_spacing', 0.05)

        # always shift phonon grid to include Gamma
        if 'phonon_kpoint_mp_spacing' in self.calc_doc:
            from matador.utils.cell_utils import calc_mp_grid, shift_to_include_gamma
            grid = calc_mp_grid(self.calc_doc['lattice_cart'],
                                self.calc_doc['phonon_kpoint_mp_spacing'])
            offset = shift_to_include_gamma(grid)
            if offset != [0, 0, 0]:
                self.calc_doc['phonon_kpoint_mp_offset'] = offset
                LOG.debug('Set phonon MP grid offset to {}'.format(offset))

        LOG.info(
            'Preprocessing completed: run3 phonon options {}'.format(todo))
Пример #14
0
    def preprocess(self):
        """ Decide which parts of the Workflow need to be performed,
        and set the appropriate CASTEP parameters.

        """
        # default todo
        todo = {
            'scf': True,
            'dos': False,
            'pdos': False,
            'broadening': False,
            'dispersion': False,
            'pdis': False
        }
        # definition of steps and names
        steps = {
            'scf': castep_spectral_scf,
            'dos': castep_spectral_dos,
            'pdos': optados_pdos,
            'broadening': optados_dos_broadening,
            'dispersion': castep_spectral_dispersion,
            'pdis': optados_pdispersion
        }

        exts = {
            'scf': {
                'input': ['.cell', '.param'],
                'output': ['.castep', '.*err', '-out.cell']
            },
            'dos': {
                'input': ['.cell', '.param'],
                'output': [
                    '.castep', '.bands', '.pdos_bin', '.dome_bin', '.*err',
                    '-out.cell'
                ]
            },
            'dispersion': {
                'input': ['.cell', '.param'],
                'output': [
                    '.castep', '.bands', '.pdos_bin', '.dome_bin', '.*err',
                    '-out.cell'
                ]
            },
            'pdis': {
                'input': ['.odi', '.pdos_bin'],
                'output': ['.odo', '.*err']
            },
            'pdos': {
                'input': ['.odi', '.pdos_bin', '.dome_bin'],
                'output': ['.odo', '.*err']
            },
            'broadening': {
                'input': ['.odi', '.pdos_bin', '.dome_bin'],
                'output': ['.odo', '.*err']
            }
        }

        if os.path.isfile(self.seed + '.check'):
            LOG.info('Found {}.check, so skipping initial SCF.'.format(
                self.seed))
            todo['scf'] = False

        if (('spectral_kpoints_path' in self.calc_doc
             or 'spectral_kpoints_list' in self.calc_doc
             or 'spectral_kpoints_path_spacing' in self.calc_doc
             or self.calc_doc.get('spectral_task',
                                  '').lower() == 'bandstructure')):
            todo['dispersion'] = not os.path.isfile(self.seed +
                                                    '.bands_dispersion')

        if ('spectral_kpoints_mp_spacing' in self.calc_doc
                or self.calc_doc.get('spectral_task', '').lower() == 'dos'):
            todo['dos'] = not os.path.isfile(self.seed + '.bands_dos')

        odi_fname = _get_optados_fname(self.seed)
        if odi_fname is not None:
            odi_dict, _ = arbitrary2dict(odi_fname)
            if todo['dispersion']:
                todo['pdis'] = 'pdispersion' in odi_dict
            if todo['dos']:
                todo['broadening'] = 'broadening' in odi_dict
                todo['pdos'] = 'pdos' in odi_dict

        for key in todo:
            if todo[key]:
                self.add_step(steps[key],
                              key,
                              input_exts=exts[key].get('input'),
                              output_exts=exts[key].get('output'))

        if self.computer.run3_settings.get('run3_settings') is not None:
            settings = self.computer.kwargs.get('run3_settings')
            # check that computer.exec was not overriden at cmd-line, then check settings file
            if settings.get(
                    'castep_executable'
            ) is not None and self.computer.executable == 'castep':
                self.castep_executable = settings.get('castep_executable',
                                                      'castep')
                self.computer.executable = self.castep_executable
            if settings.get('optados_executable') is not None:
                self.optados_executable = settings.get('optados_executable',
                                                       'optados')
                self.computer.optados_executable = self.optados_executable

        # if not using a user-requested path, use seekpath and spglib
        # to reduce to primitive and use consistent path
        if 'spectral_kpoints_list' not in self.calc_doc and 'spectral_kpoints_path' not in self.calc_doc:
            from matador.utils.cell_utils import cart2abc
            prim_doc, kpt_path = self.computer.get_seekpath_compliant_input(
                self.calc_doc,
                self.calc_doc.get('spectral_kpoints_path_spacing', 0.05))
            self.calc_doc.update(prim_doc)
            self.calc_doc['lattice_abc'] = cart2abc(
                self.calc_doc['lattice_cart'])
            if todo['dispersion']:
                self.calc_doc['spectral_kpoints_list'] = kpt_path
        elif todo['dispersion'] and 'spectral_kpoints_path' in self.calc_doc:
            self._user_defined_kpt_path = True
            LOG.warning('Using user-defined k-point path for all structures.')
            self.calc_doc['spectral_kpoints_path_spacing'] = self.calc_doc.get(
                'spectral_kpoints_path_spacing', 0.05)

        if todo['dos']:
            self.calc_doc['spectral_kpoints_mp_spacing'] = self.calc_doc.get(
                'spectral_kpoints_mp_spacing', 0.05)

        # always use continuation
        self.calc_doc['continuation'] = 'default'

        LOG.info(
            'Preprocessing completed: run3 spectral options {}'.format(todo))