Exemple #1
0
 def load_dict(self, dict0):
     """
     load the structure from a dictionary
     """
     self.group = Group(dict0["group"])
     self.lattice = Lattice.from_matrix(dict0["lattice"],
                                        ltype=self.group.lattice_type)
     self.molecular = dict0["molecular"]
     self.number = self.group.number
     self.factor = dict0["factor"]
     self.source = dict0["source"]
     self.dim = dict0["dim"]
     self.PBC = dict0["PBC"]
     self.numIons = dict0["numIons"]
     self.numMols = dict0["numMols"]
     self.valid = dict0["valid"]
     self.formula = dict0["formula"]
     sites = []
     if dict0["molecular"]:
         for site in dict0["sites"]:
             sites.append(mol_site.load_dict(site))
         self.mol_sites = sites
     else:
         for site in dict0["sites"]:
             sites.append(atom_site.load_dict(site))
         self.atom_sites = sites
Exemple #2
0
def test_cluster():
    global outstructs
    global outstrings
    print(
        "=== Testing generation of point group clusters. This may take some time. ==="
    )
    from time import time
    from spglib import get_symmetry_dataset
    from pyxtal.symmetry import Group
    from pyxtal.crystal import random_cluster
    from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

    slow = []
    failed = []
    print("  Point group # |     Symbol    |  Time Elapsed")
    skip = []  # [32,55,56]#[28,29,30,31,32,55,56]
    for sg in range(1, 57):
        if sg not in skip:
            multiplicity = len(Group(
                sg, dim=0)[0])  # multiplicity of the general position
            start = time()
            rand_crystal = random_cluster(sg, ["C"], [multiplicity], 1.0)
            end = time()
            timespent = np.around((end - start), decimals=2)
            t = str(timespent)
            if len(t) == 3:
                t += "0"
            t += " s"
            if timespent >= 1.0:
                t += " ~"
            if timespent >= 3.0:
                t += "~"
            if timespent >= 10.0:
                t += "~"
            if timespent >= 60.0:
                t += "~"
                slow.append(sg)
            if rand_crystal.valid:
                if check_struct_group(rand_crystal, sg, dim=0):
                    pass
                else:
                    t += " xxxxx"
                    outstructs.append(rand_crystal.struct)
                    outstrings.append(str("Cluster_" + str(sg) + ".vasp"))
                pgsymbol = Group(sg, dim=0).symbol
                print("\t" + str(sg) + "\t|\t" + pgsymbol + "\t|\t" + t)
            else:
                print("~~~~ Error: Could not generate space group " + str(sg) +
                      " after " + t)
                failed.append(sg)
    if slow != []:
        print(
            "~~~~ The following space groups took more than 60 seconds to generate:"
        )
        for i in slow:
            print("     " + str(i))
    if failed != []:
        print("~~~~ The following space groups failed to generate:")
        for i in failed:
            print("     " + str(i))
Exemple #3
0
    def from_pymatgen(self, structure):
        """
        Load the seed structure from Pymatgen/ASE/POSCAR/CIFs
        """
        from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as sga
        try:
            # needs to do it twice in order to get the conventional cell
            s = sga(structure)
            structure = s.get_refined_structure()
            s = sga(structure)
            sym_struc = s.get_symmetrized_structure()
            number = s.get_space_group_number()
        except:
            print("Failed to load the Pymatgen structure")
            self.valid = False

        if self.valid:
            d = sym_struc.composition.as_dict()
            species = [key for key in d.keys()]
            numIons = []
            for ele in species:
                numIons.append(int(d[ele]))
            self.numIons = numIons
            self.species = species
            self.group = Group(number)
            atom_sites = []
            for i, site in enumerate(sym_struc.equivalent_sites):
                pos = site[0].frac_coords
                wp = Wyckoff_position.from_group_and_index(
                    number, sym_struc.wyckoff_symbols[i])
                specie = site[0].specie.number
                atom_sites.append(atom_site(wp, pos, specie))
            self.atom_sites = atom_sites
            self.lattice = Lattice.from_matrix(sym_struc.lattice.matrix,
                                               ltype=self.group.lattice_type)
Exemple #4
0
    def test_swap_wp(self):
        g = Group(38)
        wp = g[4]
        wp1, trans = wp.swap_axis([1, 0, 2])

        g = Group(71)
        wp = g[5]
        wp1, trans = wp.swap_axis([0, 2, 1])
        wp1, trans = wp.swap_axis([1, 2, 0])
        wp1, trans = wp.swap_axis([2, 1, 0])
Exemple #5
0
def test_cluster():
    global outstructs
    global outstrings
    fprint("=== Testing generation of point group clusters. This may take some time. ===")

    slow = []
    failed = []
    fprint("  Point group # |     Symbol    |  Time Elapsed")
    skip = [56]  # [32,55,56]#[28,29,30,31,32,55,56]
    for sg in range(1, 57):
        if sg not in skip:
            multiplicity = len(
                Group(sg, dim=0)[0]
            )  # multiplicity of the general position
            start = time()
            rand_crystal = pyxtal()
            rand_crystal.from_random(0, sg, ["C"], [multiplicity], 1.0)
            end = time()
            timespent = np.around((end - start), decimals=2)
            t = str(timespent)
            if len(t) == 3:
                t += "0"
            t += " s"
            if timespent >= 1.0:
                t += " ~"
            if timespent >= 3.0:
                t += "~"
            if timespent >= 10.0:
                t += "~"
            if timespent >= 60.0:
                t += "~"
                slow.append(sg)
            if rand_crystal.valid:
                if check_struct_group(rand_crystal, sg, dim=0):
                    pass
                else:
                    t += " xxxxx"
                    outstructs.append(rand_crystal.to_pymatgen())
                    outstrings.append(str("Cluster_" + str(sg) + ".vasp"))
                pgsymbol = Group(sg, dim=0).symbol
                fprint("\t{}\t|\t{}\t|\t{}".format(sg, pgsymbol, t))
            else:
                fprint(
                    "~~~~ Error: Could not generate space group {} after {}".format(sg, t)
                )
                failed.append(sg)
    if slow != []:
        fprint("~~~~ The following space groups took more than 60 seconds to generate:")
        for i in slow:
            fprint("     " + str(i))
    if failed != []:
        fprint("~~~~ The following space groups failed to generate:")
        for i in failed:
            fprint("     " + str(i))
Exemple #6
0
    def from_seed(self,
                  seed,
                  molecule=None,
                  tol=1e-4,
                  relax_h=False,
                  backend='pymatgen'):
        """
        Load the seed structure from Pymatgen/ASE/POSCAR/CIFs
        Internally they will be handled by Pymatgen
        """
        from ase import Atoms
        from pymatgen import Structure

        if self.molecular:
            from pyxtal.molecule import pyxtal_molecule
            pmol = pyxtal_molecule(molecule).mol
            struc = structure_from_ext(seed, pmol, relax_h=relax_h)
            if struc.match():
                self.mol_sites = [struc.make_mol_site()]
                self.group = Group(struc.wyc.number)
                self.lattice = struc.lattice
                self.molecules = [
                    pyxtal_molecule(struc.molecule, symmetrize=False)
                ]
                self.numMols = struc.numMols
                self.diag = struc.diag
                self.valid = True  # Need to add a check function
            else:
                raise ValueError(
                    "Cannot extract the molecular crystal from cif")
        else:
            if isinstance(seed, dict):
                self.from_dict()
            elif isinstance(seed, Atoms):  #ASE atoms
                from pymatgen.io.ase import AseAtomsAdaptor
                pmg_struc = AseAtomsAdaptor.get_structure(seed)
                self._from_pymatgen(pmg_struc, tol)
            elif isinstance(seed, Structure):  #Pymatgen
                self._from_pymatgen(seed, tol)
            elif isinstance(seed, str):
                if backend == 'pymatgen':
                    pmg_struc = Structure.from_file(seed)
                    self._from_pymatgen(pmg_struc, tol)
                else:
                    self.lattice, self.atom_sites = read_cif(seed)
                    self.group = Group(self.atom_sites[0].wp.number)
                    self.diag = self.atom_sites[0].diag
                    self.valid = True
        self.factor = 1.0
        self.source = 'Seed'
        self.dim = 3
        self.PBC = [1, 1, 1]
        self._get_formula()
Exemple #7
0
def test_molecular_2D():
    global outstructs
    global outstrings
    print(
        "=== Testing generation of molecular 2D crystals. This may take some time. ==="
    )
    from time import time
    from pyxtal.symmetry import Group
    from pyxtal.molecular_crystal import molecular_crystal_2D
    from pymatgen.symmetry.analyzer import SpacegroupAnalyzer

    slow = []
    failed = []
    print("  Layer group # |     Symbol    |  Time Elapsed")
    skip = []
    for sg in range(1, 81):
        if sg not in skip:
            g = Group(sg, dim=2)
            multiplicity = len(g[0])  # multiplicity of the general position
            start = time()
            rand_crystal = molecular_crystal_2D(sg, ["H2O"], [multiplicity],
                                                4.0)
            end = time()
            timespent = np.around((end - start), decimals=2)
            t = str(timespent)
            if len(t) == 3:
                t += "0"
            t += " s"
            if timespent >= 1.0:
                t += " ~"
            if timespent >= 3.0:
                t += "~"
            if timespent >= 10.0:
                t += "~"
            if timespent >= 60.0:
                t += "~"
                slow.append(sg)
            if rand_crystal.valid:
                if check_struct_group(rand_crystal, sg, dim=2):
                    pass
                else:
                    t += " xxxxx"
                    outstructs.append(rand_crystal.struct)
                    outstrings.append(str("molecular_2D_" + str(sg) + ".vasp"))
                symbol = g.symbol
                print("\t" + str(sg) + "\t|\t" + symbol + "\t|\t" + t)
            else:
                print("~~~~ Error: Could not generate layer group " + str(sg) +
                      " after " + t)
                failed.append(sg)
    if slow != []:
        print(
            "~~~~ The following layer groups took more than 60 seconds to generate:"
        )
        for i in slow:
            print("     " + str(i))
    if failed != []:
        print("~~~~ The following layer groups failed to generate:")
        for i in failed:
            print("     " + str(i))
