Quick Start

Poraquê exposes a single, unified ASE calculator, poraque.ase.Poraque. The method is chosen dynamically with mode='ks' (Kohn-Sham) or mode='of' (orbital-free). Positions and cells are given in Ångström and converted internally to atomic units (Bohr, Hartree); energies are returned in eV.

The unified ASE calculator

from ase import Atoms
from poraque.ase import Poraque
from poraque.core import SolverSettings

atoms = Atoms("H", positions=[[2.5, 2.5, 2.5]], cell=[5, 5, 5], pbc=True)

# Orbital-free DFT.
atoms.calc = Poraque(
    mode="of",
    grid_shape=(24, 24, 24),
    settings=SolverSettings(max_iter=60, mixing=0.1),
    external_kwargs={"a": 0.8},
)

energy = atoms.get_potential_energy()   # eV
forces = atoms.get_forces()             # eV / Å (numerical)

Switch to Kohn-Sham simply by passing mode="ks". The default OF-DFT stack is TFvW + Hartree + LDA (Dirac exchange + PW92 correlation) + External; KS-DFT defaults to LDA exchange-correlation with a Hartree term.

Poraque is a fully compliant ase.calculators.calculator.Calculator, so it plugs into ASE workflows such as geometry optimization.

Periodic crystals with k-points

For periodic solids, sample the Brillouin zone with a Monkhorst-Pack grid and use pseudopotentials to keep only the valence electrons:

from ase.build import bulk
from poraque.ase import Poraque
from poraque.core import SolverSettings

si = bulk("Si", "diamond", a=5.43)
si.calc = Poraque(
    mode="ks",
    grid_shape=(16, 16, 16),
    kpts=(4, 4, 4),                 # Monkhorst-Pack sampling (ase.dft.kpoints)
    pseudopotentials="auto",        # 4 valence electrons per Si atom
    settings=SolverSettings(max_iter=40, mixing=0.5),
)
print("Total energy (eV):", si.get_potential_energy())

Low-level drivers

The System/Grid-level drivers behind the calculator are also available directly when you need full control over the functional stack:

from poraque import OFDFTCalculator
from poraque.functionals import TFvW, Hartree, LDA, External
from poraque.potentials import build_external_potential
from poraque.core import System, Grid

system = System.from_ase(atoms)
grid = Grid((24, 24, 24), system.cell, system.pbc)
v_ext = build_external_potential(grid, system, kind="soft", a=0.8)
result = OFDFTCalculator(
    system, grid, [TFvW(lambda_vw=1.0), Hartree(), LDA(), External(v_ext)]
).calculate()
print("Total energy (Hartree):", result.total_energy)

Frozen-Density Embedding

Partition a system into subsystems, each with its own solver (OF or KS), and run freeze-and-thaw:

import numpy as np
from poraque.core import Grid, System, SolverSettings
from poraque.backends.numpy import NumpyBackend
from poraque.fde import Subsystem, FDEEngine

cell = np.eye(3) * 12.0
grid = Grid((24, 24, 24), cell, pbc=True)

a = System([[5.0, 6.0, 6.0]], [1], cell, electrons=1)
b = System([[7.0, 6.0, 6.0]], [1], cell, electrons=1)

engine = FDEEngine(
    [Subsystem(a, method="ks"), Subsystem(b, method="of")],   # mixed KS-in-OF
    grid, NumpyBackend(),
    settings=SolverSettings(max_iter=8, tolerance=1e-4),
)
result = engine.freeze_and_thaw()
print("Embedded total energy:", result.total_energy)