#!/usr/bin/env python
# Copyright 2014-2020 The PySCF Developers. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Qiming Sun <osirpt.sun@gmail.com>
#
from functools import reduce
import numpy
import scipy.linalg
from pyscf import lib
from pyscf.lib import logger
from pyscf.lib.exceptions import PointGroupSymmetryError
from pyscf.symm import basis
from pyscf.symm import param
from pyscf import __config__
MULTI_IRREPS = -1
[docs]
def label_orb_symm(mol, irrep_name, symm_orb, mo, s=None,
check=getattr(__config__, 'symm_addons_label_orb_symm_check', True),
tol=getattr(__config__, 'symm_addons_label_orb_symm_tol', 1e-9)):
'''Label the symmetry of given orbitals
irrep_name can be either the symbol or the ID of the irreducible
representation. If the ID is provided, it returns the numeric code
associated with XOR operator, see :py:meth:`symm.param.IRREP_ID_TABLE`
Args:
mol : an instance of :class:`Mole`
irrep_name : list of str or int
A list of irrep ID or name, it can be either mol.irrep_id or
mol.irrep_name. It can affect the return "label".
symm_orb : list of 2d array
the symmetry adapted basis
mo : 2d array
the orbitals to label
Returns:
list of symbols or integers to represent the irreps for the given
orbitals
Examples:
>>> from pyscf import gto, scf, symm
>>> mol = gto.M(atom='H 0 0 0; H 0 0 1', basis='ccpvdz',verbose=0, symmetry=1)
>>> mf = scf.RHF(mol)
>>> mf.kernel()
>>> symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mf.mo_coeff)
['Ag', 'B1u', 'Ag', 'B1u', 'B2u', 'B3u', 'Ag', 'B2g', 'B3g', 'B1u']
>>> symm.label_orb_symm(mol, mol.irrep_id, mol.symm_orb, mf.mo_coeff)
[0, 5, 0, 5, 6, 7, 0, 2, 3, 5]
'''
nmo = mo.shape[1]
if s is None:
s = mol.intor_symmetric('int1e_ovlp')
s_mo = numpy.dot(s, mo)
norm = numpy.zeros((len(irrep_name), nmo))
for i, csym in enumerate(symm_orb):
moso = numpy.dot(csym.T.conj(), s_mo)
ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym))
try:
s_moso = lib.cho_solve(ovlpso, moso)
except numpy.linalg.LinAlgError:
ovlpso[numpy.diag_indices(csym.shape[1])] += 1e-12
s_moso = lib.cho_solve(ovlpso, moso)
norm[i] = numpy.einsum('ki,ki->i', moso.conj(), s_moso).real
norm /= numpy.sum(norm, axis=0) # for orbitals which are not normalized
iridx = numpy.argmax(norm, axis=0)
orbsym = numpy.asarray([irrep_name[i] for i in iridx])
logger.debug(mol, 'irreps of each MO %s', orbsym)
if check:
largest_norm = norm[iridx,numpy.arange(nmo)]
orbidx = numpy.where(largest_norm < 1-tol)[0]
if orbidx.size > 0:
idx = numpy.where(largest_norm < 1-tol*1e2)[0]
if idx.size > 0:
raise ValueError('orbitals %s not symmetrized, norm = %s' %
(idx, largest_norm[idx]))
else:
logger.warn(mol, 'orbitals %s not strictly symmetrized.',
numpy.unique(orbidx))
logger.warn(mol, 'They can be symmetrized with '
'pyscf.symm.symmetrize_space function.')
logger.debug(mol, 'norm = %s', largest_norm[orbidx])
return orbsym
[docs]
def symmetrize_orb(mol, mo, orbsym=None, s=None,
check=getattr(__config__, 'symm_addons_symmetrize_orb_check', False)):
'''Symmetrize the given orbitals.
This function is different to the :func:`symmetrize_space`: In this
function, each orbital is symmetrized by removing non-symmetric components.
:func:`symmetrize_space` symmetrizes the entire space by mixing different
orbitals.
Note this function might return non-orthogonal orbitals.
Call :func:`symmetrize_space` to find the symmetrized orbitals that are
close to the given orbitals.
Args:
mo : 2D float array
The orbital space to symmetrize
Kwargs:
orbsym : integer list
Irrep id for each orbital. If not given, the irreps are guessed
by calling :func:`label_orb_symm`.
s : 2D float array
Overlap matrix. If given, use this overlap than the the overlap
of the input mol.
Returns:
2D orbital coefficients
Examples:
>>> from pyscf import gto, symm, scf
>>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1',
... basis = 'sto3g')
>>> mf = scf.RHF(mol).run()
>>> mol.build(0, 0, symmetry='D2')
>>> mo = symm.symmetrize_orb(mol, mf.mo_coeff)
>>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo))
['A', 'A', 'B1', 'B2', 'B3', 'A', 'B1', 'B2', 'B3']
'''
if s is None:
s = mol.intor_symmetric('int1e_ovlp')
if orbsym is None:
orbsym = label_orb_symm(mol, mol.irrep_id, mol.symm_orb,
mo, s=s, check=check)
orbsym = numpy.asarray(orbsym)
s_mo = numpy.dot(s, mo)
mo1 = numpy.empty_like(mo)
if orbsym[0] in mol.irrep_name:
irrep_id = mol.irrep_name
else:
irrep_id = mol.irrep_id
for i, ir in enumerate(irrep_id):
idx = orbsym == ir
csym = mol.symm_orb[i]
ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym))
sc = lib.cho_solve(ovlpso, numpy.dot(csym.T.conj(), s_mo[:,idx]))
mo1[:,idx] = numpy.dot(csym, sc)
return mo1
[docs]
def find_symmetric_mo(moso, ovlpso, thr=1e-8):
'''Find the list of MO of that thransform like particular irrep
Args:
moso : 2D float array
Overlap matrix of symmetry-adapted AO and MO, it can be obtained
by reduce(numpy.dot, (csym.T.conj(), s, mo)), where csym taken from
mol.symm_orb, and s is the AO overlap matrix
ovlpso : 2D float array
Overlap matrix between symmetry-adapted AO, it can be obtained
by reduce(numpy.dot, (csym.T.conj(), s, csym))
Kwargs:
thr : float
Threshold to consider MO symmetry-adapted
Returns:
1D bool array to select symmetry adapted MO
'''
irrep_dim = ovlpso.shape[0]
try:
diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso))
except numpy.linalg.LinAlgError:
ovlpso[numpy.diag_indices(irrep_dim)] += 1e-12
diag = numpy.einsum('ki,ki->i', moso.conj(), lib.cho_solve(ovlpso, moso))
idx = abs(1-diag) < thr
return idx
[docs]
def symmetrize_space(mol, mo, s=None,
check=getattr(__config__, 'symm_addons_symmetrize_space_check', True),
tol=getattr(__config__, 'symm_addons_symmetrize_space_tol', 1e-9),
clean=getattr(__config__, 'symm_addons_symmetrize_space_clean', False)):
'''Symmetrize the given orbital space.
This function is different to the :func:`symmetrize_orb`: In this function,
the given orbitals are mixed to reveal the symmetry; :func:`symmetrize_orb`
projects out non-symmetric components for each orbital.
Args:
mol : an instance of :class:`Mole`
mo : 2D float array
The orbital space to symmetrize
Kwargs:
s : 2D float array
Overlap matrix. If not given, overlap is computed with the input mol.
check : bool
Whether to check orthogonality of input orbitals and try to fix it
tol : float
Orthogonality tolerance
clean : bool
Whether to zero out symmetry forbidden orbital coefficients
Returns:
2D orbital coefficients
Examples:
>>> from pyscf import gto, symm, scf
>>> mol = gto.M(atom = 'C 0 0 0; H 1 1 1; H -1 -1 1; H 1 -1 -1; H -1 1 -1',
... basis = 'sto3g')
>>> mf = scf.RHF(mol).run()
>>> mol.build(0, 0, symmetry='D2')
>>> mo = symm.symmetrize_space(mol, mf.mo_coeff)
>>> print(symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo))
['A', 'A', 'A', 'B1', 'B1', 'B2', 'B2', 'B3', 'B3']
'''
from pyscf.tools import mo_mapping
from pyscf.lo import orth
if s is None:
s = mol.intor_symmetric('int1e_ovlp')
nmo = mo.shape[1]
s_mo = numpy.dot(s, mo)
if check:
moso = reduce(numpy.dot, (mo.conj().T, s, mo))
max_non_orth = abs(moso - numpy.eye(nmo)).max()
logger.debug(mol, 'Non-orthogonality of input orbitals before symmetrization %8.2e', max_non_orth)
if max_non_orth > tol:
logger.info(mol, 'Input orbitals are not orthogonal, perform Lowdin orthogonalization')
mo = numpy.dot(mo, orth.lowdin(moso))
s_mo = numpy.dot(s, mo)
max_non_orth = abs(reduce(numpy.dot, (mo.conj().T, s, mo)) - numpy.eye(nmo)).max()
logger.debug(mol, 'Non-orthogonality of orbitals before symmetrization %8.2e', max_non_orth)
if max_non_orth > tol:
raise ValueError('Input orbitals are not orthogonalized')
mo1 = []
for i, csym in enumerate(mol.symm_orb):
moso = numpy.dot(csym.T.conj(), s_mo)
ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym))
# excluding orbitals which are already symmetrized
idx = find_symmetric_mo(moso, ovlpso)
orb_irrep = mo[:, idx]
if clean:
# rotate MO to symm-adapted AO and back to zero-out non-symmetric part
SALC_mo = numpy.dot(csym.T.conj(), orb_irrep)
orb_irrep = numpy.dot(csym, SALC_mo)
moso1 = moso[:, ~idx]
dm = numpy.dot(moso1, moso1.T.conj())
if dm.trace() > 1e-8 and sum(idx) < csym.shape[1]:
logger.debug(mol, '%5d orbital(s) symmetrized in irrep %3d', csym.shape[1] - sum(idx), i)
e, u = scipy.linalg.eigh(dm, ovlpso)
orb_symmetrized = numpy.dot(csym, u[:, abs(1-e) < 1e-6])
orb_irrep = numpy.hstack([orb_irrep, orb_symmetrized])
moso = numpy.dot(orb_irrep.T.conj(), numpy.dot(s, orb_irrep))
if moso.shape[0] == 0:
continue
max_non_orth = abs(moso - numpy.eye(moso.shape[0])).max()
logger.debug(mol, 'Non-orthogonality in irrep %3d after symmetrization: %8.2e', i, max_non_orth)
if max_non_orth > tol:
logger.info(mol, 'Symmetrized orbitals in irrep %3d not orthogonal, perform Lowdin orthogonalization', i)
orb_irrep = numpy.dot(orb_irrep, orth.lowdin(moso))
max_non_orth = abs(numpy.dot(orb_irrep.T.conj(), numpy.dot(s, orb_irrep))
- numpy.eye(orb_irrep.shape[1])).max()
logger.debug(mol, 'Non-orthogonality in irrep %3d after symmetrization and orthogonalization: %8.2e',
i, max_non_orth)
mo1.append(orb_irrep)
mo1 = numpy.hstack(mo1)
if mo1.shape[1] != nmo:
raise ValueError('mo1.shape[1] != nmo: %d != %d The input orbital space is not symmetrized.\n One '
'possible reason is that the input mol and orbitals '
'are of different orientation.' % (mo1.shape[1], nmo))
if check:
moso1 = reduce(numpy.dot, (mo1.conj().T, s, mo1))
max_non_orth = abs(moso1 - numpy.eye(nmo)).max()
logger.debug(mol, 'Non-orthogonality of output orbitals after symmetrization %8.2e', max_non_orth)
if max_non_orth > tol:
logger.info(mol, 'Symmetrized output orbitals are not orthogonalized, perform Lowdin orthogonalization')
mo1 = numpy.dot(mo1, orth.lowdin(moso1))
max_non_orth = abs(reduce(numpy.dot, (mo1.conj().T, s, mo1)) - numpy.eye(nmo)).max()
logger.debug(mol, 'Non-orthogonality of output orbitals after orthogonalization %8.2e', max_non_orth)
if max_non_orth > tol:
raise ValueError('Output orbitals are not orthogonalized')
idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T.conj(), s, mo1)))
return mo1[:, idx]
[docs]
def symmetrize_multidim(mol, mo, s=None,
check=getattr(__config__, 'symm_addons_symmetrize_space_check', True),
tol=getattr(__config__, 'symm_addons_symmetrize_space_tol', 1e-10),
keep_phase=getattr(__config__, 'symm_addons_symmetrize_multidim_keep_phase', True)):
'''Symmetrize orbitals with respect to multidimensional irreps.
Make coefficients of partner functions of multidimensional irreps to be the same.
The functions uses the convention of the libmsym interface, that introduces underscores to
the labels of multidimensional irreps partners.
Args:
mol : an instance of :class:`Mole`
Symmetry-adapted basis with multidimensional irreps should be generated by libmsym
mo : 2D float array
The orbital space to symmetrize
Kwargs:
s : 2D float array
Overlap matrix. If not given, overlap is computed with the input mol.
check : bool
Whether to check orthogonality of input orbitals and try to fix it
tol : float
Orthogonality tolerance
keep_phase : bool
Whether to keep original orbital phases, rather then make them coherent with the first partner
Returns:
2D orbital coefficients
'''
from pyscf.tools import mo_mapping
from pyscf.lo import orth
if s is None:
s = mol.intor_symmetric('int1e_ovlp')
nmo = mo.shape[1]
s_mo = numpy.dot(s, mo)
mo1 = []
if check:
moso = reduce(numpy.dot, (mo.conj().T, s, mo))
max_non_orth = abs(moso - numpy.eye(nmo)).max()
logger.info(mol, 'Non-orthogonality of input orbitals %8.2e', max_non_orth)
if max_non_orth > tol:
mo_lowdin = numpy.dot(mo, orth.lowdin(moso))
max_non_orth_lowdin = abs(reduce(numpy.dot, (mo_lowdin.conj().T, s, mo_lowdin)) -
numpy.eye(nmo)).max()
logger.info(mol, 'Non-orthogonality after Lowdin orthogonalization %8.2e', max_non_orth)
if (max_non_orth_lowdin - max_non_orth) > tol/100:
mo = mo_lowdin
s_mo = numpy.dot(s, mo)
logger.info(mol, 'Use Lowdin-orthogonalized input orbitals')
else:
logger.info(mol, 'Use original input orbitals')
irreps_mdim = []
irreps_1dim = []
for irrep in mol.irrep_name:
if "_" in irrep:
added = False
base = irrep.split("_")[0]
for partners in irreps_mdim:
if any(base in elem for elem in partners):
partners.append(irrep)
added = True
if not added:
# new base
irreps_mdim.append([irrep])
else:
irreps_1dim.append(irrep)
for irrep in irreps_1dim:
i = mol.irrep_name.index(irrep)
csym = mol.symm_orb[i]
moso = numpy.dot(csym.T.conj(), s_mo)
ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym))
# find MO that thransform like irrep
idx = find_symmetric_mo(moso, ovlpso)
if sum(idx) != csym.shape[1]:
raise ValueError('Number of symmetry-adapted MOs in not equal to dimensionality '
'of irrep %s: %d != %d' % (mol.irrep_name[irrep], csym.shape[1], sum(idx)))
mo1.append(mo[:, idx])
for partners in irreps_mdim:
SALC_ao_partners = []
SALC_ov_partners = []
SALC_mo_partners = []
for irrep in partners:
csym = mol.symm_orb[mol.irrep_name.index(irrep)]
SALC_ao_partners.append(csym)
moso = numpy.dot(csym.T.conj(), s_mo)
ovlpso = reduce(numpy.dot, (csym.T.conj(), s, csym))
SALC_ov_partners.append(ovlpso)
# find MO that thransform like irrep
idx = find_symmetric_mo(moso, ovlpso)
mo_irrep = mo[:, idx]
if sum(idx) != csym.shape[1]:
raise ValueError('Number of symmetry-adapted MOs in not equal to dimensionality '
'of irrep %s: %d != %d' % (mol.irrep_name[irrep], csym.shape[1], sum(idx)))
# rotate MO to symm-adapted AO
SALC_mo = numpy.dot(csym.T.conj(), mo_irrep)
SALC_mo_partners.append(SALC_mo)
irrep_dim = SALC_mo_partners[0].shape[1]
phases = [numpy.ones(irrep_dim)]
idxes = [numpy.array(range(irrep_dim))]
SALC_mo_average = SALC_mo_partners[0]
for i in range(1, len(partners)):
if not numpy.allclose(SALC_ov_partners[0], SALC_ov_partners[i]):
raise ValueError('Symmetry-adapted AO of partner functions %s and %s are not compatible'
% (partners[0], partners[i]))
ov_0i = reduce(numpy.dot, (SALC_mo_partners[0].T.conj(),
SALC_ov_partners[0],
SALC_mo_partners[i]))
idx = mo_mapping.mo_1to1map(ov_0i)
idxes.append(idx)
phase = numpy.rint(numpy.diagonal(ov_0i[:, idx]))
phases.append(phase)
partners_mo_i = phase*SALC_mo_partners[i][:, idx]
SALC_mo_average += partners_mo_i
SALC_mo_average /= len(partners)
for csym, phase, SALC_mo in zip(SALC_ao_partners, phases, SALC_mo_partners):
if keep_phase:
SALC_mo = phase*SALC_mo_average
else:
SALC_mo = SALC_mo_average
orb_done = numpy.dot(csym, SALC_mo)
moso1 = reduce(numpy.dot, (orb_done.T.conj(), s, orb_done))
max_non_orth = abs(moso1 - numpy.eye(moso1.shape[0])).max()
logger.debug(mol, 'Non-orthogonality after symmetrization of %s: %s', partners[i], max_non_orth)
if max_non_orth > tol:
orb_done = numpy.dot(orb_done, orth.lowdin(moso1))
max_non_orth = abs(numpy.dot(orb_done.T.conj(), numpy.dot(s, orb_done))
- numpy.eye(orb_done.shape[1])).max()
logger.debug(mol, 'After additional orthogonalization: %s', max_non_orth)
mo1.append(orb_done)
mo1 = numpy.hstack(mo1)
if check:
moso1 = reduce(numpy.dot, (mo1.conj().T, s, mo1))
max_non_orth = abs(moso1 - numpy.eye(nmo)).max()
logger.debug(mol, 'Non-orthogonality of output orbitals after symmetrization %8.2e', max_non_orth)
if max_non_orth > tol:
logger.info(mol, 'Output orbitals are not orthogonalized, perform Lowdin orthogonalization')
mo1 = numpy.dot(mo1, orth.lowdin(moso1))
max_non_orth = abs(reduce(numpy.dot, (mo1.conj().T, s, mo1)) - numpy.eye(nmo)).max()
logger.info(mol, 'Non-orthogonality of output orbitals after orthogonalization %8.2e', max_non_orth)
if max_non_orth > tol:
raise ValueError('Output orbitals are not orthogonalized')
idx = mo_mapping.mo_1to1map(reduce(numpy.dot, (mo.T.conj(), s, mo1)))
return mo1[:, idx]
[docs]
def std_symb(gpname):
'''std_symb('d2h') returns D2h; std_symb('D2H') returns D2h'''
if gpname == 'SO3':
return gpname
else:
return str(gpname[0].upper() + gpname[1:].lower())
[docs]
def irrep_name2id(gpname, symb):
'''Convert the irrep symbol to internal irrep ID
Args:
gpname : str
The point group symbol
symb : str
Irrep symbol
Returns:
Irrep ID, int
'''
gpname = std_symb(gpname)
symb = std_symb(symb)
if gpname == 'SO3':
return basis.so3_irrep_symb2id(symb)
elif gpname in ('Dooh', 'Coov'):
return basis.linearmole_irrep_symb2id(gpname, symb)
else:
return param.IRREP_ID_TABLE[gpname][symb]
[docs]
def irrep_id2name(gpname, irrep_id):
'''Convert the internal irrep ID to irrep symbol
Args:
gpname : str
The point group symbol
irrep_id : int
See IRREP_ID_TABLE in pyscf/symm/param.py
Returns:
Irrep symbol, str
'''
gpname = std_symb(gpname)
if gpname == 'SO3':
return basis.so3_irrep_id2symb(irrep_id)
elif gpname in ('Dooh', 'Coov'):
return basis.linearmole_irrep_id2symb(gpname, irrep_id)
else:
# irrep_id may be obtained from high symmetry (Dooh, Coov)
irrep_id_in_d2h = irrep_id % 10
return param.CHARACTER_TABLE[gpname][irrep_id_in_d2h][0]
[docs]
def irrep_name(pgname, irrep_id):
raise PointGroupSymmetryError('This function was obsoleted. Use irrep_id2name')
[docs]
def route(target, nelec, orbsym):
'''Pick orbitals to form a determinant which has the right symmetry.
If solution is not found, return []
'''
def riter(target, nelec, orbsym):
if nelec == 1:
if target in orbsym:
return [orbsym.index(target)]
else:
return []
else:
for i, ir in enumerate(orbsym):
off = i + 1
orb_left = orbsym[off:]
res = riter(target ^ ir, nelec-1, orb_left)
if res:
return [i] + [off+x for x in res]
return []
if isinstance(orbsym, numpy.ndarray):
orbsym = orbsym.tolist()
return riter(target, nelec, orbsym)
[docs]
def eigh(h, orbsym):
'''Solve eigenvalue problem based on the symmetry information for basis.
See also pyscf/lib/linalg_helper.py :func:`eigh_by_blocks`
Examples:
>>> from pyscf import gto, symm
>>> mol = gto.M(atom='H 0 0 0; H 0 0 1', basis='ccpvdz', symmetry=True)
>>> c = numpy.hstack(mol.symm_orb)
>>> vnuc_so = reduce(numpy.dot, (c.T, mol.intor('int1e_nuc_sph'), c))
>>> orbsym = symm.label_orb_symm(mol, mol.irrep_name, mol.symm_orb, c)
>>> symm.eigh(vnuc_so, orbsym)
(array([-4.50766885, -1.80666351, -1.7808565 , -1.7808565 , -1.74189134,
-0.98998583, -0.98998583, -0.40322226, -0.30242374, -0.07608981]),
...)
'''
return lib.eigh_by_blocks(h, labels=orbsym)
[docs]
def direct_prod(orbsym1, orbsym2, groupname='D2h'):
if groupname == 'SO3':
prod = orbsym1[:,None] ^ orbsym2
orbsym1_not_s = orbsym1 != 0
orbsym2_not_s = orbsym2 != 0
prod[orbsym1_not_s[:,None] & orbsym2_not_s != 0] = MULTI_IRREPS
prod[orbsym1[:,None] == orbsym2] = 0
elif groupname == 'Dooh':
orbsym1_octa = (orbsym1 // 10) * 8 + orbsym1 % 10
orbsym2_octa = (orbsym2 // 10) * 8 + orbsym2 % 10
prod = orbsym1_octa[:,None] ^ orbsym2_octa
prod = (prod % 8) + (prod // 8) * 10
orbsym1_irrepE = (orbsym1 >= 2) & (orbsym1 != 4) & (orbsym1 != 5)
orbsym2_irrepE = (orbsym2 >= 2) & (orbsym2 != 4) & (orbsym2 != 5)
prod[orbsym1_irrepE[:,None] & orbsym2_irrepE] = MULTI_IRREPS
prod[orbsym1[:,None] == orbsym2] = 0
elif groupname == 'Coov':
prod = orbsym1[:,None] ^ orbsym2
orbsym1_irrepE = orbsym1 >= 2
orbsym2_irrepE = orbsym2 >= 2
prod[orbsym1_irrepE[:,None] & orbsym2_irrepE] = MULTI_IRREPS
prod[orbsym1[:,None] == orbsym2] = 0
else: # D2h and subgroup
prod = orbsym1[:,None] ^ orbsym2
return prod
if __name__ == "__main__":
from pyscf import gto
from pyscf import scf
mol = gto.Mole()
mol.build(
atom = [['H', (0,0,0)], ['H', (0,0,1)]],
basis = {'H': 'cc-pvdz'},
symmetry = 1
)
mf = scf.RHF(mol)
mf.scf()
nao, nmo = mf.mo_coeff.shape
print(label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mf.mo_coeff))
numpy.random.seed(1)
u = numpy.random.random((nmo,nmo))*1e-2
u = scipy.linalg.expm(u - u.T)
mo = symmetrize_orb(mol, numpy.dot(mf.mo_coeff, u))
print(label_orb_symm(mol, mol.irrep_name, mol.symm_orb, mo))
orbsym = [0, 3, 0, 2, 5, 6]
res = route(7, 3, orbsym)
print(res, reduce(lambda x, y: x ^ y, [orbsym[i] for i in res]))