Esempio n. 1
0
    def test_spherical(self):
        a = PointGroupAnalyzer(CH4)
        self.assertEqual(a.sch_symbol, "Td")
        self.assertEqual(len(a.get_pointgroup()), 24)
        a = PointGroupAnalyzer(PF6)
        self.assertEqual(a.sch_symbol, "Oh")
        self.assertEqual(len(a.get_pointgroup()), 48)
        m = Molecule.from_file(os.path.join(test_dir_mol, "c60.xyz"))
        a = PointGroupAnalyzer(m)
        self.assertEqual(a.sch_symbol, "Ih")

        cube_species = ["C", "C", "C", "C", "C", "C", "C", "C"]
        cube_coords = [
            [0, 0, 0],
            [1, 0, 0],
            [0, 1, 0],
            [1, 1, 0],
            [0, 0, 1],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 1],
        ]

        m = Molecule(cube_species, cube_coords)
        a = PointGroupAnalyzer(m, 0.1)
        self.assertEqual(a.sch_symbol, "Oh")
Esempio n. 2
0
 def test_linear(self):
     coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.08],
               [0, 0.000000, -1.08]]
     mol = Molecule(["C", "H", "H"], coords)
     a = PointGroupAnalyzer(mol)
     self.assertEqual(a.sch_symbol, "D*h")
     mol = Molecule(["C", "H", "N"], coords)
     a = PointGroupAnalyzer(mol)
     self.assertEqual(a.sch_symbol, "C*v")
    def symmetry_information(self):
        """
        Returns symmetry information of the molecule as for example its point group

        :return info: a dictionary with symmetry informations
        """
        mol = mg.Molecule(self.atoms[:, 3], self.atoms[:, :3])
        analyzer = PointGroupAnalyzer(mol)
        info = {'point group': analyzer.get_pointgroup()}
        return info
Esempio n. 4
0
    def test_asym_top(self):
        coords = [
            [0.000000, 0.000000, 0.000000],
            [0.000000, 0.000000, 1.08],
            [1.026719, 0.000000, -0.363000],
            [-0.513360, -0.889165, -0.363000],
            [-0.513360, 0.889165, -0.363000],
        ]
        mol = Molecule(["C", "H", "F", "Br", "Cl"], coords)
        a = PointGroupAnalyzer(mol)

        self.assertEqual(a.sch_symbol, "C1")
        self.assertEqual(len(a.get_pointgroup()), 1)
        coords = [
            [0.000000, 0.000000, 1.08],
            [1.026719, 0.000000, -0.363000],
            [-0.513360, -0.889165, -0.363000],
            [-0.513360, 0.889165, -0.363000],
        ]
        cs_mol = Molecule(["H", "F", "Cl", "Cl"], coords)
        a = PointGroupAnalyzer(cs_mol)
        self.assertEqual(a.sch_symbol, "Cs")
        self.assertEqual(len(a.get_pointgroup()), 2)
        a = PointGroupAnalyzer(C2H2F2Br2)
        self.assertEqual(a.sch_symbol, "Ci")
        self.assertEqual(len(a.get_pointgroup()), 2)
Esempio n. 5
0
    def __init__(self, mol=None, symmetrize=True, tm=Tol_matrix(prototype="molecular")):
        mo = None
        self.smile = None
        self.torsionlist = None
        if type(mol) == str:
            # Parse molecules: either file or molecule name
            tmp = mol.split(".")
            self.name = tmp[0]
            if len(tmp) > 1:
                # Load the molecule from the given file
                if tmp[-1] in ["xyz", "gjf", "g03", "json"]:
                    if os.path.exists(mol):
                        mo = Molecule.from_file(mol)
                    else:
                        raise NameError("{:s} is not a valid path".format(mol))
                elif tmp[-1] == 'smi':
                    self.smile = tmp[0]
                    symbols, xyz, self.torsionlist = self.rdkit_mol_init(tmp[0])
                    mo = Molecule(symbols, xyz)
                    symmetrize = False
                else:
                    raise NameError("{:s} is not a supported format".format(tmp[-1]))
            else:
                # print('\nLoad the molecule {:s} from collections'.format(mol))
                mo = molecule_collection[mol]
        elif hasattr(mol, "sites"):  # pymatgen molecule
            self.name = str(mol.formula)
            mo = mol

        if mo is None:
            msg = "Could not create molecules from given input: {:s}".format(mol)
            raise NameError(msg)

        self.props = mo.site_properties

        if len(mo) > 1:
            if symmetrize:
                pga = PointGroupAnalyzer(mo)
                mo = pga.symmetrize_molecule()["sym_mol"]
            mo = self.add_site_props(mo)

        self.mol = mo
        self.tm = tm
        self.get_box()
        self.volume = self.box.volume
        self.get_radius()
        self.get_symbols()
        self.get_tols_matrix()
        xyz = self.mol.cart_coords
        self.reset_positions(xyz-self.get_center(xyz))
Esempio n. 6
0
 def test_symmetrize_molecule2(self):
     np.random.seed(77)
     distortion = np.random.randn(len(C2H2F2Br2), 3) / 20
     dist_mol = Molecule(C2H2F2Br2.species, C2H2F2Br2.cart_coords + distortion)
     PA1 = PointGroupAnalyzer(C2H2F2Br2, tolerance=0.1)
     self.assertTrue(PA1.get_pointgroup().sch_symbol == "Ci")
     PA2 = PointGroupAnalyzer(dist_mol, tolerance=0.1)
     self.assertTrue(PA2.get_pointgroup().sch_symbol == "C1")
     eq = iterative_symmetrize(dist_mol, tolerance=0.3)
     PA3 = PointGroupAnalyzer(eq["sym_mol"], tolerance=0.1)
     self.assertTrue(PA3.get_pointgroup().sch_symbol == "Ci")
Esempio n. 7
0
def xyz_to_sbu(path: str) -> Dict[str, Fragment]:
    """
    [summary]

    Parameters
    ----------
    path : str
        [description]

    Returns
    -------
    Fragment
        [description]
    """
    xyz = XYZ.from_file(path)
    names = get_xyz_names(path)
    sbu = {}
    for molecule, name in zip(xyz.all_molecules, names):
        dummies_idx = molecule.indices_from_symbol("X")
        symmetric_mol = Molecule([
            "H",
        ] * len(dummies_idx), [molecule[idx].coords for idx in dummies_idx],
                                 charge=len(dummies_idx))
        symmetry = PointGroupAnalyzer(symmetric_mol, tolerance=0.1)
        sbu[name] = Fragment(atoms=molecule, symmetry=symmetry, name=name)
    return sbu