Exemple #8
0
def symmetrize(pmg, tol=1e-3, a_tol=5.0):
    """
    symmetrize the structure from spglib

    Args:
        pmg: pymatgen structure
        tol: tolerance

    Returns:
        pymatgen structure with symmetrized lattice
    """

    atoms = (pmg.lattice.matrix, pmg.frac_coords, pmg.atomic_numbers)
    dataset = get_symmetry_dataset(atoms, tol, angle_tolerance=a_tol)
    hn = Group(dataset['number'], 3).hall_number
    if hn != dataset['hall_number']:
        dataset = get_symmetry_dataset(atoms,
                                       tol,
                                       angle_tolerance=a_tol,
                                       hall_number=hn)
    cell = dataset['std_lattice']
    pos = dataset['std_positions']
    numbers = dataset['std_types']

    return Structure(cell, numbers, pos)
Exemple #9
0
    def _from_pymatgen(self, struc, tol=1e-3, a_tol=5.0):
        """
        Load structure from Pymatgen
        should not be used directly
        """
        from pyxtal.util import get_symmetrized_pmg
        #import pymatgen.analysis.structure_matcher as sm

        self.valid = True
        try:
            sym_struc, number = get_symmetrized_pmg(struc, tol, a_tol)
            #print(sym_struc)
            #import sys; sys.exit()
        except TypeError:
            print("Failed to load the Pymatgen structure")
        #    print(struc)
        #    self.valid = False

        if self.valid:
            d = sym_struc.composition.as_dict()
            species = [key for key in d.keys()]
            numIons = []
            for ele in species:
                numIons.append(int(d[ele]))
            self.numIons = numIons
            self.species = species
            self.group = Group(number)
            matrix, ltype = sym_struc.lattice.matrix, self.group.lattice_type
            self.lattice = Lattice.from_matrix(matrix, ltype=ltype)
            atom_sites = []
            for i, site in enumerate(sym_struc.equivalent_sites):
                pos = site[0].frac_coords
                wp = Wyckoff_position.from_group_and_index(number, sym_struc.wyckoff_symbols[i])
                specie = site[0].specie.number
                pos1 = search_matched_position(self.group, wp, pos)
                if pos1 is not None:
                    atom_sites.append(atom_site(wp, pos1, specie))
                else:
                    break

            if len(atom_sites) != len(sym_struc.equivalent_sites):
                raise RuntimeError("Cannot extract the right mapping from spglib")
            else:
                self.atom_sites = atom_sites
Exemple #10
0
def read_cif(filename):
    """
    read the cif, mainly for pyxtal cif output
    Be cautious in using it to read other cif files

    Args:
        filename: path of the structure file 

    Return:
        pyxtal structure
    """
    species = []
    coords = []
    with open(filename, 'r') as f:
        lines = f.readlines()
        for i, line in enumerate(lines):
            if line.startswith('_symmetry_Int_Tables_number'):
                sg = int(line.split()[-1])
            elif line.startswith('_cell_length_a'):
                a = float(lines[i].split()[-1])
                b = float(lines[i+1].split()[-1])
                c = float(lines[i+2].split()[-1])
                alpha = float(lines[i+3].split()[-1])
                beta = float(lines[i+4].split()[-1])
                gamma = float(lines[i+5].split()[-1])
            elif line.startswith('_symmetry_cell_setting'):
                lat_type = line.split()[-1]
            elif line.startswith('_symmetry_space_group_name_H-M '):
                symbol = line.split()[-1]
                if eval(symbol) in ["Pn", "P21/n", "C2/n"]:
                    diag = True
                else:
                    diag = False

            elif line.find('_atom_site') >= 0:
                s = i
                while True:
                    s += 1
                    if lines[s].find('_atom_site') >= 0:
                        pass
                    elif len(lines[s].split()) <= 3:
                        break
                    else:
                        tmp = lines[s].split()
                        pos = [float(tmp[-4]), float(tmp[-3]), float(tmp[-2])]
                        species.append(tmp[0])
                        coords.append(pos)
                break

    wp0 = Group(sg)[0]
    lattice = Lattice.from_para(a, b, c, alpha, beta, gamma, lat_type)
    sites = []
    for specie, coord in zip(species, coords):
        pt, wp, _ = WP_merge(coord, lattice.matrix, wp0, tol=0.1)
        sites.append(atom_site(wp, pt, specie, diag))
    return lattice, sites
Exemple #11
0
def test_molecular_2D():
    global outstructs
    global outstrings
    fprint(
        "=== Testing generation of molecular 2D crystals. This may take some time. ==="
    )

    slow = []
    failed = []
    fprint("  Layer group # |     Symbol    |  Time Elapsed")
    skip = []
    for sg in range(1, 81):
        if sg not in skip:
            g = Group(sg, dim=2)
            multiplicity = len(g[0])  # multiplicity of the general position
            start = time()
            rand_crystal = pyxtal(molecular=True)
            rand_crystal.from_random(2, sg, ["H2O"], [multiplicity], 4.0)
            end = time()
            timespent = np.around((end - start), decimals=2)
            t = str(timespent)
            if len(t) == 3:
                t += "0"
            t += " s"
            if timespent >= 1.0:
                t += " ~"
            if timespent >= 3.0:
                t += "~"
            if timespent >= 10.0:
                t += "~"
            if timespent >= 60.0:
                t += "~"
                slow.append(sg)
            if rand_crystal.valid:
                if check_struct_group(rand_crystal, sg, dim=2):
                    pass
                else:
                    t += " xxxxx"
                    outstructs.append(rand_crystal.to_pymatgen())
                    outstrings.append(str("molecular_2D_" + str(sg) + ".vasp"))
                symbol = g.symbol
                fprint("\t{}\t|\t{}\t|\t{}".format(sg, symbol, t))
            else:
                fprint(
                    "~~~~ Error: Could not generate layer group {} after {}".format(sg, t)
                )
                failed.append(sg)
    if slow != []:
        fprint("~~~~ The following layer groups took more than 60 seconds to generate:")
        for i in slow:
            fprint("     " + str(i))
    if failed != []:
        fprint("~~~~ The following layer groups failed to generate:")
        for i in failed:
            fprint("     " + str(i))
Exemple #12
0
    def make_gen_wyckoff_site(self):

        if self.wp.index > 0:
            wp, ax, pos = self._find_gen_wyckoff_in_subgroup()
            ori = self.orientation.rotate_by_matrix(np.eye(3)[ax])
            lat = self.lattice.swap_axis(ids=ax)
            lat.ltype = Group(wp.number).lattice_type
            return mol_site(self.molecule, pos, ori, wp, lat, self.diag)
        else:
            print("This is already a general position")
            return self
Exemple #13
0
    def _from_pymatgen(self, struc, tol=1e-3):
        """
        Load structure from Pymatgen
        should not be used directly
        """
        from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as sga
        from pyxtal.util import symmetrize
        #import pymatgen.analysis.structure_matcher as sm

        self.valid = True
        try:
            # needs to do it twice in order to get the conventional cell
            pmg = symmetrize(struc, tol)
            s = sga(pmg, symprec=tol)
            sym_struc = s.get_symmetrized_structure()
            number = s.get_space_group_number()
            #print(sym_struc)

        except:
            print("Failed to load the Pymatgen structure")
            self.valid = False

        if self.valid:
            d = sym_struc.composition.as_dict()
            species = [key for key in d.keys()]
            numIons = []
            for ele in species:
                numIons.append(int(d[ele]))
            self.numIons = numIons
            self.species = species
            self.group = Group(number)
            atom_sites = []
            for i, site in enumerate(sym_struc.equivalent_sites):
                pos = site[0].frac_coords
                wp = Wyckoff_position.from_group_and_index(
                    number, sym_struc.wyckoff_symbols[i])
                specie = site[0].specie.number
                atom_sites.append(atom_site(wp, pos, specie, search=True))
            self.atom_sites = atom_sites
            matrix, ltype = sym_struc.lattice.matrix, self.group.lattice_type
            self.lattice = Lattice.from_matrix(matrix, ltype=ltype)
