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
)