Esempio n. 8
0
 def test_spherical(self):
     a = PointGroupAnalyzer(CH4)
     self.assertEqual(a.sch_symbol, "Td")
     self.assertEqual(len(a.get_pointgroup()), 24)
     a = PointGroupAnalyzer(PF6)
     self.assertEqual(a.sch_symbol, "Oh")
     self.assertEqual(len(a.get_pointgroup()), 48)
     m = Molecule.from_file(os.path.join(test_dir_mol, "c60.xyz"))
     a = PointGroupAnalyzer(m)
     self.assertEqual(a.sch_symbol, "Ih")
Esempio n. 9
0
 def test_dihedral(self):
     a = PointGroupAnalyzer(C2H4)
     self.assertEqual(a.sch_symbol, "D2h")
     self.assertEqual(len(a.get_pointgroup()), 8)
     a = PointGroupAnalyzer(BF3)
     self.assertEqual(a.sch_symbol, "D3h")
     self.assertEqual(len(a.get_pointgroup()), 12)
     m = Molecule.from_file(os.path.join(test_dir_mol, "b12h12.xyz"))
     a = PointGroupAnalyzer(m)
     self.assertEqual(a.sch_symbol, "Ih")
Esempio n. 10
0
 def test_get_symmetry_operations(self):
     coordinates = np.array([[0.5, 0.0, 0.0], [0.0, 0.5, 0.0],
                             [0.5, 1.0, 0.0], [1.0, 0.5, 0.0]])
     species = ['H'] * len(coordinates)
     molecule = Molecule(species, coordinates)
     so = PointGroupAnalyzer(molecule, 0.3).get_symmetry_operations()
     self.assertEqual(len(so), 16)  # D4h contains 16 symmetry elements
     for o in so:
         self.assertEqual(isinstance(o, SymmOp), True)
Esempio n. 11
0
def polyhedral_largest(frac_cord, struct):
    cart = struct.lattice.get_cartesian_coords(frac_cord)
    neigs_mess = struct.get_neighbors_in_shell(cart,0,10,include_index=True)
    neigs = sorted(neigs_mess,key=lambda i:(i[1],i[0].specie.symbol))
    k=0
    OK = False
    while not OK:
        sites = neigs[0:3+k]
        k+=1
        inds = [i[2] for i in sites]
        dis = [round(i[1],1) for i in sites]
        species = [i[0].specie.symbol for i in sites]
        dis_ = [dis[0]]
        for i in dis[1:]:
            equ = False
            for j in dis_:
                if abs(i-j)<=0.2:
                    dis_.append(j)
                    equ = True
                    break
            if not equ:
                dis_.append(i)
        pts = [i[0].coords for i in sites]
        try:
            poly = ConvexHull(pts)
        except QhullError:
            d3 = False
            continue
        else:
            if len(sites) == len(poly.vertices):
                poly_out = poly
                inds_out = inds
                dis_out = dis_
                species_out = species
                d3 = True
            elif len(sites) != len(poly.vertices) and d3:
                OK = True
                if d3:
                    poly={}
                    molecule = Molecule(species_out,poly_out.points)
                    pga = PointGroupAnalyzer(molecule)
                    nsymmop = len(pga.symmops)
                    #ind_dis = zip(inds_after_sym(inds_out,struct),dis_out)
                    #ind_dis = sorted(ind_dis, key=lambda i:(i[1],i[0]))
                    poly['frac_coord']=frac_cord
                    poly['poly']=poly_out
                    poly['species'] = species_out
                    poly['site_ind']=  inds_after_sym(inds_out,struct)
                    poly['dist'] = dis_out
                    poly['nsymmop'] = nsymmop
                    poly['struct'] = struct
                    return poly
                else:
                    return 
Esempio n. 12
0
    def get_point_symmetry(self):
        """
        Returns the point group of the molecule using pymatgen

        :return: point symmetry label
        """
        from pymatgen.core import Molecule
        from pymatgen.symmetry.analyzer import PointGroupAnalyzer

        pymatgen_mol = Molecule(self.get_symbols(), self.get_coordinates())
        symm_group = PointGroupAnalyzer(pymatgen_mol, tolerance=0.1)

        return symm_group.sch_symbol
Esempio n. 13
0
def get_symmetry(mol, already_oriented=False):
    '''
    Return a list of SymmOps for a molecule's point symmetry
    already_oriented: whether or not the principle axes of mol are already reoriented 
    '''
    pga = PointGroupAnalyzer(mol)
    #Handle linear molecules
    if '*' in pga.sch_symbol:
        if already_oriented == False:
            #Reorient the molecule
            oriented_mol, P = reoriented_molecule(mol)
            pga = PointGroupAnalyzer(oriented_mol)
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        #Add 12-fold  and reflections in place of ininitesimal rotation
        for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]:
            op = SymmOp.from_rotation_and_translation(aa2matrix(axis, pi / 6),
                                                      [0, 0, 0])
            if pga.is_valid_op(op):
                symm_m.append(op)
                #Any molecule with infinitesimal symmetry is linear;
                #Thus, it possess mirror symmetry for any axis perpendicular
                #To the rotational axis. pymatgen does not add this symmetry
                #for all linear molecules - for example, hydrogen
                if axis == [1, 0, 0]:
                    symm_m.append(SymmOp.from_xyz_string('x,-y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,y,-z'))
                elif axis == [0, 1, 0]:
                    symm_m.append(SymmOp.from_xyz_string('-x,y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,y,-z'))
                elif axis == [0, 0, 1]:
                    symm_m.append(SymmOp.from_xyz_string('-x,y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,-y,z'))
                #Generate a full list of SymmOps for the molecule's pointgroup
                symm_m = generate_full_symmops(symm_m, 1e-3)
                break
        #Reorient the SymmOps into mol's original frame
        if not already_oriented:
            new = []
            for op in symm_m:
                new.append(P.inverse * op * P)
            return new
        elif already_oriented:
            return symm_m
    #Handle nonlinear molecules
    else:
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        return symm_m
Esempio n. 14
0
def gen_cluster(system, numIons, sg=None, dimension=0, factor=1.0):
    #space group
    if sg:
        pass
    else:
        sg = random.choice(range(1, 57))
    symbol, sg = get_symbol_and_number(sg, dimension)

    numIons0 = np.array(numIons)
    cluster = random_cluster(sg, system, numIons0, factor)
    if cluster.valid:
        comp = str(cluster.struct.composition)
        comp = comp.replace(" ", "")
        #outpath ='./' + comp + '.xyz'
        #print('out file name %s'%outpath)
        #cluster.molecule.to('xyz,', outpath)
        #cluster.to_file(filename = outpath, fmt='xyz')
        #mol=Molecule.from_file(outpath)
        mol = cluster.molecule

        xmax = mol.cart_coords[:, 0].max()
        xmin = mol.cart_coords[:, 0].min()
        ymax = mol.cart_coords[:, 1].max()
        ymin = mol.cart_coords[:, 1].min()
        zmax = mol.cart_coords[:, 2].max()
        zmin = mol.cart_coords[:, 2].min()
        lx = xmax - xmin
        ly = ymax - ymin
        lz = zmax - zmin
        _a, _b, _c = sorted([lx, ly, lz])
        if _b / _a > 2 or _c / _b > 2 or _c / _a > 2:
            print("too long")
            return None

        ans = PointGroupAnalyzer(mol).sch_symbol
        print('Symmetry requested: {:d}({:s}), generated: {:s}'.format(
            sg, symbol, ans))
        #a=b=c=np.max(mol.distance_matrix)+10
        a = lx + 10
        b = ly + 10
        c = lz + 10
        st = mol.get_boxed_structure(a, b, c)
        #st.to('POSCAR','rand_'+comp+'.vasp')
        #print(st)
        print('valid')
        return st
    else:
        print('cannot generate corresponding structure, retry it!')
        return None
