Skip to content

Commit 0b68e7c

Browse files
authored
Merge pull request #290 from Ao-chuba/fix/cubic-unit-detection
Fix cubic file unit detection for negative dimensions
2 parents 41c57c9 + d341829 commit 0b68e7c

4 files changed

Lines changed: 39 additions & 38 deletions

File tree

src/grid/cubic.py

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from sympy.functions.combinatorial.numbers import bell
2626

2727
from grid.basegrid import Grid, OneDGrid
28+
from .utils import ANGSTROM_TO_BOHR
2829

2930

3031
class _HyperRectangleGrid(Grid):
@@ -701,7 +702,6 @@ def from_cube(cls, fname, weight="Trapezoid", return_data=False):
701702
- ``atcorenums``\: Pseudo-number of :math:`M` atoms in the molecule.
702703
- ``atcoords``\: Cartesian coordinates of :math:`M` atoms in the molecule.
703704
- ``data``\: the grid data stored in a flattened one-dimensional array.
704-
- ``unit``\: the unit of the grid (either "angstrom" or "bohr").
705705
706706
"""
707707
fname = str(fname)
@@ -715,44 +715,37 @@ def from_cube(cls, fname, weight="Trapezoid", return_data=False):
715715

716716
def read_grid_line(line):
717717
"""Read a number and (x, y, z) coordinate from the cube file line."""
718-
words = line.split()
719-
return (
720-
int(words[0]),
721-
np.array([float(words[1]), float(words[2]), float(words[3])], float),
722-
# all coordinates in a cube file are in atomic units
723-
)
718+
npts, *vec = line.split()
719+
return int(npts), np.asarray(vec, dtype=float)
724720

725721
# number of atoms and origin of the grid
726722
natom, origin = read_grid_line(f.readline())
727723
# number of grid points in A direction and step vector A, and so on
728724
shape0, axis0 = read_grid_line(f.readline())
729725
shape1, axis1 = read_grid_line(f.readline())
730726
shape2, axis2 = read_grid_line(f.readline())
731-
axes = np.array([axis0, axis1, axis2], dtype=float)
732-
# if shape0, shape1, shape2 are negative, the units are in bohr
733-
# otherwise in angstrom
727+
axes = np.asarray([axis0, axis1, axis2], dtype=float)
728+
# if shape0 is negative the units are in angstroms otherwise atomic units.
729+
# this was verified with cube files generated from Gaussian on february 2026.
734730
# https://gaussian.com/cubegen/
735-
unit = "bohr" if shape0 < 0 else "angstrom"
736-
# print out the units detected for clarity
737-
print(f"Cube file units detected: {unit}")
731+
coordinates_in_angstrom = shape0 < 0
738732

739733
# Convert negative shape values to positive
740-
shape = np.array([abs(shape0), abs(shape1), abs(shape2)], dtype=int)
734+
shape = np.abs(np.asarray([shape0, shape1, shape2], dtype=int))
735+
if coordinates_in_angstrom:
736+
print(f"Cube file units detected: angstrom, converting to atomic units grid.")
737+
axes *= ANGSTROM_TO_BOHR
738+
origin *= ANGSTROM_TO_BOHR
741739

742740
# if return_data=False, only grid is returned
743741
if not return_data:
744742
return cls(origin, axes, shape, weight)
745743

746744
# otherwise, return the atomic numbers, coordinates, and the grid data as well
747745
def read_coordinate_line(line):
748-
"""Read atomic number and (x, y, z) coordinate from the cube file line."""
749-
words = line.split()
750-
return (
751-
int(words[0]),
752-
float(words[1]),
753-
np.array([float(words[2]), float(words[3]), float(words[4])], float),
754-
# all coordinates in a cube file are in atomic units
755-
)
746+
"""Read atomic number, charge, and (x, y, z) coordinates from cube file line."""
747+
atnum, charge, *coords = line.split()
748+
return int(atnum), float(charge), np.asarray(coords, dtype=float)
756749

757750
numbers = np.zeros(natom, int)
758751
# get the core charges
@@ -765,6 +758,9 @@ def read_coordinate_line(line):
765758
if pseudo_numbers[i] == 0.0:
766759
pseudo_numbers[i] = numbers[i]
767760

761+
if coordinates_in_angstrom:
762+
coordinates *= ANGSTROM_TO_BOHR
763+
768764
# load data stored in the cube file
769765
data = np.zeros(tuple(shape), float).ravel()
770766
counter = 0
@@ -781,7 +777,6 @@ def read_coordinate_line(line):
781777
"atcorenums": pseudo_numbers,
782778
"atcoords": coordinates,
783779
"data": data,
784-
"unit": unit,
785780
}
786781
return cls(origin, axes, shape, weight), cube_data
787782

src/grid/data/tests/cubegen_ch4_6_gen_negative.cube

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
Cubefile created with THEOCHEM Grid
22
OUTER LOOP: X, MIDDLE LOOP: Y, INNER LOOP: Z
3-
5 -6.512793 -6.512718 -6.512750
4-
-6 2.605101 0.000000 0.000000
5-
-6 0.000000 2.605101 0.000000
6-
-6 0.000000 0.000000 2.605101
7-
6 6.000000 -0.000041 0.000035 0.000003
8-
1 1.000000 1.051607 1.517467 0.942259
9-
1 1.000000 1.296786 -1.543671 -0.481272
10-
1 1.000000 -1.476881 -0.708836 1.269987
11-
1 1.000000 -0.871678 0.735176 -1.730958
3+
5 -3.446421 -3.446421 -3.446421
4+
-6 1.378560 0.000000 0.000000
5+
-6 0.000000 1.378560 0.000000
6+
-6 0.000000 0.000000 1.378560
7+
6 6.000000 -0.000021 0.000018 0.000001
8+
1 1.000000 0.556486 0.803009 0.498622
9+
1 1.000000 0.686229 -0.816875 -0.254678
10+
1 1.000000 -0.781531 -0.375099 0.672048
11+
1 1.000000 -0.461272 0.389038 -0.915983
1212
2.66011E-09 2.43339E-09 2.14921E-08 7.16256E-08 7.41998E-08 3.40400E-08
1313
1.69019E-08 2.07614E-08 2.34886E-07 9.17704E-07 4.01950E-07 1.05928E-07
1414
7.35455E-08 3.21547E-07 2.09291E-06 6.03301E-06 1.45834E-06 1.68969E-07

src/grid/tests/test_cubic.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from grid.cubic import Tensor1DGrids, UniformGrid, _HyperRectangleGrid
2929
from grid.onedgrid import GaussLaguerre, MidPoint
30+
from grid.utils import ANGSTROM_TO_BOHR
3031

3132

3233
class TestHyperRectangleGrid(TestCase):
@@ -1246,20 +1247,20 @@ def test_uniformgrid_from_cube_negative(self):
12461247

12471248
ref_grid = UniformGrid(origin, axes, shape)
12481249

1250+
# the precision of the numbers in this file is lower than reference, large atol needed
12491251
cubefile = files("grid") / "data" / "tests" / "cubegen_ch4_6_gen_negative.cube"
12501252
grid, cube_data = UniformGrid.from_cube(cubefile, return_data=True)
12511253

1252-
assert_allclose(grid._axes, axes)
1253-
assert_allclose(grid._origin, origin)
1254+
assert_allclose(grid._axes, axes, atol=1e-4)
1255+
assert_allclose(grid._origin, origin, atol=1e-4)
12541256
assert_allclose(grid._shape, shape)
12551257
assert_allclose(cube_data["atnums"], atnums)
1256-
assert_allclose(cube_data["atcoords"], atcoords)
1258+
assert_allclose(cube_data["atcoords"], atcoords, atol=1e-4)
12571259
assert_allclose(cube_data["atcorenums"], pseudo_numbers)
12581260
assert_allclose(cube_data["data"], data_vals)
1259-
assert_equal(cube_data["unit"], "bohr")
12601261

1261-
assert_allclose(grid.points, ref_grid.points)
1262-
assert_allclose(grid.weights, ref_grid.weights)
1262+
assert_allclose(grid.points, ref_grid.points, atol=1e-4)
1263+
assert_allclose(grid.weights, ref_grid.weights, atol=1e-6)
12631264

12641265
def test_uniformgrid_generate_cube(self):
12651266
r"""Test creating uniform cubic grid from cube example."""

src/grid/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121

2222
import numpy as np
2323
from scipy.special import sph_harm_y
24+
from scipy.constants import physical_constants, angstrom
25+
26+
_BOHR_RADIUS_M = physical_constants["Bohr radius"][0]
27+
BOHR_TO_ANGSTROM = _BOHR_RADIUS_M / angstrom
28+
ANGSTROM_TO_BOHR = 1.0 / BOHR_TO_ANGSTROM
2429

2530
_bragg = np.array(
2631
[

0 commit comments

Comments
 (0)