From 50629b706859b5260f3a421c07f1d04c6c3405a0 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Tue, 22 Mar 2016 23:08:24 +0000 Subject: [PATCH 1/7] Changed bare except in GROParser to try and fix coverage detection --- package/MDAnalysis/topology/GROParser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/topology/GROParser.py b/package/MDAnalysis/topology/GROParser.py index af25d61a8fb..e2c3068b90a 100644 --- a/package/MDAnalysis/topology/GROParser.py +++ b/package/MDAnalysis/topology/GROParser.py @@ -71,7 +71,7 @@ def parse(self): charge = guess_atom_charge(name) # segid = "SYSTEM" # ignore coords and velocities, they can be read by coordinates.GRO - except: + except (ValueError, IndexError): raise IOError("Couldn't read the following line of the .gro file:\n" "{0}".format(line)) else: From de62a9b0cf9a8ada5916b7d18daf464a8ee8150b Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Wed, 6 Apr 2016 13:00:47 +0100 Subject: [PATCH 2/7] Added tests for Issue #816 --- .../MDAnalysisTests/coordinates/test_mol2.py | 25 ++++- .../data/mol2/zinc_856218.mol2 | 100 ++++++++++++++++++ testsuite/MDAnalysisTests/datafiles.py | 3 + 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 diff --git a/testsuite/MDAnalysisTests/coordinates/test_mol2.py b/testsuite/MDAnalysisTests/coordinates/test_mol2.py index c683047cf8c..054375e3aa8 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_mol2.py +++ b/testsuite/MDAnalysisTests/coordinates/test_mol2.py @@ -17,10 +17,16 @@ import tempdir import os -from numpy.testing import (assert_equal,assert_raises, assert_array_equal, - assert_array_almost_equal, TestCase) - -from MDAnalysisTests.datafiles import mol2_molecules, mol2_molecule, mol2_broken_molecule +from numpy.testing import ( + assert_equal,assert_raises, assert_array_equal, + assert_array_almost_equal, TestCase, + assert_, +) + +from MDAnalysisTests.datafiles import ( + mol2_molecules, mol2_molecule, mol2_broken_molecule, + mol2_zinc, +) from MDAnalysis import Universe import MDAnalysis as mda @@ -121,3 +127,14 @@ def test_reverse_traj(self): def test_n_frames(self): assert_equal(self.universe.trajectory.n_frames, 200, "wrong number of frames in traj") + + +class TestMOL2NoSubstructure(object): + """MOL2 file without substructure + + """ + n_atoms = 45 + + def test_load(self): + r = mda.coordinates.MOL2.MOL2Reader(mol2_zinc, n_atoms=self.n_atoms) + assert_(r.n_atoms == 45) diff --git a/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 b/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 new file mode 100644 index 00000000000..cdb22b96156 --- /dev/null +++ b/testsuite/MDAnalysisTests/data/mol2/zinc_856218.mol2 @@ -0,0 +1,100 @@ +@MOLECULE +ZINC00856218 + 45 47 0 0 0 +SMALL +USER_CHARGES + +@ATOM + 1 C1 -0.0173 1.4248 0.0099 C.3 1 <0> 0.0247 + 2 O1 0.0021 -0.0041 0.0020 O.3 1 <0> -0.3173 + 3 C2 -1.1998 -0.6372 0.0101 C.ar 1 <0> 0.1400 + 4 C3 -2.3735 0.1054 0.0195 C.ar 1 <0> -0.2092 + 5 C4 -3.5949 -0.5325 0.0272 C.ar 1 <0> -0.0372 + 6 C5 -3.6515 -1.9288 0.0256 C.ar 1 <0> -0.0654 + 7 C6 -2.4681 -2.6718 0.0162 C.ar 1 <0> -0.0517 + 8 C7 -1.2512 -2.0252 0.0031 C.ar 1 <0> -0.1554 + 9 C8 -4.9558 -2.6158 0.0344 C.2 1 <0> 0.1247 + 10 N1 -6.1039 -2.0314 0.0383 N.2 1 <0> -0.2180 + 11 N2 -7.1882 -2.9065 0.0465 N.am 1 <0> -0.4682 + 12 C9 -6.6839 -4.2889 0.0485 C.3 1 <0> 0.1455 + 13 H1 -7.0145 -4.8191 -0.8446 H 1 <0> 0.1012 + 14 C10 -5.1522 -4.1150 0.0337 C.3 1 <0> -0.1212 + 15 C11 -7.1318 -5.0151 1.2907 C.ar 1 <0> -0.0960 + 16 C12 -7.1732 -4.3504 2.5022 C.ar 1 <0> -0.0694 + 17 C13 -7.5842 -5.0152 3.6420 C.ar 1 <0> -0.1494 + 18 C14 -7.9546 -6.3474 3.5702 C.ar 1 <0> 0.0999 + 19 C15 -7.9127 -7.0120 2.3563 C.ar 1 <0> -0.1524 + 20 C16 -7.5063 -6.3438 1.2169 C.ar 1 <0> -0.0709 + 21 F1 -8.3561 -6.9983 4.6838 F 1 <0> -0.1406 + 22 C17 -8.4868 -2.5457 0.0523 C.2 1 <0> 0.5494 + 23 O2 -9.3524 -3.3956 0.0598 O.2 1 <0> -0.5129 + 24 C18 -8.8634 -1.0866 0.0494 C.3 1 <0> -0.0958 + 25 C19 -10.3876 -0.9533 0.0571 C.3 1 <0> -0.1702 + 26 C20 -10.7643 0.5059 0.0542 C.2 1 <0> 0.4921 + 27 O3 -9.8943 1.3600 0.0466 O.co2 1 <0> -0.6986 + 28 O4 -11.9390 0.8322 0.0594 O.co2 1 <0> -0.7025 + 29 H2 1.0053 1.8021 0.0021 H 1 <0> 0.1001 + 30 H3 -0.5445 1.7859 -0.8732 H 1 <0> 0.0585 + 31 H4 -0.5275 1.7763 0.9067 H 1 <0> 0.0584 + 32 H5 -2.3288 1.1845 0.0211 H 1 <0> 0.1341 + 33 H6 -4.5071 0.0457 0.0349 H 1 <0> 0.1415 + 34 H7 -2.5071 -3.7511 0.0149 H 1 <0> 0.1350 + 35 H8 -0.3361 -2.5986 -0.0083 H 1 <0> 0.1350 + 36 H9 -4.7239 -4.5563 -0.8663 H 1 <0> 0.1062 + 37 H10 -4.7105 -4.5589 0.9259 H 1 <0> 0.1045 + 38 H11 -6.8840 -3.3114 2.5580 H 1 <0> 0.1276 + 39 H12 -7.6162 -4.4958 4.5884 H 1 <0> 0.1363 + 40 H13 -8.2010 -8.0513 2.2992 H 1 <0> 0.1365 + 41 H14 -7.4778 -6.8608 0.2691 H 1 <0> 0.1338 + 42 H15 -8.4520 -0.6033 0.9355 H 1 <0> 0.0959 + 43 H16 -8.4615 -0.6093 -0.8444 H 1 <0> 0.0966 + 44 H17 -10.7991 -1.4365 -0.8291 H 1 <0> 0.0626 + 45 H18 -10.7895 -1.4305 0.9509 H 1 <0> 0.0624 +@BOND + 1 1 2 1 + 2 1 29 1 + 3 1 30 1 + 4 1 31 1 + 5 2 3 1 + 6 3 8 ar + 7 3 4 ar + 8 4 5 ar + 9 4 32 1 + 10 5 6 ar + 11 5 33 1 + 12 6 7 ar + 13 6 9 1 + 14 7 8 ar + 15 7 34 1 + 16 8 35 1 + 17 9 14 1 + 18 9 10 2 + 19 10 11 1 + 20 11 12 1 + 21 11 22 am + 22 12 13 1 + 23 12 14 1 + 24 12 15 1 + 25 14 36 1 + 26 14 37 1 + 27 15 20 ar + 28 15 16 ar + 29 16 17 ar + 30 16 38 1 + 31 17 18 ar + 32 17 39 1 + 33 18 19 ar + 34 18 21 1 + 35 19 20 ar + 36 19 40 1 + 37 20 41 1 + 38 22 23 2 + 39 22 24 1 + 40 24 25 1 + 41 24 42 1 + 42 24 43 1 + 43 25 26 1 + 44 25 44 1 + 45 25 45 1 + 46 26 27 2 + 47 26 28 1 diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 19fa47019f1..fcd4fc71914 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -85,6 +85,7 @@ "TRR_multi_frame", "merge_protein", "merge_ligand", "merge_water", "mol2_molecules", "mol2_molecule", "mol2_broken_molecule", + "mol2_zinc", "capping_input", "capping_output", "capping_ace", "capping_nma", "contacts_villin_folded", "contacts_villin_unfolded", "contacts_file", "LAMMPSdata", "trz4data", "LAMMPSdata_mini", @@ -261,6 +262,8 @@ mol2_molecules = resource_filename(__name__, "data/mol2/Molecules.mol2") mol2_molecule = resource_filename(__name__, "data/mol2/Molecule.mol2") mol2_broken_molecule = resource_filename(__name__, "data/mol2/BrokenMolecule.mol2") +# MOL2 file without substructure field +mol2_zinc = resource_filename(__name__, "data/mol2/zinc_856218.mol2") capping_input = resource_filename(__name__, "data/capping/aaqaa.gro") capping_output = resource_filename(__name__, "data/capping/maestro_aaqaa_capped.pdb") From ac9afb90424735f9acbf30e7e086d0d4accebdd6 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Wed, 6 Apr 2016 13:08:38 +0100 Subject: [PATCH 3/7] Fixes issue #816 --- package/MDAnalysis/coordinates/MOL2.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/MOL2.py b/package/MDAnalysis/coordinates/MOL2.py index 8ef6720f770..bdd8bbd9e71 100644 --- a/package/MDAnalysis/coordinates/MOL2.py +++ b/package/MDAnalysis/coordinates/MOL2.py @@ -124,8 +124,11 @@ def _read_frame(self, frame): sections, coords = self.parse_block(block) - self.ts.data['molecule'] = sections["molecule"] - self.ts.data['substructure'] = sections["substructure"] + for sect in ['molecule', 'substructure']: + try: + self.ts.data[sect] = sections[sect] + except KeyError: + pass # check if atom number changed if len(coords) != self.n_atoms: From b443a4d00e693c411f894dbde6df9e1f42b0698e Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Wed, 6 Apr 2016 13:21:56 +0100 Subject: [PATCH 4/7] Removed list appending for MOL2 coordinate reading --- package/MDAnalysis/coordinates/MOL2.py | 30 +++++++++++-------- .../MDAnalysisTests/coordinates/test_mol2.py | 4 +++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/coordinates/MOL2.py b/package/MDAnalysis/coordinates/MOL2.py index bdd8bbd9e71..46ce7e964db 100644 --- a/package/MDAnalysis/coordinates/MOL2.py +++ b/package/MDAnalysis/coordinates/MOL2.py @@ -59,6 +59,8 @@ def __init__(self, filename, **kwargs): """Read coordinates from *filename*.""" super(MOL2Reader, self).__init__(filename, **kwargs) + self.n_atoms = None + blocks = [] with util.openany(filename) as f: @@ -93,16 +95,26 @@ def parse_block(self, block): if not len(atom_lines): raise Exception("The mol2 (starting at line {0}) block has no atoms" "".format(block["start_line"])) + elif self.n_atoms is None: + # First time round, remember the number of atoms + self.n_atoms = len(atom_lines) + elif len(atom_lines) != self.n_atoms: + raise ValueError( + "MOL2Reader assumes that the number of atoms remains unchanged" + " between frames; the current " + "frame has {0}, the next frame has {1} atoms" + "".format(self.n_atoms, len(atom_lines))) + if not len(bond_lines): raise Exception("The mol2 (starting at line {0}) block has no bonds" "".format(block["start_line"])) - coords = [] - for a in atom_lines: + coords = np.zeros((self.n_atoms, 3), dtype=np.float32) + for i, a in enumerate(atom_lines): aid, name, x, y, z, atom_type, resid, resname, charge = a.split() - x, y, z = float(x), float(y), float(z) - coords.append((x, y, z)) - coords = np.array(coords, dtype=np.float32) + #x, y, z = float(x), float(y), float(z) + coords[i, :] = x, y, z + return sections, coords def _read_next_timestep(self, ts=None): @@ -130,14 +142,6 @@ def _read_frame(self, frame): except KeyError: pass - # check if atom number changed - if len(coords) != self.n_atoms: - raise ValueError( - "MOL2Reader assumes that the number of atoms remains unchanged" - " between frames; the current " - "frame has {0}, the next frame has {1} atoms" - "".format(self.n_atoms, len(coords))) - self.ts.positions = np.array(coords, dtype=np.float32) self.ts.unitcell = unitcell if self.convert_units: diff --git a/testsuite/MDAnalysisTests/coordinates/test_mol2.py b/testsuite/MDAnalysisTests/coordinates/test_mol2.py index 054375e3aa8..f203f7db9c2 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_mol2.py +++ b/testsuite/MDAnalysisTests/coordinates/test_mol2.py @@ -138,3 +138,7 @@ class TestMOL2NoSubstructure(object): def test_load(self): r = mda.coordinates.MOL2.MOL2Reader(mol2_zinc, n_atoms=self.n_atoms) assert_(r.n_atoms == 45) + + def test_universe(self): + u = mda.Universe(mol2_zinc) + assert_(len(u.atoms) == 45) From 07fc902deff851e6761b4f4ab1a04c6ad0297eb0 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Wed, 6 Apr 2016 13:32:27 +0100 Subject: [PATCH 5/7] MOL2 Writer no longer requires substructure --- package/MDAnalysis/coordinates/MOL2.py | 5 ++--- testsuite/MDAnalysisTests/coordinates/test_mol2.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/coordinates/MOL2.py b/package/MDAnalysis/coordinates/MOL2.py index 46ce7e964db..f02168a9c22 100644 --- a/package/MDAnalysis/coordinates/MOL2.py +++ b/package/MDAnalysis/coordinates/MOL2.py @@ -284,10 +284,9 @@ def encode_block(self, obj): atom_lines = "\n".join(atom_lines) try: - substructure = ["@SUBSTRUCTURE\n"] - substructure.extend(ts.data['substructure']) + substructure = ["@SUBSTRUCTURE\n"] + ts.data['substructure'] except KeyError: - raise NotImplementedError("No MOL2 substructure type found in traj") + substructure = "" molecule = ts.data['molecule'] check_sums = molecule[1].split() diff --git a/testsuite/MDAnalysisTests/coordinates/test_mol2.py b/testsuite/MDAnalysisTests/coordinates/test_mol2.py index f203f7db9c2..30a1f54afb3 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_mol2.py +++ b/testsuite/MDAnalysisTests/coordinates/test_mol2.py @@ -141,4 +141,16 @@ def test_load(self): def test_universe(self): u = mda.Universe(mol2_zinc) - assert_(len(u.atoms) == 45) + assert_(len(u.atoms) == self.n_atoms) + + def test_write_nostructure(self): + mytempdir = tempdir.TempDir() + outfile = os.path.join(mytempdir.name, 'test.mol2') + + u = mda.Universe(mol2_zinc) + with mda.Writer(outfile) as W: + W.write(u.atoms) + + u2 = mda.Universe(outfile) + + assert_(len(u.atoms) == len(u2.atoms)) From f19cdcc39b69ea0a45ee2ee9d5167a0511cf32f4 Mon Sep 17 00:00:00 2001 From: Richard Gowers Date: Wed, 6 Apr 2016 13:36:37 +0100 Subject: [PATCH 6/7] Finished Issue #816 --- package/CHANGELOG | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index a6db1d2d279..c761ff3a61f 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,7 +13,8 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -??/??/16 jandom, abhinavgupta94, orbeckst, kain88-de, hainm, jdetle, jbarnoud +??/??/16 jandom, abhinavgupta94, orbeckst, kain88-de, hainm, jdetle, jbarnoud, + richardjgowers * 0.15.0 @@ -38,6 +39,8 @@ Fixes * change_release now finds number and dev (Issue #776) * test_shear_from_matrix doesn't fail for MKL builds anymore (Issue #757) * HEADER and TITLE now appear just once in the PDB. (Issue #741) (PR #761) + * MOL2 files without substructure section can now be read (Issue #816) + * MOL2 files can be written without substructure section (Issue #816) Changes From d73c18a651ad4667a3e4de431b27ad3ec7263521 Mon Sep 17 00:00:00 2001 From: David Dotson Date: Sun, 10 Apr 2016 15:24:26 -0700 Subject: [PATCH 7/7] All deprecations on AtomGroup and friends getters and setters. (#698) * All deprecations on AtomGroup and friends getters and setters. The new topology system for #363 will arrive in 0.16.0, and this removes redundant getters and setters of various `Atom`, `Residue`, and `Segment` properties. These methods predated the properties that replace them, and this commit features deprecation warnings for each with instructions on what to use instead. Some `Atom` properties, such as `number`, `pos`, `serials`, have themselves been replaced in the new scheme (`index`, `position`, `id`, respectively). The methods `AtomGroup.translate` and `AtomGroup.rotateby` no longer take tuples of `AtomGroup` objects as optional arguments, but only take explicit vectors. Because `Residue`, `ResidueGroup`, `Segment`, and `SegmentGroup` are no longer `AtomGroup`s themselves, leaving this in creates a minor convenience at the cost of method complexity. The documentation is correspondingly complex, and in the course of updating the docstrings to numpy-style for #363, simplifying the method also simplified the documenation. Also deprecated `as_Universe` function. This has yet to be discussed on the `issue-363` branch, but discussion should happen on what its purpose is. Not addressed in this commit: 1) In #363, `__getattr__` behavior for the Groups have changed to a slightly different scheme. No warnings have been added for this change to the `__getattr__` methods yet. The new scheme goes as:: SegmentGroups __getattr__ can select by segment name Segment __getattr__ can select by residue name ResidueGroup __getattr__ can select by residue name Residue __getattr__ can select by atom name AtomGroup __getattr__ can select by atom name Should only require the following in `AtomGroup.__getattr__`, or pehaps override in `ResidueGroup` and add after a `super`: ```python if isinstance(self, ResidueGroup): warnings.warn("In version 0.15.0 this will select " "residue names, not atom names ", DeprecationWarning) ``` * Added deprecations to Residue angle methods. The methods `phi_selection`, `psi_selection`, `omega_selection`, and `chi1_selection` rely on particular atom names and the contiguity of resids to work properly. Although these are not bad assumptions for most topologies with a protein, they are inherently fragile. They have not been included in the new topology system addressing #363. * Added warnings for property access at the appropriate level. Also added warning that getattr on ResidueGroups will work only for residue names, not atom names. * _FIFTEEN -> _SIXTEEN; Segment now has segid property In the new topology scheme, atomid is just a label present defined by the topology format (gro and PDB files have them) that don't need to start from 0 or be unique. Atoms are uniquely identified by atom *index* instead, which is mostly internal to the topology system but is unique between atoms and starts from 0. Given this, we make atom id in the old system roughly behave as it will for the new scheme. Strangely, atom ids up until this point were defined by residues. I have no idea why, or what a good reason might have been for this. * Added kwarg support to atom_property decorator * Removed "id" from Atom slots. Since `Atom.id` is now a property, it shouldn't be a slot. * Added deprecations for Residue, Segment plural residue, segment props respectively Since Residues are an AtomGroup subclass here, a Residue has e.g. `resids`, which gives per-atom resids (all the same, *hopefully*). This doesn't exist in #363, so added proper deprecations. Ditto for segment properties that spit out per-residue results. Also, since we don't want our own recommendations for setters using the deprecated setters internally, did simple copy-paste of setter code to appropriate property. Also deprecated Residue.id, Residue.name, and added Residue.resid and Residue.resname as the proper alternatives. * Fixed neglected changes to copy-pasted code for property setters * Removed deprecated use of Segment.id * removed usage of deprecated functions Universe init * Removed deprecations on anchor mechanisms. The anchor mechanisms of a Universe deserve further discussion. They are used for AtomGroup pickling/unpickling, which may or may not make sense under the new system. De-deprecating for now, though. * Added deprecations to changelog; fixed some omissions. Also, moved warning decorator outside of cached decorator if present so it displays every time. --- package/CHANGELOG | 19 +- package/MDAnalysis/core/AtomGroup.py | 414 ++++++++++++++++++++++----- 2 files changed, 363 insertions(+), 70 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 1415bf30c1e..519419a9e71 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -14,7 +14,7 @@ The rules for this file: ------------------------------------------------------------------------------ ??/??/16 jandom, abhinavgupta94, orbeckst, kain88-de, hainm, jdetle, jbarnoud, - dotsdl, richardjgowers + dotsdl, richardjgowers * 0.15.0 @@ -47,6 +47,23 @@ Changes * Generalized contact analysis class added. (Issue #702) +Deprecations (Issue #599) + + * Deprecated all `get_*` and `set_*` methods of Groups. + * Deprecation warnings for accessing atom attributes from Residue, + ResidueGroup, Segment, SegmentGroup. Will not be present or will + give per-level results. + * Deprecation warnings for accessing plural residue attributes from + Residue or Segment (will disappear), or from SegmentGroup (will give + per-Segment results). + * Deprecation warnings for accessing plural segment attributes from Segment + (will disappear). + * Deprecated Atom number, pos, centroid, universe setter + * Deprecated AtomGroup serials, write_selection + * Deprecated Residue name, id + * Deprecated Segment id, name + * Deprecated as_Universe function; not needed + 02/28/16 tyler.je.reddy, kain88-de, jbarnoud, richardjgowers, orbeckst manuel.nuno.melo, Balasubra, Saxenauts, mattihappy diff --git a/package/MDAnalysis/core/AtomGroup.py b/package/MDAnalysis/core/AtomGroup.py index f8991d81dda..5d92dca4077 100644 --- a/package/MDAnalysis/core/AtomGroup.py +++ b/package/MDAnalysis/core/AtomGroup.py @@ -457,7 +457,83 @@ # And the return route _SINGULAR_PROPERTIES = {v: k for k, v in _PLURAL_PROPERTIES.items()} -_FIFTEEN_DEPRECATION = "This will be removed in version 0.15.0" +_SIXTEEN_DEPRECATION = "This will be removed in version 0.16.0" + +def warn_atom_property(func): + warnstring = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args, **kwargs): + if isinstance(self, SegmentGroup): + warnings.warn(warnstring.format('segmentgroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Segment): + warnings.warn(warnstring.format('segment', func.__name__), + DeprecationWarning) + elif isinstance(self, ResidueGroup): + warnings.warn(warnstring.format('residuegroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Residue): + warnings.warn(warnstring.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args, **kwargs) + + return outfunc + +def warn_residue_property(func): + warnstring = "In version 0.16.0, use `{}.residues.{}` instead." + warnstring_sing = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args): + if isinstance(self, SegmentGroup): + warnings.warn(warnstring.format('segmentgroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Segment): + warnings.warn(warnstring.format('segment', func.__name__), + DeprecationWarning) + elif isinstance(self, ResidueGroup): + pass + elif isinstance(self, Residue): + warnings.warn(warnstring_sing.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args) + + return outfunc + +def warn_segment_property(func): + warnstring = "In version 0.16.0, use `{}.segments.{}` instead." + warnstring_sing = "In version 0.16.0, use `{}.atoms.{}` instead." + + def outfunc(self, *args): + if isinstance(self, SegmentGroup): + pass + elif isinstance(self, Segment): + warnings.warn("In version 0.16.0, Use 'segment.residues.{}' instead.".format(func.__name__), + DeprecationWarning) + pass + elif isinstance(self, ResidueGroup): + warnings.warn(warnstring.format('residuegroup', func.__name__), + DeprecationWarning) + elif isinstance(self, Residue): + warnings.warn(warnstring_sing.format('residue', func.__name__), + DeprecationWarning) + elif isinstance(self, AtomGroup): + pass + elif isinstance(self, Atom): + pass + + return func(self, *args) + + return outfunc @functools.total_ordering @@ -492,7 +568,7 @@ class Atom(object): """ __slots__ = ( - "index", "id", "name", "type", "resname", "resid", "segid", + "index", "name", "type", "resname", "resid", "segid", "mass", "charge", "residue", "segment", "_universe", "radius", "bfactor", "resnum", "serial", "altLoc") @@ -546,11 +622,21 @@ def __add__(self, other): return AtomGroup([self] + other._atoms) @property + @deprecate(message="{}; use `index` property instead".format(_SIXTEEN_DEPRECATION)) def number(self): """The index of this atom""" return self.index @property + def id(self): + """The atom id of this atom""" + if self.serial is not None: + return self.serial + else: + return self.index + + @property + @deprecate(message="{}; use `position` property instead".format(_SIXTEEN_DEPRECATION)) def pos(self): """coordinates of the atom @@ -671,6 +757,7 @@ def force(self, vals): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain forces") + @deprecate(message="{}; use `position` property instead".format(_SIXTEEN_DEPRECATION)) def centroid(self): """The centroid of an atom is its position, :attr:`Atom.position`.""" # centroid exists for compatibility with AtomGroup @@ -685,6 +772,7 @@ def universe(self): return self._universe @universe.setter + @deprecate(message="{}; Atoms will not be able to leave their Universes.".format(_SIXTEEN_DEPRECATION)) def universe(self, new): self._universe = new @@ -1042,6 +1130,9 @@ def __getitem__(self, item): raise TypeError("Cannot slice with type: {0}".format(type(item))) def __getattr__(self, name): + if isinstance(self, ResidueGroup): + warnings.warn("In version 0.16.0 this will select " + "residue names, not atom names ", DeprecationWarning) try: return self._get_named_atom(name) except SelectionError: @@ -1143,6 +1234,7 @@ def n_segments(self): return len(self.segments) @property + @warn_atom_property @cached('indices') def indices(self): """Array of all :attr:`Atom.index` in the group. @@ -1160,6 +1252,7 @@ def indices(self): return np.array([atom.index for atom in self._atoms]) @property + @warn_atom_property @cached('masses') def masses(self): """Array of atomic masses (as defined in the topology) @@ -1170,9 +1263,10 @@ def masses(self): return np.array([atom.mass for atom in self._atoms]) @masses.setter + @warn_atom_property def masses(self, new): self._clear_caches('masses') - self.set_masses(new) + self.set("mass", new, conversion=float, cache="masses") def total_mass(self): """Total mass of the selection (masses are taken from the topology or guessed).""" @@ -1181,9 +1275,10 @@ def total_mass(self): totalMass = deprecate(total_mass, old_name='totalMass', new_name='total_mass', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) @property + @warn_atom_property def occupancies(self): """Access occupancies of atoms @@ -1202,6 +1297,7 @@ def occupancies(self): raise NoDataError('Timestep does not contain occupancy') @occupancies.setter + @warn_atom_property def occupancies(self, new): try: self.universe.coord.data['occupancy'][self.indices] = new @@ -1211,6 +1307,7 @@ def occupancies(self, new): self.universe.coord.data['occupancy'][self.indices] = new @property + @warn_atom_property def charges(self): """Array of partial charges of the atoms (as defined in the topology) @@ -1220,8 +1317,9 @@ def charges(self): return np.array([atom.charge for atom in self._atoms]) @charges.setter + @warn_atom_property def charges(self, new): - self.set_charges(new) + self.set("charge", new, conversion=float) def total_charge(self): """Sum of all partial charges (must be defined in topology).""" @@ -1230,9 +1328,10 @@ def total_charge(self): totalCharge = deprecate(total_charge, old_name='totalCharge', new_name='total_charge', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) @property + @warn_atom_property def names(self): """Returns an array of atom names. @@ -1244,10 +1343,12 @@ def names(self): return np.array([a.name for a in self._atoms]) @names.setter + @warn_atom_property def names(self, new): - self.set_names(new) + self.set("name", new, conversion=str) @property + @warn_atom_property def types(self): """Returns an array of atom types. @@ -1258,10 +1359,12 @@ def types(self): return np.array([a.type for a in self._atoms]) @types.setter + @warn_atom_property def types(self, new): - self.set_types(new) + self.set("type", new) @property + @warn_atom_property def radii(self): """Array of atomic radii (as defined in the PQR file) @@ -1271,20 +1374,24 @@ def radii(self): return np.array([atom.radius for atom in self._atoms]) @radii.setter + @warn_atom_property def radii(self, new): - self.set_radii(new) + self.set("radius", new, conversion=float) @property + @warn_atom_property def bfactors(self): """Crystallographic B-factors (from PDB) in A**2. """ return np.array([atom.bfactor for atom in self._atoms]) @bfactors.setter + @warn_atom_property def bfactors(self, new): - self.set_bfactors(new) + self.set("bfactor", new, conversion=float) @property + @warn_atom_property def altLocs(self): """numpy array of the altLocs for all atoms in this group @@ -1293,10 +1400,12 @@ def altLocs(self): return np.array([atom.altLoc for atom in self._atoms]) @altLocs.setter + @warn_atom_property def altLocs(self, new): - self.set_altlocs(new) + self.set("altLoc", new, conversion=str) @property + @deprecate(message="{}; use `ids` property instead".format(_SIXTEEN_DEPRECATION)) def serials(self): """numpy array of the serials for all atoms in this group @@ -1305,8 +1414,31 @@ def serials(self): return np.array([atom.serial for atom in self._atoms]) @serials.setter + @deprecate(message="{}; use `ids` property instead".format(_SIXTEEN_DEPRECATION)) def serials(self, new): - self.set_serials(new) + self.set("serial", new, conversion=int) + + @property + @warn_atom_property + def ids(self): + """Array of the atom ids for all atoms in this group. + + Atom ids are defined by the topology file the universe was built from, + and need not start from 0. They are usually unique to each atom, but + need not be. + + """ + out = np.array([atom.serial for atom in self._atoms]) + + if not any(out): + out = np.array([atom.id for atom in self._atoms]) + + return out + + @ids.setter + @warn_atom_property + def ids(self, new): + self.set("serial", new, conversion=int) @property @cached('residues') @@ -1353,6 +1485,7 @@ def segments(self): return SegmentGroup(segments) @property + @warn_residue_property def resids(self): """Returns an array of residue numbers. @@ -1364,10 +1497,24 @@ def resids(self): return np.array([a.resid for a in self._atoms]) @resids.setter + @warn_residue_property def resids(self, new): - self.set_resids(new) + from MDAnalysis.topology.core import build_residues + + self.set("resid", new, conversion=int) + # Note that this also automagically updates THIS AtomGroup; + # the side effect of build_residues(self.atoms) is to update all Atoms!!!! + self._fill_cache('residues', ResidueGroup(build_residues(self.atoms))) + + # make sure to update the whole universe: the Atoms are shared but + # ResidueGroups are not + if self.atoms is not self.universe.atoms: + self.universe.atoms._fill_cache( + 'residues', + ResidueGroup(build_residues(self.universe.atoms))) @property + @warn_residue_property def resnames(self): """Returns an array of residue names. @@ -1379,10 +1526,12 @@ def resnames(self): return np.array([a.resname for a in self._atoms]) @resnames.setter + @warn_residue_property def resnames(self, new): - self.set_resnames(new) + self.set("resname", new, conversion=str) @property + @warn_residue_property def resnums(self): """Returns an array of canonical residue numbers. @@ -1395,10 +1544,12 @@ def resnums(self): return np.array([a.resnum for a in self._atoms]) @resnums.setter + @warn_residue_property def resnums(self, new): - self.set_resnums(new) + self.set("resnum", new) @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -1410,8 +1561,25 @@ def segids(self): return np.array([a.segid for a in self._atoms]) @segids.setter + @warn_segment_property def segids(self, new): - self.set_segids(new) + from MDAnalysis.topology.core import build_segments + + self.set("segid", new, conversion=str) + + # also updates convenience handles for segments in universe + segments = self.universe._build_segments() + + # Note that this also automagically updates THIS AtomGroup; + # the side effect of build_residues(self.atoms) is to update all Atoms!!!! + self._fill_cache('segments', SegmentGroup(segments)) + + # make sure to update the whole universe: the Atoms are shared but + # ResidueGroups are not + if self.atoms is not self.universe.atoms: + self.universe.atoms._fill_cache( + 'segments', + SegmentGroup(segments)) def sequence(self, **kwargs): """Returns the amino acid sequence. @@ -1518,6 +1686,7 @@ def fragments(self): """ return tuple(set(a.fragment for a in self._atoms)) + @warn_atom_property def guess_bonds(self, vdwradii=None): """Guess all the bonds that exist within this AtomGroup and add to Universe. @@ -1564,6 +1733,7 @@ def guess_bonds(self, vdwradii=None): self._clear_caches('dihedrals') @property + @warn_atom_property @cached('bonds') def bonds(self): """All the bonds in this AtomGroup @@ -1581,6 +1751,7 @@ def bonds(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('angles') def angles(self): """All the angles in this AtomGroup @@ -1598,6 +1769,7 @@ def angles(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('dihedrals') def dihedrals(self): """All the dihedrals in this AtomGroup @@ -1615,6 +1787,7 @@ def dihedrals(self): return top.TopologyGroup(mybonds) @property + @warn_atom_property @cached('impropers') def impropers(self): """All the improper dihedrals in this AtomGroup @@ -1706,6 +1879,7 @@ def set_occupancies(self, occupancies): """ self.occupancies = occupancies + @deprecate(message="{}; use `names` property instead".format(_SIXTEEN_DEPRECATION)) def set_names(self, name): """Set the atom names to string for *all atoms* in the AtomGroup. @@ -1725,8 +1899,9 @@ def set_names(self, name): set_name = deprecate(set_names, old_name='set_name', new_name='set_names', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resids` property instead".format(_SIXTEEN_DEPRECATION)) def set_resids(self, resid): """Set the resids to integer *resid* for **all atoms** in the :class:`AtomGroup`. @@ -1772,8 +1947,9 @@ def set_resids(self, resid): set_resid = deprecate(set_resids, old_name='set_resid', new_name='set_resids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnums` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnums(self, resnum): """Set the resnums to *resnum* for **all atoms** in the :class:`AtomGroup`. @@ -1803,8 +1979,9 @@ def set_resnums(self, resnum): set_resnum = deprecate(set_resnums, old_name='set_resnum', new_name='set_resnums', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnames` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnames(self, resname): """Set the resnames to string *resname* for **all atoms** in the :class:`AtomGroup`. @@ -1827,8 +2004,9 @@ def set_resnames(self, resname): set_resname = deprecate(set_resnames, old_name='set_resname', new_name='set_resnames', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `segids` property instead".format(_SIXTEEN_DEPRECATION)) def set_segids(self, segid): """Set the segids to *segid* for all atoms in the :class:`AtomGroup`. @@ -1872,8 +2050,9 @@ def set_segids(self, segid): set_segid = deprecate(set_segids, old_name='set_segid', new_name='set_segids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `masses` property instead".format(_SIXTEEN_DEPRECATION)) def set_masses(self, mass): """Set the atom masses to float *mass* for **all atoms** in the AtomGroup. @@ -1893,8 +2072,9 @@ def set_masses(self, mass): set_mass = deprecate(set_masses, old_name='set_mass', new_name='set_masses', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `types` property instead".format(_SIXTEEN_DEPRECATION)) def set_types(self, atype): """Set the atom types to *atype* for **all atoms** in the AtomGroup. @@ -1914,8 +2094,9 @@ def set_types(self, atype): set_type = deprecate(set_types, old_name='set_type', new_name='set_types', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `charges` property instead".format(_SIXTEEN_DEPRECATION)) def set_charges(self, charge): """Set the partial charges to float *charge* for **all atoms** in the AtomGroup. @@ -1935,8 +2116,9 @@ def set_charges(self, charge): set_charge = deprecate(set_charges, old_name='set_charge', new_name='set_charges', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `radii` property instead".format(_SIXTEEN_DEPRECATION)) def set_radii(self, radius): """Set the atom radii to float *radius* for **all atoms** in the AtomGroup. @@ -1956,8 +2138,9 @@ def set_radii(self, radius): set_radius = deprecate(set_radii, old_name='set_radius', new_name='set_radii', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `bfactors` property instead".format(_SIXTEEN_DEPRECATION)) def set_bfactors(self, bfactor): """Set the atom bfactors to float *bfactor* for **all atoms** in the AtomGroup. @@ -1977,8 +2160,9 @@ def set_bfactors(self, bfactor): set_bfactor = deprecate(set_bfactors, old_name='set_bfactor', new_name='set_bfactors', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `altLocs` property instead".format(_SIXTEEN_DEPRECATION)) def set_altLocs(self, altLoc): """Set the altLocs to *altLoc for **all atoms** in the AtomGroup. @@ -1994,8 +2178,9 @@ def set_altLocs(self, altLoc): set_altLoc = deprecate(set_altLocs, old_name='set_altLoc', new_name='set_altLocs', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `serials` property instead".format(_SIXTEEN_DEPRECATION)) def set_serials(self, serial): """Set the serials to *serial* for **all atoms** in the AtomGroup. @@ -2011,7 +2196,7 @@ def set_serials(self, serial): set_serial = deprecate(set_serials, old_name='set_serial', new_name='set_serials', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def center_of_geometry(self, **kwargs): """Center of geometry (also known as centroid) of the selection. @@ -2035,7 +2220,7 @@ def center_of_geometry(self, **kwargs): centerOfGeometry = deprecate(center_of_geometry, old_name='centerOfGeometry', new_name='center_of_geometry', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) centroid = center_of_geometry @@ -2062,7 +2247,7 @@ def center_of_mass(self, **kwargs): centerOfMass = deprecate(center_of_mass, old_name='centerOfMass', new_name='center_of_mass', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def radius_of_gyration(self, **kwargs): """Radius of gyration. @@ -2089,7 +2274,7 @@ def radius_of_gyration(self, **kwargs): radiusOfGyration = deprecate(radius_of_gyration, old_name='radiusOfGyration', new_name='radius_of_gyration', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def shape_parameter(self, **kwargs): """Shape parameter. @@ -2125,7 +2310,7 @@ def shape_parameter(self, **kwargs): shapeParameter = deprecate(shape_parameter, old_name='shapeParameter', new_name='shape_parameter', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def asphericity(self, **kwargs): """Asphericity. @@ -2208,7 +2393,7 @@ def moment_of_inertia(self, **kwargs): momentOfInertia = deprecate(moment_of_inertia, old_name='momentOfInertia', new_name='moment_of_inertia', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def bbox(self, **kwargs): """Return the bounding box of the selection. @@ -2366,8 +2551,9 @@ def principal_axes(self, **kwargs): principalAxes = deprecate(principal_axes, old_name='principalAxes', new_name='principal_axes', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) def get_positions(self, ts=None, copy=False, dtype=np.float32): """Get a numpy array of the coordinates. @@ -2415,6 +2601,10 @@ def get_positions(self, ts=None, copy=False, dtype=np.float32): # coordinates() should NOT be removed as it has been used in many scripts, # MDAnalysis itself, and in the paper + coordinates = deprecate(coordinates, + message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) + + @deprecate(message="{}; use `positions` property instead".format(_SIXTEEN_DEPRECATION)) def set_positions(self, coords, ts=None): """Set the positions for all atoms in the group. @@ -2458,6 +2648,7 @@ def set_positions(self, coords, ts=None): .. versionadded:: 0.7.6""") + @deprecate(message="{}; use `velocities` property instead".format(_SIXTEEN_DEPRECATION)) def get_velocities(self, ts=None, copy=False, dtype=np.float32): """numpy array of the velocities. @@ -2477,6 +2668,7 @@ def get_velocities(self, ts=None, copy=False, dtype=np.float32): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain velocities") + @deprecate(message="{}; use `velocities` property instead".format(_SIXTEEN_DEPRECATION)) def set_velocities(self, v, ts=None): """Assign the velocities *v* to the timestep. @@ -2510,6 +2702,7 @@ def set_velocities(self, v, ts=None): Became an attribute. """) + @deprecate(message="{}; use `forces` property instead".format(_SIXTEEN_DEPRECATION)) def get_forces(self, ts=None, copy=False, dtype=np.float32): """ Get a numpy array of the atomic forces (if available). @@ -2548,6 +2741,7 @@ def get_forces(self, ts=None, copy=False, dtype=np.float32): except (AttributeError, NoDataError): raise NoDataError("Timestep does not contain forces") + @deprecate(message="{}; use `forces` property instead".format(_SIXTEEN_DEPRECATION)) def set_forces(self, forces, ts=None): """Set the forces for all atoms in the group. @@ -2594,7 +2788,6 @@ def set_forces(self, forces, ts=None): .. versionadded:: 0.7.7""") - def transform(self, M): r"""Apply homogenous transformation matrix *M* to the coordinates. @@ -2643,6 +2836,7 @@ def translate(self, t): sel1, sel2 = t x1, x2 = sel1.centroid(), sel2.centroid() vector = x2 - x1 + except (ValueError, AttributeError): vector = np.asarray(t) # changes the coordinates (in place) @@ -2708,6 +2902,7 @@ def rotateby(self, angle, axis, point=None): n = v / np.linalg.norm(v) if point is None: point = x1 + except (ValueError, AttributeError): n = np.asarray(axis) if point is None: @@ -2748,7 +2943,7 @@ def align_principal_axis(self, axis, vector): align_principalAxis = deprecate(align_principal_axis, old_name='align_principalAxis', new_name='align_principal_axis', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def pack_into_box(self, box=None, inplace=True): r"""Shift all atoms in this group to be within the primary unit cell. @@ -2807,7 +3002,7 @@ def pack_into_box(self, box=None, inplace=True): packIntoBox = deprecate(pack_into_box, old_name='packIntoBox', new_name='pack_into_box', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def wrap(self, compound="atoms", center="com", box=None): """Shift the contents of this AtomGroup back into the unit cell. @@ -3096,7 +3291,7 @@ def select_atoms(self, selstr, *othersel, **selgroups): selectAtoms = deprecate(select_atoms, old_name='selectAtoms', new_name='select_atoms', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def split(self, level): """Split atomgroup into a list of atomgroups by *level*. @@ -3113,6 +3308,12 @@ def split(self, level): if level == "atom": return [AtomGroup([a]) for a in self] + if level in ('resid', 'segid'): + warnings.warn("'resid' or 'segid' are no longer allowed levels " + "in version 0.16.0; instead give " + "'residue' or 'segment', respectively.", + DeprecationWarning) + # more complicated groupings try: # use own list comprehension to avoid sorting/compression by eg self.resids @@ -3209,6 +3410,7 @@ def write(self, filename=None, format="PDB", writer.close() # TODO: This is _almost_ the same code as write() --- should unify! + @deprecate(message="{}; use `write` method instead".format(_SIXTEEN_DEPRECATION)) def write_selection(self, filename=None, format="vmd", filenamefmt="%(trjname)s_%(frame)d", **kwargs): """Write AtomGroup selection to a file to be used in another programme. @@ -3318,15 +3520,14 @@ class Residue(AtomGroup): def __init__(self, name, id, atoms, resnum=None): super(Residue, self).__init__(atoms) - self.name = name - self.id = id + self._resname = name + self._resid = id if resnum is not None: - self.resnum = resnum + self._resnum = resnum else: - self.resnum = self.id # TODO: get resnum from topologies that support it + self._resnum = self._resid # TODO: get resnum from topologies that support it self.segment = None - for i, a in enumerate(atoms): - a.id = i + for a in atoms: a.resnum = self.resnum a.residue = self @@ -3337,6 +3538,50 @@ def __init__(self, name, id, atoms, resnum=None): ##if not Residue._cache.has_key(name): ## Residue._cache[name] = dict([(a.name, i) for i, a in enumerate(self._atoms)]) + @property + @deprecate(message="{}; use `resname` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self): + return self._resname + + @name.setter + @deprecate(message="{}; use `resname` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self, value): + self._resname = value + + @property + def resname(self): + return self._resname + + @resname.setter + def resname(self, value): + self._resname = value + + @property + @deprecate(message="{}; use `resid` property instead".format(_SIXTEEN_DEPRECATION)) + def id(self): + return self._resid + + @id.setter + @deprecate(message="{}; use `resid` property instead".format(_SIXTEEN_DEPRECATION)) + def id(self, value): + self._resid = value + + @property + def resid(self): + return self._resid + + @resid.setter + def resid(self, value): + self._resid = value + + @property + def resnum(self): + return self._resnum + + @resnum.setter + def resnum(self, value): + self._resnum = value + def phi_selection(self): """AtomGroup corresponding to the phi protein backbone dihedral C'-N-CA-C. @@ -3345,7 +3590,7 @@ def phi_selection(self): method returns ``None``. """ sel = self.universe.select_atoms( - 'segid {0!s} and resid {1:d} and name C'.format(self.segment.id, self.id - 1)) + \ + 'segid {0!s} and resid {1:d} and name C'.format(self.segment.segid, self.resid - 1)) + \ self['N'] + self['CA'] + self['C'] if len(sel) == 4: # select_atoms doesnt raise errors if nothing found, so check size return sel @@ -3361,7 +3606,7 @@ def psi_selection(self): """ sel = self['N'] + self['CA'] + self['C'] + \ self.universe.select_atoms( - 'segid {0!s} and resid {1:d} and name N'.format(self.segment.id, self.id + 1)) + 'segid {0!s} and resid {1:d} and name N'.format(self.segment.segid, self.resid + 1)) if len(sel) == 4: return sel else: @@ -3379,8 +3624,8 @@ def omega_selection(self): method returns ``None``. """ - nextres = self.id + 1 - segid = self.segment.id + nextres = self.resid + 1 + segid = self.segment.segid sel = self['CA'] + self['C'] + \ self.universe.select_atoms( 'segid {0!s} and resid {1:d} and name N'.format(segid, nextres), @@ -3405,7 +3650,7 @@ def chi1_selection(self): def __repr__(self): return "".format( - name=self.name, id=self.id) + name=self.resname, id=self.resid) class ResidueGroup(AtomGroup): @@ -3472,6 +3717,7 @@ def _set_residues(self, name, value, **kwargs): set = _set_residues @property + @warn_residue_property def resids(self): """Returns an array of residue numbers. @@ -3480,9 +3726,10 @@ def resids(self): .. versionchanged:: 0.11.0 Now a property and returns array of length `len(self)` """ - return np.array([r.id for r in self.residues]) + return np.array([r.resid for r in self.residues]) @property + @warn_residue_property def resnames(self): """Returns an array of residue names. @@ -3494,6 +3741,7 @@ def resnames(self): return np.array([r.name for r in self.residues]) @property + @warn_residue_property def resnums(self): """Returns an array of canonical residue numbers. @@ -3506,6 +3754,7 @@ def resnums(self): return np.array([r.resnum for r in self.residues]) @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -3520,6 +3769,7 @@ def segids(self): # a bit of a hack to use just return np.array([r[0].segid for r in self.residues]) + @deprecate(message="{}; use `resids` property instead".format(_SIXTEEN_DEPRECATION)) def set_resids(self, resid): """Set the resids to integer *resid* for **all residues** in the :class:`ResidueGroup`. @@ -3554,8 +3804,9 @@ def set_resids(self, resid): set_resid = deprecate(set_resids, old_name='set_resid', new_name='set_resids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnums` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnums(self, resnum): """Set the resnums to *resnum* for **all residues** in the :class:`ResidueGroup`. @@ -3585,8 +3836,9 @@ def set_resnums(self, resnum): set_resnum = deprecate(set_resnums, old_name='set_resnum', new_name='set_resnums', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) + @deprecate(message="{}; use `resnames` property instead".format(_SIXTEEN_DEPRECATION)) def set_resnames(self, resname): """Set the resnames to string *resname* for **all residues** in the :class:`ResidueGroup`. @@ -3610,7 +3862,7 @@ def set_resnames(self, resname): set_resname = deprecate(set_resnames, old_name='set_resname', new_name='set_resnames', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) # All other AtomGroup.set_xxx() methods should just work as # ResidueGroup.set_xxx() because we overrode self.set(); the ones above @@ -3646,14 +3898,14 @@ class Segment(ResidueGroup): , , , , , ]> - :Data: :attr:`Segment.name` is the segid from the topology or the + :Data: :attr:`Segment.segid` is the segid from the topology or the chain identifier when loaded from a PDB """ def __init__(self, name, residues): """Initialize a Segment with segid *name* from a list of :class:`Residue` instances.""" super(Segment, self).__init__(residues) - self.name = name + self._segid = name for res in self.residues: res.segment = self for atom in res: @@ -3661,13 +3913,34 @@ def __init__(self, name, residues): self._cls = ResidueGroup @property + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) def id(self): """Segment id (alias for :attr:`Segment.name`)""" - return self.name + return self._segid @id.setter + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) def id(self, x): - self.name = x + self._segid = x + + @property + def segid(self): + """Segment id (alias for :attr:`Segment.name`)""" + return self._segid + + @segid.setter + def segid(self, x): + self._segid = x + + @property + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self): + return self._segid + + @name.setter + @deprecate(message="{}; use `segid` property instead".format(_SIXTEEN_DEPRECATION)) + def name(self, x): + self._segid = x def __getattr__(self, attr): if attr[0] == 'r': @@ -3687,7 +3960,7 @@ def __getattr__(self, attr): def __repr__(self): return "".format( - name=self.name) + name=self.segid) class SegmentGroup(ResidueGroup): @@ -3745,6 +4018,7 @@ def _set_segments(self, name, value, **kwargs): set = _set_segments @property + @warn_segment_property def segids(self): """Returns an array of segment names. @@ -3753,8 +4027,9 @@ def segids(self): .. versionchanged:: 0.11.0 Now a property and returns array of length `len(self)` """ - return np.array([s.name for s in self.segments]) + return np.array([s.segid for s in self.segments]) + @deprecate(message="{}; use `segids` property instead".format(_SIXTEEN_DEPRECATION)) def set_segids(self, segid): """Set the segids to *segid* for all atoms in the :class:`SegmentGroup`. @@ -3783,7 +4058,7 @@ def set_segids(self, segid): set_segid = deprecate(set_segids, old_name='set_segid', new_name='set_segids', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def __getattr__(self, attr): if attr.startswith('s') and attr[1].isdigit(): @@ -4113,10 +4388,10 @@ def _build_segments(self): segments = build_segments(self.atoms) for seg in segments: - if seg.id[0].isdigit(): - name = 's' + seg.id + if seg.segid[0].isdigit(): + name = 's' + seg.segid else: - name = seg.id + name = seg.segid self.__dict__[name] = seg return segments @@ -4137,11 +4412,11 @@ def _init_top(self, cat, Top): guessed = self._topology.get('guessed_' + cat, set()) TopSet = top.TopologyGroup.from_indices(defined, self.atoms, - bondclass=Top, guessed=False, - remove_duplicates=True) + bondclass=Top, guessed=False, + remove_duplicates=True) TopSet += top.TopologyGroup.from_indices(guessed, self.atoms, - bondclass=Top, guessed=True, - remove_duplicates=True) + bondclass=Top, guessed=True, + remove_duplicates=True) return TopSet @@ -4622,7 +4897,7 @@ def select_atoms(self, sel, *othersel, **selgroups): selectAtoms = deprecate(select_atoms, old_name='selectAtoms', new_name='select_atoms', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def __repr__(self): return "".format( @@ -4727,6 +5002,7 @@ def _matches_unpickling(self, anchor_name, n_atoms, fname, trajname): return False +@deprecate(message=_SIXTEEN_DEPRECATION) def as_Universe(*args, **kwargs): """Return a universe from the input arguments. @@ -4752,7 +5028,7 @@ def as_Universe(*args, **kwargs): asUniverse = deprecate(as_Universe, old_name='asUniverse', new_name='as_Universe', - message=_FIFTEEN_DEPRECATION) + message=_SIXTEEN_DEPRECATION) def Merge(*args): """Return a :class:`Universe` from two or more :class:`AtomGroup` instances.