Esempio n. 15
0
def mol_get_rot_list0(mol, tol=1.e-5, log=sys.stdout):
    from pymatgen.symmetry.analyzer import PointGroupAnalyzer,\
            generate_full_symmops
    from scipy.linalg import det

    analyzer = PointGroupAnalyzer(mol)
    print(" sch_symbol = {}".format(analyzer.sch_symbol), file=log)

    # Pick rotations only.
    symmops = [
        o for o in analyzer.symmops if abs(det(o.rotation_matrix) - 1) < 1.e-5
    ]
    symmops = generate_full_symmops(symmops, tol, max_recursion_depth=50)
    rot_list = [o.rotation_matrix for o in symmops]
    return rot_list
Esempio n. 16
0
def structure_symmetry():
    structs, fnames = read_structures()
    multi_structs(structs, fnames)
    for struct, fname in zip(structs, fnames):
        if isinstance(struct, Structure):
            sa = SpacegroupAnalyzer(struct)
            print("{:<20} : {}".format('File Name', fname))
            print("{:<20} : {:<15}".format('Structure Type', 'periodicity'))
            print("{:<20} : {:<15}".format('Lattice Type',
                                           sa.get_lattice_type()))
            print("{:<20} : {:<15d}".format('Space Group ID',
                                            sa.get_space_group_number()))
            print("{:<20} : {:<15}".format('International Symbol',
                                           sa.get_space_group_symbol()))
            print("{:<20} : {:15}".format('Hall Symbol', sa.get_hall()))
            sepline()
        if isinstance(struct, Molecule):
            print("{:<20} : {}".format('File Name', fname))
            pga = PointGroupAnalyzer(struct)
            print("{:<20} : {:<15}".format('Structure Type',
                                           'non-periodicity'))
            print("{:<20} : {:<15}".format('International Symbol',
                                           str(pga.get_pointgroup())))
    return True
Esempio n. 17
0
def analyze(topology: Structure, skin: float = 5e-3) -> List[Molecule]:
    # containers for the output
    fragments = []
    # we iterate over non-dummies
    dmat = topology.distance_matrix
    dummies = numpy.array(topology.indices_from_symbol("X"))
    not_dummies = numpy.array(
        [i for i in range(len(topology)) if i not in dummies])
    # initialize and set tags
    tags = numpy.zeros(len(topology), dtype=int)
    tags[dummies] = dummies + 1
    topology.add_site_property(property_name="tags", values=tags)
    # TODO : site properties
    # get the distances between centers and connections
    distances = dmat[not_dummies][:, dummies]
    coordinations = numpy.array(topology.atomic_numbers)[not_dummies]
    partitions = numpy.argsort(distances, axis=1)
    for center_idx, best_dummies in enumerate(partitions):
        coordination = coordinations[center_idx]
        if coordination < len(best_dummies):
            best_dummies = best_dummies[:coordination]
        cutoff = distances[center_idx][best_dummies].max() + skin
        # now extract the corresponding fragment
        fragment_center = topology.sites[not_dummies[center_idx]]
        fragment_sites = topology.get_neighbors(fragment_center, r=cutoff)
        # some topologies in the RCSR have a crazy size
        # we ignore them with the heat of a thousand suns
        assert len(
            fragment_sites) <= 12, "Fragment size larger than limit of 12."
        # store as molecule to use the point group analysis
        fragment = Molecule.from_sites(
            fragment_sites)  #, charge=1, spin_multiplicity=1)
        # this is needed because X have no mass, leading to a
        # symmetrization error.
        fragment.replace_species({"X": "He"})
        pg = PointGroupAnalyzer(fragment, tolerance=0.1)
        # from pymatgen.io.ase import AseAtomsAdaptor
        # view(AseAtomsAdaptor.get_atoms(fragment))
        if pg.sch_symbol == "C1":
            raise ("NoSymm")
        fragment.replace_species({"He": "X"})
        fragments.append(Fragment(atoms=fragment, symmetry=pg, name="slot"))
    return fragments
Esempio n. 18
0
def get_random_structure(elem, num_atom, sg, dim, thickness=0):
    max_try = 100
    factor = 1.0
    i = 0
    while i < max_try:
        random.seed(time.time() * 1e8)
        if sg == 0:
            sg = get_random_spg(dim)
        symbol, sg = get_symbol_and_number(sg, dim)
        if dim == 3:
            rand_crystal = random_crystal(sg, elem, num_atom, factor)
        elif dim == 2:
            rand_crystal = random_crystal_2D(sg, elem, num_atom, thickness,
                                             factor)
        elif dim == 1:
            rand_crystal = random_crystal_1D(sg, elem, num_atom, thickness,
                                             factor)
        if dim == 0:
            rand_crystal = random_cluster(sg, elem, num_atom, factor)
        if rand_crystal.valid:
            comp = str(rand_crystal.struct.composition)
            comp = comp.replace(" ", "")
            if dim > 0:
                outpath = comp + '.cif'
                CifWriter(rand_crystal.struct,
                          symprec=0.1).write_file(filename=outpath)
                ans = get_symmetry_dataset(rand_crystal.spg_struct,
                                           symprec=1e-1)['international']
                print('Symmetry requested: {:d}({:s}), generated: {:s}'.format(
                    sg, symbol, ans))
                return True
            else:
                outpath = comp + '.xyz'
                rand_crystal.to_file(filename=outpath, fmt='xyz')
                ans = PointGroupAnalyzer(rand_crystal.molecule).sch_symbol
                print('Symmetry requested: {:d}({:s}), generated: {:s}'.format(
                    sg, symbol, ans))
                return True

        i += 1
