-
Notifications
You must be signed in to change notification settings - Fork 830
Extending test coverage for 2D visualization #5000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
951418c
4aa863a
8786c18
ccb7c72
a293f3a
2658006
5d6d9df
5607fbf
77d08d1
26b7bc3
afdd4f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -246,11 +246,12 @@ Chronological list of authors | |
| - Matthew Davies | ||
| - Jia-Xin Zhu | ||
| - Tanish Yelgoe | ||
| 2025 | ||
| 2025 | ||
| - Joshua Raphael Uy | ||
| - Namir Oues | ||
| - Lexi Xu | ||
| - BHM-Bob G | ||
| - Debasish Mohanty | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add your name at the end of the list.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| - Yu-Yuan (Stuart) Yang | ||
|
|
||
| External code | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,6 +53,140 @@ def membrane_xtc(tmpdir_factory, univ): | |
| return str(tmp_xtc) | ||
|
|
||
|
|
||
| def test_produce_list_indices_point_in_polygon_this_frame(): | ||
| # Define two squares: | ||
| # square1 covers the area [0,1]x[0,1] | ||
| # square2 covers the area [2,3]x[2,3] | ||
| square1 = [(0, 0), (1, 0), (1, 1), (0, 1)] | ||
| square2 = [(2, 2), (3, 2), (3, 3), (2, 3)] | ||
|
|
||
| # Create a list of vertex coordinates (for two squares) | ||
| vertex_list = [square1, square2] | ||
|
|
||
| # Define points: | ||
| # Point [0.5, 0.5] lies inside square1. | ||
| # Point [1.5, 1.5] lies outside both squares. | ||
| # Point [2.5, 2.5] lies inside square2. | ||
| # Point [3.5, 3.5] lies outside both squares. | ||
| points = np.array([[0.5, 0.5], [1.5, 1.5], [2.5, 2.5], [3.5, 3.5]]) | ||
|
lilyminium marked this conversation as resolved.
|
||
|
|
||
| # Call the function under test. | ||
| result = streamlines._produce_list_indices_point_in_polygon_this_frame( | ||
| vertex_list, points | ||
| ) | ||
|
|
||
| # np.where returns a tuple; thus for each square we expect: | ||
| # For square1: point index 0 is inside → (array([0]),) | ||
| # For square2: point index 2 is inside → (array([2]),) | ||
| expected = [(np.array([0]),), (np.array([2]),)] | ||
|
|
||
| # Check that each result matches the expected indices. | ||
| for res_tuple, exp_tuple in zip(result, expected): | ||
| np.testing.assert_array_equal(res_tuple[0], exp_tuple[0]) | ||
|
|
||
|
|
||
| def test_produce_list_centroids_empty(): | ||
| # Simulate an empty index set for one square: | ||
| list_indices = [(np.array([]),)] | ||
| # Dummy particle coordinate array (won't be used since indices is empty) | ||
| pts = np.array([[0, 0], [1, 1]]) | ||
| result = streamlines._produce_list_centroids_this_frame(list_indices, pts) | ||
| assert result == [None] | ||
|
|
||
|
|
||
| def test_produce_list_centroids_single_square(): | ||
| # Create an array of particle coordinates | ||
| pts = np.array([[0, 0], [1, 1], [2, 2], [3, 3]]) | ||
| # Choose indices that pick points [1] and [3] | ||
| indices_tuple = (np.array([1, 3]),) | ||
| list_indices = [indices_tuple] | ||
| result = streamlines._produce_list_centroids_this_frame(list_indices, pts) | ||
| expected = np.array([2.0, 2.0]) | ||
| np.testing.assert_array_almost_equal(result[0], expected) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should prefer
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Used assert_allclose in recent changes. |
||
|
|
||
|
|
||
| def test_produce_list_centroids_multiple_squares(): | ||
| pts = np.array([[0, 0], [2, 2], [4, 4], [6, 6]]) | ||
| # First square will use pts[0] and pts[2] -> average is [2,2] | ||
| indices1 = (np.array([0, 2]),) | ||
| # Second square will use pts[1] and pts[3] -> average is [4,4] | ||
| indices2 = (np.array([1, 3]),) | ||
| list_indices = [indices1, indices2] | ||
| result = streamlines._produce_list_centroids_this_frame(list_indices, pts) | ||
| expected1 = np.array([2, 2]) | ||
| expected2 = np.array([4, 4]) | ||
| np.testing.assert_array_equal(result[0], expected1) | ||
| np.testing.assert_array_equal(result[1], expected2) | ||
|
|
||
|
|
||
| def test_adjacent_squares(): | ||
| # Test two adjacent squares that share a boundary. | ||
| # Square1 covers [0,1]x[0,1] and square2 covers [1,2]x[0,1]. | ||
| # A point at [0.5, 0.5] should be in square1 and one at [1.5,0.5] should be in square2. | ||
| square1 = [(0, 0), (1, 0), (1, 1), (0, 1)] | ||
| square2 = [(1, 0), (2, 0), (2, 1), (1, 1)] | ||
| vertex_list = [square1, square2] | ||
| points = np.array( | ||
| [ | ||
| [0.5, 0.5], | ||
| [1.5, 0.5], | ||
| ] | ||
| ) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: I think we could write this more compactly
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why, but the black linting was giving errors, hence wrote the list in this manner.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even though I agree with @tylerjereddy , I am going to succumb to black's opinion here and just leave it in whichever way black wants it to be. Not worthwhile to mark it as an exception. |
||
| result = streamlines._produce_list_indices_point_in_polygon_this_frame( | ||
| vertex_list, points | ||
| ) | ||
| expected = [(np.array([0]),), (np.array([1]),)] | ||
| for res_tuple, exp_tuple in zip(result, expected): | ||
| np.testing.assert_array_equal(res_tuple[0], exp_tuple[0]) | ||
|
|
||
|
|
||
| def test_point_on_boundary(): | ||
| # Test that a point exactly on the square's boundary is not considered inside. | ||
| # For a square covering [0,1]x[0,1], a point at [1,0.5] lies exactly on the right edge. | ||
| # By default, matplotlib.path.Path.contains_points does not include boundary points. | ||
| square = [(0, 0), (1, 0), (1, 1), (0, 1)] | ||
| vertex_list = [square] | ||
| points = np.array([[1, 0.5]]) # exactly on the boundary | ||
|
Comment on lines
+147
to
+149
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for adding this test! Just to be rigorous (and because I'm curious), what happens if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, as it is implemented in the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, thanks. Sorry, I should have been clearer -- could you please update the test to include this test and make it clearer? Otherwise the PR looks good!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test has been added to check if the indices of the shared points on adjacent squares return expected data from |
||
| result = streamlines._produce_list_indices_point_in_polygon_this_frame( | ||
| vertex_list, points | ||
| ) | ||
| expected = [(np.array([0], dtype=int),)] | ||
| np.testing.assert_array_equal(result[0][0], expected[0][0]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I'm misunderstanding something, but above it says:
But expected has the index-0 point detected as interior, no?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, I just checked the boundary points that are included in the Matplotlib path and returns a 1D zero Numpy array. Made changes to the comment.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're just checking the 0-index here, which wouldn't check if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this specific test, I was checking a single point on the shape's boundary! As there will be one expected element in the array, I tested only the first element in the initial test. As you commented, I have added to check all the items in the list rather than the first one only. |
||
|
|
||
|
|
||
| def test_per_core_work_2D(membrane_xtc, univ): | ||
| xmin = univ.atoms.positions[..., 0].min() | ||
| xmax = univ.atoms.positions[..., 0].max() | ||
| ymin = univ.atoms.positions[..., 1].min() | ||
| ymax = univ.atoms.positions[..., 1].max() | ||
| tuple_of_limits = (xmin, xmax, ymin, ymax) | ||
| grid = streamlines.produce_grid( | ||
| tuple_of_limits=tuple_of_limits, grid_spacing=20 | ||
| ) | ||
| ( | ||
| list_square_vertex_arrays_per_core, | ||
| list_parent_index_values, | ||
| _, | ||
| _, | ||
| ) = streamlines.split_grid(grid=grid, num_cores=1) | ||
| values = streamlines.per_core_work( | ||
| topology_file_path=Martini_membrane_gro, | ||
| trajectory_file_path=membrane_xtc, | ||
| list_square_vertex_arrays_this_core=list_square_vertex_arrays_per_core[ | ||
| 0 | ||
| ], | ||
| MDA_selection="name PO4", | ||
| start_frame=1, | ||
| end_frame=2, | ||
| reconstruction_index_list=list_parent_index_values[0], | ||
| maximum_delta_magnitude=2.0, | ||
| ) | ||
| for entry in values: | ||
| res = entry[1] | ||
| assert res[0] == pytest.approx(0.8, abs=1e-1) | ||
| assert res[1] == pytest.approx(0.5, abs=1e-1) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably minor, but I suspect using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done it with the numpy.assert_allclose() ! |
||
|
|
||
|
|
||
| def test_streamplot_2D(membrane_xtc, univ): | ||
| # regression test the data structures | ||
| # generated by the 2D streamplot code | ||
|
|
@@ -122,6 +256,30 @@ def test_streamplot_2D_zero_return(membrane_xtc, univ, tmpdir): | |
| assert std == approx(0.0) | ||
|
|
||
|
|
||
| def test_streamplot_2D_max_core(membrane_xtc, univ, tmpdir): | ||
| # simple roundtrip test to ensure that | ||
| # zeroed arrays are returned by the 2D streamplot | ||
| # code when called with an empty selection | ||
| u1, v1, avg, std = streamlines.generate_streamlines( | ||
| topology_file_path=Martini_membrane_gro, | ||
| trajectory_file_path=membrane_xtc, | ||
| grid_spacing=20, | ||
| MDA_selection="name POX", | ||
| start_frame=1, | ||
| end_frame=2, | ||
| xmin=univ.atoms.positions[..., 0].min(), | ||
| xmax=univ.atoms.positions[..., 0].max(), | ||
| ymin=univ.atoms.positions[..., 1].min(), | ||
| ymax=univ.atoms.positions[..., 1].max(), | ||
| maximum_delta_magnitude=2.0, | ||
| num_cores="maximum", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be a little hesitant to test I suspect the pragmatic thing for now may be to just use a single core, or perhaps
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test case was added to consider the core inclusion case of "maximum". I have modified the cores to 2. If there is such a marker implemented later, I will modify the test case accordingly to test if the "maximum" case works as expected. |
||
| ) | ||
| assert_allclose(u1, np.zeros((5, 5))) | ||
| assert_allclose(v1, np.zeros((5, 5))) | ||
| assert avg == approx(0.0) | ||
| assert std == approx(0.0) | ||
|
|
||
|
|
||
| def test_streamplot_3D(membrane_xtc, univ, tmpdir): | ||
| # because mayavi is too heavy of a dependency | ||
| # for a roundtrip plotting test, simply | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, thanks for fixing :-)