Skip to content

Commit d7dc190

Browse files
Add OrbitalEvaluator and Gaussian cube writer utility
Adds a public, host-parallel point-set evaluator for molecular orbitals and densities, plus a self-contained Gaussian cube file writer. The two are deliberately decoupled: OrbitalEvaluator returns plain numerical arrays, and write_cube depends only on Molecule + raw field data. Either can be used independently. OrbitalEvaluator (include/gauxc/orbital_evaluator.hpp, src/orbital_evaluator.cxx) - Constructed from a BasisSet; caches the LocalHostWorkDriver internally so it can be reused across many evaluations for the same molecule. - eval_orbital / eval_orbitals: chi_j(r) = sum_mu C[mu, j] * phi_mu(r), one or many MOs at a time on an arbitrary AoS point set. - eval_density: rho(r) = sum_{mu,nu} D[mu, nu] * phi_mu(r) * phi_nu(r), for a symmetric AO density matrix. - Internally batches points with an adaptive batch size (~16 MB AO scratch per thread) and parallelises the batch loop with OpenMP. Each thread owns its own scratch. - Currently Host-only; the public API takes ExecutionSpace so device backends can be slotted in later without an ABI break. write_cube + CubeGrid (include/gauxc/external/cube.hpp, src/external/cube.cxx) - Standard Gaussian cube text format, byte-compatible with the output of PySCF / Gaussian / NWChem cubegen utilities. - CubeGrid::from_molecule builds a default axis-aligned grid that encloses the molecule with a configurable margin (default 3.0 Bohr, matching PySCF cubegen). - The data block is formatted in parallel: each (ix, iy) row is serialised independently into a pre-sized byte buffer and committed with a single fwrite at the end. The per-value formatter is a hand-rolled %13.5E that matches glibc snprintf bit-for-bit on the fast path (1-2 digit exponent) and falls back to snprintf for the rare 3-digit-exponent case. This is the same writer that produced the measured 5x end-to-end speedup over PySCF cubegen on the QDK-Chemistry workloads that motivated this PR. - Self-contained: no third-party dependencies, no GauXC kernel coupling. Tests (tests/orbital_evaluator_test.cxx, [orbital_evaluator] / [cube]): - Water cc-pVDZ kernel checks against direct eval_collocation reference: one-hot MO, multi-MO contraction, identity density, rank-1 density. - CubeGrid layout: first/last point coordinates, iz-fastest ordering. - write_cube round-trip: parses back the written file and verifies header (atoms, origin, axes) and field values to %13.5E precision. - write_cube formatter: byte-exact comparison of the data block against glibc snprintf("%13.5E", v) for a battery of edge-case values (signed zero, near-power-of-ten boundaries, 3-digit exponents). - All 1570 assertions pass; existing [collocation] suite unaffected.
1 parent 2c4a2bd commit d7dc190

8 files changed

Lines changed: 1087 additions & 0 deletions

File tree