Exemple #14
0
 def test_molecular_nodiag(self):
     sgs, diag = [5, 7, 8, 12, 13, 14], False
     for i in range(40):
         sg = choice(sgs)
         num = len(Group(sg)[0])
         c1 = pyxtal(molecular=True)
         c1.from_random(3, sg, ["aspirin"], [num], diag=diag)
         pmg1 = c1.to_pymatgen()
         c2 = c1.copy()
         c2.optimize_lattice(1)
         pmg2 = c2.to_pymatgen()
         self.assertTrue(sm.StructureMatcher().fit(pmg1, pmg2))
Exemple #15
0
 def search_position(self):
     """
     Sometimes, the initial posiition is not the proper generator
     Needs to find the proper generator
     """
     if self.wp.index > 0:
         pos = self.position
         coords = apply_ops(pos, Group(self.wp.number, self.wp.dim)[0])
         for coord in coords:
             ans = apply_ops(coord, [self.wp.ops[0]])[0]
             diff = coord - ans
             diff -= np.floor(diff)
             if np.sum(diff**2) < 1e-4:
                 self.position = coord - np.floor(coord)
                 break
Exemple #16
0
    def __init__(self, struc, ref_mols, tol=0.2, relax_h=False):

        """
        extract the mol_site information from the give cif file 
        and reference molecule
    
        Args: 
            struc: cif/poscar file or a Pymatgen Structure object
            ref_mols: a list of reference molecule (xyz file or Pyxtal molecule)
            tol: scale factor for covalent bond distance
            relax_h: whether or not relax the position for hydrogen atoms in structure
        
        """

        for ref_mol in ref_mols:
            if isinstance(ref_mol, str):
                ref_mol = pyxtal_molecule(ref_mol)
            elif isinstance(ref_mol, pyxtal_molecule):
                ref_mol = ref_mol
            else:
                print(type(ref_mol))
                raise NameError("reference molecule cannot be defined")
    
        if isinstance(struc, str):
            pmg_struc = Structure.from_file(struc)
        elif isinstance(struc, Structure):
            pmg_struc = struc
        else:
            print(type(struc))
            raise NameError("input structure cannot be intepretted")

        self.ref_mols = ref_mols
        self.tol = tol
        self.diag = False
        self.relax_h = relax_h

        sym_struc, number = get_symmetrized_pmg(pmg_struc)
        group = Group(number)
        self.group = group
        self.wyc = group[0]
        self.perm = [0,1,2]

        molecules = search_molecules_in_crystal(sym_struc, self.tol)
        if self.relax_h: molecules = self.addh(molecules)
        self.pmg_struc = sym_struc
        self.lattice = Lattice.from_matrix(sym_struc.lattice.matrix, ltype=group.lattice_type)
        self.resort(molecules)
        self.numMols = [len(self.wyc)]
Exemple #17
0
    def __init__(
            self,
            group=None,
            species=None,
            numIons=None,
            factor=1.1,
            lattice=None,
            sites=None,
            conventional=True,
            tm=Tol_matrix(prototype="atomic"),
    ):

        self.dim = 3  #periodic dimensions of the crystal
        self.PBC = [1, 1, 1]  #The periodic boundary axes of the crystal
        if type(group) != Group:
            group = Group(group, self.dim)
        self.init_common(species, numIons, factor, group, lattice, sites,
                         conventional, tm)
Exemple #18
0
    def __init__(
            self,
            group,
            species,
            numIons,
            factor=1.1,
            thickness=None,
            lattice=None,
            sites=None,
            tm=Tol_matrix(prototype="atomic"),
    ):
        self.dim = 2
        self.PBC = [1, 1, 0]

        if type(group) != Group:
            group = Group(group, self.dim)
        number = group.number  # The layer group number of the crystal
        self.thickness = thickness  # in Angstroms, in the 3rd dimenion of unit cell
        self.init_common(species, numIons, factor, number, lattice, sites, tm)
Exemple #19
0
    def __init__(
        self,
        group,
        molecules,
        numMols,
        volume_factor=1.1,
        select_high=True,
        allow_inversion=True,
        orientations=None,
        lattice=None,
        tm=Tol_matrix(prototype="molecular"),
        sites=None,
        seed=None,
        diag=False,
        relax_h=False,
    ):

        self.dim = 3  # The number of periodic dimensions (1,2,3)
        self.PBC = [1, 1, 1]
        self.diag = diag

        if type(group) != Group:
            group = Group(group, self.dim)

        self.sg = group.number
        self.selec_high = select_high
        self.seed = seed
        self.relax_h = relax_h

        self.init_common(
            molecules,
            numMols,
            volume_factor,
            select_high,
            allow_inversion,
            orientations,
            group,
            lattice,
            tm,
            sites,
        )
Exemple #20
0
def get_symmetrized_pmg(pmg, tol=1e-3, a_tol=5.0):
    """
    Symmetrized Pymatgen structure
    A slight modification to ensure that the structure adopts the
    standard setting used in interational crystallography table

    Args:
        pmg: input pymatgen structure
        tol: symmetry tolerance
    """

    pmg = symmetrize(pmg, tol, a_tol=a_tol)
    s = sga(pmg, symprec=tol, angle_tolerance=a_tol)
    hn = Group(s.get_space_group_number()).hall_number
    # make sure that the coordinates are in standard setting
    if hn != s._space_group_data["hall_number"]:
        s._space_group_data = get_symmetry_dataset(s._cell,
                                                   tol,
                                                   angle_tolerance=a_tol,
                                                   hall_number=hn)
    return s.get_symmetrized_structure(), s.get_space_group_number()
Exemple #21
0
    def __init__(
            self,
            group,
            molecules,
            numMols,
            volume_factor=1.1,
            select_high=True,
            allow_inversion=True,
            orientations=None,
            thickness=None,
            lattice=None,
            tm=Tol_matrix(prototype="molecular"),
            seed=None,
            sites=None,
    ):

        self.dim = 2
        self.numattempts = 0
        self.seed = None
        if type(group) != Group:
            group = Group(group, self.dim)
        number = group.number  # The layer group number of the crystal."""
        self.diag = False
        self.thickness = thickness  # the thickness in Angstroms
        self.PBC = [1, 1, 0]
        self.init_common(
            molecules,
            numMols,
            volume_factor,
            select_high,
            allow_inversion,
            orientations,
            group,
            lattice,
            tm,
            sites,
        )
Exemple #22
0
    def __init__(
            self,
            group=None,
            species=None,
            numIons=None,
            factor=1.1,
            lattice=None,
            sites=None,
            tm=Tol_matrix(prototype="atomic"),
            seed=None,
    ):

        self.dim = 3  #periodic dimensions of the crystal
        self.PBC = [1, 1, 1]  #The periodic boundary axes of the crystal
        if seed is None:
            if type(group) != Group:
                group = Group(group, self.dim)
            self.sg = group.number  #The international spacegroup number
            self.seed = None
            self.init_common(species, numIons, factor, group, lattice, sites,
                             tm)
        else:
            self.seed = seed
            self.from_seed()
Exemple #23
0
from pyxtal.symmetry import Group

length1 = 0
length2 = 0
length3 = 0
length_greater = 0

for g in range(1, 231):
    wyc = Group(g).get_max_t_subgroup()
    for re, h in zip(wyc['relations'], wyc['subgroup']):
        for pos in re:
            length = len(pos)
            if length == 1:
                length1 += 1

            elif length == 2:
                length2 += 1

            elif length == 3:
                length3 += 1
                #print(g, h, pos)
            else:
                length_greater += 1
                print(g, h, pos)