Esempio n. 19
0
def polyhedral_cut(poly):
    out={}
    out['frac_coord']=poly['frac_coord']
    dist_n = {}
    dis = [i for i in poly['dist']]
    for i in dis:
        if i not in dist_n:
            dist_n[i]=0
        dist_n[i]+=1
    dis = list(set(dis))
    dis.sort()
    n=0
    for i in range(len(dis)):
        if abs(dis[i]-dis[0])<=0.5:
            n+=dist_n[dis[i]]
    pts = poly['poly'].points[0:n]
    try:
        convex = ConvexHull(pts)
        verts = list(convex.vertices)
    except QhullError:
        return
    else:
        card=poly['struct'].lattice.get_cartesian_coords(poly['frac_coord'])
        pts_add = np.array(list(pts)+[card])
        conv_ = ConvexHull(pts_add)
        if conv_.volume > convex.volume:
            return
        else:
            out['poly']=convex
            out['species'] = poly['species'][0:n]
            out['dist'] = poly['dist'][0:n]
            out['site_ind']=poly['site_ind'][0:n]
            mole = Molecule(out['species'],pts)
            pga = PointGroupAnalyzer(mole)
            nsymmop = len(pga.symmops)
            out['nsymmop']=nsymmop
            return out
Esempio n. 20
0
def gen_cluster(system,numIons,sg=None,dimension=0,factor=1.0):
    #space group
    random.seed(int(time.time()*1e5))
    if sg:
       pass
    else:
       sg=random.choice(range(1,57))
    symbol, sg = get_symbol_and_number(sg, dimension)

    numIons0 = np.array(numIons)
    cluster = random_cluster(sg, system, numIons0, factor)
    if cluster.valid:
       comp = str(cluster.struct.composition)
       comp = comp.replace(" ", "")
       #outpath ='./' + comp + '.xyz'
       #print('out file name %s'%outpath)
       #cluster.molecule.to('xyz,', outpath)
       #cluster.to_file(filename = outpath, fmt='xyz')
       #mol=Molecule.from_file(outpath)
       mol=cluster.molecule
       ans = PointGroupAnalyzer(mol).sch_symbol
       print('Symmetry requested: {:d}({:s}), generated: {:s}'.format(sg, symbol, ans))
       a=max(mol.cart_coords[:,0])-min(mol.cart_coords[:,0])
       b=max(mol.cart_coords[:,1])-min(mol.cart_coords[:,1])
       c=max(mol.cart_coords[:,2])-min(mol.cart_coords[:,2])
       lx,ly,lz=sorted([a,b,c])
       if lz/ly>2 or ly/lx>2 or lz/lx>2:
          print('too long')
          return None
       st=mol.get_boxed_structure(a+vac,b+vac,c+vac)
       #st.to('POSCAR','rand_'+comp+'.vasp')
       #print(st)
       print('valid')
       return st
    else:
       print('cannot generate corresponding structure, retry it!')
       return None
Esempio n. 21
0
def getSymmetry(ids, xyz):
    mol = PointGroupAnalyzer(mg.Molecule(ids, xyz))
    return mol.sch_symbol
Esempio n. 22
0
 def test_cyclic(self):
     a = PointGroupAnalyzer(H2O2)
     self.assertEqual(a.sch_symbol, "C2")
     self.assertEqual(len(a.get_pointgroup()), 2)
     a = PointGroupAnalyzer(H2O)
     self.assertEqual(a.sch_symbol, "C2v")
     self.assertEqual(len(a.get_pointgroup()), 4)
     a = PointGroupAnalyzer(NH3)
     self.assertEqual(a.sch_symbol, "C3v")
     self.assertEqual(len(a.get_pointgroup()), 6)
     cs2 = Molecule.from_file(
         os.path.join(test_dir_mol, "Carbon_Disulfide.xyz"))
     a = PointGroupAnalyzer(cs2, eigen_tolerance=0.001)
     self.assertEqual(a.sch_symbol, "C2v")
