Point-group symmetry tools on the PySCF atomic-orbital basis
Project description
pgsym
Point-group symmetry tools on the PySCF atomic-orbital basis.
pgsym builds matrix representations of molecular point-group operations directly on the
AO basis of a PySCF Mole, and uses them to:
- detect the point group from the geometry, or build any named group (
Cₙ,Cₙᵥ,Dₙₕ,Dₙd,Sₙ, cubicT/Td/Th/O/Oh, icosahedralI/Ih, linearC∞ᵥ/D∞ₕ, atomicO(3)); - compute character tables (Burnside–Dixon) with Mulliken labels and diatomic term symbols;
- build irrep projectors and a symmetry-adapted basis, and block-diagonalise any symmetry-invariant AO operator (Fock, density, …) per irrep;
- label MOs by irrep and report per-irrep occupations (real or complex coefficients);
- run symmetry-conforming SCF with optional per-irrep occupation constraints;
- symmetrise geometries/gradients and run symmetry-constrained geometry optimisation.
It is complementary to PySCF's own symmetry handling: operator-centric, works on any Mole
(no symmetry=True required), and interoperates with PySCF's irrep names.
Installation
pip install -e . # runtime (numpy, scipy, pyscf)
pip install -e .[geomopt] # + geomeTRIC, for sym_optimize (geometry optimisation)
pip install -e .[all] # everything, incl. test deps
Requires Python ≥ 3.9.
Quickstart
Detect the group, run a symmetry-conforming RHF, and print an irrep-labelled report:
import pyscf.scf
from pyscf import gto
from pgsym import SymmetryDetection, apply_sym_eig
mol = gto.M(atom="O 0 0 0.117; H 0 0.757 -0.469; H 0 -0.757 -0.469", basis="sto-3g")
pg = SymmetryDetection(mol).point_group # -> C2v
pg.irrep_names() # assign Mulliken labels (A1, B1, ...)
mf = pyscf.scf.RHF(mol)
apply_sym_eig(mf, pg) # every eig step now stays symmetry-pure
mf.kernel()
mf.analyze() # irrep-labelled MO table + per-irrep occupations
Guided tour
Point groups: construct or detect
from pgsym import PointGroup, SymmetryDetection, Rotation, Reflection
pg = PointGroup.C2v(mol) # named factory (axis/origin/tol optional)
pg = SymmetryDetection(mol).point_group # auto-detect from geometry
# or from explicit generators (the group is the closure):
c2 = Rotation(mol, [0, 0, 1]).representation(2)
sigma = Reflection(mol, [0, 1, 0]).representation()
pg = PointGroup(mol, [c2, sigma], name="C2v")
pg.order # 4
pg.is_symmetric(mol.intor("int1e_ovlp")) # True: the overlap is group-invariant
Character tables, projectors, and symmetry-blocked diagonalisation
pg.character_table(); pg.irrep_names()
pg.print_character_table()
pg.decompose_ao() # AO multiplicity of each irrep
P = pg.irrep_projectors() # {label: (nao, nao) real projector}, sum = I
S = mol.intor("int1e_ovlp")
H = mol.intor("int1e_kin") + mol.intor("int1e_nuc")
e, C, labels = pg.symmetry_eigh(H, S) # generalised eigenproblem, solved per irrep
MO labelling and per-irrep occupations
from pgsym import classify_mo_irreps, irrep_occupations
labels = classify_mo_irreps(mo_coeff, S, pg) # mo_coeff shape (2, nao, nmo); real OR complex
occ = irrep_occupations(dm, pg, S) # {irrep: electron count}
Symmetry-conforming SCF and constrained occupations
apply_sym_eig(mf, pg) overrides only the eig step of a mean-field object, so every
iteration diagonalises the Fock per irrep and the MOs stay symmetry-pure — works for RHF,
UHF, ROHF, and the x2c classes (call it after mf.x2c()); mf.pgs = None restores the
original solver exactly.
Passing irrep_nelec additionally constrains the occupations per irrep (PySCF-style): listed
irreps are held at their electron count and the remaining electrons fill by aufbau. This lets
the SCF hold — or converge to — a specific symmetry root, including an excited configuration
ordinary aufbau would never reach:
mf = pyscf.scf.RHF(mol)
apply_sym_eig(mf, pg, irrep_nelec={"A1": 6, "B1": 2, "B2": 2}) # {irrep: n} or {irrep: (na, nb)}
mf.kernel()
A scalar n splits na = n - n//2, nb = n//2 for UHF/ROHF (it is the irrep total for RHF);
a (na, nb) tuple is used verbatim. The standalone symmetry_constrained_occupation(H, pg, occupations, ...) does the same filling for a given Fock without running an SCF.
Linear molecules and PySCF irrep names
Linear molecules (C∞v/D∞h) are labelled with diatomic term symbols (Sigmag+, Pig,
Deltau, …), which map losslessly to PySCF's linear-group irrep names (A1g, E1gx, …):
from pgsym import pyscf_to_term, term_to_pyscf
pyscf_to_term("Dooh", "A1u") # -> "Sigmau+"
term_to_pyscf("Dooh", "Piu") # -> ["E1ux", "E1uy"]
Set pg.pyscf_names = True (or pass pyscf_names=True to classify_mo_irreps,
naming="pyscf" to irrep_occupations) to render output in PySCF naming.
symmetry_constrained_occupation and irrep_nelec accept occupation targets keyed by internal
labels, term symbols, or PySCF names interchangeably.
For PySCF's finite abelian groups (D2h and its subgroups: C2h, C2v, D2, Cs, Ci,
C2, C1) the Mulliken labels already coincide with PySCF's (including Cs = A'/A"), so
PySCF irrep names work directly; pyscf_to_mulliken / mulliken_to_pyscf provide the
(identity) conversion. Note the C₂ᵥ B1/B2 plane convention can differ from PySCF's — when
cross-checking a symmetry-breaking state against PySCF, compare the physical state (read its
per-irrep counts in this package's labels) rather than matching by irrep name.
Geometry symmetrisation and constrained optimisation
from pgsym import symmetrize_molecule, sym_optimize
new_mol, report = symmetrize_molecule(mol, tol=1e-1) # snap a distorted geometry onto exact symmetry
mf = pyscf.scf.RHF(mol)
mol_eq = sym_optimize(mf) # relax while staying in the point group
sym_optimize auto-detects the group from the starting geometry and needs the geomopt extra;
apply_sym_grad(mf, pg) exposes the symmetry-projected gradient scanner for your own optimiser
call, and symmetrize_gradient(grad, mol, pg) projects a single (natm, 3) gradient.
Examples
Runnable, self-contained scripts live in examples/ — run any directly, e.g.
python examples/01_point_groups.py. See examples/README.md for the
full index.
| # | Script | Shows |
|---|---|---|
| 01 | 01_point_groups.py |
building a PointGroup (factory / generators); group invariance |
| 02 | 02_symmetry_detection.py |
auto-detecting the point group of several molecules |
| 03 | 03_character_tables.py |
character table, Mulliken names, AO decomposition |
| 04 | 04_projectors_and_blocked_diag.py |
projectors and per-irrep symmetry_eigh |
| 05 | 05_mo_irreps_and_occupations.py |
MO irrep labels and per-irrep occupations |
| 06 | 06_constrained_occupation.py |
symmetry_constrained_occupation (degenerate split; capacity guard) |
| 07 | 07_symmetry_conforming_scf.py |
apply_sym_eig; analyze(); the pgs=None fallback |
| 08 | 08_constrained_occupation_scf.py |
irrep_nelec; an excited root cross-checked against PySCF |
| 09 | 09_linear_molecules.py |
linear-group term symbols ↔ PySCF names |
| 10 | 10_symmetrize_geometry.py |
symmetrize_molecule |
| 11 | 11_symmetry_constrained_optimization.py |
sym_optimize / apply_sym_grad (needs [geomopt]) |
Documentation
The full reference manual is docs/manual.pdf (build with make in
docs/), covering:
- Overview & installation — capabilities, package layout, and how it relates to PySCF.
- Theory — the group-theoretic background and algorithms (Burnside–Dixon character tables, isotypic projectors, symmetry-adapted bases, Reynolds averaging).
- API reference — every public class and function, with signatures and source references.
- Worked examples — annotated versions of the scripts above, with expected output.
Testing
pytest
The suite runs the standalone verification scripts under tests/ as subprocesses. Real-SCF
tests are skipped automatically if the BLAS backend can't be loaded.
License
GPL-3.0-or-later. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pgsym-0.2.1.tar.gz.
File metadata
- Download URL: pgsym-0.2.1.tar.gz
- Upload date:
- Size: 73.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39f0ef481bbd75ca9a8776cce52f9f751929349599c67469e8b48fcbbc58a882
|
|
| MD5 |
e6cd3e6137913a03232cd7f69fde38de
|
|
| BLAKE2b-256 |
7e46ce3750078a31a51724d4fcf70dbbf869fa4cfc722ab92df3c79d2267b206
|
File details
Details for the file pgsym-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pgsym-0.2.1-py3-none-any.whl
- Upload date:
- Size: 70.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f89e2fa92e808df72c45bc446f9c3330cc4cf6e9a11e3d3d261ede1f64353ec
|
|
| MD5 |
927b0c66ca6bbd7cc7a3c84c20c7ab82
|
|
| BLAKE2b-256 |
cf7db7ed38e37c62a54a98e0c51e291c52e10576cab655bf203ed6845a95bdc4
|