Exemple #24
0
    def init_common(
        self,
        molecules,
        numMols,
        volume_factor,
        select_high,
        allow_inversion,
        orientations,
        group,
        lattice,
        tm,
        sites,
    ):
        # init functionality which is shared by 3D, 2D, and 1D crystals
        self.valid = False
        self.numattempts = 0  # number of attempts to generate the crystal.
        if type(group) == Group:
            self.group = group
            """A pyxtal.symmetry.Group object storing information about the space/layer
            /Rod/point group, and its Wyckoff positions."""
        else:
            self.group = Group(group, dim=self.dim)
        self.number = self.group.number
        """
        The international group number of the crystal:
        1-230 for 3D space groups
        1-80 for 2D layer groups
        1-75 for 1D Rod groups
        1-32 for crystallographic point groups
        None otherwise
        """
        self.Msgs()
        self.factor = volume_factor  # volume factor for the unit cell.
        numMols = np.array(numMols)  # must convert it to np.array
        self.numMols0 = numMols  # in the PRIMITIVE cell
        self.numMols = self.numMols0 * cellsize(
            self.group)  # in the CONVENTIONAL cell

        # boolean numbers
        self.allow_inversion = allow_inversion
        self.select_high = select_high

        # Set the tolerance matrix
        # The Tol_matrix object for checking inter-atomic distances within the structure.
        if type(tm) == Tol_matrix:
            self.tol_matrix = tm
        else:
            try:
                self.tol_matrix = Tol_matrix(prototype=tm)
            # TODO remove bare except
            except:
                msg = "Error: tm must either be a Tol_matrix object +\n"
                msg += "or a prototype string for initializing one."
                printx(msg, priority=1)
                return

        self.molecules = []  # A pyxtal_molecule objects,
        for mol in molecules:
            self.molecules.append(pyxtal_molecule(mol, self.tol_matrix))

        self.sites = {}
        for i, mol in enumerate(self.molecules):
            if sites is not None and sites[i] is not None:
                self.check_consistency(sites[i], self.numMols[i])
                self.sites[i] = sites[i]
            else:
                self.sites[i] = None

        # if seeds, directly parse the structure from cif
        # At the moment, we only support one specie
        if self.seed is not None:
            seed = structure_from_ext(self.seed,
                                      self.molecules[0].mol,
                                      relax_h=self.relax_h)
            if seed.match():
                self.mol_sites = [seed.make_mol_site()]
                self.group = Group(seed.wyc.number)
                self.lattice = seed.lattice
                self.molecules = [pyxtal_molecule(seed.molecule)]
                self.diag = seed.diag
                self.valid = True  # Need to add a check function
            else:
                raise ValueError("Cannot extract the structure from cif")

        # The valid orientations for each molecule and Wyckoff position.
        # May be copied when generating a new molecular_crystal to save a
        # small amount of time

        if orientations is None:
            self.get_orientations()
        else:
            self.valid_orientations = orientations

        if self.seed is None:
            if lattice is not None:
                # Use the provided lattice
                self.lattice = lattice
                self.volume = lattice.volume
                # Make sure the custom lattice PBC axes are correct.
                if lattice.PBC != self.PBC:
                    self.lattice.PBC = self.PBC
                    printx("\n  Warning: converting custom lattice PBC to " +
                           str(self.PBC))
            else:
                # Determine the unique axis
                if self.dim == 2:
                    if self.number in range(3, 8):
                        unique_axis = "c"
                    else:
                        unique_axis = "a"
                elif self.dim == 1:
                    if self.number in range(3, 8):
                        unique_axis = "a"
                    else:
                        unique_axis = "c"
                else:
                    unique_axis = "c"

                # Generate a Lattice instance
                self.volume = self.estimate_volume()
                # The Lattice object used to generate lattice matrices
                if self.dim == 3 or self.dim == 0:
                    self.lattice = Lattice(
                        self.group.lattice_type,
                        self.volume,
                        PBC=self.PBC,
                        unique_axis=unique_axis,
                    )
                elif self.dim == 2:
                    self.lattice = Lattice(
                        self.group.lattice_type,
                        self.volume,
                        PBC=self.PBC,
                        unique_axis=unique_axis,
                        thickness=self.thickness,
                    )
                elif self.dim == 1:
                    self.lattice = Lattice(
                        self.group.lattice_type,
                        self.volume,
                        PBC=self.PBC,
                        unique_axis=unique_axis,
                        area=self.area,
                    )

            self.generate_crystal()
Exemple #25
0
from pyxtal import pyxtal
from pyxtal.symmetry import Group
import pymatgen.analysis.structure_matcher as sm
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
import numpy as np

for G in range(143, 195):
    g = Group(G)
    letter = str(g[0].multiplicity) + g[0].letter
    C1 = pyxtal()
    C1.from_random(3, G, ['C'], [g[0].multiplicity], sites=[[letter]])
    #print(C1)
    pmg_s1 = C1.to_pymatgen()
    sga1 = SpacegroupAnalyzer(pmg_s1).get_space_group_symbol()

    # each subgroup
    #C2s = C1.subgroup(eps=0, group_type='t')
    try:
        C2s = C1.subgroup(eps=0, group_type='k', max_cell=4)
        for C2 in C2s:
            #print(C2)
            pmg_s2 = C2.to_pymatgen()
            try:
                sga2 = SpacegroupAnalyzer(
                    pmg_s2, symprec=1e-4).get_space_group_symbol()
            except:
                #print("unable to find the space group")
                sga2 = None
            print(G, C2.group.number, g.symbol, C2.group.symbol, sga1, sga2)
            if not sm.StructureMatcher().fit(pmg_s1, pmg_s2):
                print('WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW')
Exemple #26
0
    def init_common(self, species, numIons, factor, group, lattice, sites,
                    conventional, tm):
        """
        Common init functionality for 0D-3D cases of random_crystal.
        """
        self.source = 'Random'
        self.valid = False
        # Check that numIons are integers greater than 0
        for num in numIons:
            if int(num) != num or num < 1:
                printx("Error: composition must be positive integers.",
                       priority=1)
                return False
        if type(group) == Group:
            self.group = group
        else:
            self.group = Group(group, dim=self.dim)
        self.number = self.group.number
        """
        The international group number of the crystal:
        1-230 for 3D space groups
        1-80 for 2D layer groups
        1-75 for 1D Rod groups
        1-32 for crystallographic point groups
        None otherwise
        """

        # The number of attempts to generate the crystal
        # number of atoms
        # volume factor for the unit cell.
        # The number of atom in the PRIMITIVE cell
        # The number of each type of atom in the CONVENTIONAL cell.
        # A list of atomic symbols for the types of atoms
        # A list of warning messages

        self.numattempts = 0
        numIons = np.array(numIons)
        self.factor = factor
        if not conventional:
            mul = cellsize(self.group)
        else:
            mul = 1
        self.numIons = numIons * mul

        formula = ""
        for i, s in zip(self.numIons, species):
            formula += "{:s}{:d}".format(s, int(i))
        self.formula = formula

        self.species = species

        # Use the provided lattice
        if lattice is not None:
            self.lattice = lattice
            self.volume = lattice.volume
            # Make sure the custom lattice PBC axes are correct.
            if lattice.PBC != self.PBC:
                self.lattice.PBC = self.PBC
                printx("\n  Warning: converting custom lattice PBC to " +
                       str(self.PBC))

        # Generate a Lattice instance based on a given volume estimation
        elif lattice is None:

            # Determine the unique axis
            if self.dim == 2:
                if self.number in range(3, 8):
                    unique_axis = "c"
                else:
                    unique_axis = "a"
            elif self.dim == 1:
                if self.number in range(3, 8):
                    unique_axis = "a"
                else:
                    unique_axis = "c"
            else:
                unique_axis = "c"

            self.volume = self.estimate_volume()

            if self.dim == 3 or self.dim == 0:
                self.lattice = Lattice(
                    self.group.lattice_type,
                    self.volume,
                    PBC=self.PBC,
                    unique_axis=unique_axis,
                )
            elif self.dim == 2:
                self.lattice = Lattice(
                    self.group.lattice_type,
                    self.volume,
                    PBC=self.PBC,
                    unique_axis=unique_axis,
                    # NOTE self.thickness is part of 2D class
                    thickness=self.thickness,
                )
            elif self.dim == 1:
                self.lattice = Lattice(
                    self.group.lattice_type,
                    self.volume,
                    PBC=self.PBC,
                    unique_axis=unique_axis,
                    # NOTE self.area is part of 1D class
                    area=self.area,
                )
        # Set the tolerance matrix for checking inter-atomic distances
        if type(tm) == Tol_matrix:
            self.tol_matrix = tm
        else:
            try:
                self.tol_matrix = Tol_matrix(prototype=tm)
            # TODO Remove bare except
            except:
                printx(
                    ("Error: tm must either be a Tol_matrix object or "
                     "a prototype string for initializing one."),
                    priority=1,
                )
                self.valid = False
                return

        self.sites = {}
        for i, specie in enumerate(self.species):
            if sites is not None and sites[i] is not None:
                self.check_consistency(sites[i], self.numIons[i])
                self.sites[specie] = sites[i]
            else:
                self.sites[specie] = None
        # QZ: needs to check if it is compatible

        self.generate_crystal()
Exemple #27
0
def check_struct_group(crystal, group, dim=3, tol=1e-2):
    # Supress pymatgen/numpy complex casting warnings
    from pyxtal.crystal import random_crystal
    from pyxtal.molecular_crystal import molecular_crystal
    from copy import deepcopy
    import warnings

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        """Given a pymatgen structure, group number, and dimension, return
        whether or not the structure matches the group number."""
        if isinstance(crystal, (random_crystal, molecular_crystal)):
            lattice = crystal.struct.lattice.matrix
            if dim != 0:
                old_coords = deepcopy(crystal.struct.frac_coords)
                old_species = deepcopy(crystal.struct.atomic_numbers)
            elif dim == 0:
                old_coords = deepcopy(crystal.cart_coords)
                old_species = deepcopy(crystal.species)
        else:
            lattice = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            old_coords = np.array(crystal)
            old_species = ["C"] * len(old_coords)

        from pyxtal.symmetry import distance
        from pyxtal.symmetry import filtered_coords
        from copy import deepcopy

        PBC = [1, 1, 1]

        # Obtain the generators for the group
        if dim == 3:
            from pyxtal.symmetry import get_wyckoffs

            generators = get_wyckoffs(group)[0]

        elif dim == 2:
            from pyxtal.symmetry import get_layer

            generators = get_layer(group)[0]
            PBC = [1, 1, 0]
        elif dim == 1:
            from pyxtal.symmetry import get_rod

            generators = get_rod(group)[0]
            PBC = [0, 0, 1]
        elif dim == 0:
            from pyxtal.symmetry import Group

            generators = Group(group, dim=0)[0]
            PBC = [0, 0, 0]

        # TODO: Add check for lattice symmetry

        # Apply SymmOps to generate new points
        # old_coords = filtered_coords(struct.frac_coords,PBC=PBC)

        new_coords = []
        new_species = []
        for i, point in enumerate(old_coords):
            for j, op in enumerate(generators):
                if j != 0:
                    new_coords.append(op.operate(point))
                    new_species.append(old_species[i])
        # new_coords = filtered_coords(new_coords,PBC=PBC)

        # Check that all points in new list are still in old
        failed = False
        i_list = list(range(len(new_coords)))
        for i, point1 in enumerate(new_coords):
            found = False
            for j, point2 in enumerate(old_coords):
                if new_species[i] == old_species[j]:
                    difference = filtered_coords(point2 - point1, PBC=PBC)
                    if distance(difference, lattice, PBC=PBC) <= tol:
                        found = True
                        break
            if found is False:
                failed = True
                break

        if failed is False:
            return True
        else:
            return False