Esempio n. 23
0
def orientation_in_wyckoff_position(
    mol,
    wyckoff_position,
    randomize=True,
    exact_orientation=False,
    already_oriented=False,
    allow_inversion=True,
    rtol=1e-2,
):
    """
    Tests if a molecule meets the symmetry requirements of a Wyckoff position,
    and returns the valid orientations.

    Args:
        mol: a Molecule object. Orientation is arbitrary
        wyckoff_position: a pyxtal.symmetry.Wyckoff_position object
        randomize: whether or not to apply a random rotation consistent with
            the symmetry requirements
        exact_orientation: whether to only check compatibility for the provided
            orientation of the molecule. Used within general case for checking.
            If True, this function only returns True or False
        already_oriented: whether or not to reorient the principle axes
            when calling get_symmetry. Setting to True can remove redundancy,
            but is not necessary
        allow_inversion: whether or not to allow chiral molecules to be
            inverted. Should only be True if the chemical and biological
            properties of the mirror image are known to be suitable for the
            desired application

    Returns:
        a list of operations.Orientation objects which can be applied to the
        molecule while allowing it to satisfy the symmetry requirements of the
        Wyckoff position. If no orientations are found, returns False.
    """
    # For single atoms, there are no constraints
    if len(mol) == 1:
        return [Orientation([[1, 0, 0], [0, 1, 0], [0, 0, 1]], degrees=2)]

    wyckoffs = wyckoff_position.ops
    w_symm = wyckoff_position.symmetry_m

    # Obtain the Wyckoff symmetry
    symm_w = w_symm[0]
    pga = PointGroupAnalyzer(mol)

    # Check exact orientation
    if exact_orientation is True:
        mo = deepcopy(mol)
        valid = True
        for op in symm_w:
            if not pga.is_valid_op(op):
                valid = False
        if valid is True:
            return True
        elif valid is False:
            return False

    # Obtain molecular symmetry, exact_orientation==False
    symm_m = get_symmetry(mol, already_oriented=already_oriented)
    # Store OperationAnalyzer objects for each molecular SymmOp
    chiral = True
    opa_m = []
    for op_m in symm_m:
        opa = OperationAnalyzer(op_m)
        opa_m.append(opa)
        if opa.type == "rotoinversion":
            chiral = False
        elif opa.type == "inversion":
            chiral = False

    # If molecule is chiral and allow_inversion is False,
    # check if WP breaks symmetry
    if chiral is True:
        if allow_inversion is False:
            for op in wyckoffs:
                if np.linalg.det(op.rotation_matrix) < 0:
                    printx(
                        "Warning: cannot place chiral molecule in spagegroup",
                        priority=2,
                    )
                    return False

    # Store OperationAnalyzer objects for each Wyckoff symmetry SymmOp
    opa_w = []
    for op_w in symm_w:
        opa_w.append(OperationAnalyzer(op_w))

    # Check for constraints from the Wyckoff symmetry...
    # If we find ANY two constraints (SymmOps with unique axes), the molecule's
    # point group MUST contain SymmOps which can be aligned to these particular
    # constraints. However, there may be multiple compatible orientations of the
    # molecule consistent with these constraints
    constraint1 = None
    constraint2 = None
    for i, op_w in enumerate(symm_w):
        if opa_w[i].axis is not None:
            constraint1 = opa_w[i]
            for j, op_w in enumerate(symm_w):
                if opa_w[j].axis is not None:
                    dot = np.dot(opa_w[i].axis, opa_w[j].axis)
                    if (not np.isclose(dot, 1, rtol=rtol)) and (not np.isclose(
                            dot, -1, rtol=rtol)):
                        constraint2 = opa_w[j]
                        break
            break
    # Indirectly store the angle between the constraint axes
    if constraint1 is not None and constraint2 is not None:
        dot_w = np.dot(constraint1.axis, constraint2.axis)
    # Generate 1st consistent molecular constraints
    constraints_m = []
    if constraint1 is not None:
        for i, opa1 in enumerate(opa_m):
            if opa1.is_conjugate(constraint1):
                constraints_m.append([opa1, []])
                # Generate 2nd constraint in opposite direction
                extra = deepcopy(opa1)
                extra.axis = [
                    opa1.axis[0] * -1, opa1.axis[1] * -1, opa1.axis[2] * -1
                ]
                constraints_m.append([extra, []])

    # Remove redundancy for the first constraints
    list_i = list(range(len(constraints_m)))
    list_j = list(range(len(constraints_m)))
    copy = deepcopy(constraints_m)
    for i, c1 in enumerate(copy):
        if i in list_i:
            for j, c2 in enumerate(copy):
                if i > j and j in list_j and j in list_i:
                    # Check if axes are colinear
                    if np.isclose(np.dot(c1[0].axis, c2[0].axis), 1,
                                  rtol=rtol):
                        list_i.remove(j)
                        list_j.remove(j)
                    # Check if axes are symmetrically equivalent
                    else:
                        cond1 = False
                        # cond2 = False
                        for opa in opa_m:
                            if opa.type == "rotation":
                                op = opa.op
                                if np.isclose(
                                        np.dot(op.operate(c1[0].axis),
                                               c2[0].axis),
                                        1,
                                        rtol=5 * rtol,
                                ):
                                    cond1 = True
                                    break
                        if cond1 is True:  # or cond2 is True:
                            list_i.remove(j)
                            list_j.remove(j)
    c_m = deepcopy(constraints_m)
    constraints_m = []
    for i in list_i:
        constraints_m.append(c_m[i])

    # Generate 2nd consistent molecular constraints
    valid = list(range(len(constraints_m)))
    if constraint2 is not None:
        for i, c in enumerate(constraints_m):
            opa1 = c[0]
            for j, opa2 in enumerate(opa_m):
                if opa2.is_conjugate(constraint2):
                    dot_m = np.dot(opa1.axis, opa2.axis)
                    # Ensure that the angles are equal
                    if abs(dot_m - dot_w) < 0.02 or abs(dot_m + dot_w) < 0.02:
                        constraints_m[i][1].append(opa2)
                        # Generate 2nd constraint in opposite direction
                        extra = deepcopy(opa2)
                        extra.axis = [
                            opa2.axis[0] * -1,
                            opa2.axis[1] * -1,
                            opa2.axis[2] * -1,
                        ]
                        constraints_m[i][1].append(extra)
            # If no consistent constraints are found, remove first constraint
            if constraints_m[i][1] == []:
                valid.remove(i)
    copy = deepcopy(constraints_m)
    constraints_m = []
    for i in valid:
        constraints_m.append(copy[i])

    # Generate orientations consistent with the possible constraints
    orientations = []
    # Loop over molecular constraint sets
    for c1 in constraints_m:
        v1 = c1[0].axis
        v2 = constraint1.axis
        T = rotate_vector(v1, v2)
        # If there is only one constraint
        if c1[1] == []:
            o = Orientation(T, degrees=1, axis=constraint1.axis)
            orientations.append(o)
        else:
            # Loop over second molecular constraints
            for opa in c1[1]:
                phi = angle(constraint1.axis, constraint2.axis)
                phi2 = angle(constraint1.axis, np.dot(T, opa.axis))
                if np.isclose(phi, phi2, rtol=rtol):
                    r = np.sin(phi)
                    c = np.linalg.norm(np.dot(T, opa.axis) - constraint2.axis)
                    theta = np.arccos(1 - (c**2) / (2 * (r**2)))
                    # R = aa2matrix(constraint1.axis, theta)
                    R = Rotation.from_rotvec(theta *
                                             constraint1.axis).as_matrix()
                    T2 = np.dot(R, T)
                    a = angle(np.dot(T2, opa.axis), constraint2.axis)
                    if not np.isclose(a, 0, rtol=rtol):
                        T2 = np.dot(np.linalg.inv(R), T)
                    o = Orientation(T2, degrees=0)
                    orientations.append(o)

    # Ensure the identity orientation is checked if no constraints are found
    if constraints_m == []:
        o = Orientation(np.identity(3), degrees=2)
        orientations.append(o)

    # Remove redundancy from orientations
    list_i = list(range(len(orientations)))
    list_j = list(range(len(orientations)))
    for i, o1 in enumerate(orientations):
        if i in list_i:
            for j, o2 in enumerate(orientations):
                if i > j and j in list_j and j in list_i:
                    # m1 = o1.get_matrix(angle=0)
                    # m2 = o2.get_matrix(angle=0)
                    m1 = o1.matrix
                    m2 = o2.matrix
                    new_op = SymmOp.from_rotation_and_translation(
                        np.dot(m2, np.linalg.inv(m1)), [0, 0, 0])
                    P = SymmOp.from_rotation_and_translation(
                        np.linalg.inv(m1), [0, 0, 0])
                    old_op = P * new_op * P.inverse
                    if pga.is_valid_op(old_op):
                        list_i.remove(j)
                        list_j.remove(j)
    #copies = deepcopy(orientations)
    orientations_new = []
    for i in list_i:
        orientations_new.append(orientations[i])

    #Check each of the found orientations for consistency with the Wyckoff pos.
    #If consistent, put into an array of valid orientations
    allowed = []
    for o in orientations_new:
        if randomize is True:
            op = o.get_op()
        elif randomize is False:
            op = o.get_op()  #do not change
        mo = deepcopy(mol)
        mo.apply_operation(op)
        if orientation_in_wyckoff_position(mo,
                                           wyckoff_position,
                                           exact_orientation=True,
                                           randomize=False,
                                           allow_inversion=allow_inversion):
            allowed.append(o)
    if allowed == []:
        return False
    else:
        return allowed
