Skip to content

Commit

Permalink
Merge pull request #1096 from glotzerlab/fix/voronoi-segfault
Browse files Browse the repository at this point in the history
Compute Class NeighborList Lifetime
  • Loading branch information
tommy-waltmann committed Apr 21, 2023
2 parents bde386f + 93fbd43 commit 9c35778
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 6 deletions.
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to
### Added
* Filter neighborlists with `freud.locality.FilterSANN` and `freud.locality.FilterRAD`.

### Fixed
* Neighborlists generated by certain compute objects now exist after the compute object is garbage collected.

## v2.12.1 -- 2022-12-05

### Added
Expand Down
1 change: 1 addition & 0 deletions doc/source/reference/credits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ Tommy Waltmann
* Normalize n(r) in ``RDF`` class by number of query points.
* Contributed code, design, documentation, and testing for ``freud.locality.FilterSANN`` class.
* Contributed code, design, documentation, and testing for ``freud.locality.FilterRAD`` class.
* Fixed segfault in neighborlists owned by compute objects.

Maya Martirossyan

Expand Down
12 changes: 9 additions & 3 deletions freud/environment.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,9 @@ cdef class LocalDescriptors(_PairCompute):
def nlist(self):
""":class:`freud.locality.NeighborList`: The neighbor list from the
last compute."""
return freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist = freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist._compute = self
return nlist

@_Compute._computed_property
def sph(self):
Expand Down Expand Up @@ -1013,7 +1015,9 @@ cdef class AngularSeparationNeighbor(_PairCompute):
def nlist(self):
""":class:`freud.locality.NeighborList`: The neighbor list from the
last compute."""
return freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist = freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist._compute = self
return nlist


cdef class AngularSeparationGlobal(_Compute):
Expand Down Expand Up @@ -1183,7 +1187,9 @@ cdef class LocalBondProjection(_PairCompute):
def nlist(self):
""":class:`freud.locality.NeighborList`: The neighbor list from the
last compute."""
return freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist = freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist._compute = self
return nlist

@_Compute._computed_property
def projections(self):
Expand Down
1 change: 1 addition & 0 deletions freud/locality.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ cdef class NeighborQuery:
cdef class NeighborList:
cdef freud._locality.NeighborList * thisptr
cdef char _managed
cdef freud.util._Compute _compute

cdef freud._locality.NeighborList * get_ptr(self)
cdef void copy_c(self, NeighborList other)
Expand Down
14 changes: 12 additions & 2 deletions freud/locality.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ cdef class NeighborList:
# Cython won't assign NULL without cast
self.thisptr = <freud._locality.NeighborList *> NULL if _null \
else new freud._locality.NeighborList()
self._compute = None

def __dealloc__(self):
if self._managed:
Expand Down Expand Up @@ -780,6 +781,10 @@ cdef NeighborList _nlist_from_cnlist(freud._locality.NeighborList *c_nlist):
any compute method that requires a :class:`~.NeighborList` (i.e. cannot do
with just a :class:`~.NeighborQuery`) should also expose the internally
computed :class:`~.NeighborList` using this method.
Args:
c_nlist (freud._locality.NeighborList *):
C++ neighborlist object.
"""
cdef NeighborList result
result = NeighborList()
Expand Down Expand Up @@ -1291,6 +1296,7 @@ cdef class Voronoi(_Compute):
:class:`~.locality.NeighborList`: Neighbor list.
"""
self._nlist = _nlist_from_cnlist(self.thisptr.getNeighborList().get())
self._nlist._compute = self
return self._nlist

def __repr__(self):
Expand Down Expand Up @@ -1402,12 +1408,16 @@ cdef class Filter(_PairCompute):
@_Compute._computed_property
def filtered_nlist(self):
""":class:`.NeighborList`: The filtered neighbor list."""
return _nlist_from_cnlist(self._filterptr.getFilteredNlist().get())
nlist = _nlist_from_cnlist(self._filterptr.getFilteredNlist().get())
nlist._compute = self
return nlist

@_Compute._computed_property
def unfiltered_nlist(self):
""":class:`.NeighborList`: The unfiltered neighbor list."""
return _nlist_from_cnlist(self._filterptr.getUnfilteredNlist().get())
nlist = _nlist_from_cnlist(self._filterptr.getUnfilteredNlist().get())
nlist._compute = self
return nlist


cdef class FilterSANN(Filter):
Expand Down
4 changes: 3 additions & 1 deletion freud/order.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,9 @@ cdef class SolidLiquid(_PairCompute):
def nlist(self):
""":class:`freud.locality.NeighborList`: Neighbor list of solid-like
bonds."""
return freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist = freud.locality._nlist_from_cnlist(self.thisptr.getNList())
nlist._compute = self
return nlist

@_Compute._computed_property
def num_connections(self):
Expand Down
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2010-2023 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.

import freud


def nlist_lifetime_check(get_nlist_func):
"""Ensure nlist exists past the lifetime of the compute that created it."""
L = 10
N = 100
sys = freud.data.make_random_system(L, N)

