From 86462f81aa4e18678579e8be9ebf7f1d1b0d0625 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Sat, 5 Apr 2025 21:45:02 +0100 Subject: [PATCH 1/8] Initial Attempt at PR for issue #3603, would appreciate some feedback as this is just a rough attempt --- .../analysis/test_lineardensity.py | 322 +++++++++++------- 1 file changed, 204 insertions(+), 118 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py index 9f5963938b..dbb8bf029d 100644 --- a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py +++ b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py @@ -1,25 +1,3 @@ -# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 -# -# MDAnalysis --- https://www.mdanalysis.org -# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors -# (see the file AUTHORS for the full list of names) -# -# Released under the Lesser GNU Public Licence, v2.1 or any higher version -# -# Please cite your use of MDAnalysis in published work: -# -# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, -# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. -# MDAnalysis: A Python package for the rapid analysis of molecular dynamics -# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th -# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. -# doi: 10.25080/majora-629e541a-00e -# -# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. -# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. -# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 -# import MDAnalysis as mda import numpy as np import pytest @@ -30,6 +8,7 @@ from numpy.testing import assert_allclose from MDAnalysis.core._get_readers import get_reader_for from MDAnalysisTests.util import no_deprecated_call +from MDAnalysis.units import constants def test_invalid_grouping(): @@ -42,86 +21,170 @@ def test_invalid_grouping(): ld = LinearDensity(selection, grouping="centroid", binsize=5) ld.run() +### Initialising Variables ### -# test data for grouping='atoms' -expected_masses_atoms = np.array( - [ - 15.9994, - 1.008, - 1.008, - 15.9994, - 1.008, - 1.008, - 15.9994, - 1.008, - 1.008, - 15.9994, - 1.008, - 1.008, - 15.9994, - 1.008, - 1.008, - ] -) -expected_charges_atoms = np.array( - [ - -0.834, - 0.417, - 0.417, - -0.834, - 0.417, - 0.417, - -0.834, - 0.417, - 0.417, - -0.834, - 0.417, - 0.417, - -0.834, - 0.417, - 0.417, - ] -) -expected_xmass_atoms = np.array( - [0.0, 0.0, 0.0, 0.00723323, 0.00473288, 0.0, 0.0, 0.0, 0.0, 0.0] -) -expected_xcharge_atoms = np.array( - [0.0, 0.0, 0.0, 2.21582311e-05, -2.21582311e-05, 0.0, 0.0, 0.0, 0.0, 0.0] -) +expected_masses_atoms = None +expected_charges_atoms = None +expected_xmass_atoms = None +expected_xcharge_atoms = None -# test data for grouping='residues' -expected_masses_residues = np.array( - [18.0154, 18.0154, 18.0154, 18.0154, 18.0154] -) -expected_charges_residues = np.array([0, 0, 0, 0, 0]) -expected_xmass_residues = np.array( - [0.0, 0.0, 0.0, 0.00717967, 0.00478644, 0.0, 0.0, 0.0, 0.0, 0.0] -) -expected_xcharge_residues = np.array( - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] -) +expected_masses_residues = None +expected_charges_residues = None +expected_xmass_residues = None +expected_xcharge_residues = None -# test data for grouping='segments' -expected_masses_segments = np.array([90.0770]) -expected_charges_segments = np.array([0]) -expected_xmass_segments = np.array( - [0.0, 0.0, 0.0, 0.01196611, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] -) -expected_xcharge_segments = np.array( - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] -) +expected_masses_segments = None +expected_charges_segments = None +expected_xmass_segments = None +expected_xcharge_segments = None -# test data for grouping='fragments' -expected_masses_fragments = np.array( - [18.0154, 18.0154, 18.0154, 18.0154, 18.0154] -) -expected_charges_fragments = np.array([0, 0, 0, 0, 0]) -expected_xmass_fragments = np.array( - [0.0, 0.0, 0.0, 0.00717967, 0.00478644, 0.0, 0.0, 0.0, 0.0, 0.0] -) -expected_xcharge_fragments = np.array( - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] -) +''' +FRAGMENTS!!!! Can't find the documentation for Fragment +''' + +### Creating the Test Universes ### + +test_Systems = ['neutral_Particles', 'charged_Particles', 'charged_Dimers'] + +def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs): + """Generate a reference universe of 100 atoms with uniformly drawn random positions.""" + n_residues = n_atoms // atomsPerRes # Arbitrarily 5 atoms per residue + n_segments = n_residues // resPerSegs # Arbitrarily 4 residues per segment + + # Indexing atoms into residues & residues into segments + atom_resindex = np.array([[i] * (n_atoms // n_residues) for i in range(n_residues)]).flatten() + residue_segindex=np.array([[i] * (n_residues // n_segments) for i in range(n_segments)]).flatten() + + # Creating the universe + u = mda.Universe.empty(n_atoms=n_atoms, + n_residues=n_residues, + n_segments=n_segments, + atom_resindex=atom_resindex, + residue_segindex=residue_segindex) + + # Assigning the Charges & Masses + u.add_TopologyAttr('charges', values=charges) + u.add_TopologyAttr('masses', values=masses) + + u.trajectory = get_reader_for(coords)(coords, + order='fac', + n_atoms=n_atoms) + + for ts in u.trajectory: + ts.dimensions = np.array([1, 1, 1, 90, 90, 90]) + + return u + +def neutral_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): + charges = np.zeros(n_atoms) + masses = np.ones(n_atoms) + + shape = (n_frames, n_atoms, 3) + coords = np.random.random(shape) + + return make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs) + +def charged_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): + charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 + masses = np.ones(n_atoms) + + shape = (n_frames, n_atoms, 3) + coords = np.random.random(shape) + + return make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs) + +def charged_Dimers(n_dimers, n_frames, dimersPerRes, resPerSegs, dimerLength = 0.05): + n_atoms = 2 * n_dimers + + charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 + masses = np.ones(n_atoms) + + # Setting each position to be random for each timestep (independent of previous timestep) + shape = (n_frames, n_dimers, 3) + coords = np.random.random(shape) * 0.9 + np.ones(shape) * 0.05 + # Puts in the same coordinate twice per dimer + coords = np.repeat(coords, 2, axis = 1) + + # Shifts one of the atoms of each dimer by their bondLength in a random direction (defined to be in the box) + for time in coords: + for coord in time[::2,:]: + phi = np.random.random() * 2 * np.pi + theta = np.random.random() * np.pi + x = np.array([np.sin(theta)*np.cos(phi), np.sin(theta)*np.sin(phi), np.cos(theta)]) * dimerLength + coord += np.array([np.sin(theta)*np.cos(phi), np.sin(theta)*np.sin(phi), np.cos(theta)]) * dimerLength + + + return make_Universe(coords, charges, masses, n_atoms, n_frames, dimersPerRes * 2, resPerSegs) + +### Calculating the Expected Values ### + +def calc_Prop(u, prop = 'masses'): # Property can be 'masses' or 'charges' + expected_atoms = eval(f'u.atoms.{prop}') + expected_residues = np.array([sum(eval(f'res.atoms.{prop}')) for res in u.residues]) + expected_segments = np.array([sum(eval(f'seg.atoms.{prop}')) for seg in u.segments]) + + return expected_atoms, expected_residues, expected_segments + +def find_Centres(groups, prop): + centres = [] + for group in groups: + # NOTE: Absolute is taken for charges + total_Prop = sum(abs(eval(f'group.atoms.{prop}'))) + if total_Prop != 0: + centres.append(np.sum(group.atoms.positions.transpose() * abs(eval(f'group.atoms.{prop}')), axis = 1) / total_Prop) + elif total_Prop == 0: + centres.append(np.sum(group.atoms.positions.transpose() * abs(eval(f'group.atoms.{prop}')), axis = 1) / len(group.atoms)) + + return np.array(centres) + +def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'masses' or 'charges' + + propShort = 'mass' + if prop == 'charges': propShort = 'charge' + + ### Atoms + expected_atoms = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) # Works for cubic Universe + for atom in u.atoms: + for i in range(3): + expected_atoms[i][int(atom.position[i] // spliceLen)] += eval(f'atom.{propShort}') + + + + _,residue_Totals,segment_Totals = calc_Prop(u, prop) # Total of Charge OR Mass + ### Residues + expected_residues = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) + residue_Centres = find_Centres(u.residues, prop = prop) + + + for i in range(len(residue_Centres)): + for j in range(3): + expected_residues[j][int(residue_Centres[i][j] // spliceLen)] += residue_Totals[i] + + ### Segments + expected_segments = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) + segment_Centres = find_Centres(u.segments, prop = prop) + + + for i in range(len(segment_Centres)): + for j in range(3): + expected_segments[j][int(segment_Centres[i][j] // spliceLen)] += segment_Totals[i] + + + + # Scaling based on splice volumes & converting units + for i in range(3): + expected_atoms[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + expected_residues[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + expected_segments[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + expected_atoms /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py + expected_residues /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py + expected_segments /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py + + return expected_atoms, expected_residues, expected_segments + + + +#### @pytest.mark.parametrize( @@ -148,31 +211,54 @@ def test_invalid_grouping(): expected_xmass_segments, expected_xcharge_segments, ), - ( - "fragments", - expected_masses_fragments, - expected_charges_fragments, - expected_xmass_fragments, - expected_xcharge_fragments, - ), +## ( +## "fragments", +## expected_masses_fragments, +## expected_charges_fragments, +## expected_xmass_fragments, +## expected_xcharge_fragments, +## ), ], ) + def test_lineardensity( +## universe, grouping, - expected_masses, - expected_charges, - expected_xmass, - expected_xcharge, +## expected_masses, +## expected_charges, +## expected_xmass, +## expected_xcharge, ): - universe = mda.Universe(waterPSF, waterDCD) - sel_string = "all" - selection = universe.select_atoms(sel_string) - ld = LinearDensity(selection, grouping, binsize=5).run() - assert_allclose(ld.masses, expected_masses) - assert_allclose(ld.charges, expected_charges) - # rtol changed here due to floating point imprecision - assert_allclose(ld.results.x.mass_density, expected_xmass, rtol=1e-06) - assert_allclose(ld.results.x.charge_density, expected_xcharge) + + spliceLen = 0.25 + for system in test_Systems: + universe = eval(f'{system}(100, 1, 1, 1)') + + expected_masses_atoms, expected_masses_residues, expected_masses_segments = calc_Prop(universe, 'masses') + expected_charges_atoms, expected_charges_residues, expected_charges_segments = calc_Prop(universe, 'charges') + expected_xmass_atoms, expected_xmass_residues, expected_xmass_segments = calc_Densities(universe, 'masses', spliceLen) + expected_xcharge_atoms, expected_xcharge_residues, expected_xcharge_segments = calc_Densities(universe, 'charges', spliceLen) + + if grouping == 'atoms': + expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_atoms, expected_charges_atoms, expected_xmass_atoms, expected_xcharge_atoms + + elif grouping == 'residues': + expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_residues, expected_charges_residues, expected_xmass_residues, expected_xcharge_residues + + elif grouping == 'segments': + expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_segments, expected_charges_segments, expected_xmass_segments, expected_xcharge_segments + + + sel_string = "all" + selection = universe.select_atoms(sel_string) + ld = LinearDensity(selection, grouping, binsize=spliceLen).run() + assert_allclose(ld.masses, expected_masses) + assert_allclose(ld.charges, expected_charges) + # rtol changed here due to floating point imprecision + assert_allclose(ld.results.x.mass_density, expected_xmass[0], rtol=1e-06) + assert_allclose(ld.results.x.charge_density, expected_xcharge[0]) + +##test_lineardensity('atoms') @pytest.fixture(scope="module") From 09a9f6071122c9410b18a298f279ec5be48edf31 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Mon, 7 Apr 2025 17:23:54 +0100 Subject: [PATCH 2/8] Fixed pytest and Centre of Mass for issue #3603 --- .../analysis/test_lineardensity.py | 197 +++++++++--------- 1 file changed, 98 insertions(+), 99 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py index dbb8bf029d..fa625d78bb 100644 --- a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py +++ b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py @@ -1,3 +1,25 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 +# +# MDAnalysis --- https://www.mdanalysis.org +# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors +# (see the file AUTHORS for the full list of names) +# +# Released under the Lesser GNU Public Licence, v2.1 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler, +# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein. +# MDAnalysis: A Python package for the rapid analysis of molecular dynamics +# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th +# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy. +# doi: 10.25080/majora-629e541a-00e +# +# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein. +# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. +# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 +# import MDAnalysis as mda import numpy as np import pytest @@ -8,9 +30,11 @@ from numpy.testing import assert_allclose from MDAnalysis.core._get_readers import get_reader_for from MDAnalysisTests.util import no_deprecated_call + from MDAnalysis.units import constants + def test_invalid_grouping(): """Invalid groupings raise AttributeError""" universe = mda.Universe(waterPSF, waterDCD) @@ -21,26 +45,6 @@ def test_invalid_grouping(): ld = LinearDensity(selection, grouping="centroid", binsize=5) ld.run() -### Initialising Variables ### - -expected_masses_atoms = None -expected_charges_atoms = None -expected_xmass_atoms = None -expected_xcharge_atoms = None - -expected_masses_residues = None -expected_charges_residues = None -expected_xmass_residues = None -expected_xcharge_residues = None - -expected_masses_segments = None -expected_charges_segments = None -expected_xmass_segments = None -expected_xcharge_segments = None - -''' -FRAGMENTS!!!! Can't find the documentation for Fragment -''' ### Creating the Test Universes ### @@ -52,8 +56,8 @@ def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPe n_segments = n_residues // resPerSegs # Arbitrarily 4 residues per segment # Indexing atoms into residues & residues into segments - atom_resindex = np.array([[i] * (n_atoms // n_residues) for i in range(n_residues)]).flatten() - residue_segindex=np.array([[i] * (n_residues // n_segments) for i in range(n_segments)]).flatten() + atom_resindex = np.repeat(np.arange(n_residues), atomsPerRes) + residue_segindex = np.repeat(np.arange(n_segments), resPerSegs) # Creating the universe u = mda.Universe.empty(n_atoms=n_atoms, @@ -75,6 +79,14 @@ def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPe return u + +### Defining the Systems ### + +# The masses are all set to 1 +# The charges are dependent on each system +# The positions are randomly taken from a uniform distribution between 0 and 1 +# NOTE: The positions at each time frame are independent on the previous time step + def neutral_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): charges = np.zeros(n_atoms) masses = np.ones(n_atoms) @@ -99,7 +111,6 @@ def charged_Dimers(n_dimers, n_frames, dimersPerRes, resPerSegs, dimerLength = 0 charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 masses = np.ones(n_atoms) - # Setting each position to be random for each timestep (independent of previous timestep) shape = (n_frames, n_dimers, 3) coords = np.random.random(shape) * 0.9 + np.ones(shape) * 0.05 # Puts in the same coordinate twice per dimer @@ -116,6 +127,7 @@ def charged_Dimers(n_dimers, n_frames, dimersPerRes, resPerSegs, dimerLength = 0 return make_Universe(coords, charges, masses, n_atoms, n_frames, dimersPerRes * 2, resPerSegs) + ### Calculating the Expected Values ### def calc_Prop(u, prop = 'masses'): # Property can be 'masses' or 'charges' @@ -125,15 +137,14 @@ def calc_Prop(u, prop = 'masses'): # Property can be 'masses' or 'charges' return expected_atoms, expected_residues, expected_segments -def find_Centres(groups, prop): +def find_Centres(groups): # Always based on Centre of Mass (NOT Centre of Charge) centres = [] for group in groups: - # NOTE: Absolute is taken for charges - total_Prop = sum(abs(eval(f'group.atoms.{prop}'))) - if total_Prop != 0: - centres.append(np.sum(group.atoms.positions.transpose() * abs(eval(f'group.atoms.{prop}')), axis = 1) / total_Prop) - elif total_Prop == 0: - centres.append(np.sum(group.atoms.positions.transpose() * abs(eval(f'group.atoms.{prop}')), axis = 1) / len(group.atoms)) + total_Mass = sum(group.atoms.masses) + if total_Mass != 0: + centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / total_Mass) + elif total_Mass == 0: + centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / len(group.atoms)) ############## NOT NEDEDEDDED return np.array(centres) @@ -153,7 +164,7 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas _,residue_Totals,segment_Totals = calc_Prop(u, prop) # Total of Charge OR Mass ### Residues expected_residues = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) - residue_Centres = find_Centres(u.residues, prop = prop) + residue_Centres = find_Centres(u.residues) for i in range(len(residue_Centres)): @@ -162,7 +173,7 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas ### Segments expected_segments = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) - segment_Centres = find_Centres(u.segments, prop = prop) + segment_Centres = find_Centres(u.segments) for i in range(len(segment_Centres)): @@ -170,7 +181,6 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas expected_segments[j][int(segment_Centres[i][j] // spliceLen)] += segment_Totals[i] - # Scaling based on splice volumes & converting units for i in range(3): expected_atoms[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] @@ -181,84 +191,72 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas expected_segments /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py return expected_atoms, expected_residues, expected_segments - +### Creating the Expected Values ### + +spliceLen = 0.25 + +universes = [] +groupings = ['atoms', 'residues', 'segments'] +# Will be [[Atoms, Residues, Segments (of Universe 1)], [... of Universe 2], ...] +expected_masses = [] +expected_charges = [] +expected_xmass = [] +expected_xcharge = [] -#### +for system in test_Systems: + universe = eval(f'{system}(100, 1, 5, 4)') + universes.append(universe) +## expected_masses_atoms, expected_masses_residues, expected_masses_segments = calc_Prop(universe, 'masses') +## expected_charges_atoms, expected_charges_residues, expected_charges_segments = calc_Prop(universe, 'charges') +## expected_xmass_atoms, expected_xmass_residues, expected_xmass_segments = calc_Densities(universe, 'masses', spliceLen) +## expected_xcharge_atoms, expected_xcharge_residues, expected_xcharge_segments = calc_Densities(universe, 'charges', spliceLen) + + expected_masses.append(calc_Prop(universe, 'masses')) + expected_charges.append(calc_Prop(universe, 'charges')) + expected_xmass.append(calc_Densities(universe, 'masses', spliceLen)) + expected_xcharge.append(calc_Densities(universe, 'charges', spliceLen)) + +### Parametrizing for Pytest ### + +# NOTE: There is an extra [0] after the expected_xmass and expected_xcharge as the original data has all 3 co-ords, but only comparing to x here +params = [(universes[i], groupings[j], expected_masses[i][j], expected_charges[i][j], expected_xmass[i][j][0], expected_xcharge[i][j][0]) for j in range(len(groupings)) for i in range(len(universes))] @pytest.mark.parametrize( - "grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", - [ - ( - "atoms", - expected_masses_atoms, - expected_charges_atoms, - expected_xmass_atoms, - expected_xcharge_atoms, - ), - ( - "residues", - expected_masses_residues, - expected_charges_residues, - expected_xmass_residues, - expected_xcharge_residues, - ), - ( - "segments", - expected_masses_segments, - expected_charges_segments, - expected_xmass_segments, - expected_xcharge_segments, - ), -## ( -## "fragments", -## expected_masses_fragments, -## expected_charges_fragments, -## expected_xmass_fragments, -## expected_xcharge_fragments, -## ), - ], + "universe, grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", + params, ) def test_lineardensity( -## universe, + universe, grouping, -## expected_masses, -## expected_charges, -## expected_xmass, -## expected_xcharge, + expected_masses, + expected_charges, + expected_xmass, + expected_xcharge, ): + sel_string = "all" + selection = universe.select_atoms(sel_string) + ld = LinearDensity(selection, grouping, binsize=0.25).run() + assert_allclose(ld.masses, expected_masses) + assert_allclose(ld.charges, expected_charges) + # rtol changed here due to floating point imprecision + assert_allclose(ld.results.x.mass_density, expected_xmass, rtol=1e-06) + assert_allclose(ld.results.x.charge_density, expected_xcharge) - spliceLen = 0.25 - for system in test_Systems: - universe = eval(f'{system}(100, 1, 1, 1)') - - expected_masses_atoms, expected_masses_residues, expected_masses_segments = calc_Prop(universe, 'masses') - expected_charges_atoms, expected_charges_residues, expected_charges_segments = calc_Prop(universe, 'charges') - expected_xmass_atoms, expected_xmass_residues, expected_xmass_segments = calc_Densities(universe, 'masses', spliceLen) - expected_xcharge_atoms, expected_xcharge_residues, expected_xcharge_segments = calc_Densities(universe, 'charges', spliceLen) - - if grouping == 'atoms': - expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_atoms, expected_charges_atoms, expected_xmass_atoms, expected_xcharge_atoms - - elif grouping == 'residues': - expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_residues, expected_charges_residues, expected_xmass_residues, expected_xcharge_residues - - elif grouping == 'segments': - expected_masses, expected_charges, expected_xmass, expected_xcharge = expected_masses_segments, expected_charges_segments, expected_xmass_segments, expected_xcharge_segments - - - sel_string = "all" - selection = universe.select_atoms(sel_string) - ld = LinearDensity(selection, grouping, binsize=spliceLen).run() - assert_allclose(ld.masses, expected_masses) - assert_allclose(ld.charges, expected_charges) - # rtol changed here due to floating point imprecision - assert_allclose(ld.results.x.mass_density, expected_xmass[0], rtol=1e-06) - assert_allclose(ld.results.x.charge_density, expected_xcharge[0]) -##test_lineardensity('atoms') +for i in range(len(params)): + print(i) + universe = params[i][0] + sel_string = "all" + selection = universe.select_atoms(sel_string) + ld = LinearDensity(selection, params[i][1], binsize=0.25).run() + assert_allclose(ld.masses, params[i][2]) + assert_allclose(ld.charges, params[i][3]) + # rtol changed here due to floating point imprecision + assert_allclose(ld.results.x.mass_density, params[i][4], rtol=1e-06) + assert_allclose(ld.results.x.charge_density, params[i][5]) @pytest.fixture(scope="module") @@ -362,3 +360,4 @@ def test_parallel_analysis(testing_Universe): assert_allclose( ld1.results.x.mass_density, ld_whole.results.x.mass_density ) + From 920c4dfbe14ce8dd2d2a3b3bd3384d1e959bc059 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 15:29:33 +0100 Subject: [PATCH 3/8] Fixed pytest and Centre of Mass for issue #3603 --- .../analysis/test_lineardensity.py | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py index fa625d78bb..f96183b554 100644 --- a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py +++ b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py @@ -48,8 +48,6 @@ def test_invalid_grouping(): ### Creating the Test Universes ### -test_Systems = ['neutral_Particles', 'charged_Particles', 'charged_Dimers'] - def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs): """Generate a reference universe of 100 atoms with uniformly drawn random positions.""" n_residues = n_atoms // atomsPerRes # Arbitrarily 5 atoms per residue @@ -105,7 +103,7 @@ def charged_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): return make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs) -def charged_Dimers(n_dimers, n_frames, dimersPerRes, resPerSegs, dimerLength = 0.05): +def charged_Dimers(n_dimers=100, n_frames=1, dimersPerRes=5, resPerSegs=4, dimerLength = 0.05): n_atoms = 2 * n_dimers charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 @@ -128,12 +126,13 @@ def charged_Dimers(n_dimers, n_frames, dimersPerRes, resPerSegs, dimerLength = 0 return make_Universe(coords, charges, masses, n_atoms, n_frames, dimersPerRes * 2, resPerSegs) + ### Calculating the Expected Values ### def calc_Prop(u, prop = 'masses'): # Property can be 'masses' or 'charges' - expected_atoms = eval(f'u.atoms.{prop}') - expected_residues = np.array([sum(eval(f'res.atoms.{prop}')) for res in u.residues]) - expected_segments = np.array([sum(eval(f'seg.atoms.{prop}')) for seg in u.segments]) + expected_atoms = getattr(u.atoms, prop) + expected_residues = np.array([sum(getattr(res.atoms, prop)) for res in u.residues]) + expected_segments = np.array([sum(getattr(seg.atoms, prop)) for seg in u.segments]) return expected_atoms, expected_residues, expected_segments @@ -143,21 +142,23 @@ def find_Centres(groups): # Always based on Centre of Mass (NOT Centre of Charge total_Mass = sum(group.atoms.masses) if total_Mass != 0: centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / total_Mass) + # Just in case there are particles with 0 mass (somehow) elif total_Mass == 0: - centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / len(group.atoms)) ############## NOT NEDEDEDDED + centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / len(group.atoms)) return np.array(centres) def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'masses' or 'charges' propShort = 'mass' - if prop == 'charges': propShort = 'charge' + if prop == 'charges': + propShort = 'charge' ### Atoms expected_atoms = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) # Works for cubic Universe for atom in u.atoms: for i in range(3): - expected_atoms[i][int(atom.position[i] // spliceLen)] += eval(f'atom.{propShort}') + expected_atoms[i][int(atom.position[i] // spliceLen)] += getattr(atom, propShort) @@ -204,16 +205,14 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas expected_xmass = [] expected_xcharge = [] -for system in test_Systems: +test_Universes = [neutral_Particles, charged_Particles, charged_Dimers] +test_Params = [[100, 1, 5, 4], [100, 1, 5, 4], [100, 1, 5, 4, 0.05]] - universe = eval(f'{system}(100, 1, 5, 4)') +for i in range(len(test_Universes)): + + universe = test_Universes[i](*test_Params[i]) universes.append(universe) -## expected_masses_atoms, expected_masses_residues, expected_masses_segments = calc_Prop(universe, 'masses') -## expected_charges_atoms, expected_charges_residues, expected_charges_segments = calc_Prop(universe, 'charges') -## expected_xmass_atoms, expected_xmass_residues, expected_xmass_segments = calc_Densities(universe, 'masses', spliceLen) -## expected_xcharge_atoms, expected_xcharge_residues, expected_xcharge_segments = calc_Densities(universe, 'charges', spliceLen) - expected_masses.append(calc_Prop(universe, 'masses')) expected_charges.append(calc_Prop(universe, 'charges')) expected_xmass.append(calc_Densities(universe, 'masses', spliceLen)) @@ -222,10 +221,10 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas ### Parametrizing for Pytest ### # NOTE: There is an extra [0] after the expected_xmass and expected_xcharge as the original data has all 3 co-ords, but only comparing to x here -params = [(universes[i], groupings[j], expected_masses[i][j], expected_charges[i][j], expected_xmass[i][j][0], expected_xcharge[i][j][0]) for j in range(len(groupings)) for i in range(len(universes))] +pytest_Params = [(universes[i], groupings[j], expected_masses[i][j], expected_charges[i][j], expected_xmass[i][j][0], expected_xcharge[i][j][0]) for j in range(len(groupings)) for i in range(len(universes))] @pytest.mark.parametrize( "universe, grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", - params, + pytest_Params, ) def test_lineardensity( @@ -245,20 +244,6 @@ def test_lineardensity( assert_allclose(ld.results.x.mass_density, expected_xmass, rtol=1e-06) assert_allclose(ld.results.x.charge_density, expected_xcharge) - -for i in range(len(params)): - print(i) - universe = params[i][0] - sel_string = "all" - selection = universe.select_atoms(sel_string) - ld = LinearDensity(selection, params[i][1], binsize=0.25).run() - assert_allclose(ld.masses, params[i][2]) - assert_allclose(ld.charges, params[i][3]) - # rtol changed here due to floating point imprecision - assert_allclose(ld.results.x.mass_density, params[i][4], rtol=1e-06) - assert_allclose(ld.results.x.charge_density, params[i][5]) - - @pytest.fixture(scope="module") def testing_Universe(): """Generate a universe for testing whether LinearDensity works with From 2de699eb57061b7a799d8eb43780d775dd3cccb4 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 16:32:50 +0100 Subject: [PATCH 4/8] Updated AUTHORS & CHANGELOG, Improved Formatting --- package/AUTHORS | 1 + package/CHANGELOG | 3 +- .../analysis/test_lineardensity.py | 233 +++++++++++++----- 3 files changed, 169 insertions(+), 68 deletions(-) diff --git a/package/AUTHORS b/package/AUTHORS index 26b02cfba1..6e3fa1c847 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -252,6 +252,7 @@ Chronological list of authors - Lexi Xu - BHM-Bob G - Yu-Yuan (Stuart) Yang + - Echo Fia External code ------------- diff --git a/package/CHANGELOG b/package/CHANGELOG index faeaf76b59..a9517fbcf8 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -58,7 +58,8 @@ Enhancements * Enable parallelization for analysis.nucleicacids.NucPairDist (Issue #4670) * Add check and warning for empty (all zero) coordinates in RDKit converter (PR #4824) - * Added `precision` for XYZWriter (Issue #4775, PR #4771) + * Added `precision` for XYZWriter (Issue #4775, PR #4771) + * Improved tests for lineardensity.py (Issue #3603, PR #5013) Changes * MDAnalysis.analysis.psa, MDAnalysis.analysis.waterdynamics and diff --git a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py index f96183b554..b577a10602 100644 --- a/testsuite/MDAnalysisTests/analysis/test_lineardensity.py +++ b/testsuite/MDAnalysisTests/analysis/test_lineardensity.py @@ -34,7 +34,6 @@ from MDAnalysis.units import constants - def test_invalid_grouping(): """Invalid groupings raise AttributeError""" universe = mda.Universe(waterPSF, waterDCD) @@ -48,29 +47,32 @@ def test_invalid_grouping(): ### Creating the Test Universes ### -def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs): + +def make_Universe( + coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs +): """Generate a reference universe of 100 atoms with uniformly drawn random positions.""" - n_residues = n_atoms // atomsPerRes # Arbitrarily 5 atoms per residue - n_segments = n_residues // resPerSegs # Arbitrarily 4 residues per segment + n_residues = n_atoms // atomsPerRes # Arbitrarily 5 atoms per residue + n_segments = n_residues // resPerSegs # Arbitrarily 4 residues per segment # Indexing atoms into residues & residues into segments atom_resindex = np.repeat(np.arange(n_residues), atomsPerRes) residue_segindex = np.repeat(np.arange(n_segments), resPerSegs) # Creating the universe - u = mda.Universe.empty(n_atoms=n_atoms, - n_residues=n_residues, - n_segments=n_segments, - atom_resindex=atom_resindex, - residue_segindex=residue_segindex) + u = mda.Universe.empty( + n_atoms=n_atoms, + n_residues=n_residues, + n_segments=n_segments, + atom_resindex=atom_resindex, + residue_segindex=residue_segindex, + ) # Assigning the Charges & Masses - u.add_TopologyAttr('charges', values=charges) - u.add_TopologyAttr('masses', values=masses) + u.add_TopologyAttr("charges", values=charges) + u.add_TopologyAttr("masses", values=masses) - u.trajectory = get_reader_for(coords)(coords, - order='fac', - n_atoms=n_atoms) + u.trajectory = get_reader_for(coords)(coords, order="fac", n_atoms=n_atoms) for ts in u.trajectory: ts.dimensions = np.array([1, 1, 1, 90, 90, 90]) @@ -85,6 +87,7 @@ def make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPe # The positions are randomly taken from a uniform distribution between 0 and 1 # NOTE: The positions at each time frame are independent on the previous time step + def neutral_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): charges = np.zeros(n_atoms) masses = np.ones(n_atoms) @@ -92,115 +95,199 @@ def neutral_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): shape = (n_frames, n_atoms, 3) coords = np.random.random(shape) - return make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs) + return make_Universe( + coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs + ) + def charged_Particles(n_atoms, n_frames, atomsPerRes, resPerSegs): - charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 + charges = np.random.random(n_atoms) * 2 - np.ones( + n_atoms + ) # Between -1 and 1 masses = np.ones(n_atoms) shape = (n_frames, n_atoms, 3) coords = np.random.random(shape) - return make_Universe(coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs) + return make_Universe( + coords, charges, masses, n_atoms, n_frames, atomsPerRes, resPerSegs + ) + -def charged_Dimers(n_dimers=100, n_frames=1, dimersPerRes=5, resPerSegs=4, dimerLength = 0.05): +def charged_Dimers( + n_dimers=100, n_frames=1, dimersPerRes=5, resPerSegs=4, dimerLength=0.05 +): n_atoms = 2 * n_dimers - - charges = np.random.random(n_atoms) * 2 - np.ones(n_atoms) # Between -1 and 1 + + charges = np.random.random(n_atoms) * 2 - np.ones( + n_atoms + ) # Between -1 and 1 masses = np.ones(n_atoms) shape = (n_frames, n_dimers, 3) coords = np.random.random(shape) * 0.9 + np.ones(shape) * 0.05 # Puts in the same coordinate twice per dimer - coords = np.repeat(coords, 2, axis = 1) + coords = np.repeat(coords, 2, axis=1) - # Shifts one of the atoms of each dimer by their bondLength in a random direction (defined to be in the box) + # Shifts one of the atoms of each dimer by their bondLength + # in a random direction (defined to be in the box) for time in coords: - for coord in time[::2,:]: + for coord in time[::2, :]: phi = np.random.random() * 2 * np.pi theta = np.random.random() * np.pi - x = np.array([np.sin(theta)*np.cos(phi), np.sin(theta)*np.sin(phi), np.cos(theta)]) * dimerLength - coord += np.array([np.sin(theta)*np.cos(phi), np.sin(theta)*np.sin(phi), np.cos(theta)]) * dimerLength - - - return make_Universe(coords, charges, masses, n_atoms, n_frames, dimersPerRes * 2, resPerSegs) + x = ( + np.array( + [ + np.sin(theta) * np.cos(phi), + np.sin(theta) * np.sin(phi), + np.cos(theta), + ] + ) + * dimerLength + ) + coord += ( + np.array( + [ + np.sin(theta) * np.cos(phi), + np.sin(theta) * np.sin(phi), + np.cos(theta), + ] + ) + * dimerLength + ) + return make_Universe( + coords, + charges, + masses, + n_atoms, + n_frames, + dimersPerRes * 2, + resPerSegs, + ) ### Calculating the Expected Values ### -def calc_Prop(u, prop = 'masses'): # Property can be 'masses' or 'charges' + +def calc_Prop(u, prop="masses"): # Property can be 'masses' or 'charges' expected_atoms = getattr(u.atoms, prop) - expected_residues = np.array([sum(getattr(res.atoms, prop)) for res in u.residues]) - expected_segments = np.array([sum(getattr(seg.atoms, prop)) for seg in u.segments]) + expected_residues = np.array( + [sum(getattr(res.atoms, prop)) for res in u.residues] + ) + expected_segments = np.array( + [sum(getattr(seg.atoms, prop)) for seg in u.segments] + ) return expected_atoms, expected_residues, expected_segments -def find_Centres(groups): # Always based on Centre of Mass (NOT Centre of Charge) + +def find_Centres( + groups, +): # Always based on Centre of Mass (NOT Centre of Charge) centres = [] for group in groups: total_Mass = sum(group.atoms.masses) if total_Mass != 0: - centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / total_Mass) + centres.append( + np.sum( + group.atoms.positions.transpose() + * abs(group.atoms.masses), + axis=1, + ) + / total_Mass + ) # Just in case there are particles with 0 mass (somehow) elif total_Mass == 0: - centres.append(np.sum(group.atoms.positions.transpose() * abs(group.atoms.masses), axis = 1) / len(group.atoms)) + centres.append( + np.sum( + group.atoms.positions.transpose() + * abs(group.atoms.masses), + axis=1, + ) + / len(group.atoms) + ) return np.array(centres) -def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'masses' or 'charges' - propShort = 'mass' - if prop == 'charges': - propShort = 'charge' +def calc_Densities( + u, prop="masses", spliceLen=0.25 +): # Property can be 'masses' or 'charges' + + propShort = "mass" + if prop == "charges": + propShort = "charge" ### Atoms - expected_atoms = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) # Works for cubic Universe + expected_atoms = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype( + float + ) # Works for cubic Universe for atom in u.atoms: for i in range(3): - expected_atoms[i][int(atom.position[i] // spliceLen)] += getattr(atom, propShort) - - + expected_atoms[i][int(atom.position[i] // spliceLen)] += getattr( + atom, propShort + ) - _,residue_Totals,segment_Totals = calc_Prop(u, prop) # Total of Charge OR Mass + _, residue_Totals, segment_Totals = calc_Prop( + u, prop + ) # Total of Charge OR Mass ### Residues - expected_residues = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) + expected_residues = np.zeros( + (3, int(u.dimensions[0] // spliceLen)) + ).astype(float) residue_Centres = find_Centres(u.residues) - for i in range(len(residue_Centres)): for j in range(3): - expected_residues[j][int(residue_Centres[i][j] // spliceLen)] += residue_Totals[i] + expected_residues[j][ + int(residue_Centres[i][j] // spliceLen) + ] += residue_Totals[i] ### Segments - expected_segments = np.zeros((3, int(u.dimensions[0] // spliceLen))).astype(float) + expected_segments = np.zeros( + (3, int(u.dimensions[0] // spliceLen)) + ).astype(float) segment_Centres = find_Centres(u.segments) - for i in range(len(segment_Centres)): for j in range(3): - expected_segments[j][int(segment_Centres[i][j] // spliceLen)] += segment_Totals[i] - - + expected_segments[j][ + int(segment_Centres[i][j] // spliceLen) + ] += segment_Totals[i] + # Scaling based on splice volumes & converting units for i in range(3): - expected_atoms[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] - expected_residues[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] - expected_segments[i,:] /= spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] - expected_atoms /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py - expected_residues /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py - expected_segments /= constants['N_Avogadro'] * 1e-24 # To be consistent with lineardensity.py + expected_atoms[i, :] /= ( + spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + ) + expected_residues[i, :] /= ( + spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + ) + expected_segments[i, :] /= ( + spliceLen * u.dimensions[(i + 1) % 3] * u.dimensions[(i + 2) % 3] + ) + expected_atoms /= ( + constants["N_Avogadro"] * 1e-24 + ) # To be consistent with lineardensity.py + expected_residues /= ( + constants["N_Avogadro"] * 1e-24 + ) # To be consistent with lineardensity.py + expected_segments /= ( + constants["N_Avogadro"] * 1e-24 + ) # To be consistent with lineardensity.py return expected_atoms, expected_residues, expected_segments + ### Creating the Expected Values ### spliceLen = 0.25 universes = [] -groupings = ['atoms', 'residues', 'segments'] +groupings = ["atoms", "residues", "segments"] # Will be [[Atoms, Residues, Segments (of Universe 1)], [... of Universe 2], ...] -expected_masses = [] +expected_masses = [] expected_charges = [] expected_xmass = [] expected_xcharge = [] @@ -209,24 +296,36 @@ def calc_Densities(u, prop = 'masses', spliceLen = 0.25): # Property can be 'mas test_Params = [[100, 1, 5, 4], [100, 1, 5, 4], [100, 1, 5, 4, 0.05]] for i in range(len(test_Universes)): - + universe = test_Universes[i](*test_Params[i]) universes.append(universe) - expected_masses.append(calc_Prop(universe, 'masses')) - expected_charges.append(calc_Prop(universe, 'charges')) - expected_xmass.append(calc_Densities(universe, 'masses', spliceLen)) - expected_xcharge.append(calc_Densities(universe, 'charges', spliceLen)) + expected_masses.append(calc_Prop(universe, "masses")) + expected_charges.append(calc_Prop(universe, "charges")) + expected_xmass.append(calc_Densities(universe, "masses", spliceLen)) + expected_xcharge.append(calc_Densities(universe, "charges", spliceLen)) ### Parametrizing for Pytest ### # NOTE: There is an extra [0] after the expected_xmass and expected_xcharge as the original data has all 3 co-ords, but only comparing to x here -pytest_Params = [(universes[i], groupings[j], expected_masses[i][j], expected_charges[i][j], expected_xmass[i][j][0], expected_xcharge[i][j][0]) for j in range(len(groupings)) for i in range(len(universes))] +pytest_Params = [ + ( + universes[i], + groupings[j], + expected_masses[i][j], + expected_charges[i][j], + expected_xmass[i][j][0], + expected_xcharge[i][j][0], + ) + for j in range(len(groupings)) + for i in range(len(universes)) +] + + @pytest.mark.parametrize( "universe, grouping, expected_masses, expected_charges, expected_xmass, expected_xcharge", pytest_Params, ) - def test_lineardensity( universe, grouping, @@ -244,6 +343,7 @@ def test_lineardensity( assert_allclose(ld.results.x.mass_density, expected_xmass, rtol=1e-06) assert_allclose(ld.results.x.charge_density, expected_xcharge) + @pytest.fixture(scope="module") def testing_Universe(): """Generate a universe for testing whether LinearDensity works with @@ -345,4 +445,3 @@ def test_parallel_analysis(testing_Universe): assert_allclose( ld1.results.x.mass_density, ld_whole.results.x.mass_density ) - From f9060312b3edeb6150c8c0b8b0025903604824ee Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 16:34:34 +0100 Subject: [PATCH 5/8] Updated AUTHORS & CHANGELOG, Improved Formatting --- package/AUTHORS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/AUTHORS b/package/AUTHORS index 6e3fa1c847..aedeaf6631 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -252,6 +252,8 @@ Chronological list of authors - Lexi Xu - BHM-Bob G - Yu-Yuan (Stuart) Yang + - James Rowe + - Debasish Mohanty - Echo Fia External code From 661638a71fd77dafc7d71b45dfa213ad1b3d5f16 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 16:35:55 +0100 Subject: [PATCH 6/8] Updated AUTHORS & CHANGELOG, Improved Formatting --- package/AUTHORS | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package/AUTHORS b/package/AUTHORS index aedeaf6631..b51e79fe83 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -246,16 +246,17 @@ Chronological list of authors - Matthew Davies - Jia-Xin Zhu - Tanish Yelgoe - 2025 +2025 - Joshua Raphael Uy - Namir Oues - Lexi Xu - BHM-Bob G - Yu-Yuan (Stuart) Yang - James Rowe - - Debasish Mohanty + - Debasish Mohanty - Echo Fia + External code ------------- @@ -302,4 +303,4 @@ Logo The MDAnalysis 'Atom' logo was designed by Christian Beckstein; it is Copyright (c) 2011 Christian Beckstein and made available under a -Creative Commons Attribution-NoDerivs 3.0 Unported License. +Creative Commons Attribution-NoDerivs 3.0 Unported License. \ No newline at end of file From 73e586d08d9012292b3b8a67ebec677773c16e91 Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 16:37:54 +0100 Subject: [PATCH 7/8] Updated AUTHORS & CHANGELOG, Improved Formatting --- package/AUTHORS | 1 - 1 file changed, 1 deletion(-) diff --git a/package/AUTHORS b/package/AUTHORS index b51e79fe83..2de57acd9d 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -254,7 +254,6 @@ Chronological list of authors - Yu-Yuan (Stuart) Yang - James Rowe - Debasish Mohanty - - Echo Fia External code From 9b043252a357ce35e16cc1d9f958548b0515f07a Mon Sep 17 00:00:00 2001 From: Echo Fia Date: Wed, 9 Apr 2025 17:30:53 +0100 Subject: [PATCH 8/8] Updated package/AUTHORS --- package/AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/package/AUTHORS b/package/AUTHORS index 2de57acd9d..b51e79fe83 100644 --- a/package/AUTHORS +++ b/package/AUTHORS @@ -254,6 +254,7 @@ Chronological list of authors - Yu-Yuan (Stuart) Yang - James Rowe - Debasish Mohanty + - Echo Fia External code