Exemple #28
0
def test_modules():
    print("====== Testing functionality for pyXtal version 0.1dev ======")

    global failed_package
    failed_package = False  # Record if errors occur at any level

    reset()

    print("Importing sys...")
    try:
        import sys

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    print("Importing numpy...")
    try:
        import numpy as np

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    I = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])

    print("Importing pymatgen...")
    try:
        import pymatgen

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    try:
        from pymatgen.core.operations import SymmOp
    except Exception as e:
        fail(e)
        sys.exit(0)

    print("Importing pandas...")
    try:
        import pandas

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    print("Importing spglib...")
    try:
        import spglib

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    print("Importing openbabel...")
    try:
        import ase

        print("Success!")
    except:
        print(
            "Error: could not import openbabel. Try reinstalling the package.")

    print("Importing pyxtal...")
    try:
        import pyxtal

        print("Success!")
    except Exception as e:
        fail(e)
        sys.exit(0)

    print("=== Testing modules ===")

    # =====database.element=====
    print("pyxtal.database.element")
    reset()
    try:
        import pyxtal.database.element
    except Exception as e:
        fail(e)

    print("  class Element")
    try:
        from pyxtal.database.element import Element
    except Exception as e:
        fail(e)
    if passed():
        for i in range(1, 95):
            if passed():
                try:
                    ele = Element(i)
                except:
                    fail("Could not access Element # " + str(i))
                try:
                    y = ele.sf
                    y = ele.z
                    y = ele.short_name
                    y = ele.long_name
                    y = ele.valence
                    y = ele.valence_electrons
                    y = ele.covalent_radius
                    y = ele.vdw_radius
                    y = ele.get_all(0)
                except:
                    fail("Could not access attribute for element # " + str(i))
                try:
                    ele.all_z()
                    ele.all_short_names()
                    ele.all_long_names()
                    ele.all_valences()
                    ele.all_valence_electrons()
                    ele.all_covalent_radii()
                    ele.all_vdw_radii()
                except:
                    fail("Could not access class methods")

    check()

    # =====database.hall=====
    print("pyxtal.database.hall")
    reset()
    try:
        import pyxtal.database.hall
    except Exception as e:
        fail(e)

    print("  hall_from_hm")
    try:
        from pyxtal.database.hall import hall_from_hm
    except Exception as e:
        fail(e)

    if passed():
        for i in range(1, 230):
            if passed():
                try:
                    hall_from_hm(i)
                except:
                    fail("Could not access hm # " + str(i))

    check()

    # =====database.collection=====
    print("pyxtal.database.collection")
    reset()
    try:
        import pyxtal.database.collection
    except Exception as e:
        fail(e)

    print("  Collection")
    try:
        from pyxtal.database.collection import Collection
    except Exception as e:
        fail(e)

    if passed():
        for i in range(1, 230):
            if passed():
                try:
                    molecule_collection = Collection("molecules")
                except:
                    fail("Could not access hm # " + str(i))

    check()

    # =====operations=====
    print("pyxtal.operations")
    reset()
    try:
        import pyxtal.operations
    except Exception as e:
        fail(e)

    print("  random_vector")
    try:
        from pyxtal.operations import random_vector
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                random_vector()
        except Exception as e:
            fail(e)

    check()

    print("  angle")
    try:
        from pyxtal.operations import angle
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                v1 = random_vector()
                v2 = random_vector()
                angle(v1, v2)
        except Exception as e:
            fail(e)

    check()

    print("  random_shear_matrix")
    try:
        from pyxtal.operations import random_shear_matrix
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                random_shear_matrix()
        except Exception as e:
            fail(e)

    check()

    print("  is_orthogonal")
    try:
        from pyxtal.operations import is_orthogonal
    except Exception as e:
        fail(e)

    if passed():
        try:
            a = is_orthogonal([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
            b = is_orthogonal([[0, 0, 1], [1, 0, 0], [1, 0, 0]])
            if a is True and b is False:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  aa2matrix")
    try:
        from pyxtal.operations import aa2matrix
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                aa2matrix(1, 1, random=True)
        except Exception as e:
            fail(e)

    check()

    print("  matrix2aa")
    try:
        from pyxtal.operations import matrix2aa
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                m = aa2matrix(1, 1, random=True)
                aa = matrix2aa(m)
        except Exception as e:
            fail(e)

    check()

    print("  rotate_vector")
    try:
        from pyxtal.operations import rotate_vector
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                v1 = random_vector()
                v2 = random_vector()
                rotate_vector(v1, v2)
        except Exception as e:
            fail(e)

    check()

    print("  are_equal")
    try:
        from pyxtal.operations import are_equal
    except Exception as e:
        fail(e)

    if passed():
        try:
            op1 = SymmOp.from_xyz_string("x,y,z")
            op2 = SymmOp.from_xyz_string("x,y,z+1")
            a = are_equal(op1, op2, PBC=[0, 0, 1])
            b = are_equal(op1, op2, PBC=[1, 0, 0])
            if a is True and b is False:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  class OperationAnalyzer")
    try:
        from pyxtal.operations import OperationAnalyzer
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                m = aa2matrix(1, 1, random=True)
                t = random_vector()
                op1 = SymmOp.from_rotation_and_translation(m, t)
                OperationAnalyzer(op1)
        except Exception as e:
            fail(e)

    check()

    print("  class Orientation")
    try:
        from pyxtal.operations import Orientation
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in range(10):
                v1 = random_vector()
                c1 = random_vector()
                o = Orientation.from_constraint(v1, c1)
        except Exception as e:
            fail(e)

    check()

    # =====symmetry=====
    print("pyxtal.symmetry")
    reset()
    try:
        import pyxtal.symmetry
    except Exception as e:
        fail(e)

    print("  get_wyckoffs (may take a moment)")
    try:
        from pyxtal.symmetry import get_wyckoffs
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in [1, 2, 229, 230]:
                get_wyckoffs(i)
                get_wyckoffs(i, organized=True)
        except:
            fail(" Could not access Wyckoff positions for space group # " +
                 str(i))

    check()

    print("  get_wyckoff_symmetry (may take a moment)")
    try:
        from pyxtal.symmetry import get_wyckoff_symmetry
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in [1, 2, 229, 230]:
                get_wyckoff_symmetry(i)
                get_wyckoff_symmetry(i, molecular=True)
        except:
            fail("Could not access Wyckoff symmetry for space group # " +
                 str(i))

    check()

    print("  get_wyckoffs_generators (may take a moment)")
    try:
        from pyxtal.symmetry import get_wyckoff_generators
    except Exception as e:
        fail(e)

    if passed():
        try:
            for i in [1, 2, 229, 230]:
                get_wyckoff_generators(i)
        except:
            fail("Could not access Wyckoff generators for space group # " +
                 str(i))

    check()

    print("  letter_from_index")
    try:
        from pyxtal.symmetry import letter_from_index
    except Exception as e:
        fail(e)

    if passed():
        try:
            if letter_from_index(0, get_wyckoffs(47)) == "A":
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  index_from_letter")
    try:
        from pyxtal.symmetry import index_from_letter
    except Exception as e:
        fail(e)

    if passed():
        try:
            if index_from_letter("A", get_wyckoffs(47)) == 0:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  jk_from_i")
    try:
        from pyxtal.symmetry import jk_from_i
    except Exception as e:
        fail(e)

    if passed():
        try:
            w = get_wyckoffs(2, organized=True)
            j, k = jk_from_i(1, w)
            if j == 1 and k == 0:
                pass
            else:
                print(j, k)
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  i_from_jk")
    try:
        from pyxtal.symmetry import i_from_jk
    except Exception as e:
        fail(e)

    if passed():
        try:
            w = get_wyckoffs(2, organized=True)
            j, k = jk_from_i(1, w)
            i = i_from_jk(j, k, w)
            if i == 1:
                pass
            else:
                print(j, k)
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  ss_string_from_ops")
    try:
        from pyxtal.symmetry import ss_string_from_ops
    except Exception as e:
        fail(e)

    if passed():
        try:
            strings = ["1", "4 . .", "2 3 ."]
            for i, sg in enumerate([1, 75, 195]):
                ops = get_wyckoffs(sg)[0]
                ss_string_from_ops(ops, sg, dim=3)
        except Exception as e:
            fail(e)

    check()

    print("  Wyckoff_position")
    try:
        from pyxtal.symmetry import Wyckoff_position
    except Exception as e:
        fail(e)

    if passed():
        try:
            wp = Wyckoff_position.from_group_and_index(20, 1)
        except Exception as e:
            fail(e)

    check()

    print("  Group")
    try:
        from pyxtal.symmetry import Group
    except Exception as e:
        fail(e)

    if passed():
        try:
            g3 = Group(230)
            g2 = Group(80, dim=2)
            g1 = Group(75, dim=1)
        except Exception as e:
            fail(e)

    check()

    # =====crystal=====
    print("pyxtal.crystal")
    reset()
    try:
        import pyxtal.crystal
    except Exception as e:
        fail(e)

    print("  random_crystal")
    try:
        from pyxtal.crystal import random_crystal
    except Exception as e:
        fail(e)

    if passed():
        try:
            c = random_crystal(1, ["H"], [1], 10.0)
            if c.valid is True:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  random_crystal_2D")
    try:
        from pyxtal.crystal import random_crystal_2D
    except Exception as e:
        fail(e)

    if passed():
        try:
            c = random_crystal_2D(1, ["H"], [1], 10.0)
            if c.valid is True:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    # =====molecule=====
    print("pyxtal.molecule")
    reset()
    try:
        import pyxtal.molecule
    except Exception as e:
        fail(e)

    check()

    print("  Collections")
    try:
        from pyxtal.molecule import mol_from_collection
    except Exception as e:
        fail(e)

    if passed():
        try:
            h2o = mol_from_collection("H2O")
            ch4 = mol_from_collection("CH4")
        except Exception as e:
            fail(e)

    print("  get_inertia_tensor")
    try:
        from pyxtal.molecule import get_inertia_tensor
    except Exception as e:
        fail(e)

    if passed():
        try:
            get_inertia_tensor(h2o)
            get_inertia_tensor(ch4)
        except Exception as e:
            fail(e)

    check()

    print("  get_moment_of_inertia")
    try:
        from pyxtal.molecule import get_moment_of_inertia
    except Exception as e:
        fail(e)

    if passed():
        try:
            v = random_vector()
            get_moment_of_inertia(h2o, v)
            get_moment_of_inertia(ch4, v)
        except Exception as e:
            fail(e)

    check()

    print("  reoriented_molecule")
    try:
        from pyxtal.molecule import reoriented_molecule
    except Exception as e:
        fail(e)

    if passed():
        try:
            reoriented_molecule(h2o)
            reoriented_molecule(ch4)
        except Exception as e:
            fail(e)

    check()

    print("  orientation_in_wyckoff_position")
    try:
        from pyxtal.molecule import orientation_in_wyckoff_position
    except Exception as e:
        fail(e)

    if passed():
        try:
            w = get_wyckoffs(20)
            ws = get_wyckoff_symmetry(20, molecular=True)
            wp = Wyckoff_position.from_group_and_index(20, 1)
            orientation_in_wyckoff_position(h2o, wp)
            orientation_in_wyckoff_position(ch4, wp)
        except Exception as e:
            fail(e)

    check()

    # =====molecular_crystal=====
    print("pyxtal.molecular_crystal")
    reset()
    try:
        import pyxtal.crystal
    except Exception as e:
        fail(e)

    print("  molecular_crystal")
    try:
        from pyxtal.molecular_crystal import molecular_crystal
    except Exception as e:
        fail(e)

    if passed():
        try:
            c = molecular_crystal(1, ["H2O"], [1], 10.0)
            if c.valid is True:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    print("  molecular_crystal_2D")
    try:
        from pyxtal.molecular_crystal import molecular_crystal_2D
    except Exception as e:
        fail(e)

    if passed():
        try:
            c = molecular_crystal_2D(1, ["H2O"], [1], 10.0)
            if c.valid is True:
                pass
            else:
                fail()
        except Exception as e:
            fail(e)

    check()

    end(condition=2)
Exemple #29
0
    # -------------------------------- Options -------------------------
    parser = ArgumentParser()
    parser.add_argument(
        "-s",
        "--symmetry",
        dest="sg",
        type=str,
        help="desired symmetry, number or string, e.g., 36, Pbca, Ih. if None, show all list of available groups",
    )
    parser.add_argument(
        "-d",
        "--dimension",
        dest="dimension",
        default=3,
        type=int,
        help="desired dimension: (3, 2, 1, 0): default 3",
    )

    print_logo()
    options = parser.parse_args()

    dimension = options.dimension

    if options.sg is not None:
        sg = options.sg
        if sg.isnumeric():
            sg = int(sg)
        Group(sg, dimension).print_all()
    else:
        list_groups(dimension)
Exemple #30
0
class pyxtal:
    """
    Class for handling atomic crystals based on symmetry constraints. 

    Examples
    --------
    >>> from pyxtal import pyxtal
    >>> struc = pyxtal()
    >>> struc.from_random(3, 227, ['C'], [8])
    >>> struc.get_site_labels()
    {'C': ['8a']}
    >>> struc
    ------Crystal from random------
    Dimension: 3
    Composition: C8
    Group: Fd-3m (227)
    cubic lattice:   4.3529   4.3529   4.3529  90.0000  90.0000  90.0000
    Wyckoff sites:
	 C @ [0.1250 0.1250 0.1250], WP:  8a, Site symmetry: -4 3 m

    The structure object can be easily manipulated via `apply_perturbtation`
    or `subgroup` function

    >>> struc2 = struc.subgroup(H=141, once=True)
    >>> struc2
    ------Crystal from Wyckoff Split------
    Dimension: 3
    Composition: C8
    Group: I41/amd (141)
    tetragonal lattice:   3.3535   3.3535   4.6461  90.0000  90.0000  90.0000
    Wyckoff sites:
    	 C @ [0.0000 0.2500 0.3750], WP:  4b, Site symmetry: -4 m 2

    Alternatively, one can also easily compute its XRD via the `pyxtal.XRD` class

    >>> xrd = struc.get_XRD()
    >>> xrd
      2theta     d_hkl     hkl       Intensity  Multi
      32.706     2.738   [ 1  1  1]   100.00        8
      54.745     1.677   [ 2  2  0]    40.95       12
      65.249     1.430   [ 3  1  1]    20.65       24
      81.116     1.186   [ 4  0  0]     5.15        6
      90.236     1.088   [ 3  3  1]     8.24       24
     105.566     0.968   [ 4  2  2]    14.44       24
     115.271     0.913   [ 5  1  1]    10.03       24
     133.720     0.838   [ 4  4  0]     9.80       12
     148.177     0.802   [ 5  3  1]    28.27       48

    Finally, the structure can be saved to different formats

    >>> struc.to_file('my.cif')
    >>> struc.to_file('my_poscar', fmt='poscar')

    or to Pymatgen/ASE structure object

    >>> pmg_struc = struc.to_pymatgen()
    >>> ase_struc = struc.to_ase()
    """
    def __init__(self, molecular=False):
        self.valid = False
        self.molecular = molecular
        self.diag = False
        self.numIons = None
        self.numMols = None

    def __str__(self):
        if self.valid:
            s = "------Crystal from {:s}------".format(self.source)
            s += "\nDimension: {}".format(self.dim)
            s += "\nComposition: {}".format(self.formula)
            if self.group.number in [7, 14, 15] and self.diag:
                symbol = self.group.alias
            else:
                symbol = self.group.symbol
            s += "\nGroup: {} ({})".format(symbol, self.group.number)
            s += "\n{}".format(self.lattice)
            s += "\nWyckoff sites:"
            if self.molecular:
                for wyc in self.mol_sites:
                    s += "\n\t{}".format(wyc)
            else:
                for wyc in self.atom_sites:
                    s += "\n\t{}".format(wyc)
        else:
            s = "\nStructure not available."
        return s

    def __repr__(self):
        return str(self)

    def get_dof(self):
        """
        get the number of dof for the given structures:
        """
        if self.molecular:
            sites = self.mol_sites
        else:
            sites = self.atom_sites
        dof = 0
        for site in sites:
            dof += site.dof

        return self.lattice.dof + dof

    def get_site_labels(self):
        """
        quick function to get the site_labels as a dictionary
        """
        if self.molecular:
            sites = self.mol_sites
            names = [site.molecule.name for site in sites]
        else:
            sites = self.atom_sites
            names = [site.specie for site in sites]

        dicts = {}
        for name, site in zip(names, sites):
            label = str(site.wp.multiplicity) + site.wp.letter
            if name not in dicts.keys():
                dicts[name] = [label]
            else:
                dicts[name].append(label)
        return dicts

    def from_random(
        self,
        dim=3,
        group=None,
        species=None,
        numIons=None,
        factor=1.1,
        thickness=None,
        area=None,
        lattice=None,
        sites=None,
        conventional=True,
        diag=False,
        t_factor=1.0,
        max_count=10,
        force_pass=False,
    ):
        if self.molecular:
            prototype = "molecular"
        else:
            prototype = "atomic"
        tm = Tol_matrix(prototype=prototype, factor=t_factor)

        count = 0
        quit = False

        while True:
            count += 1
            if self.molecular:
                if dim == 3:
                    struc = molecular_crystal(group,
                                              species,
                                              numIons,
                                              factor,
                                              lattice=lattice,
                                              sites=sites,
                                              conventional=conventional,
                                              diag=diag,
                                              tm=tm)
                elif dim == 2:
                    struc = molecular_crystal_2D(group,
                                                 species,
                                                 numIons,
                                                 factor,
                                                 thickness=thickness,
                                                 sites=sites,
                                                 conventional=conventional,
                                                 tm=tm)
                elif dim == 1:
                    struc = molecular_crystal_1D(group,
                                                 species,
                                                 numIons,
                                                 factor,
                                                 area=area,
                                                 sites=sites,
                                                 conventional=conventional,
                                                 tm=tm)
            else:
                if dim == 3:
                    struc = random_crystal(group, species, numIons, factor,
                                           lattice, sites, conventional, tm)
                elif dim == 2:
                    struc = random_crystal_2D(group, species, numIons, factor,
                                              thickness, lattice, sites,
                                              conventional, tm)
                elif dim == 1:
                    struc = random_crystal_1D(group, species, numIons, factor,
                                              area, lattice, sites,
                                              conventional, tm)
                else:
                    struc = random_cluster(group, species, numIons, factor,
                                           lattice, sites, tm)
            if force_pass:
                quit = True
                break
            elif struc.valid:
                quit = True
                break

            if count >= max_count:
                raise RuntimeError(
                    "It takes long time to generate the structure, check inputs"
                )

        if quit:
            self.valid = struc.valid
            self.dim = dim
            try:
                self.lattice = struc.lattice
                if self.molecular:
                    self.numMols = struc.numMols
                    self.molecules = struc.molecules
                    self.mol_sites = struc.mol_sites
                    self.diag = struc.diag
                else:
                    self.numIons = struc.numIons
                    self.species = struc.species
                    self.atom_sites = struc.atom_sites
                self.group = struc.group
                self.PBC = struc.PBC
                self.source = 'random'
                self.factor = struc.factor
                self.number = struc.number
                self._get_formula()
            except:
                pass

    def from_seed(self, seed, molecule=None, relax_h=False):
        """
        Load the seed structure from Pymatgen/ASE/POSCAR/CIFs
        Internally they will be handled by Pymatgen
        """
        from ase import Atoms
        from pymatgen import Structure

        if self.molecular:
            from pyxtal.molecule import pyxtal_molecule
            pmol = pyxtal_molecule(molecule).mol
            struc = structure_from_ext(seed, pmol, relax_h=relax_h)
            if struc.match():
                self.mol_sites = [struc.make_mol_site()]
                self.group = Group(struc.wyc.number)
                self.lattice = struc.lattice
                self.molecules = [
                    pyxtal_molecule(struc.molecule, symmetrize=False)
                ]
                self.numMols = struc.numMols
                self.diag = struc.diag
                self.valid = True  # Need to add a check function
            else:
                raise ValueError(
                    "Cannot extract the molecular crystal from cif")
        else:
            if isinstance(seed, dict):
                self.from_dict()
            elif isinstance(seed, Atoms):  #ASE atoms
                from pymatgen.io.ase import AseAtomsAdaptor
                pmg_struc = AseAtomsAdaptor.get_structure(seed)
                self._from_pymatgen(pmg_struc)
            elif isinstance(seed, Structure):  #Pymatgen
                self._from_pymatgen(seed)
            elif isinstance(seed, str):
                pmg_struc = Structure.from_file(seed)
                self._from_pymatgen(pmg_struc)

        self.factor = 1.0
        self.number = self.group.number
        self.source = 'Seed'
        self.dim = 3
        self.PBC = [1, 1, 1]
        self._get_formula()

    def _from_pymatgen(self, structure):
        """
        Load structure from Pymatgen
        should not be used directly
        """
        from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as sga

        self.valid = True
        try:
            # needs to do it twice in order to get the conventional cell
            s = sga(structure)
            structure = s.get_refined_structure()
            s = sga(structure)
            sym_struc = s.get_symmetrized_structure()
            number = s.get_space_group_number()
        except:
            print("Failed to load the Pymatgen structure")
            self.valid = False

        if self.valid:
            d = sym_struc.composition.as_dict()
            species = [key for key in d.keys()]
            numIons = []
            for ele in species:
                numIons.append(int(d[ele]))
            self.numIons = numIons
            self.species = species
            self.group = Group(number)
            atom_sites = []
            for i, site in enumerate(sym_struc.equivalent_sites):
                pos = site[0].frac_coords
                wp = Wyckoff_position.from_group_and_index(
                    number, sym_struc.wyckoff_symbols[i])
                specie = site[0].specie.number
                atom_sites.append(atom_site(wp, pos, specie))
            self.atom_sites = atom_sites
            matrix, ltype = sym_struc.lattice.matrix, self.group.lattice_type
            self.lattice = Lattice.from_matrix(matrix, ltype=ltype)

    def check_short_distances(self, r=0.7, exclude_H=True):
        """
        A function to check short distance pairs
        Mainly used for debug, powered by pymatgen

        Args:
            r: the given cutoff distances
            exclude_H: whether or not exclude the H atoms

        Returns:
            list of pairs within the cutoff
        """
        if self.dim > 0:
            pairs = []
            pmg_struc = self.to_pymatgen()
            if exclude_H:
                pmg_struc.remove_species('H')
            res = pmg_struc.get_all_neighbors(r)
            for i, neighs in enumerate(res):
                for n in neighs:
                    pairs.append(
                        [pmg_struc.sites[i].specie, n.specie, n.nn_distance])
        else:
            raise NotImplementedError("Does not support cluster for now")
        return pairs

    def to_file(self, filename=None, fmt=None, permission='w', sym_num=None):
        """
        Creates a file with the given filename and file type to store the structure.
        By default, creates cif files for crystals and xyz files for clusters.
        For other formats, Pymatgen is used

        Args:
            filename: the file path
            fmt: the file type (`cif`, `xyz`, etc.)
            permission: `w` or `a+`

        Returns:
            Nothing. Creates a file at the specified path
        """
        if self.valid:
            if fmt is None:
                if self.dim == 0:
                    fmt = 'xyz'
                else:
                    fmt = 'cif'

            if fmt == "cif":
                if self.dim == 3:
                    return write_cif(self,
                                     filename,
                                     "from_pyxtal",
                                     permission,
                                     sym_num=sym_num)
                else:
                    pmg_struc = self.to_pymatgen()
                    if self.molecular:
                        pmg_struc.sort()
                return pmg_struc.to(fmt=fmt, filename=filename)
            else:
                pmg_struc = self.to_pymatgen()
                if self.molecular:
                    pmg_struc.sort()
                return pmg_struc.to(fmt=fmt, filename=filename)
        else:
            raise RuntimeError(
                "Cannot create file: structure did not generate")

    def subgroup(self,
                 H=None,
                 eps=0.05,
                 idx=None,
                 once=False,
                 group_type='t',
                 max_index=4):
        """
        generate a structure with lower symmetry

        Args:
            H: space group number (int)
            eps: pertubation term (float)
            idx: list
            once: generate only one structure, otherwise output all

        Returns:
            a list of pyxtal structures with lower symmetries
        """

        #randomly choose a subgroup from the available list
        if group_type == 't':
            dicts = self.group.get_max_t_subgroup()  #['subgroup']
        else:
            dicts = self.group.get_max_k_subgroup()  #['subgroup']
        Hs = dicts['subgroup']
        indices = dicts['index']
        if idx is None:
            idx = [i for i, id in enumerate(indices) if id <= max_index]
            #idx = range(len(Hs))
        else:
            for id in idx:
                if id >= len(Hs):
                    raise ValueError(
                        "The idx exceeds the number of possible splits")
        if H is not None:
            idx = [id for id in idx if Hs[id] == H]

        if len(idx) == 0:
            raise RuntimeError("No subgroup to perform the split")
        if self.molecular:
            struc_sites = self.mol_sites
        else:
            struc_sites = self.atom_sites

        sites = [
            str(site.wp.multiplicity) + site.wp.letter for site in struc_sites
        ]
        valid_splitters = []
        bad_splitters = []
        for id in idx:
            splitter = wyckoff_split(G=self.group.number,
                                     wp1=sites,
                                     idx=id,
                                     group_type=group_type)
            if splitter.valid_split:
                valid_splitters.append(splitter)
            else:
                bad_splitters.append(splitter)

        if len(valid_splitters) == 0:
            # do one more step
            new_strucs = []
            for splitter in bad_splitters:
                trail_struc = self.subgroup_by_splitter(splitter)
                new_strucs.append(
                    trail_struc.subgroup(once=True, group_type=group_type))
            return new_strucs
        else:
            if once:
                return self.subgroup_by_splitter(choice(valid_splitters),
                                                 eps=eps)
            else:
                new_strucs = []
                for splitter in valid_splitters:
                    new_strucs.append(
                        self.subgroup_by_splitter(splitter, eps=eps))
            return new_strucs

    def subgroup_by_splitter(self, splitter, eps=0.05):
        """
        transform the crystal to subgroup symmetry from a splitter object
        """
        lat1 = np.dot(splitter.R[:3, :3].T, self.lattice.matrix)
        multiples = np.linalg.det(splitter.R[:3, :3])
        new_struc = deepcopy(self)
        new_struc.group = splitter.H
        lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type)
        lattice = lattice.mutate(degree=eps, frozen=True)

        h = splitter.H.number
        split_sites = []
        if self.molecular:
            # below only works when the cell does not change
            for i, site in enumerate(self.mol_sites):
                pos = site.position
                mol = site.molecule
                ori = site.orientation
                coord0 = mol.mol.cart_coords.dot(ori.matrix.T)
                wp1 = site.wp
                ori.reset_matrix(np.eye(3))
                for ops1, ops2 in zip(splitter.G2_orbits[i],
                                      splitter.H_orbits[i]):
                    #reset molecule
                    coord1 = np.dot(coord0, ops1[0].affine_matrix[:3, :3].T)
                    _mol = mol.copy()
                    _mol.reset_positions(coord1)

                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    pos0 += eps * (np.random.sample(3) - 0.5)

                    wp, _ = Wyckoff_position.from_symops(ops2,
                                                         h,
                                                         permutation=False)
                    split_sites.append(mol_site(_mol, pos0, ori, wp, lattice))
            new_struc.mol_sites = split_sites
            new_struc.numMols = [
                int(multiples * numMol) for numMol in self.numMols
            ]

        else:
            for i, site in enumerate(self.atom_sites):
                pos = site.position
                for ops1, ops2 in zip(splitter.G2_orbits[i],
                                      splitter.H_orbits[i]):
                    pos0 = apply_ops(pos, ops1)[0]
                    pos0 -= np.floor(pos0)
                    pos0 += eps * (np.random.sample(3) - 0.5)
                    wp, _ = Wyckoff_position.from_symops(ops2,
                                                         h,
                                                         permutation=False)
                    split_sites.append(atom_site(wp, pos0, site.specie))

            new_struc.atom_sites = split_sites
            new_struc.numIons = [
                int(multiples * numIon) for numIon in self.numIons
            ]
        new_struc.lattice = lattice
        new_struc.source = 'Wyckoff Split'

        return new_struc

    def apply_perturbation(self, d_lat=0.05, d_coor=0.05, d_rot=1):
        """
        perturb the structure without breaking the symmetry

        Args:
            d_coor: magnitude of perturbation on atomic coordinates (in A)
            d_lat: magnitude of perturbation on lattice (in percentage)
        """

        self.lattice = self.lattice.mutate(degree=d_lat)

        if self.molecular:
            for i, site in enumerate(self.mol_sites):
                site.perturbate(lattice=self.lattice.matrix,
                                trans=d_coor,
                                rot=d_rot)
        else:
            for i, site in enumerate(self.atom_sites):
                site.perturbate(lattice=self.lattice.matrix, magnitude=d_coor)

        self.source = 'Perturbation'

    def copy(self):
        """
        simply copy the structure
        """
        return deepcopy(self)

    def _get_coords_and_species(self, absolute=False, unitcell=True):
        """
        extract the coordinates and species information 

        Args:
            abosulte: if True, return the cartesian coords otherwise fractional

        Returns:
            total_coords (N*3 numpy array) and the list of species
        """
        species = []
        total_coords = None
        if self.molecular:
            for site in self.mol_sites:
                coords, site_species = site.get_coords_and_species(
                    absolute, unitcell=unitcell)
                species.extend(site_species)
                if total_coords is None:
                    total_coords = coords
                else:
                    total_coords = np.append(total_coords, coords, axis=0)
        else:
            for site in self.atom_sites:
                species.extend([site.specie] * site.multiplicity)
                if total_coords is None:
                    total_coords = site.coords
                else:
                    total_coords = np.append(total_coords, site.coords, axis=0)

            if absolute:
                total_coords = total_coords.dot(self.lattice.matrix)

        return total_coords, species

    def _get_formula(self):
        """
        A quick function to get the formula.
        """

        formula = ""
        if self.molecular:
            numspecies = self.numMols
            species = [str(mol) for mol in self.molecules]
        else:
            numspecies = self.numIons
            species = self.species
        for i, s in zip(numspecies, species):
            formula += "{:s}{:d}".format(s, int(i))
        self.formula = formula

    def to_ase(self, resort=True):
        """
        export to ase Atoms object.
        """
        from ase import Atoms
        if self.valid:
            if self.dim > 0:
                lattice = self.lattice.copy()
                if self.molecular:
                    coords, species = self._get_coords_and_species(True)
                    latt, coords = lattice.add_vacuum(coords,
                                                      frac=False,
                                                      PBC=self.PBC)
                    atoms = Atoms(species,
                                  positions=coords,
                                  cell=latt,
                                  pbc=self.PBC)
                    if resort:
                        permutation = np.argsort(atoms.numbers)
                        atoms = atoms[permutation]
                    return atoms
                else:
                    coords, species = self._get_coords_and_species()
                    latt, coords = lattice.add_vacuum(coords, PBC=self.PBC)
                    return Atoms(species,
                                 scaled_positions=coords,
                                 cell=latt,
                                 pbc=self.PBC)
            else:
                coords, species = self._get_coords_and_species(True)
                return Atoms(species, positions=coords)
        else:
            raise RuntimeError("No valid structure can be converted to ase.")

    def to_pymatgen(self):
        """
        export to Pymatgen structure object.
        """
        from pymatgen.core.structure import Structure, Molecule

        if self.valid:
            if self.dim > 0:
                lattice = self.lattice.copy()
                coords, species = self._get_coords_and_species()
                # Add space above and below a 2D or 1D crystals
                latt, coords = lattice.add_vacuum(coords, PBC=self.PBC)
                return Structure(latt, species, coords)
            else:
                # Clusters are handled as large molecules
                coords, species = self._get_coords_and_species(True)
                return Molecule(species, coords)
        else:
            raise RuntimeError(
                "No valid structure can be converted to pymatgen.")

    def get_XRD(self, **kwargs):
        """
        compute the PXRD object.

        ** kwargs include
            - wavelength (1.54184)
            - thetas [0, 180]
            - preferred_orientation: False
            - march_parameter: None
        """

        return XRD(self.to_ase(), **kwargs)

    def show(self, **kwargs):
        """
        display the crystal structure
        """
        if self.molecular:
            from pyxtal.viz import display_molecular
            return display_molecular(self, **kwargs)
        else:
            from pyxtal.viz import display_atomic
            return display_atomic(self, **kwargs)

    def optimize_lattice(self):
        """
        optimize the lattice if the cell has a bad inclination angles
        """
        if self.molecular:
            for i in range(5):
                lattice, trans, opt = self.lattice.optimize()
                if opt:
                    for site in self.mol_sites:
                        pos_abs = np.dot(site.position, self.lattice.matrix)
                        pos_frac = pos_abs.dot(lattice.inv_matrix)
                        site.position = pos_frac - np.floor(pos_frac)
                        site.lattice = lattice
                        # for P21/c, Pc, C2/c, check if opt the inclination angle
                        if self.group.number in [7, 14, 15]:
                            for j, op in enumerate(site.wp.ops):
                                vec = op.translation_vector.dot(trans)
                                vec -= np.floor(vec)
                                op1 = op.from_rotation_and_translation(
                                    op.rotation_matrix, vec)
                                site.wp.ops[j] = op1
                    _, perm = Wyckoff_position.from_symops(
                        site.wp.ops, self.group.number)
                    if not isinstance(perm, list):
                        self.diag = True
                    else:
                        self.diag = False
                    self.lattice = lattice
                else:
                    break

#    def save(self, filename=None):
#        """
#        Save the structure
#        Args:
#            filename: the file to save txt information
#        """
#        dict0 = self.save_dict(db_filename)
#        with open(filename, "w") as fp:
#            json.dump(dict0, fp)
#
#        print("save the GP model to", filename, "and database to", db_filename)
#
#    def load(self, filename=None):
#        """
#        load the structure
#        Args:
#            filename: the file to save txt information
#        """
#        #print(filename)
#        with open(filename, "r") as fp:
#            dict0 = json.load(fp)
#        self.load_from_dict(dict0, N_max=N_max, device=device)
#        self.fit(opt=opt)
#        print("load the GP model from ", filename)
#

    def save_dict(self):
        """
        save the model as a dictionary
        """
        sites = []
        if self.molecular:
            for site in self.mol_sites:
                sites.append(site.save_dict())
        else:
            for site in self.atom_sites:
                sites.append(site.save_dict())

        dict0 = {
            "lattice": self.lattice.matrix,
            "sites": sites,
            "group": self.group.number,
            "molecular": self.molecular,
            "numIons": self.numIons,
            "numMols": self.numMols,
            "factor": self.factor,
            "PBC": self.PBC,
            "formula": self.formula,
            "source": self.source,
            "dim": self.dim,
            "valid": self.valid,
        }

        return dict0

    def load_dict(self, dict0):
        """
        load the structure from a dictionary
        """
        self.group = Group(dict0["group"])
        self.lattice = Lattice.from_matrix(dict0["lattice"],
                                           ltype=self.group.lattice_type)
        self.molecular = dict0["molecular"]
        self.number = self.group.number
        self.factor = dict0["factor"]
        self.source = dict0["source"]
        self.dim = dict0["dim"]
        self.PBC = dict0["PBC"]
        self.numIons = dict0["numIons"]
        self.numMols = dict0["numMols"]
        self.valid = dict0["valid"]
        self.formula = dict0["formula"]
        sites = []
        if dict0["molecular"]:
            for site in dict0["sites"]:
                sites.append(mol_site.load_dict(site))
            self.mol_sites = sites
        else:
            for site in dict0["sites"]:
                sites.append(atom_site.load_dict(site))
            self.atom_sites = sites