Esempio n. 24
0
def get_symmetry(mol, already_oriented=False):
    """
    Return a molecule's point symmetry.
    Note: for linear molecules, infinitessimal rotations are treated as 6-fold
    rotations, which works for 3d and 2d point groups.

    Args:
        mol: a Molecule object
        already_oriented: whether or not the principle axes of mol are already
            reoriented. Can save time if True, but is not required.

    Returns:
        a list of SymmOp objects which leave the molecule unchanged when applied
    """
    # For single atoms, we cannot represent the point group using a list of operations
    if len(mol) == 1:
        return []
    pga = PointGroupAnalyzer(mol)
    # Handle linear molecules
    if "*" in pga.sch_symbol:
        if not already_oriented:
            # Reorient the molecule
            oriented_mol, P = reoriented_molecule(mol)
            pga = PointGroupAnalyzer(oriented_mol)
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        # Add 12-fold  and reflections in place of ininitesimal rotation
        for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]:
            # op = SymmOp.from_rotation_and_translation(aa2matrix(axis, np.pi/6), [0,0,0])
            m1 = Rotation.from_rotvec(np.pi / 6 * axis).as_matrix()
            op = SymmOp.from_rotation_and_translation(m1, [0, 0, 0])
            if pga.is_valid_op(op):
                symm_m.append(op)
                # Any molecule with infinitesimal symmetry is linear;
                # Thus, it possess mirror symmetry for any axis perpendicular
                # To the rotational axis. pymatgen does not add this symmetry
                # for all linear molecules - for example, hydrogen
                if axis == [1, 0, 0]:
                    symm_m.append(SymmOp.from_xyz_string("x,-y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,y,-z"))
                    #r = SymmOp.from_xyz_string("-x,y,-z")
                elif axis == [0, 1, 0]:
                    symm_m.append(SymmOp.from_xyz_string("-x,y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,y,-z"))
                    #r = SymmOp.from_xyz_string("-x,-y,z")
                elif axis == [0, 0, 1]:
                    symm_m.append(SymmOp.from_xyz_string("-x,y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,-y,z"))
                    #r = SymmOp.from_xyz_string("x,-y,-z")
                # Generate a full list of SymmOps for the molecule's pointgroup
                symm_m = generate_full_symmops(symm_m, 1e-3)
                break
        # Reorient the SymmOps into mol's original frame
        if not already_oriented:
            new = []
            for op in symm_m:
                new.append(P.inverse * op * P)
            return new
        elif already_oriented:
            return symm_m
    # Handle nonlinear molecules
    else:
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        return symm_m
Esempio n. 25
0
from __future__ import print_function

import unittest
import pickle

with open('mol.pickle', 'rb') as f:
    mol = pickle.load(f)

from pymatgen.symmetry.analyzer import PointGroupAnalyzer
analyzer = PointGroupAnalyzer(mol)
print(" sch_symbol = ", analyzer.sch_symbol)

from pymatgen.symmetry.analyzer import generate_full_symmops
symmops = generate_full_symmops(analyzer.symmops, tol=1.e-7)
print(" num symmops = ", len(symmops))


class KnowValues(unittest.TestCase):
    def test_PointGroupAnalyzer(self):
        self.assertTrue(analyzer.sch_symbol == 'D4h')
        self.assertTrue(len(symmops) == 16)


if __name__ == "__main__":
    print("Tests for pymatgen.")
    unittest.main()

Esempio n. 26
0
 def test_tricky(self):
     m = Molecule.from_file(os.path.join(test_dir_mol, "dh.xyz"))
     a = PointGroupAnalyzer(m, 0.1)
     self.assertEqual(a.sch_symbol, "D*h")
Esempio n. 27
0
def parse_symmetry(pos):
    mol = Molecule(["C"] * len(pos), pos)
    pga = PointGroupAnalyzer(mol)
    return pga.sch_symbol
Esempio n. 28
0
    def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun):
        try:
            fullpath = os.path.abspath(dir_name)
            d = jsanitize(self.additional_fields, strict=True)
            d["schema"] = {
                "code": "atomate",
                "version": QChemDrone.__version__
            }
            d["dir_name"] = fullpath
            if multirun:
                d["calcs_reversed"] = self.process_qchem_multirun(
                    dir_name, qcinput_files, qcoutput_files)
            else:
                d["calcs_reversed"] = [
                    self.process_qchemrun(dir_name, taskname,
                                          qcinput_files.get(taskname),
                                          output_filename)
                    for taskname, output_filename in qcoutput_files.items()
                ]

            # reverse the calculations data order so newest calc is first
            d["calcs_reversed"].reverse()

            d_calc_init = d["calcs_reversed"][-1]
            d_calc_final = d["calcs_reversed"][0]

            d["input"] = {
                "initial_molecule": d_calc_init["initial_molecule"],
                "job_type": d_calc_init["input"]["rem"]["job_type"]
            }
            d["output"] = {
                "initial_molecule": d_calc_final["initial_molecule"],
                "job_type": d_calc_final["input"]["rem"]["job_type"]
            }

            if d["output"]["job_type"] == "opt" or d["output"][
                    "job_type"] == "optimization":
                d["output"]["optimized_molecule"] = d_calc_final[
                    "molecule_from_optimized_geometry"]
                d["output"]["final_energy"] = d_calc_final["final_energy"]
                if d_calc_final["opt_constraint"]:
                    d["output"]["constraint"] = [
                        d_calc_final["opt_constraint"][0],
                        float(d_calc_final["opt_constraint"][6])
                    ]
            if d["output"]["job_type"] == "freq" or d["output"][
                    "job_type"] == "frequency":
                d["output"]["frequencies"] = d_calc_final["frequencies"]
                d["output"]["enthalpy"] = d_calc_final["enthalpy"]
                d["output"]["entropy"] = d_calc_final["entropy"]
                if d["input"]["job_type"] == "opt" or d["input"][
                        "job_type"] == "optimization":
                    d["output"]["optimized_molecule"] = d_calc_final[
                        "initial_molecule"]
                    d["output"]["final_energy"] = d["calcs_reversed"][1][
                        "final_energy"]

            if "special_run_type" in d:
                if d["special_run_type"] == "frequency_flattener":
                    d["num_frequencies_flattened"] = (len(qcinput_files) /
                                                      2) - 1

            total_cputime = 0.0
            total_walltime = 0.0
            nan_found = False
            for calc in d["calcs_reversed"]:
                if calc["walltime"] != "nan":
                    total_walltime += calc["walltime"]
                else:
                    nan_found = True
                if calc["cputime"] != "nan":
                    total_cputime += calc["cputime"]
                else:
                    nan_found = True
            if nan_found:
                d["walltime"] = "nan"
                d["cputime"] = "nan"
            else:
                d["walltime"] = total_walltime
                d["cputime"] = total_cputime

            comp = d["output"]["initial_molecule"].composition
            d["formula_pretty"] = comp.reduced_formula
            d["formula_anonymous"] = comp.anonymized_formula
            d["chemsys"] = "-".join(sorted(set(d_calc_final["species"])))
            d["pointgroup"] = PointGroupAnalyzer(
                d["output"]["initial_molecule"]).sch_symbol

            bb = BabelMolAdaptor(d["output"]["initial_molecule"])
            pbmol = bb.pybel_mol
            smiles = pbmol.write(str("smi")).split()[0]
            d["smiles"] = smiles

            d["state"] = "successful" if d_calc_final[
                "completion"] else "unsuccessful"
            d["last_updated"] = datetime.datetime.utcnow()
            return d

        except Exception:
            logger.error(traceback.format_exc())
            logger.error("Error in " + os.path.abspath(dir_name) + ".\n" +
                         traceback.format_exc())
            raise
