From 40449808c76d1fa10deada48aea223cc0fa3462a Mon Sep 17 00:00:00 2001 From: dhruvDev23 Date: Sat, 7 Feb 2026 13:36:21 +0530 Subject: [PATCH 1/3] Adds get_atomic_property method (issue #216) --- src/grid/molgrid.py | 43 +++++++++++++++++++++++++++++ src/grid/tests/test_molgrid.py | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/grid/molgrid.py b/src/grid/molgrid.py index 4ad59532d..663952f0f 100644 --- a/src/grid/molgrid.py +++ b/src/grid/molgrid.py @@ -611,6 +611,49 @@ def __getitem__(self, index: int): ) return self._atgrids[index] + def get_atomic_property(self, property_values): + """Convert property at grid points to property per atom. + + This method takes a property evaluated at all grid points and calculated how much + of that propery belongs to each atom in the molecule. + + Parameters + ---------- + property_values : np.narray + Must have the same length as the number of points in the molecular grid. + """ + + # check if the input is valid + if not isinstance(property_values, np.ndarray): + raise TypeError( + f"property_values must be of numpy array type, got {type(property_values)}" + ) + + if property_values.size != self.size: + raise ValueError( + "property_values is not the same size as grid. \n" + f"property_values.size: {property_values.size}, self.size: {self.size}" + ) + + # initialize output array + num_atoms = len(self.atcoords) + atomic_property = np.zeros(num_atoms) + + # calculate property for each atom + for i in range(num_atoms): + # get the range of grid points for each atom + start_index = self.indices[i] + end_index = self.indices[i + 1] + + # finding property of each atom + atomic_property[i] = np.sum( + property_values[start_index:end_index] + * self.aim_weights[start_index:end_index] + * self.atweights[start_index:end_index] + ) + + return atomic_property + def _generate_default_rgrid(atnum: int): r""" diff --git a/src/grid/tests/test_molgrid.py b/src/grid/tests/test_molgrid.py index 0ca52b3ec..8614c8ef5 100644 --- a/src/grid/tests/test_molgrid.py +++ b/src/grid/tests/test_molgrid.py @@ -810,6 +810,55 @@ def test_integrate_hirshfeld_weights_pair_1s(self): occupation = mg.integrate(fn) assert_almost_equal(occupation, 2.5, decimal=5) + def test_get_atomic_property_constant(self): + """Test with constant property values""" + coords = np.array([[0, 0, -0.5], [0, 0, 0.5]]) + mg = MolGrid.from_preset( + atnums=np.array([1, 1]), + atcoords=coords, + preset="coarse", + aim_weights=BeckeWeights(order=3), + ) + + property_values = np.ones(mg.size) + atomic_properties = mg.get_atomic_property(property_values) + + assert atomic_properties.size == 2 + assert_allclose(np.sum(atomic_properties), mg.integrate(property_values)) + + def test_get_atomic_property_wrong_size(self): + """Test error handling for wrong array size""" + coords = np.array([[0, 0, -0.5], [0, 0, 0.5]]) + mg = MolGrid.from_preset( + atnums=np.array([1, 1]), + atcoords=coords, + preset="coarse", + aim_weights=BeckeWeights(order=3), + ) + + wrong_size = np.ones(mg.size + 1) + with pytest.raises(ValueError, match="property_values is not the same size as grid"): + mg.get_atomic_property(wrong_size) + + def test_get_atomic_property_nonuniform(self): + """Test with non-uniform property(distance from origin)""" + coords = np.array([[0, 0, -0.5], [0, 0, 0.5]]) + mg = MolGrid.from_preset( + atnums=np.array([1, 1]), + atcoords=coords, + preset="coarse", + aim_weights=BeckeWeights(order=3), + ) + + property_values = np.linalg.norm(mg.points, axis=1) + atomic_properties = mg.get_atomic_property(property_values) + + # Due to symmetry, both atoms should have equal properties + assert atomic_properties.size == 2 + + assert_allclose(atomic_properties[0], atomic_properties[1], rtol=1e-3) + assert_allclose(np.sum(atomic_properties), mg.integrate(property_values), rtol=1e-3) + def test_interpolation_with_gaussian_center(): r"""Test interpolation with molecular grid of sum of two Gaussian examples.""" From 0aaac0cb652af5bce618b3711997a80b48fe88b3 Mon Sep 17 00:00:00 2001 From: Dhruv Kumar Date: Mon, 9 Feb 2026 20:34:33 +0530 Subject: [PATCH 2/3] Update src/grid/molgrid.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/grid/molgrid.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/grid/molgrid.py b/src/grid/molgrid.py index 663952f0f..59fed7894 100644 --- a/src/grid/molgrid.py +++ b/src/grid/molgrid.py @@ -614,13 +614,19 @@ def __getitem__(self, index: int): def get_atomic_property(self, property_values): """Convert property at grid points to property per atom. - This method takes a property evaluated at all grid points and calculated how much - of that propery belongs to each atom in the molecule. + This method takes a property evaluated at all grid points and calculates how much + of that property belongs to each atom in the molecule. Parameters ---------- - property_values : np.narray + property_values : np.ndarray Must have the same length as the number of points in the molecular grid. + + Returns + ------- + np.ndarray + One-dimensional array of length equal to the number of atoms, containing + the property value associated with each atom. """ # check if the input is valid From 9a87a6fc5772ee6dbf28d4a4661a43b874d9d908 Mon Sep 17 00:00:00 2001 From: dhruvDev23 Date: Mon, 9 Feb 2026 21:09:48 +0530 Subject: [PATCH 3/3] update get_atomic_property and test_get_atomic_property_wrong_size --- src/grid/molgrid.py | 9 ++++----- src/grid/tests/test_molgrid.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/grid/molgrid.py b/src/grid/molgrid.py index 59fed7894..2a746dc49 100644 --- a/src/grid/molgrid.py +++ b/src/grid/molgrid.py @@ -635,10 +635,11 @@ def get_atomic_property(self, property_values): f"property_values must be of numpy array type, got {type(property_values)}" ) + property_values = np.asarray(property_values).reshape(-1) if property_values.size != self.size: raise ValueError( - "property_values is not the same size as grid. \n" - f"property_values.size: {property_values.size}, self.size: {self.size}" + "property_values must be one-dimensional with the same length as the grid.\n" + f"property_values.shape: {property_values.shape}, expected: ({self.size},)" ) # initialize output array @@ -653,9 +654,7 @@ def get_atomic_property(self, property_values): # finding property of each atom atomic_property[i] = np.sum( - property_values[start_index:end_index] - * self.aim_weights[start_index:end_index] - * self.atweights[start_index:end_index] + property_values[start_index:end_index] * self.weights[start_index:end_index] ) return atomic_property diff --git a/src/grid/tests/test_molgrid.py b/src/grid/tests/test_molgrid.py index 8614c8ef5..95474618e 100644 --- a/src/grid/tests/test_molgrid.py +++ b/src/grid/tests/test_molgrid.py @@ -837,7 +837,7 @@ def test_get_atomic_property_wrong_size(self): ) wrong_size = np.ones(mg.size + 1) - with pytest.raises(ValueError, match="property_values is not the same size as grid"): + with pytest.raises(ValueError, match="property_values must be one-dimensional"): mg.get_atomic_property(wrong_size) def test_get_atomic_property_nonuniform(self):