Molecules¶
The Molecule class holds a list of Atom objects, a total charge,
and a spin multiplicity.
from vibeqc import Atom, Molecule
mol = Molecule(
atoms=[
Atom(8, [0.0, 0.0, 0.0]), # positions in bohr
Atom(1, [0.0, 1.43, -0.98]),
Atom(1, [0.0, -1.43, -0.98]),
],
charge=0,
multiplicity=1,
)
Reading from files¶
XYZ coordinates (positions in Ångström are auto-converted to bohr):
from vibeqc import from_xyz
mol = from_xyz("examples/h2o.xyz")
mol = from_xyz("examples/oh.xyz", charge=-1)
Anything ASE reads — CIF, PDB, POSCAR, Gaussian inputs, etc.:
from ase.io import read
atoms = read("crystal.cif")
# Convert to a vibe-qc Molecule (for molecules) or build a
# PeriodicSystem (for solids) — see the ASE integration page.
Programmatic construction¶
Atom(Z, xyz) takes the atomic number and a Cartesian position in
bohr. The position can be a list, tuple, or NumPy array. The
underlying molecule API exposes:
mol.atoms # list[Atom] — property
mol.charge # int — property
mol.multiplicity # int — property
mol.n_electrons() # int, derived from atomic numbers − charge
mol.nuclear_repulsion() # Σ_{A<B} Z_A Z_B / R_AB (Hartree)
Cached values (atoms, charge, multiplicity) are exposed as
properties — access them without parentheses. Computed quantities
(n_electrons(), nuclear_repulsion()) remain methods because they
do non-trivial work each call.
Configuring open-shell systems¶
vibe-qc’s spin information lives on the Molecule, not on the
SCF-driver options. Triplet O₂:
import vibeqc as vq
o2 = vq.Molecule(
atoms=[vq.Atom(8, [0, 0, +1.14]),
vq.Atom(8, [0, 0, -1.14])],
multiplicity=3, # 2S + 1 = 3 → triplet
)
basis = vq.BasisSet(o2, "6-31g*")
result = vq.run_uhf(o2, basis) # α/β occupations derived
There is no spin field on UHFOptions, UKSOptions,
PeriodicRHFOptions, or any other SCF-options class — setting
UHFOptions().spin = 2 raises AttributeError. The single source
of truth for spin is Molecule.multiplicity, which keeps every
RHF / UHF / RKS / UKS run against the same molecule consistent.
This is a common point of confusion for users coming from PySCF
(where mol.spin lives on the molecule but the SCF driver also
accepts spin overrides) or from input-file codes (where spin sits
in the run block).
Molecule.__init__ validates that multiplicity is consistent
with the electron count: n_electrons and multiplicity must have
the same parity, otherwise construction raises ValueError. For
charged species set charge= alongside multiplicity=:
oh_minus = vq.Molecule(
atoms=[vq.Atom(8, [0, 0, 0]), vq.Atom(1, [0, 0, 1.83])],
charge=-1, # OH⁻
multiplicity=1, # singlet
)