Esempio n. 29
0
    def _GetSiteEnvironments(cls,
                             coord,
                             cell,
                             SiteTypes,
                             cutoff,
                             pbc,
                             get_permutations=True,
                             eigen_tol=1e-5):
        """Extract local environments from primitive cell
        
        Parameters
        ----------
        coord : n x 3 list or numpy array of scaled positions. n is the number 
            of atom.
        cell : 3 x 3 list or numpy array
        SiteTypes : n list of string. String must be S or A followed by a 
            number. S indicates a spectator sites and A indicates a active 
            sites.
        cutoff : float. cutoff distance in angstrom for collecting local
            environment.
        pbc : list of boolean. Periodic boundary condition
        get_permutations : boolean. Whether to find permutatated neighbor list or not.
        eigen_tol : tolerance for eigenanalysis of point group analysis in
            pymatgen.
        
        Returns
        ------
        list of local_env : list of local_env class
        """
        #%% Check error
        assert isinstance(coord, (list, np.ndarray))
        assert isinstance(cell, (list, np.ndarray))
        assert len(coord) == len(SiteTypes)
        #%% Initialize
        # TODO: Technically, user doesn't even have to supply site index, because
        #       pymatgen can be used to automatically categorize sites..
        coord = np.mod(coord, 1)
        pbc = np.array(pbc)
        #%% Map sites to other elements..
        # TODO: Available pymatgne functions are very limited when DummySpecie is
        #       involved. This may be perhaps fixed in the future. Until then, we
        #       simply bypass this by mapping site to an element
        # Find available atomic number to map site to it
        availableAN = [i + 1 for i in reversed(range(0, 118))]

        # Organize Symbols and record mapping
        symbols = []
        site_idxs = []
        SiteSymMap = {}  # mapping
        SymSiteMap = {}
        for i, SiteType in enumerate(SiteTypes):
            if SiteType not in SiteSymMap:
                symbol = Element.from_Z(availableAN.pop())
                SiteSymMap[SiteType] = symbol
                SymSiteMap[symbol] = SiteType

            else:
                symbol = SiteSymMap[SiteType]
            symbols.append(symbol)
            if 'A' in SiteType:
                site_idxs.append(i)
        #%% Get local environments of each site
        # Find neighbors and permutations using pymatgen
        lattice = Lattice(cell)
        structure = Structure(lattice, symbols, coord)
        neighbors = structure.get_all_neighbors(cutoff, include_index=True)
        site_envs = []
        for site_idx in site_idxs:
            local_env_sym = [symbols[site_idx]]
            local_env_xyz = [structure[site_idx].coords]
            local_env_dist = [0.0]
            local_env_sitemap = [site_idx]
            for n in neighbors[site_idx]:
                # if PBC condition is fulfilled..
                c = np.around(n[0].frac_coords, 10)
                withinPBC = np.logical_and(0 <= c, c < 1)
                if np.all(withinPBC[~pbc]):
                    local_env_xyz.append(n[0].coords)
                    local_env_sym.append(n[0].specie)
                    local_env_dist.append(n[1])
                    local_env_sitemap.append(n[2])
            local_env_xyz = np.subtract(local_env_xyz,
                                        np.mean(local_env_xyz, 0))

            perm = []
            if get_permutations:
                finder = PointGroupAnalyzer(Molecule(local_env_sym,
                                                     local_env_xyz),
                                            eigen_tolerance=eigen_tol)
                pg = finder.get_pointgroup()
                for i, op in enumerate(pg):
                    newpos = op.operate_multi(local_env_xyz)
                    perm.append(
                        np.argmin(cdist(local_env_xyz, newpos),
                                  axis=1).tolist())

            site_env = {
                'pos': local_env_xyz,
                'sitetypes': [SymSiteMap[s] for s in local_env_sym],
                'env2config': local_env_sitemap,
                'permutations': perm,
                'dist': local_env_dist
            }
            site_envs.append(site_env)
        return site_envs