nlist = get_nlist_func(sys)

assert nlist.point_indices is not None
assert nlist.query_point_indices is not None
assert nlist.neighbor_counts is not None
9 changes: 9 additions & 0 deletions tests/test_environment_AngularSeparation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2010-2023 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.

import conftest
import numpy as np
import numpy.testing as npt
import pytest
Expand Down Expand Up @@ -51,6 +52,14 @@ def test_compute(self):
for j in [0, 1]:
npt.assert_allclose(ang.angles[i][j], np.pi / 16, atol=1e-6)

def test_nlist_lifetime(self):
def _get_nlist(sys):
asn = freud.environment.AngularSeparationNeighbor()
asn.compute(sys, orientations=np.zeros((100, 4)), neighbors=dict(r_max=2.0))
return asn.nlist

conftest.nlist_lifetime_check(_get_nlist)

def test_repr(self):
ang = freud.environment.AngularSeparationGlobal()
assert str(ang) == str(eval(repr(ang)))
Expand Down
14 changes: 14 additions & 0 deletions tests/test_environment_LocalBondProjection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2010-2023 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.

import conftest
import numpy as np
import numpy.testing as npt
import pytest
Expand Down Expand Up @@ -33,6 +34,19 @@ def test_nlist(self):

npt.assert_array_equal(nlist[:], ang.nlist[:])

def test_nlist_lifetime(self):
def _get_nlist(sys):
lbp = freud.environment.LocalBondProjection()
lbp.compute(
sys,
np.zeros((100, 4)),
proj_vecs=np.zeros((100, 3)),
neighbors=dict(r_max=2),
)
return lbp.nlist

conftest.nlist_lifetime_check(_get_nlist)

def test_attribute_access(self):
boxlen = 10
N = 100
Expand Down
11 changes: 11 additions & 0 deletions tests/test_environment_LocalDescriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from functools import lru_cache

import conftest
import numpy as np
import numpy.testing as npt
import pytest
Expand Down Expand Up @@ -124,6 +125,16 @@ def test_global(self):

assert sphs.shape[0] == N * num_neighbors

def test_nlist_lifetime(self):
"""Ensure the nlist lives past the lifetime of the LocalDescriptors object."""

def _get_nlist(system):
ld = freud.environment.LocalDescriptors(l_max=3)
ld.compute(system, neighbors=dict(r_max=2))
return ld.nlist

conftest.nlist_lifetime_check(_get_nlist)

def test_particle_local(self):
N = 1000
num_neighbors = 4
Expand Down
10 changes: 10 additions & 0 deletions tests/test_locality_Filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from abc import abstractmethod

import conftest
import numpy as np
import numpy.testing as npt
import pytest
Expand Down Expand Up @@ -118,6 +119,15 @@ def test_random_system(self, terminate_after_blocked):
npt.assert_allclose(nlist_1.point_indices, nlist_2.point_indices)
npt.assert_allclose(nlist_1.query_point_indices, nlist_2.query_point_indices)

@pytest.mark.parametrize("nlist_property", ["filtered_nlist", "unfiltered_nlist"])
def test_nlist_lifetime(self, nlist_property):
def _get_nlist(sys):
filt = self.get_filter_object()
filt.compute(sys)
return getattr(filt, nlist_property)

conftest.nlist_lifetime_check(_get_nlist)


class TestRAD(FilterTest):
"""Tests specific to the RAD filtering method."""
Expand Down
11 changes: 11 additions & 0 deletions tests/test_locality_Voronoi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2010-2023 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.

import conftest
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
Expand Down Expand Up @@ -255,6 +256,16 @@ def test_voronoi_tess_3d(self):
)
npt.assert_allclose(wrapped_distances, vor.nlist.distances)

def test_voronoi_neighbors_lifetime(self):
"""Ensure the voronoi nlist lives past the lifetime of the voronoi object."""

def _get_voronoi_nlist(system):
voro = freud.locality.Voronoi()
voro.compute(system)
return voro.nlist

conftest.nlist_lifetime_check(_get_voronoi_nlist)

@pytest.mark.parametrize(
"func, neighbors",
[
Expand Down
9 changes: 9 additions & 0 deletions tests/test_order_SolidLiquid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2010-2023 The Regents of the University of Michigan
# This file is from the freud project, released under the BSD 3-Clause License.

import conftest
import matplotlib
import numpy.testing as npt
import pytest
Expand Down Expand Up @@ -73,6 +74,14 @@ def test_multiple_compute(self):
assert comp_default.cluster_sizes[0] == len(positions)
npt.assert_array_equal(comp_default.num_connections, 12)

def test_nlist_lifetime(self):
def _get_nlist(sys):
sl = freud.order.SolidLiquid(2, 0.5, 0.2)
sl.compute(sys, neighbors=dict(r_max=2))
return sl.nlist

conftest.nlist_lifetime_check(_get_nlist)

def test_attribute_access(self):
box, positions = freud.data.UnitCell.fcc().generate_system(4, scale=2)
sph_l = 6
Expand Down

0 comments on commit 9c35778

Please sign in to comment.