include/gauxc/external/cube.hpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* GauXC Copyright (c) 2020-2024, The Regents of the University of California,
3+
* through Lawrence Berkeley National Laboratory (subject to receipt of
4+
* any required approvals from the U.S. Dept. of Energy).
5+
*
6+
* (c) 2024-2026, Microsoft Corporation
7+
*
8+
* All rights reserved.
9+
*
10+
* See LICENSE.txt for details
11+
*/
12+
#pragma once
13+
14+
#include <array>
15+
#include <cstdint>
16+
#include <string>
17+
#include <vector>
18+
19+
#include <gauxc/molecule.hpp>
20+
21+
namespace GauXC {
22+
23+
/** @brief Specification of a Gaussian-cube-format 3D rectangular grid.
24+
*
25+
* The grid is axis-aligned with the Cartesian frame (the standard
26+
* cube-format axis vectors are simply (spacing[0], 0, 0), (0, spacing[1], 0),
27+
* (0, 0, spacing[2])). All quantities are in atomic units (Bohr).
28+
*
29+
* Total number of points: nx*ny*nz. Storage cost per scalar field:
30+
* 8*nx*ny*nz bytes (double precision).
31+
*/
32+
struct CubeGrid {
33+
std::array<double, 3> origin{0.0, 0.0, 0.0}; ///< (0,0,0) corner, Bohr
34+
std::array<double, 3> spacing{0.2, 0.2, 0.2}; ///< Step on each axis, Bohr
35+
int64_t nx = 80;
36+
int64_t ny = 80;
37+
int64_t nz = 80;
38+
39+
/// Total number of grid points.
40+
int64_t num_points() const noexcept { return nx * ny * nz; }
41+
42+
/** @brief Build a default grid that tightly encloses a molecule.
43+
*
44+
* The bounding box of the atomic centres is extended by `margin` Bohr on
45+
* each side and discretised with the requested number of points along
46+
* each axis. Spacing is chosen so that the first and last grid points
47+
* coincide with the extended bounding-box corners (PySCF cubegen
48+
* convention).
49+
*
50+
* @param mol Molecule whose atomic centres define the bounding box.
51+
* @param nx,ny,nz Number of grid points along each axis.
52+
* @param margin Margin (Bohr) added on each side. Default 3.0 matches
53+
* the PySCF cubegen default.
54+
*/
55+
static CubeGrid from_molecule(const Molecule& mol,
56+
int64_t nx = 80, int64_t ny = 80,
57+
int64_t nz = 80, double margin = 3.0);
58+
59+
/** @brief Materialise the grid points as an AoS coordinate array.
60+
*
61+
* Returns a vector of length 3*num_points() laid out in row-major
62+
* (ix, iy, iz) order with iz varying fastest, matching the field-element
63+
* ordering expected by `write_cube`. Suitable to be passed directly as
64+
* the `points` argument to `OrbitalEvaluator::eval_orbital` /
65+
* `eval_density`.
66+
*/
67+
std::vector<double> points() const;
68+
};
69+
70+
/** @brief Write a Gaussian cube file.
71+
*
72+
* Field layout: row-major (ix, iy, iz) with iz varying fastest. Length must
73+
* equal `grid.num_points()`. Values are written in fixed `%13.5E` format
74+
* with six values per line, matching the standard cube-file convention
75+
* produced by PySCF / Gaussian / NWChem.
76+
*
77+
* This routine performs only file I/O; it has no dependency on the GauXC
78+
* numerical kernels and may be used independently of `OrbitalEvaluator`.
79+
*
80+
* @param path Output path. Parent directory must exist.
81+
* @param mol Molecule (atomic numbers + Cartesian coordinates in Bohr)
82+
* written into the cube-file header.
83+
* @param grid Grid specification.
84+
* @param field Length-`grid.num_points()` scalar field.
85+
* @param comment Optional first comment line (the second comment line is
86+
* always "Generated by GauXC"). If empty, a default first
87+
* line is used.
88+
*/
89+
void write_cube(const std::string& path,
90+
const Molecule& mol,
91+
const CubeGrid& grid,
92+
const double* field,
93+
const std::string& comment = "");
94+
95+
} // namespace GauXC
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* GauXC Copyright (c) 2020-2024, The Regents of the University of California,
3+
* through Lawrence Berkeley National Laboratory (subject to receipt of
4+
* any required approvals from the U.S. Dept. of Energy).
5+
*
6+
* (c) 2024-2026, Microsoft Corporation
7+
*
8+
* All rights reserved.
9+
*
10+
* See LICENSE.txt for details
11+
*/
12+
#pragma once
13+
14+
#include <cstdint>
15+
#include <memory>
16+
17+
#include <gauxc/basisset.hpp>
18+
#include <gauxc/enums.hpp>
19+
20+
namespace GauXC {
21+
22+
/** @brief Evaluate molecular orbitals and densities on arbitrary point sets.
23+
*
24+
* Wraps the host collocation kernel exposed by the local work driver with a
25+
* thread-parallel batched evaluation loop, hiding the driver factory and
26+
* the per-thread AO scratch from callers. The class is designed to be
27+
* constructed once per molecule and reused across many evaluations (e.g.
28+
* one per active-space orbital) without re-initialising the driver.
29+
*
30+
* The evaluator is intentionally decoupled from any particular file format:
31+
* it returns plain numerical arrays. For Gaussian cube file I/O, see
32+
* `gauxc/external/cube.hpp`.
33+
*
34+
* Currently only `ExecutionSpace::Host` is supported; passing any other
35+
* value will throw on construction. Device support can be added later by
36+
* routing through a device-side collocation driver while keeping the same
37+
* signatures.
38+
*/
39+
class OrbitalEvaluator {
40+
public:
41+
/** @brief Construct an evaluator bound to a basis set.
42+
*
43+
* @param basis Basis set (copied internally).
44+
* @param exec Execution space; only `ExecutionSpace::Host` is supported.
45+
*/
46+
explicit OrbitalEvaluator(BasisSet<double> basis,
47+
ExecutionSpace exec = ExecutionSpace::Host);
48+
49+
~OrbitalEvaluator() noexcept;
50+
51+
// Non-copyable, movable
52+
OrbitalEvaluator(const OrbitalEvaluator&) = delete;
53+
OrbitalEvaluator& operator=(const OrbitalEvaluator&) = delete;
54+
OrbitalEvaluator(OrbitalEvaluator&&) noexcept;
55+
OrbitalEvaluator& operator=(OrbitalEvaluator&&) noexcept;
56+
57+
/// Number of basis functions (rows of the AO matrix).
58+
int32_t nbf() const noexcept;
59+
60+
/// Underlying basis set.
61+
const BasisSet<double>& basis() const noexcept;
62+
63+
/** @brief Evaluate a single MO chi(r) = sum_mu C[mu] * phi_mu(r).
64+
*
65+
* @param[in] npts Number of evaluation points.
66+
* @param[in] points AoS array of length 3*npts: (x0,y0,z0,x1,y1,z1,...)
67+
* in atomic units (Bohr).
68+
* @param[in] C MO coefficient vector, length nbf().
69+
* @param[out] out Length-npts array of MO values.
70+
*/
71+
void eval_orbital(int64_t npts, const double* points,
72+
const double* C, double* out) const;
73+
74+
/** @brief Evaluate `nmo` MOs simultaneously.
75+
*
76+
* Storage layout:
77+
* - `C` : (nbf, nmo) column-major, leading dimension `ldc` (>= nbf).
78+
* - `out` : (npts, nmo) column-major, leading dimension `ldo` (>= npts).
79+
*
80+
* Equivalent to calling `eval_orbital` `nmo` times but amortises the AO
81+
* collocation evaluation across all MOs (single AO buffer, GEMM contraction).
82+
*/
83+
void eval_orbitals(int64_t npts, const double* points,
84+
int32_t nmo, const double* C, int64_t ldc,
85+
double* out, int64_t ldo) const;
86+
87+
/** @brief Evaluate the electron density
88+
* rho(r) = sum_{mu,nu} D[mu,nu] * phi_mu(r) * phi_nu(r).
89+
*
90+
* @param[in] npts Number of evaluation points.
91+
* @param[in] points AoS array of length 3*npts in Bohr.
92+
* @param[in] D (nbf, nbf) symmetric density matrix, column-major,
93+
* leading dimension `ldd` (>= nbf).
94+
* @param[out] out Length-npts array of density values.
95+
*/
96+
void eval_density(int64_t npts, const double* points,
97+
const double* D, int64_t ldd,
98+
double* out) const;
99+
100+
private:
101+
struct Impl;
102+
std::unique_ptr<Impl> pimpl_;
103+
};
104+
105+
} // namespace GauXC

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ add_library( gauxc
4141
molgrid_impl.cxx
4242
molgrid_defaults.cxx
4343
atomic_radii.cxx
44+
orbital_evaluator.cxx
4445
)
4546

4647
target_include_directories( gauxc

src/external/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
#
1010
# See LICENSE.txt for details
1111
#
12+
# Cube file writer is a self-contained utility (no third-party deps); always build it.
13+
target_sources( gauxc PRIVATE cube.cxx )
14+
1215
if( GAUXC_ENABLE_HDF5 )
1316
include(FetchContent)
1417
find_package(HDF5)

0 commit comments

Comments
 (0)