Esempio n. 30
0
    def generate_doc(self, dir_name, qcinput_files, qcoutput_files, multirun):
        try:
            fullpath = os.path.abspath(dir_name)
            d = jsanitize(self.additional_fields, strict=True)
            d["schema"] = {
                "code": "atomate",
                "version": QChemDrone.__version__
            }
            d["dir_name"] = fullpath

            # If a saved "orig" input file is present, parse it incase the error handler made changes
            # to the initial input molecule or rem params, which we might want to filter for later
            if len(qcinput_files) > len(qcoutput_files):
                orig_input = QCInput.from_file(
                    os.path.join(dir_name, qcinput_files.pop("orig")))
                d["orig"] = {}
                d["orig"]["molecule"] = orig_input.molecule.as_dict()
                d["orig"]["molecule"]["charge"] = int(
                    d["orig"]["molecule"]["charge"])
                d["orig"]["rem"] = orig_input.rem
                d["orig"]["opt"] = orig_input.opt
                d["orig"]["pcm"] = orig_input.pcm
                d["orig"]["solvent"] = orig_input.solvent
                d["orig"]["smx"] = orig_input.smx

            if multirun:
                d["calcs_reversed"] = self.process_qchem_multirun(
                    dir_name, qcinput_files, qcoutput_files)
            else:
                d["calcs_reversed"] = [
                    self.process_qchemrun(dir_name, taskname,
                                          qcinput_files.get(taskname),
                                          output_filename)
                    for taskname, output_filename in qcoutput_files.items()
                ]

            # reverse the calculations data order so newest calc is first
            d["calcs_reversed"].reverse()

            d["structure_change"] = []
            d["warnings"] = {}
            for entry in d["calcs_reversed"]:
                if ("structure_change" in entry
                        and "structure_change" not in d["warnings"]):
                    if entry["structure_change"] != "no_change":
                        d["warnings"]["structure_change"] = True
                if "structure_change" in entry:
                    d["structure_change"].append(entry["structure_change"])
                for key in entry["warnings"]:
                    if key not in d["warnings"]:
                        d["warnings"][key] = True

            d_calc_init = d["calcs_reversed"][-1]
            d_calc_final = d["calcs_reversed"][0]

            d["input"] = {
                "initial_molecule": d_calc_init["initial_molecule"],
                "job_type": d_calc_init["input"]["rem"]["job_type"],
            }
            d["output"] = {
                "initial_molecule": d_calc_final["initial_molecule"],
                "job_type": d_calc_final["input"]["rem"]["job_type"],
                "mulliken": d_calc_final["Mulliken"][-1],
            }
            if "RESP" in d_calc_final:
                d["output"]["resp"] = d_calc_final["RESP"][-1]
            elif "ESP" in d_calc_final:
                d["output"]["esp"] = d_calc_final["ESP"][-1]

            if (d["output"]["job_type"] == "opt"
                    or d["output"]["job_type"] == "optimization"):
                if "molecule_from_optimized_geometry" in d_calc_final:
                    d["output"]["optimized_molecule"] = d_calc_final[
                        "molecule_from_optimized_geometry"]
                    d["output"]["final_energy"] = d_calc_final["final_energy"]
                else:
                    d["output"]["final_energy"] = "unstable"
                if d_calc_final["opt_constraint"]:
                    d["output"]["constraint"] = [
                        d_calc_final["opt_constraint"][0],
                        float(d_calc_final["opt_constraint"][6]),
                    ]
            if (d["output"]["job_type"] == "freq"
                    or d["output"]["job_type"] == "frequency"):
                d["output"]["frequencies"] = d_calc_final["frequencies"]
                d["output"]["enthalpy"] = d_calc_final["total_enthalpy"]
                d["output"]["entropy"] = d_calc_final["total_entropy"]
                if (d["input"]["job_type"] == "opt"
                        or d["input"]["job_type"] == "optimization"):
                    d["output"]["optimized_molecule"] = d_calc_final[
                        "initial_molecule"]
                    d["output"]["final_energy"] = d["calcs_reversed"][1][
                        "final_energy"]

            opt_trajectory = []
            calcs = copy.deepcopy(d["calcs_reversed"])
            calcs.reverse()
            for calc in calcs:
                job_type = calc["input"]["rem"]["job_type"]
                if job_type == "opt" or job_type == "optimization":
                    for ii, geom in enumerate(calc["geometries"]):
                        site_properties = {"Mulliken": calc["Mulliken"][ii]}
                        if "RESP" in calc:
                            site_properties["RESP"] = calc["RESP"][ii]
                        mol = Molecule(
                            species=calc["species"],
                            coords=geom,
                            charge=calc["charge"],
                            spin_multiplicity=calc["multiplicity"],
                            site_properties=site_properties,
                        )
                        traj_entry = {"molecule": mol}
                        traj_entry["energy"] = calc["energy_trajectory"][ii]
                        opt_trajectory.append(traj_entry)
            if opt_trajectory != []:
                d["opt_trajectory"] = opt_trajectory

            if "final_energy" not in d["output"]:
                if d_calc_final["final_energy"] != None:
                    d["output"]["final_energy"] = d_calc_final["final_energy"]
                else:
                    d["output"]["final_energy"] = d_calc_final["SCF"][-1][-1][
                        0]

            if d_calc_final["completion"]:
                total_cputime = 0.0
                total_walltime = 0.0
                for calc in d["calcs_reversed"]:
                    if calc["walltime"] is not None:
                        total_walltime += calc["walltime"]
                    if calc["cputime"] is not None:
                        total_cputime += calc["cputime"]
                d["walltime"] = total_walltime
                d["cputime"] = total_cputime
            else:
                d["walltime"] = None
                d["cputime"] = None

            comp = d["output"]["initial_molecule"].composition
            d["formula_pretty"] = comp.reduced_formula
            d["formula_anonymous"] = comp.anonymized_formula
            d["formula_alphabetical"] = comp.alphabetical_formula
            d["chemsys"] = "-".join(sorted(set(d_calc_final["species"])))
            if d_calc_final["point_group"] != None:
                d["pointgroup"] = d_calc_final["point_group"]
            else:
                try:
                    d["pointgroup"] = PointGroupAnalyzer(
                        d["output"]["initial_molecule"]).sch_symbol
                except ValueError:
                    d["pointgroup"] = "PGA_error"

            bb = BabelMolAdaptor(d["output"]["initial_molecule"])
            pbmol = bb.pybel_mol
            smiles = pbmol.write("smi").split()[0]
            d["smiles"] = smiles

            d["state"] = "successful" if d_calc_final[
                "completion"] else "unsuccessful"
            if "special_run_type" in d:
                if d["special_run_type"] == "frequency_flattener":
                    if d["state"] == "successful":
                        orig_num_neg_freq = sum(
                            1
                            for freq in d["calcs_reversed"][-2]["frequencies"]
                            if freq < 0)
                        orig_energy = d_calc_init["final_energy"]
                        final_num_neg_freq = sum(
                            1 for freq in d_calc_final["frequencies"]
                            if freq < 0)
                        final_energy = d["calcs_reversed"][1]["final_energy"]
                        d["num_frequencies_flattened"] = (orig_num_neg_freq -
                                                          final_num_neg_freq)
                        if final_num_neg_freq > 0:  # If a negative frequency remains,
                            # and it's too large to ignore,
                            if (final_num_neg_freq > 1 or abs(
                                    d["output"]["frequencies"][0]) >= 15.0):
                                d["state"] = "unsuccessful"  # then the flattening was unsuccessful
                        if final_energy > orig_energy:
                            d["warnings"]["energy_increased"] = True

            d["last_updated"] = datetime.datetime.utcnow()
            return d

        except Exception:
            logger.error(traceback.format_exc())
            logger.error("Error in " + os.path.abspath(dir_name) + ".\n" +
                         traceback.format_exc())
            raise