From 671f1b46a1ee62bea0573260ccf1411039721e4a Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 30 Jun 2016 21:45:36 +0200 Subject: [PATCH 001/101] new Cython-based DCD file handling code first dcd stumb cython file first working darft of dcd reimplmentation This is a good wirst step open and close dcd files add context manager support remove pass statement correctly handle is_open flag cases start dcd read header stub add include guards for fastio.h read DCD Header free memory when closing remarks handling comment all the things convert remarks to python strings make n_atoms public in dcd make delta public make more stuff public periodic property --- package/MDAnalysis/lib/formats/__init__.py | 3 +- .../MDAnalysis/lib/formats/include/correl.h | 188 +++++ .../lib/formats/include/endianswap.h | 173 ++++ .../MDAnalysis/lib/formats/include/fastio.h | 458 +++++++++++ .../MDAnalysis/lib/formats/include/readdcd.h | 764 ++++++++++++++++++ package/MDAnalysis/lib/formats/libdcd.pyx | 148 ++++ package/setup.py | 8 +- 7 files changed, 1738 insertions(+), 4 deletions(-) create mode 100644 package/MDAnalysis/lib/formats/include/correl.h create mode 100644 package/MDAnalysis/lib/formats/include/endianswap.h create mode 100644 package/MDAnalysis/lib/formats/include/fastio.h create mode 100644 package/MDAnalysis/lib/formats/include/readdcd.h create mode 100644 package/MDAnalysis/lib/formats/libdcd.pyx diff --git a/package/MDAnalysis/lib/formats/__init__.py b/package/MDAnalysis/lib/formats/__init__.py index 5a851008ea8..cf9eb30c6ed 100644 --- a/package/MDAnalysis/lib/formats/__init__.py +++ b/package/MDAnalysis/lib/formats/__init__.py @@ -21,5 +21,6 @@ # from __future__ import absolute_import from . import libmdaxdr +from . import libdcd -__all__ = ['libmdaxdr'] +__all__ = ['libmdaxdr', 'libdcd'] diff --git a/package/MDAnalysis/lib/formats/include/correl.h b/package/MDAnalysis/lib/formats/include/correl.h new file mode 100644 index 00000000000..f3dacb042e7 --- /dev/null +++ b/package/MDAnalysis/lib/formats/include/correl.h @@ -0,0 +1,188 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode:nil; -*- */ +/* vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 */ +/* + MDAnalysis --- http://mdanalysis.googlecode.com + Copyright (c) 2006-2014 Naveen Michaud-Agrawal, + Elizabeth J. Denning, Oliver Beckstein, + and contributors (see AUTHORS for the full list) + Released under the GNU Public Licence, v2 or any higher version + + Please cite your use of MDAnalysis in published work: + + 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, + in press. +*/ + +#ifndef CORREL_H +#define CORREL_H + +#include +/* Python.h for 'typedef int Py_intptr_t;' (fixes Issue 19) */ +#include + +static void +copyseries(int frame, char *data, const Py_intptr_t *strides, + const float *tempX, const float *tempY, const float *tempZ, + const char* datacode, int numdata, const int* atomlist, const int* atomcounts, + int lowerb, double* aux) +{ + char code; + int index1 = 0, index2 = 0, index3 = 0, index4 = 0; + double x1, x2, y1, y2, z1, z2, x3, y3, z3, aux1, aux2; + int i = 0, j = 0, atomno = 0; + int dataset = 0; + int stride0, stride1; + + stride0 = strides[0]; + stride1 = strides[1]; + + /* If I eventually switch to using frame,property ordering for timeseries data + stride0 = strides[1]; + stride1 = strides[0]; + */ + + for (i=0;i 0) || (aux2 > 0 && aux1 < 0)) { + aux1 *= -1; + } + // Check if the dihedral has wrapped around 2 pi + aux2 = *(double*)(data + dataset*stride0 + (frame-1)*stride1); + if (fabs(aux1-aux2) > M_PI) { + if (aux1 > 0) { aux1 -= 2*M_PI; } + else { aux1 += 2*M_PI; } + } + *(double*)(data + dataset++*stride0 + frame*stride1) = aux1; + break; + case 'w': + /* dipole orientation of 3-site water: ^ d + index1 = oxygen, index2, index3 = hydrogen | + returns d ,O, + d = rO - (rH1 + rH2)/2 H | H + | + */ + index1 = atomlist[atomno++]-lowerb; // O + index2 = atomlist[atomno++]-lowerb; // H1 + index3 = atomlist[atomno++]-lowerb; // H2 + x1 = tempX[index1] - 0.5*(tempX[index2] + tempX[index3]); // dx + y1 = tempY[index1] - 0.5*(tempY[index2] + tempY[index3]); // dy + z1 = tempZ[index1] - 0.5*(tempZ[index2] + tempZ[index3]); // dz + *(double*)(data + dataset++*stride0 + frame*stride1) = x1; + *(double*)(data + dataset++*stride0 + frame*stride1) = y1; + *(double*)(data + dataset++*stride0 + frame*stride1) = z1; + break; + } + } +} + +// This accounts for periodic boundary conditions +// taken from MMTK +#define distance_vector_2(d, r1, r2, data) \ + { \ + double xh = 0.5*(data)[0]; \ + double yh = 0.5*(data)[1]; \ + double zh = 0.5*(data)[2]; \ + d[0] = r2[0]-r1[0]; \ + if (d[0] > xh) d[0] -= (data)[0]; \ + if (d[0] <= -xh) d[0] += (data)[0]; \ + d[1] = r2[1]-r1[1]; \ + if (d[1] > yh) d[1] -= (data)[1]; \ + if (d[1] <= -yh) d[1] += (data)[1]; \ + d[2] = r2[2]-r1[2]; \ + if (d[2] > zh) d[2] -= (data)[2]; \ + if (d[2] <= -zh) d[2] += (data)[2]; \ + } + +#endif diff --git a/package/MDAnalysis/lib/formats/include/endianswap.h b/package/MDAnalysis/lib/formats/include/endianswap.h new file mode 100644 index 00000000000..6f1ced37483 --- /dev/null +++ b/package/MDAnalysis/lib/formats/include/endianswap.h @@ -0,0 +1,173 @@ +/*************************************************************************** + *cr + *cr (C) Copyright 1995-2003 The Board of Trustees of the + *cr University of Illinois + *cr All Rights Reserved + *cr + ***************************************************************************/ +/*************************************************************************** + * RCS INFORMATION: + * + * $RCSfile: endianswap.h,v $ + * $Author: eamon $ $Locker: $ $State: Exp $ + * $Revision: 1.3 $ $Date: 2004/04/16 15:37:00 $ + * + *************************************************************************** + * DESCRIPTION: + * Byte swapping routines used in various plugins + * There are two versions of each routine, one that's safe to use in + * all cases (but is slow) and one that is only safe to use on memory + * addresses that are aligned to the word size that's being byte-swapped + * but are much much much faster. Use the aligned versions of these + * routines whenever possible. The 'ndata' length count parameters and + * internal loops should be safe to use on huge memory arrays on 64-bit + * machines. + * + ***************************************************************************/ + +#ifndef ENDIAN_SWAP_H +#define ENDIAN_SWAP_H + +/* works on unaligned 2-byte quantities */ +static void swap2_unaligned(void *v, long ndata) { + long i; + char * dataptr = (char *) v; + char tmp; + + for (i = 0; i < ndata-1; i += 2) { + tmp = dataptr[i]; + dataptr[i] = dataptr[i+1]; + dataptr[i+1] = tmp; + } +} + + +/* works on unaligned 4-byte quantities */ +static void swap4_unaligned(void *v, long ndata) { + long i; + char *dataptr; + char tmp; + + dataptr = (char *) v; + for (i=0; i>8)&0xff) | ((*N&0xff)<<8)); + } +} + + +/* Only works with aligned 4-byte quantities, will cause a bus error */ +/* on some platforms if used on unaligned data. */ +static void swap4_aligned(void *v, long ndata) { + int *data = (int *) v; + long i; + int *N; + for (i=0; i>24)&0xff) | ((*N&0xff)<<24) | + ((*N>>8)&0xff00) | ((*N&0xff00)<<8)); + } +} + + +/* Only works with aligned 8-byte quantities, will cause a bus error */ +/* on some platforms if used on unaligned data. */ +static void swap8_aligned(void *v, long ndata) { + /* Use int* internally to prevent bugs caused by some compilers */ + /* and hardware that would potentially load data into an FP reg */ + /* and hose everything, such as the old "jmemcpy()" bug in NAMD */ + int *data = (int *) v; + long i; + int *N; + int t0, t1; + + for (i=0; i>24)&0xff) | ((t0&0xff)<<24) | + ((t0>>8)&0xff00) | ((t0&0xff00)<<8)); + + t1 = N[1]; + t1=(((t1>>24)&0xff) | ((t1&0xff)<<24) | + ((t1>>8)&0xff00) | ((t1&0xff00)<<8)); + + N[0] = t1; + N[1] = t0; + } +} + +#if 0 +/* Other implementations that might be faster in some cases */ + +/* swaps the endianism of an eight byte word. */ +void mdio_swap8(double *i) { + char c; + char *n; + n = (char *) i; + c = n[0]; + n[0] = n[7]; + n[7] = c; + c = n[1]; + n[1] = n[6]; + n[6] = c; + c = n[2]; + n[2] = n[5]; + n[5] = c; + c = n[3]; + n[3] = n[4]; + n[4] = c; +} + +#endif + +#endif + diff --git a/package/MDAnalysis/lib/formats/include/fastio.h b/package/MDAnalysis/lib/formats/include/fastio.h new file mode 100644 index 00000000000..4eeda73e21f --- /dev/null +++ b/package/MDAnalysis/lib/formats/include/fastio.h @@ -0,0 +1,458 @@ +/*************************************************************************** + *cr + *cr (C) Copyright 1995-2009 The Board of Trustees of the + *cr University of Illinois + *cr All Rights Reserved + *cr + ***************************************************************************/ +/*************************************************************************** + * RCS INFORMATION: + * + * $RCSfile: fastio.h,v $ + * $Author: johns $ $Locker: $ $State: Exp $ + * $Revision: 1.20 $ $Date: 2009/04/29 15:45:29 $ + * + *************************************************************************** + * DESCRIPTION: + * This is a simple abstraction layer for system-dependent I/O calls + * that allow plugins to do binary I/O using the fastest possible method. + * + * This code is intended for use by binary trajectory reader plugins that + * work with multi-gigabyte data sets, reading only binary data. + * + ***************************************************************************/ + +#ifndef FASTIO_H +#define FASTIO_H + +/* Compiling on windows */ +#if defined(_MSC_VER) + +#if 1 +/* use native Windows I/O calls */ +#define FASTIO_NATIVEWIN32 1 + +#include +#include + +typedef HANDLE fio_fd; +typedef LONGLONG fio_size_t; +typedef void * fio_caddr_t; + +typedef struct { + fio_caddr_t iov_base; + int iov_len; +} fio_iovec; + +#define FIO_READ 0x01 +#define FIO_WRITE 0x02 + +#define FIO_SEEK_CUR FILE_CURRENT +#define FIO_SEEK_SET FILE_BEGIN +#define FIO_SEEK_END FILE_END + +static int fio_win32convertfilename(const char *filename, char *newfilename, int maxlen) { + int i; + int len=strlen(filename); + + if ((len + 1) >= maxlen) + return -1; + + for (i=0; i + +typedef FILE * fio_fd; +typedef size_t fio_size_t; /* MSVC doesn't uinversally support ssize_t */ +typedef void * fio_caddr_t; /* MSVC doesn't universally support caddr_t */ + +typedef struct { + fio_caddr_t iov_base; + int iov_len; +} fio_iovec; + +#define FIO_READ 0x01 +#define FIO_WRITE 0x02 + +#define FIO_SEEK_CUR SEEK_CUR +#define FIO_SEEK_SET SEEK_SET +#define FIO_SEEK_END SEEK_END + +static int fio_open(const char *filename, int mode, fio_fd *fd) { + char * modestr; + FILE *fp; + + if (mode == FIO_READ) + modestr = "rb"; + + if (mode == FIO_WRITE) + modestr = "wb"; + + fp = fopen(filename, modestr); + if (fp == NULL) { + return -1; + } else { + *fd = fp; + return 0; + } +} + +static int fio_fclose(fio_fd fd) { + return fclose(fd); +} + +static fio_size_t fio_fread(void *ptr, fio_size_t size, + fio_size_t nitems, fio_fd fd) { + return fread(ptr, size, nitems, fd); +} + +static fio_size_t fio_readv(fio_fd fd, const fio_iovec * iov, int iovcnt) { + int i; + fio_size_t len = 0; + + for (i=0; i +#include +#include +#include + +typedef int fio_fd; +typedef off_t fio_size_t; /* off_t is 64-bits with LFS builds */ + +/* enable use of kernel readv() if available */ +#if defined(__sun) || defined(__APPLE_CC__) || defined(__linux) +#define USE_KERNEL_READV 1 +#endif + +typedef void * fio_caddr_t; + +#if defined(USE_KERNEL_READV) +#include +typedef struct iovec fio_iovec; +#else + +typedef struct { + fio_caddr_t iov_base; + int iov_len; +} fio_iovec; +#endif + + +#define FIO_READ 0x01 +#define FIO_WRITE 0x02 + +#define FIO_SEEK_CUR SEEK_CUR +#define FIO_SEEK_SET SEEK_SET +#define FIO_SEEK_END SEEK_END + +static int fio_open(const char *filename, int mode, fio_fd *fd) { + int nfd; + int oflag = 0; + + if (mode == FIO_READ) + oflag = O_RDONLY; + + if (mode == FIO_WRITE) + oflag = O_WRONLY | O_CREAT | O_TRUNC; + + nfd = open(filename, oflag, 0666); + if (nfd < 0) { + return -1; + } else { + *fd = nfd; + return 0; + } +} + +static int fio_fclose(fio_fd fd) { + return close(fd); +} + +static fio_size_t fio_fread(void *ptr, fio_size_t size, + fio_size_t nitems, fio_fd fd) { + int i; + fio_size_t len = 0; + int cnt = 0; + + for (i=0; i= 0) + return 0; /* success (emulate behavior of fseek) */ + else + return -1; /* failure (emulate behavior of fseek) */ +} + +static fio_size_t fio_ftell(fio_fd fd) { + return lseek(fd, 0, SEEK_CUR); +} + +#endif + + +/* higher level routines that are OS independent */ + +static int fio_write_int32(fio_fd fd, int i) { + return (fio_fwrite(&i, 4, 1, fd) != 1); +} + +static int fio_read_int32(fio_fd fd, int *i) { + return (fio_fread(i, 4, 1, fd) != 1); +} + +static int fio_write_str(fio_fd fd, const char *str) { + int len = strlen(str); + return (fio_fwrite((void *) str, len, 1, fd) != 1); +} + +#endif // FASTIO_H diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h new file mode 100644 index 00000000000..c9d0f9999c9 --- /dev/null +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -0,0 +1,764 @@ +/*************************************************************************** + *cr + *cr (C) Copyright 1995-2003 The Board of Trustees of the + *cr University of Illinois + *cr All Rights Reserved + *cr + ***************************************************************************/ + +/*************************************************************************** + * RCS INFORMATION: + * + * $RCSfile: readdcd.h,v $ + * $Author: johns $ $Locker: $ $State: Exp $ + * $Revision: 1.32 $ $Date: 2004/09/21 20:52:37 $ + * + ***************************************************************************/ + +#ifndef READ_DCD_H +#define READ_DCD_H + +#include +#include +#include +#include +#include "endianswap.h" +#include "fastio.h" + +/* DEFINE ERROR CODES THAT MAY BE RETURNED BY DCD ROUTINES */ +#define DCD_SUCCESS 0 /* No problems */ +#define DCD_EOF -1 /* Normal EOF */ +#define DCD_DNE -2 /* DCD file does not exist */ +#define DCD_OPENFAILED -3 /* Open of DCD file failed */ +#define DCD_BADREAD -4 /* read call on DCD file failed */ +#define DCD_BADEOF -5 /* premature EOF found in DCD file */ +#define DCD_BADFORMAT -6 /* format of DCD file is wrong */ +#define DCD_FILEEXISTS -7 /* output file already exists */ +#define DCD_BADMALLOC -8 /* malloc failed */ + +/* + * Read the header information from a dcd file. + * Input: fd - a file struct opened for binary reading. + * Output: 0 on success, negative error code on failure. + * Side effects: *natoms set to number of atoms per frame + * *nsets set to number of frames in dcd file + * *istart set to starting timestep of dcd file + * *nsavc set to timesteps between dcd saves + * *delta set to value of trajectory timestep + * *nfixed set to number of fixed atoms + * *freeind may be set to heap-allocated space + * *reverse set to one if reverse-endian, zero if not. + * *charmm set to internal code for handling charmm data. + */ +static int read_dcdheader(fio_fd fd, int *natoms, int *nsets, int *istart, int *nsavc, + double *delta, int *nfixed, int **freeind, + float **fixedcoords, int *reverse, int *charmm, + char **remarks, int *len_remarks); + +/* + * Read a dcd timestep from a dcd file + * Input: fd - a file struct opened for binary reading, from which the + * header information has already been read. + * natoms, nfixed, first, *freeind, reverse, charmm - the corresponding + * items as set by read_dcdheader + * first - true if this is the first frame we are reading. + * x, y, z: space for natoms each of floats. + * unitcell - space for six floats to hold the unit cell data. + * Not set if no unit cell data is present. + * Output: 0 on success, negative error code on failure. + * Side effects: x, y, z contain the coordinates for the timestep read. + * unitcell holds unit cell data if present. + */ +static int read_dcdstep(fio_fd fd, int natoms, float *x, float *y, float *z, + float *unitcell, int nfixed, int first, int *freeind, + float *fixedcoords, int reverse, int charmm); + +/* + * Read a subset of a timestep from a dcd file + * Input: fd - a file struct opened for binary reading, from which the + * header information has already been read + * natoms, nfixed, first, *freeind, reverse, charmm - the corresponding + * items as set by read_dcdheader + * first - true if this is the first frame we are reading. + * lowerb, upperb - the range of atoms to read data for + * x, y, z: space for upperb-lowerb+1 each of floats + * unitcell - space for six floats to hold the unit cell data. + * Not set if no unit cell data is present. + * Ouput: 0 on success, negative error code on failure. + * Side effects: x, y, z contain coordinates for the range of atoms + * unitcell holds unit cell data if present. + */ +static int read_dcdsubset(fio_fd fd, int natoms, int lowerb, int upperb, float *x, float *y, float *z, + float *unitcell, int nfixed, int first, int *freeind, + float *fixedcoords, int reverse, int charmm); + +/* + * Skip past a timestep. If there are fixed atoms, this cannot be used with + * the first timestep. + * Input: fd - a file struct from which the header has already been read + * natoms - number of atoms per timestep + * nfixed - number of fixed atoms + * charmm - charmm flags as returned by read_dcdheader + * Output: 0 on success, negative error code on failure. + * Side effects: One timestep will be skipped; fd will be positioned at the + * next timestep. + */ +static int skip_dcdstep(fio_fd fd, int natoms, int nfixed, int charmm, int numstep); + +/* + * clean up dcd data + * Input: nfixed, freeind - elements as returned by read_dcdheader + * Output: None + * Side effects: Space pointed to by freeind is freed if necessary. + */ +static void close_dcd_read(int *freeind, float *fixedcoords); + +/* + * Write a header for a new dcd file + * Input: fd - file struct opened for binary writing + * remarks - string to be put in the remarks section of the header. + * The string will be truncated to 70 characters. + * natoms, istart, nsavc, delta - see comments in read_dcdheader + * Output: 0 on success, negative error code on failure. + * Side effects: Header information is written to the dcd file. + */ +static int write_dcdheader(fio_fd fd, const char *remarks, int natoms, + int istart, int nsavc, double delta, int with_unitcell, + int charmm); + +/* + * Write a timestep to a dcd file + * Input: fd - a file struct for which a dcd header has already been written + * curframe: Count of frames written to this file, starting with 1. + * curstep: Count of timesteps elapsed = istart + curframe * nsavc. + * natoms - number of elements in x, y, z arrays + * x, y, z: pointers to atom coordinates + * Output: 0 on success, negative error code on failure. + * Side effects: coordinates are written to the dcd file. + */ +static int write_dcdstep(fio_fd fd, int curstep, int curframe, + int natoms, const float *x, const float *y, const float *z, + const double *unitcell, int charmm); + + + +#define DCD_IS_CHARMM 0x01 +#define DCD_HAS_4DIMS 0x02 +#define DCD_HAS_EXTRA_BLOCK 0x04 + +/* READ Macro to make porting easier */ +#define READ(fd, buf, size) \ + fio_fread(((void *) buf), (size), 1, (fd)) + + +/* WRITE Macro to make porting easier */ +#define WRITE(fd, buf, size) \ + fio_fwrite(((void *) buf), (size), 1, (fd)) + +/* XXX This is broken - fread never returns -1 */ +#define CHECK_FREAD(X, msg) if (X==-1) \ + { \ + return(DCD_BADREAD); \ + } + +#define CHECK_FEOF(X, msg) if (X==0) \ + { \ + return(DCD_BADEOF); \ + } + +static int read_dcdheader(fio_fd fd, int *N, int *NSET, int *ISTART, + int *NSAVC, double *DELTA, int *NAMNF, + int **FREEINDEXES, float **fixedcoords, int *reverseEndian, + int *charmm, char **remarks, int *len_remarks) +{ + int input_integer; /* buffer space */ + int ret_val; + char hdrbuf[84]; /* char buffer used to store header */ + int NTITLE; + + /* First thing in the file should be an 84 */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading first int from dcd file"); + CHECK_FEOF(ret_val, "reading first int from dcd file"); + + /* Check magic number in file header and determine byte order*/ + if (input_integer != 84) { + /* check to see if its merely reversed endianism */ + /* rather than a totally incorrect file magic number */ + swap4_aligned(&input_integer, 1); + + if (input_integer == 84) { + *reverseEndian=1; + } else { + /* not simply reversed endianism, but something rather more evil */ + return DCD_BADFORMAT; + } + } else { + *reverseEndian=0; + } + + /* Buffer the entire header for random access */ + ret_val = READ(fd, hdrbuf, 84); + CHECK_FREAD(ret_val, "buffering header"); + CHECK_FEOF(ret_val, "buffering header"); + + /* Check for the ID string "COORD" */ + if (hdrbuf[0] != 'C' || hdrbuf[1] != 'O' || + hdrbuf[2] != 'R' || hdrbuf[3] != 'D') { + return DCD_BADFORMAT; + } + + /* CHARMm-genereate DCD files set the last integer in the */ + /* header, which is unused by X-PLOR, to its version number. */ + /* Checking if this is nonzero tells us this is a CHARMm file */ + /* and to look for other CHARMm flags. */ + if (*((int *) (hdrbuf + 80)) != 0) { + (*charmm) = DCD_IS_CHARMM; + if (*((int *) (hdrbuf + 44)) != 0) + (*charmm) |= DCD_HAS_EXTRA_BLOCK; + + if (*((int *) (hdrbuf + 48)) == 1) + (*charmm) |= DCD_HAS_4DIMS; + } else { + (*charmm) = 0; + } + + /* Store the number of sets of coordinates (NSET) */ + (*NSET) = *((int *) (hdrbuf + 4)); + if (*reverseEndian) swap4_unaligned(NSET, 1); + + /* Store ISTART, the starting timestep */ + (*ISTART) = *((int *) (hdrbuf + 8)); + if (*reverseEndian) swap4_unaligned(ISTART, 1); + + /* Store NSAVC, the number of timesteps between dcd saves */ + (*NSAVC) = *((int *) (hdrbuf + 12)); + if (*reverseEndian) swap4_unaligned(NSAVC, 1); + + /* Store NAMNF, the number of fixed atoms */ + (*NAMNF) = *((int *) (hdrbuf + 36)); + if (*reverseEndian) swap4_unaligned(NAMNF, 1); + + /* Read in the timestep, DELTA */ + /* Note: DELTA is stored as a double with X-PLOR but as a float with CHARMm */ + if ((*charmm) & DCD_IS_CHARMM) { + float ftmp; + ftmp = *((float *)(hdrbuf+40)); /* is this safe on Alpha? */ + if (*reverseEndian) + swap4_aligned(&ftmp, 1); + + *DELTA = (double)ftmp; + } else { + (*DELTA) = *((double *)(hdrbuf + 40)); + if (*reverseEndian) swap8_unaligned(DELTA, 1); + } + + /* Get the end size of the first block */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading second 84 from dcd file"); + CHECK_FEOF(ret_val, "reading second 84 from dcd file"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (input_integer != 84) { + return DCD_BADFORMAT; + } + + /* Read in the size of the next block */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading size of title block"); + CHECK_FEOF(ret_val, "reading size of title block"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (((input_integer-4) % 80) == 0) { + /* Read NTITLE, the number of 80 character title strings there are */ + ret_val = READ(fd, &NTITLE, sizeof(int)); + CHECK_FREAD(ret_val, "reading NTITLE"); + CHECK_FEOF(ret_val, "reading NTITLE"); + if (*reverseEndian) swap4_aligned(&NTITLE, 1); + *len_remarks = NTITLE*80; + *remarks = (char*)malloc(*len_remarks); + ret_val = fio_fread(*remarks, *len_remarks, 1, fd); + CHECK_FEOF(ret_val, "reading TITLE"); + + /* Get the ending size for this block */ + ret_val = READ(fd, &input_integer, sizeof(int)); + + CHECK_FREAD(ret_val, "reading size of title block"); + CHECK_FEOF(ret_val, "reading size of title block"); + } else { + return DCD_BADFORMAT; + } + + /* Read in an integer '4' */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading a '4'"); + CHECK_FEOF(ret_val, "reading a '4'"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (input_integer != 4) { + return DCD_BADFORMAT; + } + + /* Read in the number of atoms */ + ret_val = READ(fd, N, sizeof(int)); + CHECK_FREAD(ret_val, "reading number of atoms"); + CHECK_FEOF(ret_val, "reading number of atoms"); + if (*reverseEndian) swap4_aligned(N, 1); + + /* Read in an integer '4' */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading a '4'"); + CHECK_FEOF(ret_val, "reading a '4'"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (input_integer != 4) { + return DCD_BADFORMAT; + } + + *FREEINDEXES = NULL; + *fixedcoords = NULL; + if (*NAMNF != 0) { + (*FREEINDEXES) = (int *) calloc(((*N)-(*NAMNF)), sizeof(int)); + if (*FREEINDEXES == NULL) + return DCD_BADMALLOC; + + *fixedcoords = (float *) calloc((*N)*4 - (*NAMNF), sizeof(float)); + if (*fixedcoords == NULL) + return DCD_BADMALLOC; + + /* Read in index array size */ + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading size of index array"); + CHECK_FEOF(ret_val, "reading size of index array"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (input_integer != ((*N)-(*NAMNF))*4) { + return DCD_BADFORMAT; + } + + ret_val = READ(fd, (*FREEINDEXES), ((*N)-(*NAMNF))*sizeof(int)); + CHECK_FREAD(ret_val, "reading size of index array"); + CHECK_FEOF(ret_val, "reading size of index array"); + + if (*reverseEndian) + swap4_aligned((*FREEINDEXES), ((*N)-(*NAMNF))); + + ret_val = READ(fd, &input_integer, sizeof(int)); + CHECK_FREAD(ret_val, "reading size of index array"); + CHECK_FEOF(ret_val, "reading size of index array"); + if (*reverseEndian) swap4_aligned(&input_integer, 1); + + if (input_integer != ((*N)-(*NAMNF))*4) { + return DCD_BADFORMAT; + } + } + + return DCD_SUCCESS; +} + +static int read_charmm_extrablock(fio_fd fd, int charmm, int reverseEndian, + float *unitcell) { + int i, input_integer; + + if ((charmm & DCD_IS_CHARMM) && (charmm & DCD_HAS_EXTRA_BLOCK)) { + /* Leading integer must be 48 */ + if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) + return DCD_BADREAD; + if (reverseEndian) swap4_aligned(&input_integer, 1); + if (input_integer == 48) { + double tmp[6]; + if (fio_fread(tmp, 48, 1, fd) != 1) return DCD_BADREAD; + if (reverseEndian) + swap8_aligned(tmp, 6); + for (i=0; i<6; i++) unitcell[i] = (float)tmp[i]; + } else { + /* unrecognized block, just skip it */ + if (fio_fseek(fd, input_integer, FIO_SEEK_CUR)) return DCD_BADREAD; + } + if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) return DCD_BADREAD; + } + + return DCD_SUCCESS; +} + +static int read_fixed_atoms(fio_fd fd, int N, int num_free, const int *indexes, + int reverseEndian, const float *fixedcoords, + float *freeatoms, float *pos) { + int i, input_integer; + + /* Read leading integer */ + if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) return DCD_BADREAD; + if (reverseEndian) swap4_aligned(&input_integer, 1); + if (input_integer != 4*num_free) return DCD_BADFORMAT; + + /* Read free atom coordinates */ + if (fio_fread(freeatoms, 4*num_free, 1, fd) != 1) return DCD_BADREAD; + if (reverseEndian) + swap4_aligned(freeatoms, num_free); + + /* Copy fixed and free atom coordinates into position buffer */ + memcpy(pos, fixedcoords, 4*N); + for (i=0; i 1) { + seekoffset *= numsteps; + } + + rc = fio_fseek(fd, seekoffset, FIO_SEEK_CUR); + if (rc == -1) return DCD_BADEOF; + + return DCD_SUCCESS; +} + +static int jump_to_dcdstep(fio_fd fd, int natoms, int nsets, int nfixed, int charmm, int header_size, int step) { + int rc; + if (step > nsets) { + return DCD_BADEOF; + } + // Calculate file offset + off_t extrablocksize, ndims, firstframesize, framesize; + off_t pos; + extrablocksize = charmm & DCD_HAS_EXTRA_BLOCK ? 48 + 8 : 0; + ndims = charmm & DCD_HAS_4DIMS ? 4 : 3; + firstframesize = (natoms+2) * ndims * sizeof(float) + extrablocksize; + framesize = (natoms-nfixed+2) * ndims * sizeof(float) + extrablocksize; + // Use zero indexing + if (step == 0) { + pos = header_size; + } + else { + pos = header_size + firstframesize + framesize * (step-1); + } + rc = fio_fseek(fd, pos, FIO_SEEK_SET); + if (rc == -1) return DCD_BADEOF; + return DCD_SUCCESS; +} + +#define NFILE_POS 8L +#define NSTEP_POS 20L + +static int write_dcdstep(fio_fd fd, int curframe, int curstep, int N, + const float *X, const float *Y, const float *Z, + const double *unitcell, int charmm) { + int out_integer; + + if (charmm) { + /* write out optional unit cell */ + if (unitcell != NULL) { + out_integer = 48; /* 48 bytes (6 doubles) */ + fio_write_int32(fd, out_integer); + WRITE(fd, unitcell, out_integer); + fio_write_int32(fd, out_integer); + } + } + + /* write out coordinates */ + out_integer = N*4; /* N*4 bytes per X/Y/Z array (N floats per array) */ + fio_write_int32(fd, out_integer); + WRITE(fd, X, out_integer); + fio_write_int32(fd, out_integer); + fio_write_int32(fd, out_integer); + WRITE(fd, Y, out_integer); + fio_write_int32(fd, out_integer); + fio_write_int32(fd, out_integer); + WRITE(fd, Z, out_integer); + fio_write_int32(fd, out_integer); + + /* update the DCD header information */ + fio_fseek(fd, NFILE_POS, FIO_SEEK_SET); + fio_write_int32(fd, curframe); + fio_fseek(fd, NSTEP_POS, FIO_SEEK_SET); + fio_write_int32(fd, curstep); + fio_fseek(fd, 0, FIO_SEEK_END); + + return DCD_SUCCESS; +} + +static int write_dcdheader(fio_fd fd, const char *remarks, int N, + int ISTART, int NSAVC, double DELTA, int with_unitcell, + int charmm) { + int out_integer; + float out_float; + char title_string[200]; + time_t cur_time; + struct tm *tmbuf; + char time_str[81]; + + out_integer = 84; + WRITE(fd, (char *) & out_integer, sizeof(int)); + strcpy(title_string, "CORD"); + WRITE(fd, title_string, 4); + fio_write_int32(fd, 0); /* Number of frames in file, none written yet */ + fio_write_int32(fd, ISTART); /* Starting timestep */ + fio_write_int32(fd, NSAVC); /* Timesteps between frames written to the file */ + fio_write_int32(fd, 0); /* Number of timesteps in simulation */ + fio_write_int32(fd, 0); /* NAMD writes NSTEP or ISTART - NSAVC here? */ + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + if (charmm) { + out_float = DELTA; + WRITE(fd, (char *) &out_float, sizeof(float)); + if (with_unitcell) { + fio_write_int32(fd, 1); + } else { + fio_write_int32(fd, 0); + } + } else { + WRITE(fd, (char *) &DELTA, sizeof(double)); + } + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + fio_write_int32(fd, 0); + if (charmm) { + fio_write_int32(fd, 24); /* Pretend to be Charmm version 24 */ + } else { + fio_write_int32(fd, 0); + } + fio_write_int32(fd, 84); + fio_write_int32(fd, 164); + fio_write_int32(fd, 2); + + strncpy(title_string, remarks, 80); + title_string[79] = '\0'; + WRITE(fd, title_string, 80); + + cur_time=time(NULL); + tmbuf=localtime(&cur_time); + strftime(time_str, 80, "REMARKS Created %d %B, %Y at %R", tmbuf); + WRITE(fd, time_str, 80); + + fio_write_int32(fd, 164); + fio_write_int32(fd, 4); + fio_write_int32(fd, N); + fio_write_int32(fd, 4); + + return DCD_SUCCESS; +} + +static void close_dcd_read(int *indexes, float *fixedcoords) { + free(indexes); + free(fixedcoords); +} + +#endif + diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx new file mode 100644 index 00000000000..e32e7ecb1cc --- /dev/null +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -0,0 +1,148 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# +# MDAnalysis --- http://www.MDAnalysis.org +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# 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 +# + +from libc.stdio cimport SEEK_SET, SEEK_CUR, SEEK_END +ctypedef int fio_fd; +_whence_vals = {"FIO_SEEK_SET": SEEK_SET, + "FIO_SEEK_CUR": SEEK_CUR, + "FIO_SEEK_END": SEEK_END} + +from libc.stdint cimport uintptr_t +from libc.stdlib cimport free + +cdef enum: + FIO_READ = 0x01 + FIO_WRITE = 0x02 + +cdef enum: + DCD_IS_CHARMM = 0x01 + DCD_HAS_4DIMS = 0x02 + DCD_HAS_EXTRA_BLOCK = 0x04 + +DCD_ERRORS = { + 0: 'No Problem', + -1: 'Normal EOF', + -2: 'DCD file does not exist', + -3: 'Open of DCD file failed', + -4: 'read call on DCD file failed', + -5: 'premature EOF found in DCD file', + -6: 'format of DCD file is wrong', + -7: 'output file already exiss', + -8: 'malloc failed' +} + + +cdef extern from 'include/fastio.h': + int fio_open(const char *filename, int mode, fio_fd *fd) + int fio_fclose(fio_fd fd) +# need to find a typedef for off_t +# fio_size_t fio_ftell(fio_fd fd) + +cdef extern from 'include/readdcd.h': + int read_dcdheader(fio_fd fd, int *n_atoms, int *nsets, int *istart, + int *nsavc, double *delta, int *nfixed, int **freeind, + float **fixedcoords, int *reverse, int *charmm, + char **remarks, int *len_remarks) + void close_dcd_read(int *freeind, float *fixedcoords); + + +cdef class DCDFile: + cdef fio_fd fp + cdef readonly fname + cdef int is_open + cdef readonly int n_atoms + cdef int nsets + cdef readonly int istart + cdef readonly int nsavc + cdef readonly double delta + cdef readonly int nfixed + cdef int *freeind + cdef float *fixedcoords + cdef int reverse + cdef int charmm + + def __cinit__(self, fname, mode='r'): + self.fname = fname.encode('utf-8') + self.n_atoms = 0 + self.is_open = False + self.open(self.fname, mode) + + def __dealloc__(self): + self.close() + + def __enter__(self): + """Support context manager""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Support context manager""" + self.close() + # always propagate exceptions forward + return False + + def open(self, filename, mode='r'): + if mode == 'r': + fio_mode = FIO_READ + elif mode == 'w': + fio_mode = FIO_WRITE + else: + raise IOError("unkown mode '{}', use either r or w".format(mode)) + ok = fio_open(self.fname, fio_mode, &self.fp) + if ok != 0: + raise IOError("couldn't open file: {}\n" + "ErrorCode: {}".format(self.fname, ok)) + self.is_open = True + + def close(self): + if self.is_open: + # In case there are fixed atoms we should free the memory again. + # Both pointers are guaranted to be non NULL if either one is. + if self.freeind != NULL: + close_dcd_read(self.freeind, self.fixedcoords); + + ok = fio_fclose(self.fp) + self.is_open = False + if ok != 0: + raise IOError("couldn't close file: {}\n" + "ErrorCode: {}".format(self.fname, ok)) + + def read_header(self): + if not self.is_open: + raise RuntimeError("No file open") + + cdef char* c_remarks + cdef int len_remarks = 0 + + ok = read_dcdheader(self.fp, &self.n_atoms, &self.nsets, &self.istart, + &self.nsavc, &self.delta, &self.nfixed, &self.freeind, + &self.fixedcoords, &self.reverse, + &self.charmm, &c_remarks, &len_remarks) + if ok != 0: + raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + + if c_remarks != NULL: + py_remarks = c_remarks[:len_remarks] + free(c_remarks) + else: + py_remarks = "" + + + return py_remarks + + @property + def periodic(self): + return bool((self.charmm & DCD_IS_CHARMM) and + (self.charmm & DCD_HAS_EXTRA_BLOCK)) diff --git a/package/setup.py b/package/setup.py index 8f9226f030b..56fe2942aee 100755 --- a/package/setup.py +++ b/package/setup.py @@ -295,9 +295,9 @@ def extensions(config): include_dirs = [get_numpy_include] - dcd = MDAExtension('coordinates._dcdmodule', - ['MDAnalysis/coordinates/src/dcd.c'], - include_dirs=include_dirs + ['MDAnalysis/coordinates/include'], + dcd = MDAExtension('lib.formats.libdcd', + ['MDAnalysis/lib/formats/libdcd' + source_suffix], + include_dirs=include_dirs + ['MDAnalysis/lib/formats/include'], define_macros=define_macros, extra_compile_args=extra_compile_args) distances = MDAExtension('lib.c_distances', @@ -356,6 +356,8 @@ def extensions(config): transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred] + pre_exts = [dcd, distances, distances_omp, qcprot, + transformation, libmdaxdr, util] cython_generated = [] if use_cython: extensions = cythonize(pre_exts) From 9e40e7aadf3c1e2847778c908be60e90889ea790 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sat, 1 Oct 2016 22:40:30 +0200 Subject: [PATCH 002/101] implement frame reading - estimate number of frames - work on read_next_frame - improve reading API - finish reading and iteration protocol - add seek support --- package/MDAnalysis/lib/formats/libdcd.pyx | 168 ++++++++++++++++++++-- 1 file changed, 159 insertions(+), 9 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index e32e7ecb1cc..d07dfb1c80d 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -14,12 +14,30 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +from os import path +import numpy as np +from collections import namedtuple + +cimport numpy as np + + from libc.stdio cimport SEEK_SET, SEEK_CUR, SEEK_END -ctypedef int fio_fd; _whence_vals = {"FIO_SEEK_SET": SEEK_SET, "FIO_SEEK_CUR": SEEK_CUR, "FIO_SEEK_END": SEEK_END} +# Tell cython about the off_t type. It doesn't need to match exactly what is +# defined since we don't expose it to python but the cython compiler needs to +# know about it. +cdef extern from 'sys/types.h': + ctypedef int off_t + +ctypedef int fio_fd; +ctypedef off_t fio_size_t + +ctypedef np.float32_t DTYPE_t +DTYPE = np.float32 + from libc.stdint cimport uintptr_t from libc.stdlib cimport free @@ -44,20 +62,24 @@ DCD_ERRORS = { -8: 'malloc failed' } - cdef extern from 'include/fastio.h': int fio_open(const char *filename, int mode, fio_fd *fd) int fio_fclose(fio_fd fd) -# need to find a typedef for off_t -# fio_size_t fio_ftell(fio_fd fd) + fio_size_t fio_ftell(fio_fd fd) + fio_size_t fio_fseek(fio_fd fd, fio_size_t offset, int whence) cdef extern from 'include/readdcd.h': int read_dcdheader(fio_fd fd, int *n_atoms, int *nsets, int *istart, int *nsavc, double *delta, int *nfixed, int **freeind, - float **fixedcoords, int *reverse, int *charmm, + float **fixedcoords, int *reverse_endian, int *charmm, char **remarks, int *len_remarks) - void close_dcd_read(int *freeind, float *fixedcoords); + void close_dcd_read(int *freeind, float *fixedcoords) + int read_dcdstep(fio_fd fd, int n_atoms, float *X, float *Y, float *Z, + float *unitcell, int num_fixed, + int first, int *indexes, float *fixedcoords, + int reverse_endian, int charmm) +DCDFrame = namedtuple('DCDFrame', 'x unitcell') cdef class DCDFile: cdef fio_fd fp @@ -71,8 +93,18 @@ cdef class DCDFile: cdef readonly int nfixed cdef int *freeind cdef float *fixedcoords - cdef int reverse + cdef int reverse_endian cdef int charmm + cdef str mode + cdef readonly int n_dims + cdef readonly int n_frames + cdef bint b_read_header + cdef int current_frame + cdef readonly remarks + cdef int reached_eof + cdef int firstframesize + cdef int framesize + cdef int header_size def __cinit__(self, fname, mode='r'): self.fname = fname.encode('utf-8') @@ -93,18 +125,44 @@ cdef class DCDFile: # always propagate exceptions forward return False + def __iter__(self): + self.close() + self.open(self.fname, self.mode) + return self + + def __next__(self): + if self.reached_eof: + raise StopIteration + return self.read() + + def __len__(self): + if not self.is_open: + raise RuntimeError('No file currently opened') + return self.n_frames + + def tell(self): + return self.current_frame + def open(self, filename, mode='r'): + if self.is_open: + self.close() + if mode == 'r': fio_mode = FIO_READ elif mode == 'w': fio_mode = FIO_WRITE else: raise IOError("unkown mode '{}', use either r or w".format(mode)) + self.mode = mode + ok = fio_open(self.fname, fio_mode, &self.fp) if ok != 0: raise IOError("couldn't open file: {}\n" "ErrorCode: {}".format(self.fname, ok)) self.is_open = True + self.current_frame = 0 + self.remarks = self._read_header() + self.reached_eof = False def close(self): if self.is_open: @@ -119,7 +177,8 @@ cdef class DCDFile: raise IOError("couldn't close file: {}\n" "ErrorCode: {}".format(self.fname, ok)) - def read_header(self): + + def _read_header(self): if not self.is_open: raise RuntimeError("No file open") @@ -128,7 +187,7 @@ cdef class DCDFile: ok = read_dcdheader(self.fp, &self.n_atoms, &self.nsets, &self.istart, &self.nsavc, &self.delta, &self.nfixed, &self.freeind, - &self.fixedcoords, &self.reverse, + &self.fixedcoords, &self.reverse_endian, &self.charmm, &c_remarks, &len_remarks) if ok != 0: raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) @@ -139,10 +198,101 @@ cdef class DCDFile: else: py_remarks = "" + self.n_dims = 3 if not self.charmm & DCD_HAS_4DIMS else 4 + self.n_frames = self._estimate_n_frames() + self.b_read_header = True + + # make sure fixed atoms have been read + self.read() + self.seek(0) return py_remarks + def _estimate_n_frames(self): + extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 + self.firstframesize = self.n_atoms + 2 * self.n_dims * sizeof(float) + extrablocksize + self.framesize = ((self.n_atoms - self.nfixed + 2) * self.n_dims * sizeof(float) + + extrablocksize) + filesize = path.getsize(self.fname) + # It's safe to use ftell, even though ftell returns a long, because the + # header size is < 4GB. + self.header_size = fio_ftell(self.fp) + # TODO: check that nframessize is larger then 0, the c-implementation + # used to do that. + nframessize = filesize - self.header_size - self.firstframesize + return nframessize / self.framesize + 1 + @property def periodic(self): return bool((self.charmm & DCD_IS_CHARMM) and (self.charmm & DCD_HAS_EXTRA_BLOCK)) + + + def read(self): + if self.reached_eof: + raise RuntimeError('Reached last frame in TRR, seek to 0') + if not self.is_open: + raise RuntimeError("No file open") + if self.mode != 'r': + raise RuntimeError('File opened in mode: {}. Reading only allow ' + 'in mode "r"'.format('self.mode')) + + cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=DTYPE, + order='F') + cdef np.ndarray unitcell = np.empty(6, dtype=DTYPE) + + cdef DTYPE_t[::1] x = xyz[:, 0] + cdef DTYPE_t[::1] y = xyz[:, 1] + cdef DTYPE_t[::1] z = xyz[:, 2] + + first_frame = self.current_frame == 0 + + ok = read_dcdstep(self.fp, self.n_atoms, &x[0], + &y[0], &z[0], + unitcell.data, self.nfixed, first_frame, + self.freeind, self.fixedcoords, + self.reverse_endian, self.charmm) + if ok != 0 and ok != -4: + raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + + # we couldn't read any more frames. + if ok == -4: + self.reached_eof = True + raise StopIteration + + self.current_frame += 1 + + return DCDFrame(xyz, unitcell) + + def seek(self, frame): + """Seek to Frame. + + Please note that this function will generate internal file offsets if + they haven't been set before. For large file this means the first seek + can be very slow. Later seeks will be very fast. + + Parameters + ---------- + frame : int + seek the file to given frame + + Raises + ------ + IOError + If you seek for more frames than are available or if the + seek fails (the low-level system error is reported). + """ + if frame >= self.n_frames: + raise IOError('Trying to seek over max number of frames') + self.reached_eof = False + + cdef fio_size_t offset + if frame == 0: + offset = self.header_size + else: + offset = self.header_size + self.firstframesize + self.framesize * (frame - 1) + + ok = fio_fseek(self.fp, offset, _whence_vals["FIO_SEEK_SET"]) + if ok != 0: + raise IOError("DCD seek failed with system errno={}".format(ok)) + self.current_frame = frame From f4d7740755476ce94e944fd9c96e70f40011926c Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Fri, 21 Oct 2016 16:45:15 +0100 Subject: [PATCH 003/101] TST: Added first draft of testing module for new Cython-based DCD file handling interface. TST: All libdcd unit tests now pass, except one related to unit cell dimensions. TST: Added several more unit tests to test_libdcd.py. TST: updated unit tests to reflect appropriate Error raising for test_read_write_mode_file and test_open_wrong_mode. --- .../MDAnalysisTests/formats/test_libdcd.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 testsuite/MDAnalysisTests/formats/test_libdcd.py diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py new file mode 100644 index 00000000000..1e7cbf11c11 --- /dev/null +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -0,0 +1,101 @@ +from __future__ import print_function + +from nose.tools import raises +from numpy.testing import assert_equal, assert_array_equal +from numpy.testing import assert_array_almost_equal +from numpy.testing import assert_almost_equal + +from MDAnalysis.lib.formats.libdcd import DCDFile +from MDAnalysisTests.datafiles import DCD + +from unittest import TestCase +from MDAnalysisTests.tempdir import run_in_tempdir +import numpy as np + +class DCDReadFrameTest(TestCase): + + def setUp(self): + self.dcdfile = DCDFile(DCD) + + def tearDown(self): + del self.dcdfile + + def test_read_coords(self): + # confirm shape of coordinate data against result from previous + # MDAnalysis implementation of DCD file handling + dcd_frame = self.dcdfile.read() + xyz = dcd_frame[0] + assert_equal(xyz.shape, (3341, 3)) + + def test_read_unit_cell(self): + # confirm unit cell read against result from previous + # MDAnalysis implementation of DCD file handling + dcd_frame = self.dcdfile.read() + unitcell = dcd_frame[1] + expected = np.array([ 0., 0., 0., 90., 90., 90.], + dtype=np.float32) + assert_equal(unitcell, expected) + + def test_seek_over_max(self): + # should raise IOError if beyond 98th frame + with self.assertRaises(IOError): + self.dcdfile.seek(102) + + def test_seek_normal(self): + # frame seek within range is tested + new_frame = 91 + self.dcdfile.seek(new_frame) + assert_equal(self.dcdfile.tell(), new_frame) + + def test_seek_negative(self): + # frame seek with negative number + with self.assertRaises(IOError): + self.dcdfile.seek(-78) + + def test_iteration(self): + self.dcdfile.__next__() + self.dcdfile.__next__() + self.dcdfile.__next__() + expected_frame = 3 + assert_equal(self.dcdfile.tell(), expected_frame) + + def test_zero_based_frames(self): + expected_frame = 0 + assert_equal(self.dcdfile.tell(), expected_frame) + + def test_length_traj(self): + expected = 98 + assert_equal(len(self.dcdfile), expected) + + def test_context_manager(self): + frame = 22 + with self.dcdfile as f: + f.seek(frame) + assert_equal(f.tell(), frame) + + @raises(IOError) + def test_open_wrong_mode(self): + DCDFile('foo', 'e') + + @raises(IOError) + def test_raise_not_existing(self): + DCDFile('foo') + + def test_n_atoms(self): + assert_equal(self.dcdfile.n_atoms, 3341) + + @raises(IOError) + @run_in_tempdir() + def test_read_write_mode_file(self): + with DCDFile('foo', 'w') as f: + f.read() + + @raises(RuntimeError) + def test_read_closed(self): + self.dcdfile.close() + self.dcdfile.read() + + def test_iteration_2(self): + with self.dcdfile as f: + for frame in f: + pass From a2ccb3d367cbfd67b4c36a88f4e1bbd0bde90cdc Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Tue, 10 Jan 2017 16:05:52 -0700 Subject: [PATCH 004/101] initial work on DCD writing ENH: rebase against develop / handle merge conflicts / remove old DCD imports from package. ENH: Very early draft of DCD header writing Cython code and very crude associated unit test. ENH: libdcd.pyx now avoids file overwriting and handles writing more robustly for _write_header. ENH: Updated DCD unit tests to accommodate improvement to DCD file header writing. --- package/MDAnalysis/coordinates/__init__.py | 10 ----- package/MDAnalysis/lib/formats/libdcd.pyx | 27 +++++++++++- package/setup.py | 3 -- .../MDAnalysisTests/formats/test_libdcd.py | 41 ++++++++++++++++++- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/package/MDAnalysis/coordinates/__init__.py b/package/MDAnalysis/coordinates/__init__.py index 21404535a42..5f027d8a32a 100644 --- a/package/MDAnalysis/coordinates/__init__.py +++ b/package/MDAnalysis/coordinates/__init__.py @@ -719,13 +719,3 @@ class can choose an appropriate reader automatically. from . import memory from . import MMTF from . import null - - -try: - from . import DCD - from . import LAMMPS -except ImportError as e: - # The import is expected to fail under Python 3. - # It should not fail on Python 2, however. - if six.PY2: - raise e diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index d07dfb1c80d..8bbfb6f9476 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -78,6 +78,9 @@ cdef extern from 'include/readdcd.h': float *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) + int write_dcdheader(fio_fd fd, const char *remarks, int natoms, + int istart, int nsavc, double delta, int with_unitcell, + int charmm); DCDFrame = namedtuple('DCDFrame', 'x unitcell') @@ -110,6 +113,9 @@ cdef class DCDFile: self.fname = fname.encode('utf-8') self.n_atoms = 0 self.is_open = False + if path.isfile(self.fname) and mode == 'w': + raise RuntimeError('''Aborting -- attempted to overwrite an + existing file path.''') self.open(self.fname, mode) def __dealloc__(self): @@ -161,7 +167,8 @@ cdef class DCDFile: "ErrorCode: {}".format(self.fname, ok)) self.is_open = True self.current_frame = 0 - self.remarks = self._read_header() + if self.mode == 'r': + self.remarks = self._read_header() self.reached_eof = False def close(self): @@ -296,3 +303,21 @@ cdef class DCDFile: if ok != 0: raise IOError("DCD seek failed with system errno={}".format(ok)) self.current_frame = frame + + def _write_header(self): + + if not self.is_open: + raise RuntimeError("No file open") + + if not self.mode=='w': + raise IOError("Incorrect file mode for writing.") + + cdef char c_remarks + cdef int len_remarks = 0 + cdef int with_unitcell = 1 + + ok = write_dcdheader(self.fp, &c_remarks, self.n_atoms, self.istart, + self.nsavc, self.delta, with_unitcell, + self.charmm) + if ok != 0: + raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) diff --git a/package/setup.py b/package/setup.py index 56fe2942aee..b8ffb57f188 100755 --- a/package/setup.py +++ b/package/setup.py @@ -355,9 +355,6 @@ def extensions(config): pre_exts = [dcd, distances, distances_omp, qcprot, transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred] - - pre_exts = [dcd, distances, distances_omp, qcprot, - transformation, libmdaxdr, util] cython_generated = [] if use_cython: extensions = cythonize(pre_exts) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 1e7cbf11c11..8c38c37cd05 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -10,7 +10,9 @@ from unittest import TestCase from MDAnalysisTests.tempdir import run_in_tempdir +from MDAnalysisTests import tempdir import numpy as np +import os class DCDReadFrameTest(TestCase): @@ -84,7 +86,7 @@ def test_raise_not_existing(self): def test_n_atoms(self): assert_equal(self.dcdfile.n_atoms, 3341) - @raises(IOError) + @raises(RuntimeError) @run_in_tempdir() def test_read_write_mode_file(self): with DCDFile('foo', 'w') as f: @@ -99,3 +101,40 @@ def test_iteration_2(self): with self.dcdfile as f: for frame in f: pass + +class DCDWriteHeaderTest(TestCase): + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(DCD, 'r') + + def tearDown(self): + try: + os.unlink(self.testfile) + except OSError: + pass + del self.tmpdir + + def test_write_header_crude(self): + # test that _write_header() can produce a very crude + # header for a new / empty file + self.dcdfile._write_header() + self.dcdfile.close() + + # we're not actually asserting anything, yet + # run with: nosetests test_libdcd.py --nocapture + # to see printed output from nose + with open(self.testfile, "rb") as f: + for element in f: + print(element) + + def test_write_header_mode_sensitivy(self): + # an exception should be raised on any attempt to use + # _write_header with a DCDFile object in 'r' mode + with self.assertRaises(IOError): + self.dcdfile_r._write_header() + + + From ab0f981f7b1cea73e4de1f270ff9ce8216bf118d Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 14 Jan 2017 09:06:34 -0700 Subject: [PATCH 005/101] ENH: restored DCD-related import checking in MDAnalysis/coordinates/__init__.py after upstream python 3 fixes. --- package/MDAnalysis/coordinates/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/package/MDAnalysis/coordinates/__init__.py b/package/MDAnalysis/coordinates/__init__.py index 5f027d8a32a..21404535a42 100644 --- a/package/MDAnalysis/coordinates/__init__.py +++ b/package/MDAnalysis/coordinates/__init__.py @@ -719,3 +719,13 @@ class can choose an appropriate reader automatically. from . import memory from . import MMTF from . import null + + +try: + from . import DCD + from . import LAMMPS +except ImportError as e: + # The import is expected to fail under Python 3. + # It should not fail on Python 2, however. + if six.PY2: + raise e From c749a134696e76ab438fc974017021570887f140 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 14 Jan 2017 16:05:41 -0700 Subject: [PATCH 006/101] exception handling in DCD ENH: Replaced all usage of RuntimeError exception with IOError in libdcd.pyx, for consistency with MDA codebase. ENH: test_libdcd.py now correctly expects IOError instead of RuntimeError from libdcd. --- package/MDAnalysis/lib/formats/libdcd.pyx | 14 +++++++------- testsuite/MDAnalysisTests/formats/test_libdcd.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 8bbfb6f9476..9021ece52a4 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -114,7 +114,7 @@ cdef class DCDFile: self.n_atoms = 0 self.is_open = False if path.isfile(self.fname) and mode == 'w': - raise RuntimeError('''Aborting -- attempted to overwrite an + raise IOError('''Aborting -- attempted to overwrite an existing file path.''') self.open(self.fname, mode) @@ -143,7 +143,7 @@ cdef class DCDFile: def __len__(self): if not self.is_open: - raise RuntimeError('No file currently opened') + raise IOError('No file currently opened') return self.n_frames def tell(self): @@ -187,7 +187,7 @@ cdef class DCDFile: def _read_header(self): if not self.is_open: - raise RuntimeError("No file open") + raise IOError("No file open") cdef char* c_remarks cdef int len_remarks = 0 @@ -237,11 +237,11 @@ cdef class DCDFile: def read(self): if self.reached_eof: - raise RuntimeError('Reached last frame in TRR, seek to 0') + raise IOError('Reached last frame in TRR, seek to 0') if not self.is_open: - raise RuntimeError("No file open") + raise IOError("No file open") if self.mode != 'r': - raise RuntimeError('File opened in mode: {}. Reading only allow ' + raise IOError('File opened in mode: {}. Reading only allow ' 'in mode "r"'.format('self.mode')) cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=DTYPE, @@ -307,7 +307,7 @@ cdef class DCDFile: def _write_header(self): if not self.is_open: - raise RuntimeError("No file open") + raise IOError("No file open") if not self.mode=='w': raise IOError("Incorrect file mode for writing.") diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 8c38c37cd05..3da0cc7d9a4 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -86,13 +86,13 @@ def test_raise_not_existing(self): def test_n_atoms(self): assert_equal(self.dcdfile.n_atoms, 3341) - @raises(RuntimeError) + @raises(IOError) @run_in_tempdir() def test_read_write_mode_file(self): with DCDFile('foo', 'w') as f: f.read() - @raises(RuntimeError) + @raises(IOError) def test_read_closed(self): self.dcdfile.close() self.dcdfile.read() From 6182da5749bf937cd3742ef7dd3a1fca6734f5df Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 14 Jan 2017 16:08:27 -0700 Subject: [PATCH 007/101] ENH: added py3 compatibility for block_import. --- testsuite/MDAnalysisTests/util.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/util.py b/testsuite/MDAnalysisTests/util.py index fc78dd31671..3f72e96b78c 100644 --- a/testsuite/MDAnalysisTests/util.py +++ b/testsuite/MDAnalysisTests/util.py @@ -37,11 +37,15 @@ from contextlib import contextmanager from functools import wraps import importlib -import mock +try: + import mock +except ImportError: # python 3 + from unittest import mock import os from numpy.testing import assert_warns + def block_import(package): """Block import of a given package From 09ff489b746b9a0a695548446f44430e063227a3 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 16 Jan 2017 13:03:59 +0100 Subject: [PATCH 008/101] compile _dcdmodule again This will help to pass travis tests during development. --- package/setup.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/package/setup.py b/package/setup.py index b8ffb57f188..44000be8a43 100755 --- a/package/setup.py +++ b/package/setup.py @@ -295,11 +295,16 @@ def extensions(config): include_dirs = [get_numpy_include] - dcd = MDAExtension('lib.formats.libdcd', - ['MDAnalysis/lib/formats/libdcd' + source_suffix], - include_dirs=include_dirs + ['MDAnalysis/lib/formats/include'], + dcd = MDAExtension('coordinates._dcdmodule', + ['MDAnalysis/coordinates/src/dcd.c'], + include_dirs=include_dirs + ['MDAnalysis/coordinates/include'], define_macros=define_macros, extra_compile_args=extra_compile_args) + libdcd = MDAExtension('lib.formats.libdcd', + ['MDAnalysis/lib/formats/libdcd' + source_suffix], + include_dirs=include_dirs + ['MDAnalysis/lib/formats/include'], + define_macros=define_macros, + extra_compile_args=extra_compile_args) distances = MDAExtension('lib.c_distances', ['MDAnalysis/lib/c_distances' + source_suffix], include_dirs=include_dirs + ['MDAnalysis/lib/include'], @@ -352,7 +357,7 @@ def extensions(config): include_dirs = include_dirs+['MDAnalysis/analysis/encore/dimensionality_reduction/include'], libraries=["m"], extra_compile_args=["-O3", "-ffast-math","-std=c99"]) - pre_exts = [dcd, distances, distances_omp, qcprot, + pre_exts = [dcd, libdcd, distances, distances_omp, qcprot, transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred] cython_generated = [] From 1475c996e0835db3cddf9bc7f769398dd907f990 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 28 Jan 2017 19:31:05 -0700 Subject: [PATCH 009/101] ENH: New DCD reading code now appears to handle unit cell information properly. All tests in test_libdcd.py pass in python 2.7 and 3.5. --- .../MDAnalysis/lib/formats/include/readdcd.h | 26 +++++++++++++++++++ package/MDAnalysis/lib/formats/libdcd.pyx | 22 +++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h index c9d0f9999c9..91bd2938055 100644 --- a/package/MDAnalysis/lib/formats/include/readdcd.h +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -431,6 +431,9 @@ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, fl //int ret_val; /* Return value from read */ fio_size_t seekpos; int input_integer; + float alpha, beta, gamma; + unitcell[0] = unitcell[2] = unitcell[5] = 0.0f; + unitcell[1] = unitcell[3] = unitcell[4] = 90.0f; if ((num_fixed==0) || first) { int rc, range; @@ -496,6 +499,29 @@ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, fl } else { return DCD_BADFORMAT; } + if (unitcell[1] >= -1.0 && unitcell[1] <= 1.0 && + unitcell[3] >= -1.0 && unitcell[3] <= 1.0 && + unitcell[4] >= -1.0 && unitcell[4] <= 1.0) { + /* This file was generated by Charmm, or by NAMD > 2.5, with the angle */ + /* cosines of the periodic cell angles written to the DCD file. */ + /* This formulation improves rounding behavior for orthogonal cells */ + /* so that the angles end up at precisely 90 degrees, unlike acos(). */ + /* (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; */ + /* see Issue 187) */ + alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; + beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; + gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; + } else { + /* This file was likely generated by NAMD 2.5 and the periodic cell */ + /* angles are specified in degrees rather than angle cosines. */ + alpha = unitcell[4]; + beta = unitcell[3]; + gamma = unitcell[1]; + } + unitcell[4] = alpha; + unitcell[3] = beta; + unitcell[1] = gamma; + return DCD_SUCCESS; } diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 9021ece52a4..376f4723063 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -17,6 +17,7 @@ from os import path import numpy as np from collections import namedtuple +from MDAnalysis.lib.mdamath import triclinic_box cimport numpy as np @@ -78,6 +79,11 @@ cdef extern from 'include/readdcd.h': float *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) + int read_dcdsubset(fio_fd fd, int n_atoms, int lowerb, int upperb, + float *X, float *Y, float *Z, + float *unitcell, int num_fixed, + int first, int *indexes, float *fixedcoords, + int reverse_endian, int charmm) int write_dcdheader(fio_fd fd, const char *remarks, int natoms, int istart, int nsavc, double delta, int with_unitcell, int charmm); @@ -254,7 +260,11 @@ cdef class DCDFile: first_frame = self.current_frame == 0 - ok = read_dcdstep(self.fp, self.n_atoms, &x[0], + cdef int lowerb = 0 + cdef int upperb = self.n_atoms - 1 + + ok = read_dcdsubset(self.fp, self.n_atoms, lowerb, upperb, + &x[0], &y[0], &z[0], unitcell.data, self.nfixed, first_frame, self.freeind, self.fixedcoords, @@ -269,6 +279,16 @@ cdef class DCDFile: self.current_frame += 1 + _ts_order = [0, 2, 5, 4, 3, 1] + uc = np.take(unitcell, _ts_order) + if np.any(uc < 0.) or np.any(uc[3:] > 180.): + # might be new CHARMM: box matrix vectors + H = unitcell + e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] + uc = triclinic_box(e1, e2, e3) + + unitcell = uc + return DCDFrame(xyz, unitcell) def seek(self, frame): From 5df264e59a988b3a43f84632d4e25614cfc2750a Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sun, 29 Jan 2017 19:29:32 -0700 Subject: [PATCH 010/101] ENH: Fixed an IOError message in libdcd.pyx that referred to TRR files instead of DCD files. --- package/MDAnalysis/lib/formats/libdcd.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 376f4723063..b755749d708 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -243,7 +243,7 @@ cdef class DCDFile: def read(self): if self.reached_eof: - raise IOError('Reached last frame in TRR, seek to 0') + raise IOError('Reached last frame in DCD, seek to 0') if not self.is_open: raise IOError("No file open") if self.mode != 'r': From c783f2d47d30c04c80506ed94a87bc344d719a59 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sun, 29 Jan 2017 19:51:21 -0700 Subject: [PATCH 011/101] DCD writing ENH: DCDFile now has an early draft of the write() method & there's a crude associated initial unit test. ENH: added additional arguments and docstring to write method of DCDFile, based on the write method in libmdaxdr.pyx. ENH: The write method of DCDFile now calls the header writing code first, as required. Also, fixed a unit test to account for the recently increased number of arguments accepted by the write method of DCDFile. ENH: DCDFile unit tests updated to reflect the fact that the write method can now be called with various arguments, without raising an exception (though the actual DCDFile that is written is not quite right yet). ENH: DCDFile write method arguments now accepting python input, though the C-level method is temporarily disabled. ENH: Reactivated the write_dcdstep C code in the write method of DCDFile. A unit test modification confirms that a binary output file is produced by the latter, although it is not yet a proper DCD file as DCDFile can't read it back in. ENH: Initial cleanup of ctypedef statements in libdcd.pyx based on reviewer comments in PR#1155. ENH: The DCDFile write method now requires the double type for box and unit tests have been adjusted accordingly, based on reviewer comments in PR#1155. ENH: Expanded the DCDFile header writing capabilities. ENH: Added more unit tests for DCDFile write() method. use consistent double dimensions ENH: Added 2 new unit tests for DCDFile writing behaviour. ENH: Added initial code to allow REMARKS section of DCD file written by DCDFile to be user-controlled. Currently, the title and time fields from the C-header file seem to get intermingled in the .remarks property of the DCDFile read back in. ENH: General cleanup of DCDFile write() method arguments in code proper and unit tests. ENH: Removed an extraneous variable from the setUp of DCDWriteTest. ENH: python 2/3 string handling compatiblity fixes for DCDFile. ENH: DCDFile writing of REMARKS section has been adjusted to allow specification of a properly formatted string up to 160 chars in length. ENH: Added 3 new writing-related tests for the new DCDFile object. ENH: The number of 80 character title strings written by default to new DCD files has been increased to 3, allowing exact preservation of the remarks section from the MDA test DCD file when writing. ENH: the DCDFile firstframesize property has been patched to the apparently correct arithmetic. --- .../MDAnalysis/lib/formats/include/readdcd.h | 28 ++-- package/MDAnalysis/lib/formats/libdcd.pyx | 97 +++++++++--- .../MDAnalysisTests/formats/test_libdcd.py | 149 +++++++++++++++++- 3 files changed, 234 insertions(+), 40 deletions(-) diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h index 91bd2938055..cc20759f2ac 100644 --- a/package/MDAnalysis/lib/formats/include/readdcd.h +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -70,7 +70,7 @@ static int read_dcdheader(fio_fd fd, int *natoms, int *nsets, int *istart, int * * unitcell holds unit cell data if present. */ static int read_dcdstep(fio_fd fd, int natoms, float *x, float *y, float *z, - float *unitcell, int nfixed, int first, int *freeind, + double *unitcell, int nfixed, int first, int *freeind, float *fixedcoords, int reverse, int charmm); /* @@ -89,7 +89,7 @@ static int read_dcdstep(fio_fd fd, int natoms, float *x, float *y, float *z, * unitcell holds unit cell data if present. */ static int read_dcdsubset(fio_fd fd, int natoms, int lowerb, int upperb, float *x, float *y, float *z, - float *unitcell, int nfixed, int first, int *freeind, + double *unitcell, int nfixed, int first, int *freeind, float *fixedcoords, int reverse, int charmm); /* @@ -357,7 +357,7 @@ static int read_dcdheader(fio_fd fd, int *N, int *NSET, int *ISTART, } static int read_charmm_extrablock(fio_fd fd, int charmm, int reverseEndian, - float *unitcell) { + double *unitcell) { int i, input_integer; if ((charmm & DCD_IS_CHARMM) && (charmm & DCD_HAS_EXTRA_BLOCK)) { @@ -370,7 +370,7 @@ static int read_charmm_extrablock(fio_fd fd, int charmm, int reverseEndian, if (fio_fread(tmp, 48, 1, fd) != 1) return DCD_BADREAD; if (reverseEndian) swap8_aligned(tmp, 6); - for (i=0; i<6; i++) unitcell[i] = (float)tmp[i]; + for (i=0; i<6; i++) unitcell[i] = tmp[i]; } else { /* unrecognized block, just skip it */ if (fio_fseek(fd, input_integer, FIO_SEEK_CUR)) return DCD_BADREAD; @@ -426,7 +426,7 @@ static int read_charmm_4dim(fio_fd fd, int charmm, int reverseEndian) { /* XXX This is completely broken for fixed coordinates */ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, float *Y, float *Z, - float *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, + double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverseEndian, int charmm) { //int ret_val; /* Return value from read */ fio_size_t seekpos; @@ -526,7 +526,7 @@ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, fl } static int read_dcdstep(fio_fd fd, int N, float *X, float *Y, float *Z, - float *unitcell, int num_fixed, + double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverseEndian, int charmm) { int ret_val; /* Return value from read */ @@ -718,10 +718,9 @@ static int write_dcdheader(fio_fd fd, const char *remarks, int N, int charmm) { int out_integer; float out_float; - char title_string[200]; + char title_string[241]; time_t cur_time; struct tm *tmbuf; - char time_str[81]; out_integer = 84; WRITE(fd, (char *) & out_integer, sizeof(int)); @@ -762,16 +761,11 @@ static int write_dcdheader(fio_fd fd, const char *remarks, int N, } fio_write_int32(fd, 84); fio_write_int32(fd, 164); - fio_write_int32(fd, 2); + fio_write_int32(fd, 3); /* the number of 80 character title strings */ - strncpy(title_string, remarks, 80); - title_string[79] = '\0'; - WRITE(fd, title_string, 80); - - cur_time=time(NULL); - tmbuf=localtime(&cur_time); - strftime(time_str, 80, "REMARKS Created %d %B, %Y at %R", tmbuf); - WRITE(fd, time_str, 80); + strncpy(title_string, remarks, 241); + title_string[240] = '\0'; + WRITE(fd, title_string, 240); fio_write_int32(fd, 164); fio_write_int32(fd, 4); diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index b755749d708..3fd8e6a8a58 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -18,6 +18,7 @@ from os import path import numpy as np from collections import namedtuple from MDAnalysis.lib.mdamath import triclinic_box +import six cimport numpy as np @@ -36,8 +37,10 @@ cdef extern from 'sys/types.h': ctypedef int fio_fd; ctypedef off_t fio_size_t -ctypedef np.float32_t DTYPE_t -DTYPE = np.float32 +ctypedef np.float32_t FLOAT_T +ctypedef np.float64_t DOUBLE_T +FLOAT = np.float32 +DOUBLE = np.float64 from libc.stdint cimport uintptr_t from libc.stdlib cimport free @@ -76,17 +79,20 @@ cdef extern from 'include/readdcd.h': char **remarks, int *len_remarks) void close_dcd_read(int *freeind, float *fixedcoords) int read_dcdstep(fio_fd fd, int n_atoms, float *X, float *Y, float *Z, - float *unitcell, int num_fixed, + double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) int read_dcdsubset(fio_fd fd, int n_atoms, int lowerb, int upperb, float *X, float *Y, float *Z, - float *unitcell, int num_fixed, + double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) int write_dcdheader(fio_fd fd, const char *remarks, int natoms, int istart, int nsavc, double delta, int with_unitcell, int charmm); + int write_dcdstep(fio_fd fd, int curstep, int curframe, + int natoms, const float *x, const float *y, const float *z, + const double *unitcell, int charmm); DCDFrame = namedtuple('DCDFrame', 'x unitcell') @@ -111,9 +117,9 @@ cdef class DCDFile: cdef int current_frame cdef readonly remarks cdef int reached_eof - cdef int firstframesize - cdef int framesize - cdef int header_size + cdef readonly int firstframesize + cdef readonly int framesize + cdef readonly int header_size def __cinit__(self, fname, mode='r'): self.fname = fname.encode('utf-8') @@ -223,7 +229,7 @@ cdef class DCDFile: def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 - self.firstframesize = self.n_atoms + 2 * self.n_dims * sizeof(float) + extrablocksize + self.firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize self.framesize = ((self.n_atoms - self.nfixed + 2) * self.n_dims * sizeof(float) + extrablocksize) filesize = path.getsize(self.fname) @@ -250,13 +256,13 @@ cdef class DCDFile: raise IOError('File opened in mode: {}. Reading only allow ' 'in mode "r"'.format('self.mode')) - cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=DTYPE, + cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=FLOAT, order='F') - cdef np.ndarray unitcell = np.empty(6, dtype=DTYPE) + cdef np.ndarray unitcell = np.empty(6, dtype=DOUBLE) - cdef DTYPE_t[::1] x = xyz[:, 0] - cdef DTYPE_t[::1] y = xyz[:, 1] - cdef DTYPE_t[::1] z = xyz[:, 2] + cdef FLOAT_T[::1] x = xyz[:, 0] + cdef FLOAT_T[::1] y = xyz[:, 1] + cdef FLOAT_T[::1] z = xyz[:, 2] first_frame = self.current_frame == 0 @@ -264,9 +270,9 @@ cdef class DCDFile: cdef int upperb = self.n_atoms - 1 ok = read_dcdsubset(self.fp, self.n_atoms, lowerb, upperb, - &x[0], - &y[0], &z[0], - unitcell.data, self.nfixed, first_frame, + &x[0], + &y[0], &z[0], + unitcell.data, self.nfixed, first_frame, self.freeind, self.fixedcoords, self.reverse_endian, self.charmm) if ok != 0 and ok != -4: @@ -324,7 +330,8 @@ cdef class DCDFile: raise IOError("DCD seek failed with system errno={}".format(ok)) self.current_frame = frame - def _write_header(self): + def _write_header(self, remarks, int n_atoms, int starting_step, + int ts_between_saves, double time_step): if not self.is_open: raise IOError("No file open") @@ -332,12 +339,62 @@ cdef class DCDFile: if not self.mode=='w': raise IOError("Incorrect file mode for writing.") - cdef char c_remarks + #cdef char c_remarks cdef int len_remarks = 0 cdef int with_unitcell = 1 - ok = write_dcdheader(self.fp, &c_remarks, self.n_atoms, self.istart, - self.nsavc, self.delta, with_unitcell, + if isinstance(remarks, six.string_types): + remarks = bytearray(remarks, 'ascii') + + ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, + ts_between_saves, time_step, with_unitcell, self.charmm) if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) + + def write(self, xyz, double [:] box, int step, int natoms, + int ts_between_saves, int charmm, double time_step, remarks): + """write one frame into DCD file. + + Parameters + ---------- + xyz : ndarray, shape=(n_atoms, 3) + cartesion coordinates + box : ndarray, shape=(3, 3) + Box vectors for this frame + step : int + current step number, 1 indexed + time : float + current time + natoms : int + number of atoms in frame + + Raises + ------ + IOError + """ + if not self.is_open: + raise IOError("No file open") + if self.mode != 'w': + raise IOError('File opened in mode: {}. Writing only allowed ' + 'in mode "w"'.format('self.mode')) + + #cdef double [:,:] unitcell = box + cdef FLOAT_T[::1] x = xyz[:, 0] + cdef FLOAT_T[::1] y = xyz[:, 1] + cdef FLOAT_T[::1] z = xyz[:, 2] + + # prerequisite is a file struct for which the dcd header data + # has already been written + self._write_header(remarks=remarks, n_atoms=xyz.shape[0], starting_step=step, + ts_between_saves=ts_between_saves, + time_step=time_step) + + + if self.current_frame == 0: + self.n_atoms = xyz.shape[0] + + ok = write_dcdstep(self.fp, step, self.current_frame, + self.n_atoms, &x[0], + &y[1], &z[2], + &box[0], charmm) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 3da0cc7d9a4..8410b4ea878 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -6,9 +6,10 @@ from numpy.testing import assert_almost_equal from MDAnalysis.lib.formats.libdcd import DCDFile -from MDAnalysisTests.datafiles import DCD +from MDAnalysisTests.datafiles import PSF, DCD from unittest import TestCase +import MDAnalysis from MDAnalysisTests.tempdir import run_in_tempdir from MDAnalysisTests import tempdir import numpy as np @@ -120,7 +121,9 @@ def tearDown(self): def test_write_header_crude(self): # test that _write_header() can produce a very crude # header for a new / empty file - self.dcdfile._write_header() + self.dcdfile._write_header(remarks='Crazy!', n_atoms=22, + starting_step=12, ts_between_saves=10, + time_step=0.02) self.dcdfile.close() # we're not actually asserting anything, yet @@ -134,7 +137,147 @@ def test_write_header_mode_sensitivy(self): # an exception should be raised on any attempt to use # _write_header with a DCDFile object in 'r' mode with self.assertRaises(IOError): - self.dcdfile_r._write_header() + self.dcdfile_r._write_header(remarks='Crazy!', n_atoms=22, + starting_step=12, ts_between_saves=10, + time_step=0.02) +class DCDWriteTest(TestCase): + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(DCD, 'r') + + with self.dcdfile_r as f_in, self.dcdfile as f_out: + for frame in f_in: + frame = frame._asdict() + f_out.write(xyz=frame['x'], + box=frame['unitcell'].astype(np.float64), + step=f_in.istart, + natoms=frame['x'].shape[0], + charmm=0, + time_step=f_in.delta, + ts_between_saves=f_in.nsavc, + remarks=f_in.remarks) + + + def tearDown(self): + try: + os.unlink(self.testfile) + except OSError: + pass + del self.tmpdir + + def test_write_mode(self): + # ensure that writing of DCD files only occurs with properly + # opened files + with self.assertRaises(IOError): + self.dcdfile_r.write(xyz=np.zeros((3,3)), + box=np.zeros(6, dtype=np.float64), + step=0, + natoms=330, + charmm=0, + time_step=22.2, + ts_between_saves=3, + remarks='') + + def test_written_dcd_coordinate_data_shape(self): + # written coord shape should match for all frames + expected = (3341, 3) + with DCDFile(self.testfile) as f: + for frame in f: + xyz = f.read()[0] + assert_equal(xyz.shape, expected) + + def test_written_unit_cell(self): + # written unit cell dimensions should match for all frames + expected = np.array([ 0., 0., 0., 90., 90., 90.], + dtype=np.float32) + with DCDFile(self.testfile) as f: + for frame in f: + unitcell = f.read()[1] + assert_equal(unitcell, expected) + + def test_written_num_frames(self): + expected = 98 + with DCDFile(self.testfile) as f: + assert_equal(len(f), expected) + + def test_written_seek(self): + # ensure that we can seek properly on written DCD file + new_frame = 91 + with DCDFile(self.testfile) as f: + f.seek(new_frame) + assert_equal(f.tell(), new_frame) + + def test_written_zero_based_frames(self): + # ensure that the first written DCD frame is 0 + expected_frame = 0 + with DCDFile(self.testfile) as f: + assert_equal(f.tell(), expected_frame) + + def test_written_remarks(self): + # ensure that the REMARKS field *can be* preserved exactly + # in the written DCD file + expected = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' + with DCDFile(self.testfile) as f: + print('len(expected):', len(expected)) + print('len(f.remarks):', len(f.remarks)) + assert_equal(f.remarks.decode(), expected) + + def test_written_nsavc(self): + # ensure that nsavc, the timesteps between frames written + # to file, is preserved in the written DCD file + expected = self.dcdfile_r.nsavc + actual = DCDFile(self.testfile).nsavc + assert_equal(actual, expected) + + def test_written_istart(self): + # ensure that istart, the starting timestep, is preserved + # in the written DCD file + expected = self.dcdfile_r.istart + actual = DCDFile(self.testfile).istart + assert_equal(actual, expected) + + def test_written_delta(self): + # ensure that delta, the trajectory timestep, is preserved in + # the written DCD file + expected = self.dcdfile_r.delta + actual = DCDFile(self.testfile).delta + assert_equal(actual, expected) + +class DCDByteArithmeticTest(TestCase): + + def setUp(self): + self.dcdfile = DCDFile(DCD, 'r') + self.filesize = os.path.getsize(DCD) + + def test_relative_frame_sizes(self): + # the first frame of a DCD file should always be >= in size + # to subsequent frames, as the first frame contains the same + # atoms + (optional) fixed atoms + first_frame_size = self.dcdfile.firstframesize + general_frame_size = self.dcdfile.framesize + self.assertGreaterEqual(first_frame_size, general_frame_size) + + def test_file_size_breakdown(self): + # the size of a DCD file is equivalent to the sum of the header + # size, first frame size, and (N - 1 frames) * size per general + # frame + expected = self.filesize + actual = self.dcdfile.header_size + self.dcdfile.firstframesize + \ + ((self.dcdfile.n_frames - 1) * self.dcdfile.framesize) + assert_equal(actual, expected) + + def test_nframessize_int(self): + # require that the (nframessize / framesize) value used by DCDFile + # is an integer (because nframessize / framesize + 1 = total frames, + # which must also be an int) + nframessize = self.filesize - self.dcdfile.header_size - \ + self.dcdfile.firstframesize + self.assertTrue(float(nframessize) % float(self.dcdfile.framesize) == 0) + + From a93d37c49ff7beb27e68cf4e34c162eb3aefa0c5 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Mon, 13 Feb 2017 11:52:46 -0700 Subject: [PATCH 012/101] DCD unit test improvements ENH: Added unit tests for NAMD-format DCD file input to DCDFile. Minor adjustments to code to handle new format. One unit test currently fails -- writing the NAMD-formatted REMARKS section to a new DCD file. ENH: Added more thorough / correct unit test code for header remarks section string handling in test_libdcd.py, and removed extraneous debug print statements. ENH: patched minor error from accidental deletion in test suite for new DCD code. --- package/MDAnalysis/lib/formats/libdcd.pyx | 5 +- .../MDAnalysisTests/formats/test_libdcd.py | 133 ++++++++++++++---- 2 files changed, 110 insertions(+), 28 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 3fd8e6a8a58..a98b4a9936c 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -344,7 +344,10 @@ cdef class DCDFile: cdef int with_unitcell = 1 if isinstance(remarks, six.string_types): - remarks = bytearray(remarks, 'ascii') + try: + remarks = bytearray(remarks, 'ascii') + except UnicodeDecodeError: + remarks = bytearray(remarks) ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, ts_between_saves, time_step, with_unitcell, diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 8410b4ea878..cef8a952717 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -6,7 +6,8 @@ from numpy.testing import assert_almost_equal from MDAnalysis.lib.formats.libdcd import DCDFile -from MDAnalysisTests.datafiles import PSF, DCD +from MDAnalysisTests.datafiles import (PSF, DCD, DCD_NAMD_TRICLINIC, + PSF_NAMD_TRICLINIC) from unittest import TestCase import MDAnalysis @@ -19,16 +20,34 @@ class DCDReadFrameTest(TestCase): def setUp(self): self.dcdfile = DCDFile(DCD) + self.natoms = 3341 + self.traj_length = 98 + self.new_frame = 91 + self.context_frame = 22 + self.num_iters = 3 + self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' def tearDown(self): del self.dcdfile + def test_header_remarks(self): + # confirm correct header remarks section reading + with self.dcdfile as f: + list_chars = [] + for element in f.remarks: + list_chars.append(element) + + list_chars = [] + for element in self.expected_remarks: + list_chars.append(element) + self.assertEqual(len(f.remarks), len(self.expected_remarks)) + def test_read_coords(self): # confirm shape of coordinate data against result from previous # MDAnalysis implementation of DCD file handling dcd_frame = self.dcdfile.read() xyz = dcd_frame[0] - assert_equal(xyz.shape, (3341, 3)) + assert_equal(xyz.shape, (self.natoms, 3)) def test_read_unit_cell(self): # confirm unit cell read against result from previous @@ -46,9 +65,8 @@ def test_seek_over_max(self): def test_seek_normal(self): # frame seek within range is tested - new_frame = 91 - self.dcdfile.seek(new_frame) - assert_equal(self.dcdfile.tell(), new_frame) + self.dcdfile.seek(self.new_frame) + assert_equal(self.dcdfile.tell(), self.new_frame) def test_seek_negative(self): # frame seek with negative number @@ -56,25 +74,26 @@ def test_seek_negative(self): self.dcdfile.seek(-78) def test_iteration(self): - self.dcdfile.__next__() - self.dcdfile.__next__() - self.dcdfile.__next__() - expected_frame = 3 - assert_equal(self.dcdfile.tell(), expected_frame) + expected = 0 + while self.num_iters > 0: + self.dcdfile.__next__() + self.num_iters -= 1 + expected += 1 + + assert_equal(self.dcdfile.tell(), expected) def test_zero_based_frames(self): expected_frame = 0 assert_equal(self.dcdfile.tell(), expected_frame) def test_length_traj(self): - expected = 98 + expected = self.traj_length assert_equal(len(self.dcdfile), expected) def test_context_manager(self): - frame = 22 with self.dcdfile as f: - f.seek(frame) - assert_equal(f.tell(), frame) + f.seek(self.context_frame) + assert_equal(f.tell(), self.context_frame) @raises(IOError) def test_open_wrong_mode(self): @@ -85,7 +104,7 @@ def test_raise_not_existing(self): DCDFile('foo') def test_n_atoms(self): - assert_equal(self.dcdfile.n_atoms, 3341) + assert_equal(self.dcdfile.n_atoms, self.natoms) @raises(IOError) @run_in_tempdir() @@ -150,6 +169,10 @@ def setUp(self): self.testfile = self.tmpdir.name + '/test.dcd' self.dcdfile = DCDFile(self.testfile, 'w') self.dcdfile_r = DCDFile(DCD, 'r') + self.natoms = 3341 + self.expected_frames = 98 + self.seek_frame = 91 + self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' with self.dcdfile_r as f_in, self.dcdfile as f_out: for frame in f_in: @@ -186,9 +209,13 @@ def test_write_mode(self): def test_written_dcd_coordinate_data_shape(self): # written coord shape should match for all frames - expected = (3341, 3) + expected = (self.natoms, 3) with DCDFile(self.testfile) as f: - for frame in f: + if f.n_frames > 1: + for frame in f: + xyz = f.read()[0] + assert_equal(xyz.shape, expected) + else: xyz = f.read()[0] assert_equal(xyz.shape, expected) @@ -197,21 +224,23 @@ def test_written_unit_cell(self): expected = np.array([ 0., 0., 0., 90., 90., 90.], dtype=np.float32) with DCDFile(self.testfile) as f: - for frame in f: + if f.n_frames > 1: + for frame in f: + unitcell = f.read()[1] + assert_equal(unitcell, expected) + else: unitcell = f.read()[1] assert_equal(unitcell, expected) def test_written_num_frames(self): - expected = 98 with DCDFile(self.testfile) as f: - assert_equal(len(f), expected) + assert_equal(len(f), self.expected_frames) def test_written_seek(self): # ensure that we can seek properly on written DCD file - new_frame = 91 with DCDFile(self.testfile) as f: - f.seek(new_frame) - assert_equal(f.tell(), new_frame) + f.seek(self.seek_frame) + assert_equal(f.tell(), self.seek_frame) def test_written_zero_based_frames(self): # ensure that the first written DCD frame is 0 @@ -222,11 +251,8 @@ def test_written_zero_based_frames(self): def test_written_remarks(self): # ensure that the REMARKS field *can be* preserved exactly # in the written DCD file - expected = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' with DCDFile(self.testfile) as f: - print('len(expected):', len(expected)) - print('len(f.remarks):', len(f.remarks)) - assert_equal(f.remarks.decode(), expected) + assert_equal(f.remarks, self.expected_remarks) def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written @@ -281,3 +307,56 @@ def test_nframessize_int(self): self.assertTrue(float(nframessize) % float(self.dcdfile.framesize) == 0) +class DCDByteArithmeticTestNAMD(DCDByteArithmeticTest, TestCase): + # repeat byte arithmetic tests for NAMD format DCD + + def setUp(self): + self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC, 'r') + self.filesize = os.path.getsize(DCD_NAMD_TRICLINIC) + + +class DCDWriteTestNAMD(DCDWriteTest, TestCase): + # repeat writing tests for NAMD format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(DCD_NAMD_TRICLINIC, 'r') + self.natoms = 5545 + self.expected_frames = 1 + self.seek_frame = 0 + self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' + + with self.dcdfile_r as f_in, self.dcdfile as f_out: + for frame in f_in: + frame = frame._asdict() + f_out.write(xyz=frame['x'], + box=frame['unitcell'].astype(np.float64), + step=f_in.istart, + natoms=frame['x'].shape[0], + charmm=0, + time_step=f_in.delta, + ts_between_saves=f_in.nsavc, + remarks=f_in.remarks) + +class DCDWriteHeaderTestNAMD(DCDWriteHeaderTest, TestCase): + # repeat header writing tests for NAMD format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(DCD_NAMD_TRICLINIC, 'r') + +class DCDReadFrameTestNAMD(DCDReadFrameTest, TestCase): + # repeat frame reading tests for NAMD format DCD + + def setUp(self): + self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC) + self.natoms = 5545 + self.traj_length = 1 + self.new_frame = 0 + self.context_frame = 0 + self.num_iters = 0 + self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' From 0020b3e50bf096a75e02c035d1abaea0b5c5327f Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 9 Mar 2017 13:55:15 -0700 Subject: [PATCH 013/101] ENH: Added proper DCDFile remarks / header string handling -- all unit tests now pass in python 2.7 & 3.5 --- package/MDAnalysis/lib/formats/include/readdcd.h | 3 +-- package/MDAnalysis/lib/formats/libdcd.pyx | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h index cc20759f2ac..3888c51f81a 100644 --- a/package/MDAnalysis/lib/formats/include/readdcd.h +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -763,8 +763,7 @@ static int write_dcdheader(fio_fd fd, const char *remarks, int N, fio_write_int32(fd, 164); fio_write_int32(fd, 3); /* the number of 80 character title strings */ - strncpy(title_string, remarks, 241); - title_string[240] = '\0'; + strncpy(title_string, remarks, 240); WRITE(fd, title_string, 240); fio_write_int32(fd, 164); diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index a98b4a9936c..eb841ec9d19 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -19,6 +19,8 @@ import numpy as np from collections import namedtuple from MDAnalysis.lib.mdamath import triclinic_box import six +import string +import sys cimport numpy as np @@ -225,6 +227,15 @@ cdef class DCDFile: self.read() self.seek(0) + if sys.version_info[0] < 3: + py_remarks = unicode(py_remarks, 'ascii', "ignore") + py_remarks = str(py_remarks.encode('ascii', 'ignore')) + else: + if isinstance(py_remarks, bytes): + py_remarks = py_remarks.decode('ascii', 'ignore') + + py_remarks = "".join(s for s in py_remarks if s in string.printable) + return py_remarks def _estimate_n_frames(self): From ced318bb4168e6b4001d435ad0bbe17487db837a Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 16 Mar 2017 17:21:58 -0600 Subject: [PATCH 014/101] DCDFile variables that are testing-only / implementation details are now prefixed with an underscore in response to comments in PR#1155. --- package/MDAnalysis/lib/formats/libdcd.pyx | 20 +++++++++---------- .../MDAnalysisTests/formats/test_libdcd.py | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index eb841ec9d19..3c2cd59ffa9 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -119,9 +119,9 @@ cdef class DCDFile: cdef int current_frame cdef readonly remarks cdef int reached_eof - cdef readonly int firstframesize - cdef readonly int framesize - cdef readonly int header_size + cdef readonly int _firstframesize + cdef readonly int _framesize + cdef readonly int _header_size def __cinit__(self, fname, mode='r'): self.fname = fname.encode('utf-8') @@ -240,17 +240,17 @@ cdef class DCDFile: def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 - self.firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize - self.framesize = ((self.n_atoms - self.nfixed + 2) * self.n_dims * sizeof(float) + + self._firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize + self._framesize = ((self.n_atoms - self.nfixed + 2) * self.n_dims * sizeof(float) + extrablocksize) filesize = path.getsize(self.fname) # It's safe to use ftell, even though ftell returns a long, because the # header size is < 4GB. - self.header_size = fio_ftell(self.fp) + self._header_size = fio_ftell(self.fp) # TODO: check that nframessize is larger then 0, the c-implementation # used to do that. - nframessize = filesize - self.header_size - self.firstframesize - return nframessize / self.framesize + 1 + nframessize = filesize - self._header_size - self._firstframesize + return nframessize / self._framesize + 1 @property def periodic(self): @@ -332,9 +332,9 @@ cdef class DCDFile: cdef fio_size_t offset if frame == 0: - offset = self.header_size + offset = self._header_size else: - offset = self.header_size + self.firstframesize + self.framesize * (frame - 1) + offset = self._header_size + self._firstframesize + self._framesize * (frame - 1) ok = fio_fseek(self.fp, offset, _whence_vals["FIO_SEEK_SET"]) if ok != 0: diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index cef8a952717..9d4dc026be1 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -279,32 +279,32 @@ class DCDByteArithmeticTest(TestCase): def setUp(self): self.dcdfile = DCDFile(DCD, 'r') - self.filesize = os.path.getsize(DCD) + self._filesize = os.path.getsize(DCD) def test_relative_frame_sizes(self): # the first frame of a DCD file should always be >= in size # to subsequent frames, as the first frame contains the same # atoms + (optional) fixed atoms - first_frame_size = self.dcdfile.firstframesize - general_frame_size = self.dcdfile.framesize + first_frame_size = self.dcdfile._firstframesize + general_frame_size = self.dcdfile._framesize self.assertGreaterEqual(first_frame_size, general_frame_size) def test_file_size_breakdown(self): # the size of a DCD file is equivalent to the sum of the header # size, first frame size, and (N - 1 frames) * size per general # frame - expected = self.filesize - actual = self.dcdfile.header_size + self.dcdfile.firstframesize + \ - ((self.dcdfile.n_frames - 1) * self.dcdfile.framesize) + expected = self._filesize + actual = self.dcdfile._header_size + self.dcdfile._firstframesize + \ + ((self.dcdfile.n_frames - 1) * self.dcdfile._framesize) assert_equal(actual, expected) def test_nframessize_int(self): # require that the (nframessize / framesize) value used by DCDFile # is an integer (because nframessize / framesize + 1 = total frames, # which must also be an int) - nframessize = self.filesize - self.dcdfile.header_size - \ - self.dcdfile.firstframesize - self.assertTrue(float(nframessize) % float(self.dcdfile.framesize) == 0) + nframessize = self._filesize - self.dcdfile._header_size - \ + self.dcdfile._firstframesize + self.assertTrue(float(nframessize) % float(self.dcdfile._framesize) == 0) class DCDByteArithmeticTestNAMD(DCDByteArithmeticTest, TestCase): @@ -312,7 +312,7 @@ class DCDByteArithmeticTestNAMD(DCDByteArithmeticTest, TestCase): def setUp(self): self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC, 'r') - self.filesize = os.path.getsize(DCD_NAMD_TRICLINIC) + self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) class DCDWriteTestNAMD(DCDWriteTest, TestCase): From 04de2c087d5d00c9aa8c7ad1bdb408b3fd67cffe Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 16 Mar 2017 17:30:54 -0600 Subject: [PATCH 015/101] Replaced a standard library assertion with a numpy assertion in the libdcd test suite, based on reviewer feedback in PR#1155. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 9d4dc026be1..a93b4d7979f 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -40,7 +40,7 @@ def test_header_remarks(self): list_chars = [] for element in self.expected_remarks: list_chars.append(element) - self.assertEqual(len(f.remarks), len(self.expected_remarks)) + assert_equal(len(f.remarks), len(self.expected_remarks)) def test_read_coords(self): # confirm shape of coordinate data against result from previous From 7bcec2666dd87fc5e86d6867bff3fa9ba420f4a4 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 16 Mar 2017 17:38:08 -0600 Subject: [PATCH 016/101] DCDFile now allows overwriting, based on feedback in PR#1155. --- package/MDAnalysis/lib/formats/libdcd.pyx | 3 --- 1 file changed, 3 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 3c2cd59ffa9..c7adceef102 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -127,9 +127,6 @@ cdef class DCDFile: self.fname = fname.encode('utf-8') self.n_atoms = 0 self.is_open = False - if path.isfile(self.fname) and mode == 'w': - raise IOError('''Aborting -- attempted to overwrite an - existing file path.''') self.open(self.fname, mode) def __dealloc__(self): From d3573e11f7d84fb91c316fb9209fd9dac1c77c81 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 16 Mar 2017 17:55:43 -0600 Subject: [PATCH 017/101] current_frame variable is now updated in write method of DCDFile. --- package/MDAnalysis/lib/formats/libdcd.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index c7adceef102..c577a7678bc 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -409,3 +409,5 @@ cdef class DCDFile: self.n_atoms, &x[0], &y[1], &z[2], &box[0], charmm) + + self.current_frame += 1 From fdb768578ed13026dbf53504192a2963a77e3826 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 20 Apr 2017 17:14:25 -0600 Subject: [PATCH 018/101] Removed spurious header writing; fixed an issue in write() method of DCDFile such that single-frame NAMD DCD trajectory writing exactly reproduces the coordinates from the input file. --- package/MDAnalysis/lib/formats/libdcd.pyx | 12 ++--- .../MDAnalysisTests/formats/test_libdcd.py | 44 +++++++++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index c577a7678bc..a74b126beea 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -395,19 +395,15 @@ cdef class DCDFile: cdef FLOAT_T[::1] y = xyz[:, 1] cdef FLOAT_T[::1] z = xyz[:, 2] - # prerequisite is a file struct for which the dcd header data - # has already been written - self._write_header(remarks=remarks, n_atoms=xyz.shape[0], starting_step=step, - ts_between_saves=ts_between_saves, - time_step=time_step) - - if self.current_frame == 0: + self._write_header(remarks=remarks, n_atoms=xyz.shape[0], starting_step=step, + ts_between_saves=ts_between_saves, + time_step=time_step) self.n_atoms = xyz.shape[0] ok = write_dcdstep(self.fp, step, self.current_frame, self.n_atoms, &x[0], - &y[1], &z[2], + &y[0], &z[0], &box[0], charmm) self.current_frame += 1 diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index a93b4d7979f..3549fd117d1 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -167,8 +167,9 @@ class DCDWriteTest(TestCase): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' + self.readfile = DCD self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(DCD, 'r') + self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 3341 self.expected_frames = 98 self.seek_frame = 91 @@ -186,7 +187,6 @@ def setUp(self): ts_between_saves=f_in.nsavc, remarks=f_in.remarks) - def tearDown(self): try: os.unlink(self.testfile) @@ -275,6 +275,43 @@ def test_written_delta(self): actual = DCDFile(self.testfile).delta assert_equal(actual, expected) + def test_coord_match(self): + # ensure that all coordinates match in each frame for the + # written DCD file relative to original + test = DCDFile(self.testfile) + ref = DCDFile(self.readfile) + + if test.n_frames > 1: + for frame in test: + written_coords = test.read()[0] + ref_coords = ref.read()[0] + assert_equal(written_coords, ref_coords) + else: + written_coords = test.read()[0] + ref_coords = ref.read()[0] + assert_equal(written_coords, ref_coords) + + +class DCDByteArithmeticTest(TestCase): + + def setUp(self): + self.dcdfile = DCDFile(DCD, 'r') + self._filesize = os.path.getsize(DCD) + + def test_relative_frame_sizes(self): + # the first frame of a DCD file should always be >= in size + # to subsequent frames, as the first frame contains the same + # atoms + (optional) fixed atoms + first_frame_size = self.dcdfile._firstframesize + general_frame_size = self.dcdfile._framesize + + for frame in test: + print('frame iteration') + written_coords = test.read()[0] + print('after test read') + ref_coords = ref.read()[0] + assert_equal(written_coords, ref_coords) + class DCDByteArithmeticTest(TestCase): def setUp(self): @@ -322,7 +359,8 @@ def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(DCD_NAMD_TRICLINIC, 'r') + self.readfile = DCD_NAMD_TRICLINIC + self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 5545 self.expected_frames = 1 self.seek_frame = 0 From 6c7a691d37dde933a99958dab5b41abaab0ad232 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 20 Apr 2017 19:32:12 -0600 Subject: [PATCH 019/101] Patched issue with frame iteration in test_coord_match -- all unit tests now pass. --- .../MDAnalysisTests/formats/test_libdcd.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 3549fd117d1..149363bc437 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -280,17 +280,12 @@ def test_coord_match(self): # written DCD file relative to original test = DCDFile(self.testfile) ref = DCDFile(self.readfile) - - if test.n_frames > 1: - for frame in test: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - assert_equal(written_coords, ref_coords) - else: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - assert_equal(written_coords, ref_coords) - + curr_frame = 0 + while curr_frame < test.n_frames: + written_coords = test.read()[0] + ref_coords = ref.read()[0] + curr_frame += 1 + assert_equal(written_coords, ref_coords) class DCDByteArithmeticTest(TestCase): From 60929805d8af8f22d822a10675c8555a83fd5e0e Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 20 Apr 2017 22:34:02 -0600 Subject: [PATCH 020/101] Attempted formatting fixes in test_libdcd.py. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 149363bc437..0c55ed1cd97 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -1,4 +1,5 @@ from __future__ import print_function +from __future__ import absolute_import from nose.tools import raises from numpy.testing import assert_equal, assert_array_equal @@ -280,12 +281,12 @@ def test_coord_match(self): # written DCD file relative to original test = DCDFile(self.testfile) ref = DCDFile(self.readfile) - curr_frame = 0 - while curr_frame < test.n_frames: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - curr_frame += 1 - assert_equal(written_coords, ref_coords) + curr_frame = 0 + while curr_frame < test.n_frames: + written_coords = test.read()[0] + ref_coords = ref.read()[0] + curr_frame += 1 + assert_equal(written_coords, ref_coords) class DCDByteArithmeticTest(TestCase): From 0d43e4f0ada0fd10fcd24d73c11e7d70fffef76c Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Fri, 21 Apr 2017 16:01:39 -0600 Subject: [PATCH 021/101] added DCD regression tests against data from old DCDReader Added unit test test_read_coord_values, and associated testing data, for DCDFile reading fidelity. test_libdcd.py adjusted to allow pickle to open new test data under python 2 and 3 execution. The testsuite included data now covers Python pickle files as some were added for the new DCDFile framework testing. DCDFile unit testing now uses .npy for archival coord storage instead of pickle, for long-term format stability reasons. --- .../data/legacy_DCD_NAMD_coords.npy | Bin 0 -> 66620 bytes .../data/legacy_DCD_adk_coords.npy | Bin 0 -> 80264 bytes testsuite/MDAnalysisTests/datafiles.py | 6 +++++ .../MDAnalysisTests/formats/test_libdcd.py | 24 +++++++++++++++++- testsuite/setup.py | 2 +- 5 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/legacy_DCD_NAMD_coords.npy create mode 100644 testsuite/MDAnalysisTests/data/legacy_DCD_adk_coords.npy diff --git a/testsuite/MDAnalysisTests/data/legacy_DCD_NAMD_coords.npy b/testsuite/MDAnalysisTests/data/legacy_DCD_NAMD_coords.npy new file mode 100644 index 0000000000000000000000000000000000000000..4bcb7c0f70ce2dd3f0012448b145bad84520c9d4 GIT binary patch literal 66620 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¬X@>2imE}B$8#D>d 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 literal 0 HcmV?d00001 diff --git a/testsuite/MDAnalysisTests/data/legacy_DCD_adk_coords.npy b/testsuite/MDAnalysisTests/data/legacy_DCD_adk_coords.npy new file mode 100644 index 0000000000000000000000000000000000000000..6d1a498c158b8cbe264f41b21d35c2c97bfe8e01 GIT binary patch literal 80264 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 Date: Tue, 9 May 2017 15:06:10 -0600 Subject: [PATCH 022/101] DCD unit cell data handling and tests - The use of read_dcdsubset in DCDFile Cython code has been replaced by the expected read_dcdstep. Unit cell handling behavior in the latter C-level function has been patched to reflect previous adjustment in MDA relating to DCD file unit cell formatting. All unit tests passing, including the reading of the NAMD DCD unit cell info - Testing of unit cell data writing has been restricted--NAMD unit cell data writing is not currently supported. - Adjustments to DCD unit cell handling code that enable a read-write-read unit cell cycle for the DCD test file, and also allow a read-write-read cycle for randomly generated charmm unit cell data. - Added Charmm36 triclinic trajectory data unit tests -- all of them pass. - Removed extraneous unit cell handling code from C-level read_dcdsubset. --- .../MDAnalysis/lib/formats/include/readdcd.h | 54 ++--- package/MDAnalysis/lib/formats/libdcd.pyx | 36 +++- .../data/legacy_DCD_c36_coords.npy | Bin 0 -> 9080 bytes testsuite/MDAnalysisTests/datafiles.py | 5 + .../MDAnalysisTests/formats/test_libdcd.py | 194 +++++++++++++++--- 5 files changed, 231 insertions(+), 58 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/legacy_DCD_c36_coords.npy diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h index 3888c51f81a..f4f7b99e5da 100644 --- a/package/MDAnalysis/lib/formats/include/readdcd.h +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -136,7 +136,7 @@ static int write_dcdheader(fio_fd fd, const char *remarks, int natoms, * Output: 0 on success, negative error code on failure. * Side effects: coordinates are written to the dcd file. */ -static int write_dcdstep(fio_fd fd, int curstep, int curframe, +static int write_dcdstep(fio_fd fd, int curframe, int curstep, int natoms, const float *x, const float *y, const float *z, const double *unitcell, int charmm); @@ -431,9 +431,6 @@ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, fl //int ret_val; /* Return value from read */ fio_size_t seekpos; int input_integer; - float alpha, beta, gamma; - unitcell[0] = unitcell[2] = unitcell[5] = 0.0f; - unitcell[1] = unitcell[3] = unitcell[4] = 90.0f; if ((num_fixed==0) || first) { int rc, range; @@ -499,28 +496,6 @@ static int read_dcdsubset(fio_fd fd, int N, int lowerb, int upperb, float *X, fl } else { return DCD_BADFORMAT; } - if (unitcell[1] >= -1.0 && unitcell[1] <= 1.0 && - unitcell[3] >= -1.0 && unitcell[3] <= 1.0 && - unitcell[4] >= -1.0 && unitcell[4] <= 1.0) { - /* This file was generated by Charmm, or by NAMD > 2.5, with the angle */ - /* cosines of the periodic cell angles written to the DCD file. */ - /* This formulation improves rounding behavior for orthogonal cells */ - /* so that the angles end up at precisely 90 degrees, unlike acos(). */ - /* (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; */ - /* see Issue 187) */ - alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; - beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; - gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; - } else { - /* This file was likely generated by NAMD 2.5 and the periodic cell */ - /* angles are specified in degrees rather than angle cosines. */ - alpha = unitcell[4]; - beta = unitcell[3]; - gamma = unitcell[1]; - } - unitcell[4] = alpha; - unitcell[3] = beta; - unitcell[1] = gamma; return DCD_SUCCESS; } @@ -530,6 +505,9 @@ static int read_dcdstep(fio_fd fd, int N, float *X, float *Y, float *Z, int first, int *indexes, float *fixedcoords, int reverseEndian, int charmm) { int ret_val; /* Return value from read */ + float alpha, beta, gamma; + unitcell[0] = unitcell[2] = unitcell[5] = 0.0f; + unitcell[1] = unitcell[3] = unitcell[4] = 90.0f; if ((num_fixed==0) || first) { int tmpbuf[6]; /* temp storage for reading formatting info */ @@ -619,6 +597,30 @@ static int read_dcdstep(fio_fd fd, int N, float *X, float *Y, float *Z, if (ret_val) return ret_val; } + + if (unitcell[1] >= -1.0 && unitcell[1] <= 1.0 && + unitcell[3] >= -1.0 && unitcell[3] <= 1.0 && + unitcell[4] >= -1.0 && unitcell[4] <= 1.0) { + /* This file was generated by Charmm, or by NAMD > 2.5, with the angle */ + /* cosines of the periodic cell angles written to the DCD file. */ + /* This formulation improves rounding behavior for orthogonal cells */ + /* so that the angles end up at precisely 90 degrees, unlike acos(). */ + /* (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; */ + /* see Issue 187) */ + alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; + beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; + gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; + } else { + /* This file was likely generated by NAMD 2.5 and the periodic cell */ + /* angles are specified in degrees rather than angle cosines. */ + alpha = unitcell[4]; + beta = unitcell[3]; + gamma = unitcell[1]; + } + unitcell[4] = alpha; + unitcell[3] = beta; + unitcell[1] = gamma; + return DCD_SUCCESS; } diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index a74b126beea..5405f3df6b9 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -92,7 +92,7 @@ cdef extern from 'include/readdcd.h': int write_dcdheader(fio_fd fd, const char *remarks, int natoms, int istart, int nsavc, double delta, int with_unitcell, int charmm); - int write_dcdstep(fio_fd fd, int curstep, int curframe, + int write_dcdstep(fio_fd fd, int curframe, int curstep, int natoms, const float *x, const float *y, const float *z, const double *unitcell, int charmm); @@ -274,10 +274,7 @@ cdef class DCDFile: first_frame = self.current_frame == 0 - cdef int lowerb = 0 - cdef int upperb = self.n_atoms - 1 - - ok = read_dcdsubset(self.fp, self.n_atoms, lowerb, upperb, + ok = read_dcdstep(self.fp, self.n_atoms, &x[0], &y[0], &z[0], unitcell.data, self.nfixed, first_frame, @@ -339,7 +336,8 @@ cdef class DCDFile: self.current_frame = frame def _write_header(self, remarks, int n_atoms, int starting_step, - int ts_between_saves, double time_step): + int ts_between_saves, double time_step, + int charmm): if not self.is_open: raise IOError("No file open") @@ -351,6 +349,7 @@ cdef class DCDFile: cdef int len_remarks = 0 cdef int with_unitcell = 1 + if isinstance(remarks, six.string_types): try: remarks = bytearray(remarks, 'ascii') @@ -359,7 +358,7 @@ cdef class DCDFile: ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, ts_between_saves, time_step, with_unitcell, - self.charmm) + charmm) if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) @@ -394,14 +393,33 @@ cdef class DCDFile: cdef FLOAT_T[::1] x = xyz[:, 0] cdef FLOAT_T[::1] y = xyz[:, 1] cdef FLOAT_T[::1] z = xyz[:, 2] + cdef float alpha, beta, gamma, a, b, c; if self.current_frame == 0: self._write_header(remarks=remarks, n_atoms=xyz.shape[0], starting_step=step, ts_between_saves=ts_between_saves, - time_step=time_step) + time_step=time_step, + charmm=charmm) self.n_atoms = xyz.shape[0] - ok = write_dcdstep(self.fp, step, self.current_frame, + # looks like self.nsavc is just 0 all the time + step = self.current_frame * self.nsavc + + # we only support writing charmm format unit cell info + alpha = box[3] + beta = box[4] + gamma = box[5] + a = box[0] + b = box[1] + c = box[2] + box[0] = a + box[1] = gamma + box[2] = b + box[3] = beta + box[4] = alpha + box[5] = c + + ok = write_dcdstep(self.fp, self.current_frame, step, self.n_atoms, &x[0], &y[0], &z[0], &box[0], charmm) diff --git a/testsuite/MDAnalysisTests/data/legacy_DCD_c36_coords.npy b/testsuite/MDAnalysisTests/data/legacy_DCD_c36_coords.npy new file mode 100644 index 0000000000000000000000000000000000000000..065a51cfeaae974f910b693dacb889cd8f22550c GIT binary patch literal 9080 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 1: - for frame in f: - unitcell = f.read()[1] - assert_equal(unitcell, expected) - else: - unitcell = f.read()[1] - assert_equal(unitcell, expected) + ref = DCDFile(self.readfile) + test = DCDFile(self.testfile) + curr_frame = 0 + while curr_frame < test.n_frames: + written_unitcell = test.read()[1] + ref_unitcell = ref.read()[1] + curr_frame += 1 + assert_equal(written_unitcell, ref_unitcell) def test_written_num_frames(self): with DCDFile(self.testfile) as f: @@ -322,9 +328,7 @@ def test_relative_frame_sizes(self): general_frame_size = self.dcdfile._framesize for frame in test: - print('frame iteration') written_coords = test.read()[0] - print('after test read') ref_coords = ref.read()[0] assert_equal(written_coords, ref_coords) @@ -366,6 +370,13 @@ class DCDByteArithmeticTestNAMD(DCDByteArithmeticTest, TestCase): def setUp(self): self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC, 'r') self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) + +class DCDByteArithmeticTestCharmm36(DCDByteArithmeticTest, TestCase): + # repeat byte arithmetic tests for Charmm36 format DCD + + def setUp(self): + self.dcdfile = DCDFile(DCD_TRICLINIC, 'r') + self._filesize = os.path.getsize(DCD_TRICLINIC) class DCDWriteTestNAMD(DCDWriteTest, TestCase): @@ -394,6 +405,44 @@ def setUp(self): ts_between_saves=f_in.nsavc, remarks=f_in.remarks) + def test_written_unit_cell(self): + # there's no expectation that we can write unit cell + # data in NAMD format at the moment + pass + +class DCDWriteTestCharmm36(DCDWriteTest, TestCase): + # repeat writing tests for Charmm36 format DCD + # no expectation that we can write unit cell info though (yet) + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.readfile = DCD_TRICLINIC + self.dcdfile_r = DCDFile(self.readfile, 'r') + self.natoms = 375 + self.expected_frames = 10 + self.seek_frame = 7 + self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' + + with self.dcdfile_r as f_in, self.dcdfile as f_out: + for frame in f_in: + frame = frame._asdict() + f_out.write(xyz=frame['x'], + box=frame['unitcell'].astype(np.float64), + step=f_in.istart, + natoms=frame['x'].shape[0], + charmm=0, + time_step=f_in.delta, + ts_between_saves=f_in.nsavc, + remarks=f_in.remarks) + + def test_written_unit_cell(self): + # there's no expectation that we can write unit cell + # data in NAMD format at the moment + pass + + class DCDWriteHeaderTestNAMD(DCDWriteHeaderTest, TestCase): # repeat header writing tests for NAMD format DCD @@ -403,6 +452,15 @@ def setUp(self): self.dcdfile = DCDFile(self.testfile, 'w') self.dcdfile_r = DCDFile(DCD_NAMD_TRICLINIC, 'r') +class DCDWriteHeaderTestCharmm36(DCDWriteHeaderTest, TestCase): + # repeat header writing tests for Charmm36 format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(DCD_TRICLINIC, 'r') + class DCDReadFrameTestNAMD(DCDReadFrameTest, TestCase): # repeat frame reading tests for NAMD format DCD @@ -416,3 +474,93 @@ def setUp(self): self.selected_legacy_frames = [0] self.legacy_data = legacy_DCD_NAMD_coords self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' + # expected unit cell based on previous DCD framework read in: + self.expected_unit_cell = np.array([ 38.42659378, 38.39310074, 44.75979996, + 90. , 90. , 60.02891541], + dtype=np.float32) + + def tearDown(self): + del self.dcdfile + +class DCDReadFrameTestCharmm36(DCDReadFrameTest, TestCase): + # repeat frame reading tests for Charmm36 format DCD + + def setUp(self): + self.dcdfile = DCDFile(DCD_TRICLINIC) + self.natoms = 375 + self.traj_length = 10 + self.new_frame = 2 + self.context_frame = 5 + self.num_iters = 7 + self.selected_legacy_frames = [1, 4] + self.legacy_data = legacy_DCD_c36_coords + self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' + # expected unit cell based on previous DCD framework read in: + self.expected_unit_cell = np.array([ 35.44603729, 35.06156158, 34.15850067, + 91.32801819, 61.73519516, 44.4070282], + dtype=np.float32) + + def tearDown(self): + del self.dcdfile + +class DCDWriteTestRandom(TestCase): + # should only be supported for Charmm24 format writing (for now) + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = self.tmpdir.name + '/test.dcd' + self.readfile = DCD + self.dcdfile = DCDFile(self.testfile, 'w') + self.dcdfile_r = DCDFile(self.readfile, 'r') + self.natoms = 3341 + self.expected_frames = 98 + self.seek_frame = 91 + self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' + + # we should probably pin down the random seed in a numpy + # array rather than having tests that are actually random + # between runs + self.list_random_unit_cell_dims = [] + with self.dcdfile_r as f_in, self.dcdfile as f_out: + for frame in f_in: + random_unitcell = np.random.random(6).astype(np.float64) + self.list_random_unit_cell_dims.append(random_unitcell) + frame_dict = frame._asdict() + box=frame_dict['unitcell'].astype(np.float64) + f_out.write(xyz=frame_dict['x'], + box=random_unitcell, + step=f_in.istart, + natoms=frame_dict['x'].shape[0], + charmm=1, # DCD should be CHARMM + time_step=f_in.delta, + ts_between_saves=f_in.nsavc, + remarks=f_in.remarks) + + def tearDown(self): + try: + os.unlink(self.testfile) + except OSError: + pass + del self.tmpdir + + def test_written_unit_cell_random(self): + # written unit cell dimensions should match for all frames + # using randomly generated unit cells but some processing + # of the cosine data stored in charmm format is needed + # as well as shuffling of the orders in the unitcell + # array based on the prcoessing performed by + # DCDFile read and more generally relating to Issue 187 + test = DCDFile(self.testfile) + curr_frame = 0 + while curr_frame < test.n_frames: + written_unitcell = test.read()[1] + ref_unitcell = self.list_random_unit_cell_dims[curr_frame] + ref_unitcell[1] = math.degrees(math.acos(ref_unitcell[1])) + ref_unitcell[3] = math.degrees(math.acos(ref_unitcell[3])) + ref_unitcell[4] = math.degrees(math.acos(ref_unitcell[4])) + + _ts_order = [0, 2, 5, 4, 3, 1] + ref_unitcell = np.take(ref_unitcell, _ts_order) + curr_frame += 1 + assert_allclose(written_unitcell, ref_unitcell, + rtol=1e-05) From 021665d69186e430cc729df9038fe66eae209540 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Wed, 10 May 2017 21:55:06 -0600 Subject: [PATCH 023/101] test_header_remarks has been adjusted to perform a direct string comparison, as requested in PR comments. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 4ec5550d41c..8d5d7ac86a3 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -45,13 +45,6 @@ def tearDown(self): def test_header_remarks(self): # confirm correct header remarks section reading with self.dcdfile as f: - list_chars = [] - for element in f.remarks: - list_chars.append(element) - - list_chars = [] - for element in self.expected_remarks: - list_chars.append(element) assert_equal(len(f.remarks), len(self.expected_remarks)) def test_read_coords(self): From 0d605769a6c871138f17d136ea0f5a36dd2a63f7 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Wed, 10 May 2017 22:02:23 -0600 Subject: [PATCH 024/101] Simplified code in test_iteration() unit test based on PR comments. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 8d5d7ac86a3..34b9265176d 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -93,13 +93,9 @@ def test_seek_negative(self): self.dcdfile.seek(-78) def test_iteration(self): - expected = 0 - while self.num_iters > 0: + for i in range(self.num_iters): self.dcdfile.__next__() - self.num_iters -= 1 - expected += 1 - - assert_equal(self.dcdfile.tell(), expected) + assert_equal(self.dcdfile.tell(), self.num_iters) def test_zero_based_frames(self): expected_frame = 0 From 6d7e8b6b9feec3bfc180d2ad81b872c22a9b13d8 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Wed, 10 May 2017 22:15:45 -0600 Subject: [PATCH 025/101] DCDWriteTestRandom now pins down the random seed for testing robustness. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 34b9265176d..2a25d2e1326 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -506,18 +506,14 @@ def setUp(self): self.seek_frame = 91 self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - # we should probably pin down the random seed in a numpy - # array rather than having tests that are actually random - # between runs - self.list_random_unit_cell_dims = [] + np.random.seed(1178083) + self.random_unitcells = np.random.random((self.expected_frames, 6)).astype(np.float64) with self.dcdfile_r as f_in, self.dcdfile as f_out: - for frame in f_in: - random_unitcell = np.random.random(6).astype(np.float64) - self.list_random_unit_cell_dims.append(random_unitcell) + for index, frame in enumerate(f_in): frame_dict = frame._asdict() box=frame_dict['unitcell'].astype(np.float64) f_out.write(xyz=frame_dict['x'], - box=random_unitcell, + box=self.random_unitcells[index], step=f_in.istart, natoms=frame_dict['x'].shape[0], charmm=1, # DCD should be CHARMM @@ -543,7 +539,7 @@ def test_written_unit_cell_random(self): curr_frame = 0 while curr_frame < test.n_frames: written_unitcell = test.read()[1] - ref_unitcell = self.list_random_unit_cell_dims[curr_frame] + ref_unitcell = self.random_unitcells[curr_frame] ref_unitcell[1] = math.degrees(math.acos(ref_unitcell[1])) ref_unitcell[3] = math.degrees(math.acos(ref_unitcell[3])) ref_unitcell[4] = math.degrees(math.acos(ref_unitcell[4])) From 9091ac932729002fdb34e6d34d173c72fd723e9b Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 22 May 2017 09:20:00 +0200 Subject: [PATCH 026/101] fixes in libdcd fix repeated iteration of dcd file fix repeated context manager of dcd file --- package/MDAnalysis/lib/formats/libdcd.pyx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 5405f3df6b9..40c3042a899 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -134,6 +134,8 @@ cdef class DCDFile: def __enter__(self): """Support context manager""" + if not self.is_open: + self.open(self.fname, self.mode) return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -178,9 +180,10 @@ cdef class DCDFile: "ErrorCode: {}".format(self.fname, ok)) self.is_open = True self.current_frame = 0 + self.reached_eof = False + # Has to come last since it checks the reached_eof flag if self.mode == 'r': self.remarks = self._read_header() - self.reached_eof = False def close(self): if self.is_open: From b26de04de28db57e451341b4b10ffdfdd6bb4a22 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 23 May 2017 14:26:26 +0200 Subject: [PATCH 027/101] Refactor libdcd test - remove TestCase usage - use context manager with short lived files - stop leaking of file dscriptors --- .../MDAnalysisTests/formats/test_libdcd.py | 357 ++++++++---------- 1 file changed, 167 insertions(+), 190 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 2a25d2e1326..1d9984d5bfa 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -2,9 +2,7 @@ from __future__ import absolute_import from nose.tools import raises -from numpy.testing import assert_equal, assert_array_equal -from numpy.testing import assert_array_almost_equal -from numpy.testing import assert_almost_equal +from numpy.testing import assert_equal from numpy.testing import assert_allclose from MDAnalysis.lib.formats.libdcd import DCDFile @@ -16,18 +14,17 @@ PSF_TRICLINIC, DCD_TRICLINIC) -from unittest import TestCase -import MDAnalysis from MDAnalysisTests.tempdir import run_in_tempdir from MDAnalysisTests import tempdir import numpy as np import os import math -class DCDReadFrameTest(TestCase): + +class TestDCDReadFrame(): def setUp(self): - self.dcdfile = DCDFile(DCD) + self.dcdfile = DCD self.natoms = 3341 self.traj_length = 98 self.new_frame = 91 @@ -39,18 +36,16 @@ def setUp(self): self.expected_unit_cell = np.array([ 0., 0., 0., 90., 90., 90.], dtype=np.float32) - def tearDown(self): - del self.dcdfile - def test_header_remarks(self): # confirm correct header remarks section reading - with self.dcdfile as f: + with DCDFile(self.dcdfile) as f: assert_equal(len(f.remarks), len(self.expected_remarks)) def test_read_coords(self): # confirm shape of coordinate data against result from previous # MDAnalysis implementation of DCD file handling - dcd_frame = self.dcdfile.read() + with DCDFile(self.dcdfile) as dcd: + dcd_frame = dcd.read() xyz = dcd_frame[0] assert_equal(xyz.shape, (self.natoms, 3)) @@ -60,55 +55,58 @@ def test_read_coord_values(self): # to reduce repo storage burden, we only compare for a few # randomly selected frames - self.legacy_DCD_frame_data = np.load(self.legacy_data) + legacy_DCD_frame_data = np.load(self.legacy_data) - for index, frame_num in enumerate(self.selected_legacy_frames): - self.dcdfile.seek(frame_num) - actual_coords = self.dcdfile.read()[0] - desired_coords = self.legacy_DCD_frame_data[index] - assert_equal(actual_coords, - desired_coords) + with DCDFile(self.dcdfile) as dcd: + for index, frame_num in enumerate(self.selected_legacy_frames): + dcd.seek(frame_num) + actual_coords = dcd.read()[0] + desired_coords = legacy_DCD_frame_data[index] + assert_equal(actual_coords, + desired_coords) def test_read_unit_cell(self): # confirm unit cell read against result from previous # MDAnalysis implementation of DCD file handling - dcd_frame = self.dcdfile.read() + with DCDFile(self.dcdfile) as dcd: + dcd_frame = dcd.read() unitcell = dcd_frame[1] assert_allclose(unitcell, self.expected_unit_cell, rtol=1e-05) + @raises(IOError) def test_seek_over_max(self): # should raise IOError if beyond 98th frame - with self.assertRaises(IOError): - self.dcdfile.seek(102) + with DCDFile(DCD) as dcd: + dcd.seek(102) def test_seek_normal(self): # frame seek within range is tested - self.dcdfile.seek(self.new_frame) - assert_equal(self.dcdfile.tell(), self.new_frame) + with DCDFile(self.dcdfile) as dcd: + dcd.seek(self.new_frame) + assert_equal(dcd.tell(), self.new_frame) + @raises(IOError) def test_seek_negative(self): # frame seek with negative number - with self.assertRaises(IOError): - self.dcdfile.seek(-78) + with DCDFile(self.dcdfile) as dcd: + dcd.seek(-78) def test_iteration(self): - for i in range(self.num_iters): - self.dcdfile.__next__() - assert_equal(self.dcdfile.tell(), self.num_iters) + with DCDFile(self.dcdfile) as dcd: + for i in range(self.num_iters): + dcd.__next__() + assert_equal(dcd.tell(), self.num_iters) def test_zero_based_frames(self): expected_frame = 0 - assert_equal(self.dcdfile.tell(), expected_frame) + with DCDFile(self.dcdfile) as dcd: + assert_equal(dcd.tell(), expected_frame) def test_length_traj(self): expected = self.traj_length - assert_equal(len(self.dcdfile), expected) - - def test_context_manager(self): - with self.dcdfile as f: - f.seek(self.context_frame) - assert_equal(f.tell(), self.context_frame) + with DCDFile(self.dcdfile) as dcd: + assert_equal(len(dcd), expected) @raises(IOError) def test_open_wrong_mode(self): @@ -119,7 +117,8 @@ def test_raise_not_existing(self): DCDFile('foo') def test_n_atoms(self): - assert_equal(self.dcdfile.n_atoms, self.natoms) + with DCDFile(self.dcdfile) as dcd: + assert_equal(dcd.n_atoms, self.natoms) @raises(IOError) @run_in_tempdir() @@ -129,37 +128,42 @@ def test_read_write_mode_file(self): @raises(IOError) def test_read_closed(self): - self.dcdfile.close() - self.dcdfile.read() + with DCDFile(self.dcdfile) as dcd: + dcd.close() + dcd.read() def test_iteration_2(self): - with self.dcdfile as f: - for frame in f: - pass + with DCDFile(self.dcdfile) as dcd: + with dcd as f: + for frame in f: + pass + # second iteration should work from start again + for frame in f: + pass + -class DCDWriteHeaderTest(TestCase): +class TestDCDWriteHeader(): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' - self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(DCD, 'r') + self.dcdfile = DCD def tearDown(self): - try: + try: os.unlink(self.testfile) except OSError: pass del self.tmpdir - + def test_write_header_crude(self): # test that _write_header() can produce a very crude # header for a new / empty file - self.dcdfile._write_header(remarks='Crazy!', n_atoms=22, - starting_step=12, ts_between_saves=10, - time_step=0.02, - charmm=1) - self.dcdfile.close() + with DCDFile(self.testfile, 'w') as dcd: + dcd._write_header(remarks='Crazy!', n_atoms=22, + starting_step=12, ts_between_saves=10, + time_step=0.02, + charmm=1) # we're not actually asserting anything, yet # run with: nosetests test_libdcd.py --nocapture @@ -168,62 +172,63 @@ def test_write_header_crude(self): for element in f: print(element) + @raises(IOError) def test_write_header_mode_sensitivy(self): # an exception should be raised on any attempt to use # _write_header with a DCDFile object in 'r' mode - with self.assertRaises(IOError): - self.dcdfile_r._write_header(remarks='Crazy!', n_atoms=22, - starting_step=12, ts_between_saves=10, - time_step=0.02, - charmm=1) + with DCDFile(self.dcdfile) as dcd: + dcd._write_header(remarks='Crazy!', n_atoms=22, + starting_step=12, ts_between_saves=10, + time_step=0.02, + charmm=1) -class DCDWriteTest(TestCase): +class TestDCDWrite(): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' self.readfile = DCD - self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 3341 self.expected_frames = 98 self.seek_frame = 91 self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' + self._write_files() - with self.dcdfile_r as f_in, self.dcdfile as f_out: + def _write_files(self): + with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: for frame in f_in: - frame_dict = frame._asdict() - box=frame_dict['unitcell'].astype(np.float64) - f_out.write(xyz=frame_dict['x'], + box=frame.unitcell.astype(np.float64) + f_out.write(xyz=frame.x, box=box, step=f_in.istart, - natoms=frame_dict['x'].shape[0], + natoms=frame.x.shape[0], charmm=1, # DCD should be CHARMM time_step=f_in.delta, ts_between_saves=f_in.nsavc, remarks=f_in.remarks) def tearDown(self): - try: + try: os.unlink(self.testfile) except OSError: pass del self.tmpdir + @raises(IOError) def test_write_mode(self): # ensure that writing of DCD files only occurs with properly # opened files - with self.assertRaises(IOError): - self.dcdfile_r.write(xyz=np.zeros((3,3)), - box=np.zeros(6, dtype=np.float64), - step=0, - natoms=330, - charmm=0, - time_step=22.2, - ts_between_saves=3, - remarks='') + with DCDFile(self.readfile) as dcd: + dcd.write(xyz=np.zeros((3,3)), + box=np.zeros(6, dtype=np.float64), + step=0, + natoms=330, + charmm=0, + time_step=22.2, + ts_between_saves=3, + remarks='') def test_written_dcd_coordinate_data_shape(self): # written coord shape should match for all frames @@ -239,14 +244,13 @@ def test_written_dcd_coordinate_data_shape(self): def test_written_unit_cell(self): # written unit cell dimensions should match for all frames - ref = DCDFile(self.readfile) - test = DCDFile(self.testfile) - curr_frame = 0 - while curr_frame < test.n_frames: - written_unitcell = test.read()[1] - ref_unitcell = ref.read()[1] - curr_frame += 1 - assert_equal(written_unitcell, ref_unitcell) + with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: + curr_frame = 0 + while curr_frame < test.n_frames: + written_unitcell = test.read()[1] + ref_unitcell = ref.read()[1] + curr_frame += 1 + assert_equal(written_unitcell, ref_unitcell) def test_written_num_frames(self): with DCDFile(self.testfile) as f: @@ -273,158 +277,137 @@ def test_written_remarks(self): def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written # to file, is preserved in the written DCD file - expected = self.dcdfile_r.nsavc - actual = DCDFile(self.testfile).nsavc - assert_equal(actual, expected) + with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: + assert_equal(dcd.nsavc, dcd_r.nsavc) def test_written_istart(self): # ensure that istart, the starting timestep, is preserved # in the written DCD file - expected = self.dcdfile_r.istart - actual = DCDFile(self.testfile).istart - assert_equal(actual, expected) + with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: + assert_equal(dcd.istart, dcd_r.istart) def test_written_delta(self): # ensure that delta, the trajectory timestep, is preserved in # the written DCD file - expected = self.dcdfile_r.delta - actual = DCDFile(self.testfile).delta - assert_equal(actual, expected) + with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: + assert_equal(dcd.delta, dcd_r.delta) def test_coord_match(self): # ensure that all coordinates match in each frame for the # written DCD file relative to original - test = DCDFile(self.testfile) - ref = DCDFile(self.readfile) - curr_frame = 0 - while curr_frame < test.n_frames: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - curr_frame += 1 - assert_equal(written_coords, ref_coords) + with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: + curr_frame = 0 + while curr_frame < test.n_frames: + written_coords = test.read()[0] + ref_coords = ref.read()[0] + curr_frame += 1 + assert_equal(written_coords, ref_coords) -class DCDByteArithmeticTest(TestCase): + +class TestDCDByteArithmetic(): def setUp(self): - self.dcdfile = DCDFile(DCD, 'r') + + self.dcdfile = DCD self._filesize = os.path.getsize(DCD) def test_relative_frame_sizes(self): # the first frame of a DCD file should always be >= in size # to subsequent frames, as the first frame contains the same # atoms + (optional) fixed atoms - first_frame_size = self.dcdfile._firstframesize - general_frame_size = self.dcdfile._framesize + with DCDFile(self.dcdfile) as dcd: + first_frame_size = dcd._firstframesize + general_frame_size = dcd._framesize - for frame in test: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - assert_equal(written_coords, ref_coords) + # for frame in test: + # written_coords = test.read()[0] + # ref_coords = ref.read()[0] + # assert_equal(written_coords, ref_coords) -class DCDByteArithmeticTest(TestCase): + +class TestDCDByteArithmetic(): def setUp(self): - self.dcdfile = DCDFile(DCD, 'r') + self.dcdfile = DCD self._filesize = os.path.getsize(DCD) def test_relative_frame_sizes(self): # the first frame of a DCD file should always be >= in size # to subsequent frames, as the first frame contains the same # atoms + (optional) fixed atoms - first_frame_size = self.dcdfile._firstframesize - general_frame_size = self.dcdfile._framesize - self.assertGreaterEqual(first_frame_size, general_frame_size) + with DCDFile(self.dcdfile) as dcd: + first_frame_size = dcd._firstframesize + general_frame_size = dcd._framesize + + assert_equal(first_frame_size >= general_frame_size, True) def test_file_size_breakdown(self): # the size of a DCD file is equivalent to the sum of the header # size, first frame size, and (N - 1 frames) * size per general # frame expected = self._filesize - actual = self.dcdfile._header_size + self.dcdfile._firstframesize + \ - ((self.dcdfile.n_frames - 1) * self.dcdfile._framesize) + with DCDFile(self.dcdfile) as dcd: + actual = dcd._header_size + dcd._firstframesize + ((dcd.n_frames - 1) * dcd._framesize) assert_equal(actual, expected) def test_nframessize_int(self): - # require that the (nframessize / framesize) value used by DCDFile + # require that the (nframessize / framesize) value used by DCDFile # is an integer (because nframessize / framesize + 1 = total frames, # which must also be an int) - nframessize = self._filesize - self.dcdfile._header_size - \ - self.dcdfile._firstframesize - self.assertTrue(float(nframessize) % float(self.dcdfile._framesize) == 0) + with DCDFile(self.dcdfile) as dcd: + nframessize = self._filesize - dcd._header_size - dcd._firstframesize + assert_equal(float(nframessize) % float(dcd._framesize), 0) -class DCDByteArithmeticTestNAMD(DCDByteArithmeticTest, TestCase): + +class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): # repeat byte arithmetic tests for NAMD format DCD def setUp(self): - self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC, 'r') + self.dcdfile = DCD_NAMD_TRICLINIC self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) -class DCDByteArithmeticTestCharmm36(DCDByteArithmeticTest, TestCase): + +class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): # repeat byte arithmetic tests for Charmm36 format DCD def setUp(self): - self.dcdfile = DCDFile(DCD_TRICLINIC, 'r') + self.dcdfile = DCD_TRICLINIC self._filesize = os.path.getsize(DCD_TRICLINIC) - -class DCDWriteTestNAMD(DCDWriteTest, TestCase): + +class TestDCDWriteNAMD(TestDCDWrite): # repeat writing tests for NAMD format DCD def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' - self.dcdfile = DCDFile(self.testfile, 'w') self.readfile = DCD_NAMD_TRICLINIC - self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 5545 self.expected_frames = 1 self.seek_frame = 0 self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' - - with self.dcdfile_r as f_in, self.dcdfile as f_out: - for frame in f_in: - frame = frame._asdict() - f_out.write(xyz=frame['x'], - box=frame['unitcell'].astype(np.float64), - step=f_in.istart, - natoms=frame['x'].shape[0], - charmm=0, - time_step=f_in.delta, - ts_between_saves=f_in.nsavc, - remarks=f_in.remarks) + self._write_files() def test_written_unit_cell(self): # there's no expectation that we can write unit cell # data in NAMD format at the moment pass -class DCDWriteTestCharmm36(DCDWriteTest, TestCase): + +class TestDCDWriteCharmm36(TestDCDWrite): # repeat writing tests for Charmm36 format DCD # no expectation that we can write unit cell info though (yet) def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' - self.dcdfile = DCDFile(self.testfile, 'w') self.readfile = DCD_TRICLINIC - self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 375 self.expected_frames = 10 self.seek_frame = 7 self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' - - with self.dcdfile_r as f_in, self.dcdfile as f_out: - for frame in f_in: - frame = frame._asdict() - f_out.write(xyz=frame['x'], - box=frame['unitcell'].astype(np.float64), - step=f_in.istart, - natoms=frame['x'].shape[0], - charmm=0, - time_step=f_in.delta, - ts_between_saves=f_in.nsavc, - remarks=f_in.remarks) + self._write_files() def test_written_unit_cell(self): # there's no expectation that we can write unit cell @@ -432,29 +415,29 @@ def test_written_unit_cell(self): pass -class DCDWriteHeaderTestNAMD(DCDWriteHeaderTest, TestCase): +class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): # repeat header writing tests for NAMD format DCD def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' - self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(DCD_NAMD_TRICLINIC, 'r') + self.dcdfile = DCD_NAMD_TRICLINIC -class DCDWriteHeaderTestCharmm36(DCDWriteHeaderTest, TestCase): + +class TestDCDWriteHeaderCharmm36(TestDCDWriteHeader): # repeat header writing tests for Charmm36 format DCD def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' - self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(DCD_TRICLINIC, 'r') + self.dcdfile = DCD_TRICLINIC + -class DCDReadFrameTestNAMD(DCDReadFrameTest, TestCase): +class TestDCDReadFrameTestNAMD(TestDCDReadFrame): # repeat frame reading tests for NAMD format DCD def setUp(self): - self.dcdfile = DCDFile(DCD_NAMD_TRICLINIC) + self.dcdfile = DCD_NAMD_TRICLINIC self.natoms = 5545 self.traj_length = 1 self.new_frame = 0 @@ -465,17 +448,15 @@ def setUp(self): self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' # expected unit cell based on previous DCD framework read in: self.expected_unit_cell = np.array([ 38.42659378, 38.39310074, 44.75979996, - 90. , 90. , 60.02891541], - dtype=np.float32) + 90. , 90. , 60.02891541], + dtype=np.float32) - def tearDown(self): - del self.dcdfile -class DCDReadFrameTestCharmm36(DCDReadFrameTest, TestCase): +class TestDCDReadFrameTestCharmm36(TestDCDReadFrame): # repeat frame reading tests for Charmm36 format DCD def setUp(self): - self.dcdfile = DCDFile(DCD_TRICLINIC) + self.dcdfile = DCD_TRICLINIC self.natoms = 375 self.traj_length = 10 self.new_frame = 2 @@ -486,21 +467,17 @@ def setUp(self): self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' # expected unit cell based on previous DCD framework read in: self.expected_unit_cell = np.array([ 35.44603729, 35.06156158, 34.15850067, - 91.32801819, 61.73519516, 44.4070282], - dtype=np.float32) + 91.32801819, 61.73519516, 44.4070282], + dtype=np.float32) - def tearDown(self): - del self.dcdfile -class DCDWriteTestRandom(TestCase): +class TestDCDWriteRandom(): # should only be supported for Charmm24 format writing (for now) def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' self.readfile = DCD - self.dcdfile = DCDFile(self.testfile, 'w') - self.dcdfile_r = DCDFile(self.readfile, 'r') self.natoms = 3341 self.expected_frames = 98 self.seek_frame = 91 @@ -508,7 +485,7 @@ def setUp(self): np.random.seed(1178083) self.random_unitcells = np.random.random((self.expected_frames, 6)).astype(np.float64) - with self.dcdfile_r as f_in, self.dcdfile as f_out: + with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: for index, frame in enumerate(f_in): frame_dict = frame._asdict() box=frame_dict['unitcell'].astype(np.float64) @@ -535,17 +512,17 @@ def test_written_unit_cell_random(self): # as well as shuffling of the orders in the unitcell # array based on the prcoessing performed by # DCDFile read and more generally relating to Issue 187 - test = DCDFile(self.testfile) - curr_frame = 0 - while curr_frame < test.n_frames: - written_unitcell = test.read()[1] - ref_unitcell = self.random_unitcells[curr_frame] - ref_unitcell[1] = math.degrees(math.acos(ref_unitcell[1])) - ref_unitcell[3] = math.degrees(math.acos(ref_unitcell[3])) - ref_unitcell[4] = math.degrees(math.acos(ref_unitcell[4])) - - _ts_order = [0, 2, 5, 4, 3, 1] - ref_unitcell = np.take(ref_unitcell, _ts_order) - curr_frame += 1 - assert_allclose(written_unitcell, ref_unitcell, - rtol=1e-05) + with DCDFile(self.testfile) as test: + curr_frame = 0 + while curr_frame < test.n_frames: + written_unitcell = test.read()[1] + ref_unitcell = self.random_unitcells[curr_frame] + ref_unitcell[1] = math.degrees(math.acos(ref_unitcell[1])) + ref_unitcell[3] = math.degrees(math.acos(ref_unitcell[3])) + ref_unitcell[4] = math.degrees(math.acos(ref_unitcell[4])) + + _ts_order = [0, 2, 5, 4, 3, 1] + ref_unitcell = np.take(ref_unitcell, _ts_order) + curr_frame += 1 + assert_allclose(written_unitcell, ref_unitcell, + rtol=1e-05) From c8d50d224cfb4b486722c14c33f4c0cccdd8d038 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Fri, 26 May 2017 11:36:34 -0600 Subject: [PATCH 028/101] Added new property-based unit test, test_written_remarks_property(), which reveals that DCDFile is not able to handle writing when an empty REMARKS string is provided. --- .../MDAnalysisTests/formats/test_libdcd.py | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 1d9984d5bfa..6ed1b005303 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -19,6 +19,8 @@ import numpy as np import os import math +from hypothesis import given +import hypothesis.strategies as st class TestDCDReadFrame(): @@ -189,16 +191,23 @@ class TestDCDWrite(): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile2 = self.tmpdir.name + '/test2.dcd' self.readfile = DCD self.natoms = 3341 self.expected_frames = 98 self.seek_frame = 91 self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self._write_files() + self._write_files(testfile=self.testfile, + remarks_setting='input') + + def _write_files(self, testfile, remarks_setting): - def _write_files(self): with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: for frame in f_in: + if remarks_setting == 'input': + remarks = f_in.remarks + else: # accept the random remarks strings from hypothesis + remarks = remarks_setting box=frame.unitcell.astype(np.float64) f_out.write(xyz=frame.x, box=box, @@ -207,7 +216,7 @@ def _write_files(self): charmm=1, # DCD should be CHARMM time_step=f_in.delta, ts_between_saves=f_in.nsavc, - remarks=f_in.remarks) + remarks=remarks) def tearDown(self): try: @@ -274,6 +283,16 @@ def test_written_remarks(self): with DCDFile(self.testfile) as f: assert_equal(f.remarks, self.expected_remarks) + @given(st.text()) # handle the full unicode range of strings + def test_written_remarks_property(self, remarks_str): + # property based testing for writing of a wide range of string + # values to REMARKS field + self._write_files(testfile=self.testfile2, + remarks_setting=remarks_str) + expected_remarks = remarks_str + with DCDFile(self.testfile2) as f: + assert_equal(f.remarks, expected_remarks) + def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written # to file, is preserved in the written DCD file @@ -382,12 +401,14 @@ class TestDCDWriteNAMD(TestDCDWrite): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile2 = self.tmpdir.name + '/test2.dcd' self.readfile = DCD_NAMD_TRICLINIC self.natoms = 5545 self.expected_frames = 1 self.seek_frame = 0 self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' - self._write_files() + self._write_files(testfile=self.testfile, + remarks_setting='input') def test_written_unit_cell(self): # there's no expectation that we can write unit cell @@ -402,12 +423,14 @@ class TestDCDWriteCharmm36(TestDCDWrite): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile2 = self.tmpdir.name + '/test2.dcd' self.readfile = DCD_TRICLINIC self.natoms = 375 self.expected_frames = 10 self.seek_frame = 7 self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' - self._write_files() + self._write_files(testfile=self.testfile, + remarks_setting='input') def test_written_unit_cell(self): # there's no expectation that we can write unit cell From d14ffb5e632872e4fac5a13a5e7b608e001ffd27 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 12:11:23 +0200 Subject: [PATCH 029/101] fix write_file function use testfile from function arguments --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 6ed1b005303..94818ec703b 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -202,7 +202,7 @@ def setUp(self): def _write_files(self, testfile, remarks_setting): - with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: + with DCDFile(self.readfile) as f_in, DCDFile(testfile, 'w') as f_out: for frame in f_in: if remarks_setting == 'input': remarks = f_in.remarks From 24656c32233cd31fdf33b964594095c723f5eb33 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:03:38 +0200 Subject: [PATCH 030/101] add write dtype handling --- package/MDAnalysis/lib/formats/libdcd.pyx | 17 ++++-- .../MDAnalysisTests/formats/test_libdcd.py | 56 ++++++++++++++++--- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 40c3042a899..29e53cb9be2 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -365,15 +365,15 @@ cdef class DCDFile: if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) - def write(self, xyz, double [:] box, int step, int natoms, + def write(self, xyz, box, int step, int natoms, int ts_between_saves, int charmm, double time_step, remarks): """write one frame into DCD file. Parameters ---------- - xyz : ndarray, shape=(n_atoms, 3) + xyz : array_like, shape=(n_atoms, 3) cartesion coordinates - box : ndarray, shape=(3, 3) + box : array_like, shape=(6) Box vectors for this frame step : int current step number, 1 indexed @@ -393,6 +393,15 @@ cdef class DCDFile: 'in mode "w"'.format('self.mode')) #cdef double [:,:] unitcell = box + xyz = np.asarray(xyz, order='F', dtype=np.float32) + cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) + + if c_box.size != 6: + raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + + if xyz.shape != (natoms, 3): + raise ValueError("xyz shape is wrong should be (natoms, 3), got:".format(xyz.shape)) + cdef FLOAT_T[::1] x = xyz[:, 0] cdef FLOAT_T[::1] y = xyz[:, 1] cdef FLOAT_T[::1] z = xyz[:, 2] @@ -425,6 +434,6 @@ cdef class DCDFile: ok = write_dcdstep(self.fp, self.current_frame, step, self.n_atoms, &x[0], &y[0], &z[0], - &box[0], charmm) + &c_box[0], charmm) self.current_frame += 1 diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 94818ec703b..70e5beaf220 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -283,15 +283,15 @@ def test_written_remarks(self): with DCDFile(self.testfile) as f: assert_equal(f.remarks, self.expected_remarks) - @given(st.text()) # handle the full unicode range of strings - def test_written_remarks_property(self, remarks_str): - # property based testing for writing of a wide range of string - # values to REMARKS field - self._write_files(testfile=self.testfile2, - remarks_setting=remarks_str) - expected_remarks = remarks_str - with DCDFile(self.testfile2) as f: - assert_equal(f.remarks, expected_remarks) + # @given(st.text()) # handle the full unicode range of strings + # def test_written_remarks_property(self, remarks_str): + # # property based testing for writing of a wide range of string + # # values to REMARKS field + # self._write_files(testfile=self.testfile2, + # remarks_setting=remarks_str) + # expected_remarks = remarks_str + # with DCDFile(self.testfile2) as f: + # assert_equal(f.remarks, expected_remarks) def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written @@ -322,6 +322,44 @@ def test_coord_match(self): curr_frame += 1 assert_equal(written_coords, ref_coords) + def test_write_wrong_dtype(self): + """we should allow passing a range of dtypes""" + for dtype in (np.int32, np.int64, np.float32, np.float64): + with DCDFile(self.testfile, 'w') as out: + natoms = 10 + xyz = np.ones((natoms, 3), dtype=dtype) + box = np.ones(6, dtype=dtype) + out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, + ts_between_saves=1, remarks='test') + + def test_write_array_like(self): + """we should allow passing a range of dtypes""" + for array_like in (np.array, list): + with DCDFile(self.testfile, 'w') as out: + natoms = 10 + xyz = array_like([[1, 1, 1] for i in range(natoms)]) + box = array_like([i for i in range(6)]) + out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, + ts_between_saves=1, remarks='test') + + @raises(ValueError) + def test_write_wrong_shape_xyz(self): + with DCDFile(self.testfile, 'w') as out: + natoms = 10 + xyz = np.ones((natoms+1, 3)) + box = np.ones(6) + out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, + ts_between_saves=1, remarks='test') + + @raises(ValueError) + def test_write_wrong_shape_box(self): + with DCDFile(self.testfile, 'w') as out: + natoms = 10 + xyz = np.ones((natoms, 3)) + box = np.ones(8) + out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, + ts_between_saves=1, remarks='test') + class TestDCDByteArithmetic(): From e4382e3ef440a0f37a5a3c4705b9ad827244f6e7 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:12:38 +0200 Subject: [PATCH 031/101] check tell in iteration --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 70e5beaf220..e4e3e861660 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -96,7 +96,7 @@ def test_seek_negative(self): def test_iteration(self): with DCDFile(self.dcdfile) as dcd: - for i in range(self.num_iters): + for _ in range(self.num_iters): dcd.__next__() assert_equal(dcd.tell(), self.num_iters) @@ -137,11 +137,11 @@ def test_read_closed(self): def test_iteration_2(self): with DCDFile(self.dcdfile) as dcd: with dcd as f: - for frame in f: - pass + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) # second iteration should work from start again - for frame in f: - pass + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) class TestDCDWriteHeader(): From 2596974b0de335d17079d6bd4723c20e4fc9e7a7 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:23:18 +0200 Subject: [PATCH 032/101] use sensible warnings when only a header is written We shouldn't do that but other programs might produce a dcd with a header and no frames stored. --- package/MDAnalysis/lib/formats/libdcd.pyx | 15 +++++++++---- .../MDAnalysisTests/formats/test_libdcd.py | 22 ++++++++++++++++--- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 29e53cb9be2..4a764fb048b 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -224,8 +224,13 @@ cdef class DCDFile: self.b_read_header = True # make sure fixed atoms have been read - self.read() - self.seek(0) + try: + self.read() + self.seek(0) + except: + # if this fails the file is empty. Set flag and warn using during read + if self.n_frames != 0: + raise IOError("DCD is corrupted") if sys.version_info[0] < 3: py_remarks = unicode(py_remarks, 'ascii', "ignore") @@ -266,6 +271,8 @@ cdef class DCDFile: if self.mode != 'r': raise IOError('File opened in mode: {}. Reading only allow ' 'in mode "r"'.format('self.mode')) + if self.n_frames == 0: + raise IOError("opened empty file. No frames are saved") cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=FLOAT, order='F') @@ -359,8 +366,8 @@ cdef class DCDFile: except UnicodeDecodeError: remarks = bytearray(remarks) - ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, - ts_between_saves, time_step, with_unitcell, + ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, + ts_between_saves, time_step, with_unitcell, charmm) if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index e4e3e861660..7a03b951eeb 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -170,9 +170,25 @@ def test_write_header_crude(self): # we're not actually asserting anything, yet # run with: nosetests test_libdcd.py --nocapture # to see printed output from nose - with open(self.testfile, "rb") as f: - for element in f: - print(element) + with DCDFile(self.testfile) as dcd: + assert_equal(dcd.remarks, 'Crazy!') + assert_equal(dcd.n_atoms, 22) + + @raises(IOError) + def test_write_header_only(self): + # test that _write_header() can produce a very crude + # header for a new / empty file + with DCDFile(self.testfile, 'w') as dcd: + dcd._write_header(remarks='Crazy!', n_atoms=22, + starting_step=12, ts_between_saves=10, + time_step=0.02, + charmm=1) + + # we're not actually asserting anything, yet + # run with: nosetests test_libdcd.py --nocapture + # to see printed output from nose + with DCDFile(self.testfile) as dcd: + dcd.read() @raises(IOError) def test_write_header_mode_sensitivy(self): From 7632871f2fd3af89a9b84dfd9286a9b7e94d5fbe Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:24:53 +0200 Subject: [PATCH 033/101] remove double definition --- .../MDAnalysisTests/formats/test_libdcd.py | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 7a03b951eeb..6c6ef56573e 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -377,28 +377,7 @@ def test_write_wrong_shape_box(self): ts_between_saves=1, remarks='test') -class TestDCDByteArithmetic(): - - def setUp(self): - - self.dcdfile = DCD - self._filesize = os.path.getsize(DCD) - - def test_relative_frame_sizes(self): - # the first frame of a DCD file should always be >= in size - # to subsequent frames, as the first frame contains the same - # atoms + (optional) fixed atoms - with DCDFile(self.dcdfile) as dcd: - first_frame_size = dcd._firstframesize - general_frame_size = dcd._framesize - - # for frame in test: - # written_coords = test.read()[0] - # ref_coords = ref.read()[0] - # assert_equal(written_coords, ref_coords) - - -class TestDCDByteArithmetic(): +class TestDCDByteArithmetic(object): def setUp(self): self.dcdfile = DCD From f919d79cafcc32a3f8f397a6ffb7ea7953c0b500 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:27:23 +0200 Subject: [PATCH 034/101] restucture libdcd test order --- .../MDAnalysisTests/formats/test_libdcd.py | 111 +++++++++--------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 6c6ef56573e..20061361350 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -377,57 +377,6 @@ def test_write_wrong_shape_box(self): ts_between_saves=1, remarks='test') -class TestDCDByteArithmetic(object): - - def setUp(self): - self.dcdfile = DCD - self._filesize = os.path.getsize(DCD) - - def test_relative_frame_sizes(self): - # the first frame of a DCD file should always be >= in size - # to subsequent frames, as the first frame contains the same - # atoms + (optional) fixed atoms - with DCDFile(self.dcdfile) as dcd: - first_frame_size = dcd._firstframesize - general_frame_size = dcd._framesize - - assert_equal(first_frame_size >= general_frame_size, True) - - def test_file_size_breakdown(self): - # the size of a DCD file is equivalent to the sum of the header - # size, first frame size, and (N - 1 frames) * size per general - # frame - expected = self._filesize - with DCDFile(self.dcdfile) as dcd: - actual = dcd._header_size + dcd._firstframesize + ((dcd.n_frames - 1) * dcd._framesize) - assert_equal(actual, expected) - - def test_nframessize_int(self): - # require that the (nframessize / framesize) value used by DCDFile - # is an integer (because nframessize / framesize + 1 = total frames, - # which must also be an int) - with DCDFile(self.dcdfile) as dcd: - nframessize = self._filesize - dcd._header_size - dcd._firstframesize - assert_equal(float(nframessize) % float(dcd._framesize), 0) - - - -class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): - # repeat byte arithmetic tests for NAMD format DCD - - def setUp(self): - self.dcdfile = DCD_NAMD_TRICLINIC - self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) - - -class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): - # repeat byte arithmetic tests for Charmm36 format DCD - - def setUp(self): - self.dcdfile = DCD_TRICLINIC - self._filesize = os.path.getsize(DCD_TRICLINIC) - - class TestDCDWriteNAMD(TestDCDWrite): # repeat writing tests for NAMD format DCD @@ -543,19 +492,18 @@ def setUp(self): self.random_unitcells = np.random.random((self.expected_frames, 6)).astype(np.float64) with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: for index, frame in enumerate(f_in): - frame_dict = frame._asdict() - box=frame_dict['unitcell'].astype(np.float64) - f_out.write(xyz=frame_dict['x'], + box=frame.unitcell.astype(np.float64) + f_out.write(xyz=frame.x, box=self.random_unitcells[index], step=f_in.istart, - natoms=frame_dict['x'].shape[0], + natoms=frame.x.shape[0], charmm=1, # DCD should be CHARMM time_step=f_in.delta, ts_between_saves=f_in.nsavc, remarks=f_in.remarks) def tearDown(self): - try: + try: os.unlink(self.testfile) except OSError: pass @@ -582,3 +530,54 @@ def test_written_unit_cell_random(self): curr_frame += 1 assert_allclose(written_unitcell, ref_unitcell, rtol=1e-05) + + +class TestDCDByteArithmetic(object): + + def setUp(self): + self.dcdfile = DCD + self._filesize = os.path.getsize(DCD) + + def test_relative_frame_sizes(self): + # the first frame of a DCD file should always be >= in size + # to subsequent frames, as the first frame contains the same + # atoms + (optional) fixed atoms + with DCDFile(self.dcdfile) as dcd: + first_frame_size = dcd._firstframesize + general_frame_size = dcd._framesize + + assert_equal(first_frame_size >= general_frame_size, True) + + def test_file_size_breakdown(self): + # the size of a DCD file is equivalent to the sum of the header + # size, first frame size, and (N - 1 frames) * size per general + # frame + expected = self._filesize + with DCDFile(self.dcdfile) as dcd: + actual = dcd._header_size + dcd._firstframesize + ((dcd.n_frames - 1) * dcd._framesize) + assert_equal(actual, expected) + + def test_nframessize_int(self): + # require that the (nframessize / framesize) value used by DCDFile + # is an integer (because nframessize / framesize + 1 = total frames, + # which must also be an int) + with DCDFile(self.dcdfile) as dcd: + nframessize = self._filesize - dcd._header_size - dcd._firstframesize + assert_equal(float(nframessize) % float(dcd._framesize), 0) + + + +class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): + # repeat byte arithmetic tests for NAMD format DCD + + def setUp(self): + self.dcdfile = DCD_NAMD_TRICLINIC + self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) + + +class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): + # repeat byte arithmetic tests for Charmm36 format DCD + + def setUp(self): + self.dcdfile = DCD_TRICLINIC + self._filesize = os.path.getsize(DCD_TRICLINIC) From 23272c5c71926d283b906f37d3ee368e08eca3f4 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:36:59 +0200 Subject: [PATCH 035/101] deactivate correl tests for now --- .../MDAnalysisTests/coordinates/test_dcd.py | 68 ++++++++----------- 1 file changed, 27 insertions(+), 41 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 3c300985d07..73263b3fe98 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -157,31 +157,31 @@ def test_volume(self): err_msg="wrong volume for unitcell (no unitcell " "in DCD so this should be 0)") - def test_timeseries_slicing(self): - # check that slicing behaves correctly - # should before issue #914 resolved - x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), - (0, 5, 5), (3, 5, 1), (None, None, None)] - for start, stop, step in x: - yield self._slice_generation_test, start, stop, step - - def test_backwards_stepping(self): - x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] - for start, stop, step in x: - yield self._failed_slices_test, start, stop, step - - def _slice_generation_test(self, start, stop, step): - self.u = mda.Universe(PSF, DCD) - ts = self.u.trajectory.timeseries(self.u.atoms) - ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) - - @knownfailure - def _failed_slices_test(self, start, stop, step): - self.u = mda.Universe(PSF, DCD) - ts = self.u.trajectory.timeseries(self.u.atoms) - ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) + # def test_timeseries_slicing(self): + # # check that slicing behaves correctly + # # should before issue #914 resolved + # x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), + # (0, 5, 5), (3, 5, 1), (None, None, None)] + # for start, stop, step in x: + # yield self._slice_generation_test, start, stop, step + + # def test_backwards_stepping(self): + # x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] + # for start, stop, step in x: + # yield self._failed_slices_test, start, stop, step + + # def _slice_generation_test(self, start, stop, step): + # self.u = mda.Universe(PSF, DCD) + # ts = self.u.trajectory.timeseries(self.u.atoms) + # ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) + # assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) + + # @knownfailure + # def _failed_slices_test(self, start, stop, step): + # self.u = mda.Universe(PSF, DCD) + # ts = self.u.trajectory.timeseries(self.u.atoms) + # ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) + # assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) def test_DCDReader_set_dt(dt=100., frame=3): @@ -397,10 +397,12 @@ def test_read_triclinic(self): def test_write_triclinic(self): """test writing of triclinic unitcell (Issue 187) for NAMD or new CHARMM format (at least since c36b2)""" + print("writing") with self.u.trajectory.OtherWriter(self.dcd) as w: for ts in self.u.trajectory: w.write(ts) w = mda.Universe(self.topology, self.dcd) + print("reading\n") for ts_orig, ts_copy in zip(self.u.trajectory, w.trajectory): assert_almost_equal(ts_orig.dimensions, ts_copy.dimensions, 4, @@ -466,19 +468,3 @@ def test_coordinates(self): ts_orig.frame)) -class TestDCDTimestep(BaseTimestepTest): - Timestep = mda.coordinates.DCD.Timestep - name = "DCD" - has_box = True - set_box = True - unitcell = np.array([10., 90., 11., 90., 90., 12.]) - uni_args = (PSF, DCD) - - def test_ts_order_define(self): - """Check that users can hack in a custom unitcell order""" - old = self.Timestep._ts_order - self.ts._ts_order = [0, 2, 5, 1, 3, 4] - self.ts.dimensions = np.array([10, 11, 12, 80, 85, 90]) - assert_allclose(self.ts._unitcell, np.array([10, 80, 11, 85, 90, 12])) - self.ts._ts_order = old - self.ts.dimensions = np.zeros(6) From e2e8e84aed4ed576abe9542167331c09648b8456 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 13:54:48 +0200 Subject: [PATCH 036/101] start work on new dcd reader --- package/MDAnalysis/coordinates/DCD.py | 632 +++++--------------------- 1 file changed, 115 insertions(+), 517 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 3c769b2a762..272c6ad393c 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -91,565 +91,163 @@ from ..exceptions import NoDataError from . import base from . import core -# Add the c functions to their respective classes so they act as class methods -from . import _dcdmodule -class Timestep(base.Timestep): - #: Indices into :attr:`Timestep._unitcell` (``[A, gamma, B, beta, alpha, - #: C]``, provided by the :class:`DCDReader` C code) to pull out - #: ``[A, B, C, alpha, beta, gamma]``. - _ts_order = [0, 2, 5, 4, 3, 1] - - @property - def dimensions(self): - """unitcell dimensions (*A*, *B*, *C*, *alpha*, *beta*, *gamma*) - - lengths *A*, *B*, *C* are in the MDAnalysis length unit (Å), and - angles are in degrees. - - :attr:`dimensions` is read-only because it transforms the actual format - of the unitcell (which differs between different trajectory formats) to - the representation described here, which is used everywhere in - MDAnalysis. - - The ordering of the angles in the unitcell is the same as in recent - versions of VMD's DCDplugin_ (2013), namely the `X-PLOR DCD format`_: - The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` from - the DCD file (actually, the direction cosines are stored instead of the - angles but the underlying C code already does this conversion); if any - of these values are < 0 or if any of the angles are > 180 degrees then - it is assumed it is a new-style CHARMM unitcell (at least since c36b2) - in which box vectors were recorded. - - - .. warning:: The DCD format is not well defined. Check your unit cell - dimensions carefully, especially when using triclinic - boxes. Different software packages implement different conventions - and MDAnalysis is currently implementing the newer NAMD/VMD convention - and tries to guess the new CHARMM one. Old CHARMM trajectories might - give wrong unitcell values. For more details see `Issue 187`_. - - .. versionchanged:: 0.9.0 - Unitcell is now interpreted in the newer NAMD DCD format as ``[A, - gamma, B, beta, alpha, C]`` instead of the old MDAnalysis/CHARMM - ordering ``[A, alpha, B, beta, gamma, C]``. We attempt to detect the - new CHARMM DCD unitcell format (see `Issue 187`_ for a discussion). - - .. _`X-PLOR DCD format`: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/dcdplugin.html - .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 - .. _DCDplugin: http://www.ks.uiuc.edu/Research/vmd/plugins/doxygen/dcdplugin_8c-source.html#l00947 - """ - - # Layout of unitcell is [A, alpha, B, beta, gamma, C] --- (originally CHARMM DCD) - # override for other formats; this strange ordering is kept for historical reasons - # (the user should not need concern themselves with this) - ## orig MDAnalysis 0.8.1/dcd.c (~2004) - ##return np.take(self._unitcell, [0,2,5,1,3,4]) - - # MDAnalysis 0.9.0 with recent dcd.c (based on 2013 molfile - # DCD plugin, which implements the ordering of recent NAMD - # (>2.5?)). See Issue 187. - uc = np.take(self._unitcell, self._ts_order) - # heuristic sanity check: uc = A,B,C,alpha,beta,gamma - # XXX: should we worry about these comparisons with floats? - if np.any(uc < 0.) or np.any(uc[3:] > 180.): - # might be new CHARMM: box matrix vectors - H = self._unitcell - e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] - uc = core.triclinic_box(e1, e2, e3) - return uc - - @dimensions.setter - def dimensions(self, box): - """Set unitcell with (*A*, *B*, *C*, *alpha*, *beta*, *gamma*) - - .. versionadded:: 0.9.0 - """ - # note that we can re-use self._ts_order with put! - np.put(self._unitcell, self._ts_order, box) - - -class DCDWriter(base.WriterBase): - """Write to a CHARMM/NAMD DCD trajectory file. - - Parameters - ---------- - filename : str - name of output file - n_atoms : int (optional) - number of atoms in dcd file - start : int (optional) - starting frame number - step : int (optional) - skip between subsequent timesteps (indicate that `step` **MD - integrator steps (!)** make up one trajectory frame); default is 1. - delta : float (optional) - timestep (**MD integrator time step (!)**, in AKMA units); default is - 20.45482949774598 (corresponding to 1 ps). - remarks : str (optional) - comments to annotate dcd file - dt : float (optional) - **Override** `step` and `delta` so that the DCD records that - `dt` ps lie between two frames. (It sets `step` = 1 and - `delta` ``= AKMA(dt)``.) The default is ``None``, in which case - `step` and `delta` are used. - convert_units : bool (optional) - units are converted to the MDAnalysis base format; ``None`` selects - the value of :data:`MDAnalysis.core.flags` ['convert_lengths']. - (see :ref:`flags-label`) - - Note - ---- - The keyword arguments set the **low-level attributes of the DCD** according - to the CHARMM format. The time between two frames would be `delta` * - `step`! For convenience, one can alternatively supply the `dt` keyword - (see above) to just tell the writer that it should record "There are `dt` - ps between each frame". - - The Writer will write the **unit cell information** to the DCD in a format - compatible with NAMD and older CHARMM versions, namely the unit cell - lengths in Angstrom and the angle cosines (see :class:`Timestep`). Newer - versions of CHARMM (at least c36b2) store the matrix of the box - vectors. Writing this matrix to a DCD is currently not supported (although - reading is supported with the :class:`DCDReader`); instead the angle - cosines are written, which *might make the DCD file unusable in CHARMM - itself*. See `Issue 187`_ for further information. - - The writing behavior of the :class:`DCDWriter` is identical to that of the - DCD molfile plugin of VMD with the exception that by default it will use - AKMA time units. - - Example - ------- - Typical usage:: - - with DCDWriter("new.dcd", u.atoms.n_atoms) as w: - for ts in u.trajectory - w.write_next_timestep(ts) - - Keywords are available to set some of the low-level attributes of the DCD. - - .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 +class DCDReader(base.ReaderBase): + """DCD Reader """ format = 'DCD' - multiframe = True flavor = 'CHARMM' units = {'time': 'AKMA', 'length': 'Angstrom'} - def __init__(self, filename, n_atoms, start=0, step=1, - delta=mdaunits.convert(1., 'ps', 'AKMA'), dt=None, - remarks="Created by DCDWriter", convert_units=None): - if n_atoms == 0: - raise ValueError("DCDWriter: no atoms in output trajectory") - elif n_atoms is None: - # probably called from MDAnalysis.Writer() so need to give user a gentle heads up... - raise ValueError("DCDWriter: REQUIRES the number of atoms in the 'n_atoms' argument\n" + - " " * len("ValueError: ") + - "For example: n_atoms=universe.atoms.n_atoms") - self.filename = filename - # convert length and time to base units on the fly? - self.convert_units = flags['convert_lengths'] if convert_units is None \ - else convert_units - self.n_atoms = n_atoms - - self.frames_written = 0 - self.start = start - if dt is not None: - if dt > 0: - # ignore step and delta - self.step = 1 - self.delta = mdaunits.convert(dt, 'ps', 'AKMA') - else: - raise ValueError("DCDWriter: dt must be > 0, not {0}".format(dt)) - else: - self.step = step - self.delta = delta - self.dcdfile = open(self.filename, 'wb') - self.remarks = remarks - self._write_dcd_header(self.n_atoms, self.start, self.step, self.delta, self.remarks) - - def _dcd_header(self): - """Returns contents of the DCD header C structure:: - typedef struct { - fio_fd fd; // FILE * - fio_size_t header_size; // size_t == sizeof(int) - int natoms; - int nsets; - int setsread; - int istart; - int nsavc; - double delta; - int nfixed; - int *freeind; - float *fixedcoords; - int reverse; - int charmm; - int first; - int with_unitcell; - } dcdhandle; - - .. deprecated:: 0.7.5 - This function only exists for debugging purposes and might - be removed without notice. Do not rely on it. - - """ - # was broken (no idea why [orbeckst]), see Issue 27 - # 'PiiiiiidiPPiiii' should be the unpack string according to the struct. - # struct.unpack("LLiiiiidiPPiiii",self._dcd_C_str) - # seems to do the job on Mac OS X 10.6.4 ... but I have no idea why, - # given that the C code seems to define them as normal integers - desc = [ - 'file_desc', 'header_size', 'natoms', 'nsets', 'setsread', 'istart', - 'nsavc', 'delta', 'nfixed', 'freeind_ptr', 'fixedcoords_ptr', - 'reverse', 'charmm', 'first', 'with_unitcell'] - return dict(zip(desc, struct.unpack("LLiiiiidiPPiiii", self._dcd_C_str))) - - def write_next_timestep(self, ts=None): - """Write a new timestep to the DCD file. - - Parameters + def __init__(self, filename, convert_units=True, **kwargs): + """Parameters ---------- - ts : `Timestep` (optional) - `Timestep` object containing coordinates to be written to DCD file; - by default it uses the current `Timestep` associated with the - Writer. + filename : str + trajectory filename + convert_units : bool (optional) + convert units to MDAnalysis units + **kwargs : dict + General reader arguments. - Raises - ------ - :exc:`ValueError` - if wrong number of atoms supplied - :exc:`~MDAnalysis.NoDataError` - if no coordinates to be written. + """ + super(DCDReader, self).__init__(filename, + convert_units=convert_units, + **kwargs) + self._file = DCDFile(self.filename) + self.n_atoms = self._file.n_atoms - .. versionchanged:: 0.7.5 - Raises :exc:`ValueError` instead of generic :exc:`Exception` - if wrong number of atoms supplied and :exc:`~MDAnalysis.NoDataError` - if no coordinates to be written. + dt = self._file.delta - """ - if ts is None: - try: - ts = self.ts - except AttributeError: - raise NoDataError("DCDWriter: no coordinate data to write to trajectory file") - if not ts.n_atoms == self.n_atoms: - raise ValueError("DCDWriter: Timestep does not have the correct number of atoms") - unitcell = self.convert_dimensions_to_unitcell(ts).astype(np.float32) # must be float32 (!) - if not ts._pos.flags.f_contiguous: # Not in fortran format - ts = Timestep.from_timestep(ts) # wrap in a new fortran formatted Timestep + self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) + frame = self._file.read() + self._frame = 0 + self._frame_to_ts(frame, self.ts) + # these should only be initialized once + self.ts.dt = dt + self._file.seek(0) + self.ts.dimensions = frame.unitcell if self.convert_units: - pos = self.convert_pos_to_native(ts._pos, - inplace=False) # possibly make a copy to avoid changing the trajectory - self._write_next_frame(pos[:, 0], pos[:, 1], pos[:, 2], unitcell) - self.frames_written += 1 - - def convert_dimensions_to_unitcell(self, ts, _ts_order=Timestep._ts_order): - """Read dimensions from timestep *ts* and return appropriate native unitcell. - - See Also - -------- - :attr:`Timestep.dimensions` - """ - # unitcell is A,B,C,alpha,beta,gamma - convert to order expected by low level DCD routines - lengths, angles = ts.dimensions[:3], ts.dimensions[3:] - self.convert_pos_to_native(lengths) - unitcell = np.zeros_like(ts.dimensions) - # __write_DCD_frame() wants uc_array = [A, gamma, B, beta, alpha, C] and - # will write the lengths and the angle cosines to the DCD. NOTE: It - # will NOT write CHARMM36+ box matrix vectors. When round-tripping a - # C36+ DCD, we loose the box vector information. However, MDAnalysis - # itself will not detect this because the DCDReader contains the - # heuristic to deal with either lengths/angles or box matrix on the fly. - # - # use np.put so that we can re-use ts_order (otherwise we - # would need a ts_reverse_order such as _ts_reverse_order = [0, 5, 1, 4, 3, 2]) - np.put(unitcell, _ts_order, np.concatenate([lengths, angles])) - return unitcell + self.convert_pos_from_native(self.ts.dimensions[:3]) def close(self): - """Close trajectory and flush buffers.""" - if hasattr(self, 'dcdfile') and self.dcdfile is not None: - self._finish_dcd_write() - self.dcdfile.close() - self.dcdfile = None - - -class DCDReader(base.ReaderBase): - """Reads from a DCD file - - :Data: - ts - :class:`Timestep` object containing coordinates of current frame - - :Methods: - ``dcd = DCD(dcdfilename)`` - open dcd file and read header - ``len(dcd)`` - return number of frames in dcd - ``for ts in dcd:`` - iterate through trajectory - ``for ts in dcd[start:stop:skip]:`` - iterate through a trajectory - ``dcd[i]`` - random access into the trajectory (i corresponds to frame number) - ``data = dcd.timeseries(...)`` - retrieve a subset of coordinate information for a group of atoms - - Note - ---- - The DCD file format is not well defined. In particular, NAMD and CHARMM use - it differently. Currently, MDAnalysis tries to guess the correct format for - the unitcell representation but it can be wrong. **Check the unitcell - dimensions**, especially for triclinic unitcells (see `Issue 187`_ and - :attr:`Timestep.dimensions`). A second potential issue are the units of - time (TODO). - - - .. versionchanged:: 0.9.0 - The underlying DCD reader (written in C and derived from the - catdcd/molfile plugin code of VMD) is now reading the unitcell in - NAMD ordering: ``[A, B, C, sin(gamma), sin(beta), - sin(alpha)]``. See `Issue 187`_ for further details. - - .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 - - .. versionchanged:: 0.11.0 - Frames now 0-based instead of 1-based. - Native frame number read into `ts._frame`. - Removed `skip` keyword and functionality. - - """ - format = 'DCD' - flavor = 'CHARMM' - units = {'time': 'AKMA', 'length': 'Angstrom'} - _Timestep = Timestep + """close reader""" + self._file.close() - def __init__(self, dcdfilename, **kwargs): - super(DCDReader, self).__init__(dcdfilename, **kwargs) - - self.dcdfilename = self.filename # dcdfilename is legacy - self.dcdfile = None # set right away because __del__ checks - - # Issue #32: segfault if dcd is 0-size - # Hack : test here... (but should be fixed in dcd.c) - stats = os.stat(self.filename) - if stats.st_size == 0: - raise IOError(errno.EIO, "DCD file is zero size", self.filename) - - self.dcdfile = open(self.filename, 'rb') - self.n_atoms = 0 - self.n_frames = 0 - self.fixed = 0 - self.periodic = False - - # This reads skip_timestep and delta from header - self._read_dcd_header() - - # Convert delta to ps - delta = mdaunits.convert(self.delta, self.units['time'], 'ps') - - self._ts_kwargs.setdefault('dt', self.skip_timestep * delta) - self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) - # Read in the first timestep - self._read_next_timestep() - - def _dcd_header(self): # pragma: no cover - """Returns contents of the DCD header C structure:: - typedef struct { - fio_fd fd; // FILE * - fio_size_t header_size; // size_t == sizeof(int) - int natoms; - int nsets; - int setsread; - int istart; - int nsavc; - double delta; - int nfixed; - int *freeind; - float *fixedcoords; - int reverse; - int charmm; - int first; - int with_unitcell; - } dcdhandle; - - .. deprecated:: 0.7.5 - This function only exists for debugging purposes and might - be removed without notice. Do not rely on it. - - """ - # was broken (no idea why [orbeckst]), see Issue 27 - # 'PiiiiiidiPPiiii' should be the unpack string according to the struct. - # struct.unpack("LLiiiiidiPPiiii",self._dcd_C_str) - # seems to do the job on Mac OS X 10.6.4 ... but I have no idea why, - # given that the C code seems to define them as normal integers - desc = [ - 'file_desc', 'header_size', 'natoms', 'nsets', 'setsread', 'istart', - 'nsavc', 'delta', 'nfixed', 'freeind_ptr', 'fixedcoords_ptr', 'reverse', - 'charmm', 'first', 'with_unitcell'] - return dict(zip(desc, struct.unpack("LLiiiiidiPPiiii", self._dcd_C_str))) + @property + def n_frames(self): + """number of frames in trajectory""" + return len(self._file) def _reopen(self): - self.ts.frame = -1 - self._reset_dcd_read() + """reopen trajectory""" + self.ts.frame = 0 + self._frame = -1 + self._file.close() + self._file.open(self.filename.encode('utf-8'), 'r') + + def _read_frame(self, i): + """read frame i""" + self._frame = i - 1 + try: + self._file.seek(i) + timestep = self._read_next_timestep() + except IOError: + warnings.warn('seek failed, recalculating offsets and retrying') + offsets = self._file.calc_offsets() + self._file.set_offsets(offsets) + self._read_offsets(store=True) + self._file.seek(i) + timestep = self._read_next_timestep() + return timestep def _read_next_timestep(self, ts=None): - """Read the next frame - - .. versionchanged 0.11.0:: - Native frame read into ts._frame, ts.frame naively iterated - """ + """copy next frame into timestep""" + if self._frame == self.n_frames - 1: + raise IOError(errno.EIO, 'trying to go over trajectory limit') if ts is None: ts = self.ts - ts._frame = self._read_next_frame(ts._x, ts._y, ts._z, ts._unitcell, 1) - ts.frame += 1 + frame = self._file.read() + self._frame += 1 + self._frame_to_ts(frame, ts) return ts - def _read_frame(self, frame): - """Skip to frame and read + def Writer(self, filename, n_atoms=None, **kwargs): + """Return writer for trajectory format""" + if n_atoms is None: + n_atoms = self.n_atoms + return self._writer(filename, n_atoms=n_atoms, **kwargs) - .. versionchanged:: 0.11.0 - Native frame read into ts._frame, ts.frame naively set to frame - """ - self._jump_to_frame(frame) - ts = self.ts - ts._frame = self._read_next_frame(ts._x, ts._y, ts._z, ts._unitcell, 1) - ts.frame = frame - return ts + def _frame_to_ts(self, frame, ts): + """convert a trr-frame to a mda TimeStep""" + ts.time = self._file.tell() * self._file.delta + ts.frame = self._frame + ts.data['step'] = self._file.tell() - def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, - format='afc'): - """Return a subset of coordinate data for an AtomGroup + ts.dimensions = frame.unitcell + ts.positions = frame.x - Parameters - ---------- - asel : :class:`~MDAnalysis.core.groups.AtomGroup` - The :class:`~MDAnalysis.core.groups.AtomGroup` to read the - coordinates from. Defaults to None, in which case the full set of - coordinate data is returned. - start : int (optional) - Begin reading the trajectory at frame index `start` (where 0 is the index - of the first frame in the trajectory); the default ``None`` starts - at the beginning. - stop : int (optional) - End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. - The trajectory is read to the end with the default ``None``. - step : int (optional) - Step size for reading; the default ``None`` is equivalent to 1 and means to - read every frame. - format : str (optional) - the order/shape of the return data array, corresponding - to (a)tom, (f)rame, (c)oordinates all six combinations - of 'a', 'f', 'c' are allowed ie "fac" - return array - where the shape is (frame, number of atoms, - coordinates) - - - .. deprecated:: 0.16.0 - `skip` has been deprecated in favor of the standard keyword `step`. + if self.convert_units: + self.convert_pos_from_native(ts.dimensions[:3]) + self.convert_pos_from_native(ts.positions) - """ - if skip is not None: - step = skip - warnings.warn("Skip is deprecated and will be removed in" - "in 1.0. Use step instead.", - category=DeprecationWarning) - - start, stop, step = self.check_slice_indices(start, stop, step) - - if asel is not None: - if len(asel) == 0: - raise NoDataError("Timeseries requires at least one atom to analyze") - atom_numbers = list(asel.indices) - else: - atom_numbers = list(range(self.n_atoms)) - - if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: - raise ValueError("Invalid timeseries format") - # Check if the atom numbers can be grouped for efficiency, then we can read partial buffers - # from trajectory file instead of an entire timestep - # XXX needs to be implemented - return self._read_timeseries(atom_numbers, start, stop, step, format) + return ts - def close(self): - if self.dcdfile is not None: - self._finish_dcd_read() - self.dcdfile.close() - self.dcdfile = None - def Writer(self, filename, **kwargs): - """Returns a DCDWriter for `filename` with the same parameters as this DCD. +class DCDWriter(base.WriterBase): + """Base class for libmdaxdr file formats xtc and trr""" - Defaults for all values are obtained from the `DCDReader` itself but - all values can be changed through keyword arguments. + multiframe = True + flavor = 'CHARMM' + units = {'time': 'AKMA', 'length': 'Angstrom'} + def __init__(self, filename, n_atoms, convert_units=True, **kwargs): + """ Parameters ---------- filename : str - filename of the output DCD trajectory - n_atoms : int (optional) - number of atoms - start : int (optional) - number of the first recorded MD step - step : int (optional) - indicate that `step` **MD steps (!)** make up one trajectory frame - delta : float (optional) - **MD integrator time step (!)**, in AKMA units - dt : float (optional) - **Override** `step` and `delta` so that the DCD records that - `dt` ps lie between two frames. (It sets `step` `` = 1`` and - `delta` `` = AKMA(dt)``.) The default is ``None``, in which case - `step` and `delta` are used. - - remarks : str (optional) - string that is stored in the DCD header - - Returns - ------- - :class:`DCDWriter` - - - Note - ---- - The keyword arguments set the low-level attributes of the DCD according - to the CHARMM format. The time between two frames would be `delta` * - `step`! - - Here `step` is really the number of MD integrator time steps that - occured after this frame, including the frame itself that is the - coordinate snapshot and `delta` is the integrator stime step. The DCD - file format contains this information so it needs to be provided here. + filename of trajectory + n_atoms : int + number of atoms to be written + convert_units : bool (optional) + convert from MDAnalysis units to format specific units + **kwargs : dict + General writer arguments + """ + self.filename = filename + self._convert_units = convert_units + self.n_atoms = n_atoms + self._file = self._file(self.filename, 'w') + def write_next_timestep(self, ts): + """Write timestep object into trajectory. + + Parameters + ---------- + ts: TimeStep See Also -------- - :class:`DCDWriter` - + .write(AtomGroup/Universe/TimeStep) + The normal write() method takes a more general input """ - n_atoms = kwargs.pop('n_atoms', self.n_atoms) - kwargs.setdefault('start', self.start_timestep) - kwargs.setdefault('step', self.skip_timestep) - kwargs.setdefault('delta', self.delta) - kwargs.setdefault('remarks', self.remarks) - # dt keyword is simply passed through if provided - return DCDWriter(filename, n_atoms, **kwargs) + xyz = ts.positions.copy() + time = ts.time + step = ts.frame + dimensions = ts.dimensions - @property - def dt(self): - """Time between two trajectory frames in picoseconds.""" - return self.ts.dt + if self._convert_units: + xyz = self.convert_pos_to_native(xyz, inplace=False) + dimensions = self.convert_dimensions_to_unitcell(ts, inplace=False) + self._file.write(xyz=xyz, box=dimensions, step=step, natoms=xyz.shape[0], + charmm=1, time_step=ts.dt * step, ts_between_saves=1, remarks='test') -DCDReader._read_dcd_header = types.MethodType(_dcdmodule.__read_dcd_header, None, DCDReader) -DCDReader._read_next_frame = types.MethodType(_dcdmodule.__read_next_frame, None, DCDReader) -DCDReader._jump_to_frame = types.MethodType(_dcdmodule.__jump_to_frame, None, DCDReader) -DCDReader._reset_dcd_read = types.MethodType(_dcdmodule.__reset_dcd_read, None, DCDReader) -DCDReader._finish_dcd_read = types.MethodType(_dcdmodule.__finish_dcd_read, None, DCDReader) -DCDReader._read_timeseries = types.MethodType(_dcdmodule.__read_timeseries, None, DCDReader) + def close(self): + """close trajectory""" + self._file.close() -DCDWriter._write_dcd_header = types.MethodType(_dcdmodule.__write_dcd_header, None, DCDWriter) -DCDWriter._write_next_frame = types.MethodType(_dcdmodule.__write_next_frame, None, DCDWriter) -DCDWriter._finish_dcd_write = types.MethodType(_dcdmodule.__finish_dcd_write, None, DCDWriter) + def __del__(self): + self.close() From d446fcd6dd69cde2f0914bef10d7ee7deb0542be Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 14:45:11 +0200 Subject: [PATCH 037/101] fix unicode name in open --- package/MDAnalysis/lib/formats/libdcd.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 4a764fb048b..cf9087d3f9f 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -172,7 +172,7 @@ cdef class DCDFile: fio_mode = FIO_WRITE else: raise IOError("unkown mode '{}', use either r or w".format(mode)) - self.mode = mode + self.mode = str(mode) ok = fio_open(self.fname, fio_mode, &self.fp) if ok != 0: From 5fb38787521b5092c4ed07c3c833f889ba4b8a13 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 15:39:52 +0200 Subject: [PATCH 038/101] improve dt unit handling --- package/MDAnalysis/coordinates/DCD.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 272c6ad393c..617c7c44f9a 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -119,7 +119,8 @@ def __init__(self, filename, convert_units=True, **kwargs): self.n_atoms = self._file.n_atoms - dt = self._file.delta + delta = mdaunits.convert(self._file.delta, self.units['time'], 'ps') + dt = delta * self._file.nsavc self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) frame = self._file.read() @@ -195,6 +196,10 @@ def _frame_to_ts(self, frame, ts): return ts + @property + def dt(self): + return self.ts.dt + class DCDWriter(base.WriterBase): """Base class for libmdaxdr file formats xtc and trr""" From e06b0ff94d322df6f0e596444d1e3e9db38f5d2d Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 28 May 2017 15:38:15 +0200 Subject: [PATCH 039/101] Fix unitcell handling Unitcells are not handled uniformly. We take a [A, B, C, alpha, beta, gamma] and return the same as well. --- .../MDAnalysis/lib/formats/include/readdcd.h | 27 --- package/MDAnalysis/lib/formats/libdcd.pyx | 155 ++++++++++-------- .../MDAnalysisTests/formats/test_libdcd.py | 20 +-- 3 files changed, 94 insertions(+), 108 deletions(-) diff --git a/package/MDAnalysis/lib/formats/include/readdcd.h b/package/MDAnalysis/lib/formats/include/readdcd.h index f4f7b99e5da..b248097f4db 100644 --- a/package/MDAnalysis/lib/formats/include/readdcd.h +++ b/package/MDAnalysis/lib/formats/include/readdcd.h @@ -505,9 +505,6 @@ static int read_dcdstep(fio_fd fd, int N, float *X, float *Y, float *Z, int first, int *indexes, float *fixedcoords, int reverseEndian, int charmm) { int ret_val; /* Return value from read */ - float alpha, beta, gamma; - unitcell[0] = unitcell[2] = unitcell[5] = 0.0f; - unitcell[1] = unitcell[3] = unitcell[4] = 90.0f; if ((num_fixed==0) || first) { int tmpbuf[6]; /* temp storage for reading formatting info */ @@ -597,30 +594,6 @@ static int read_dcdstep(fio_fd fd, int N, float *X, float *Y, float *Z, if (ret_val) return ret_val; } - - if (unitcell[1] >= -1.0 && unitcell[1] <= 1.0 && - unitcell[3] >= -1.0 && unitcell[3] <= 1.0 && - unitcell[4] >= -1.0 && unitcell[4] <= 1.0) { - /* This file was generated by Charmm, or by NAMD > 2.5, with the angle */ - /* cosines of the periodic cell angles written to the DCD file. */ - /* This formulation improves rounding behavior for orthogonal cells */ - /* so that the angles end up at precisely 90 degrees, unlike acos(). */ - /* (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; */ - /* see Issue 187) */ - alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; - beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; - gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; - } else { - /* This file was likely generated by NAMD 2.5 and the periodic cell */ - /* angles are specified in degrees rather than angle cosines. */ - alpha = unitcell[4]; - beta = unitcell[3]; - gamma = unitcell[1]; - } - unitcell[4] = alpha; - unitcell[3] = beta; - unitcell[1] = gamma; - return DCD_SUCCESS; } diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index cf9087d3f9f..7c147944ff9 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -26,6 +26,8 @@ cimport numpy as np from libc.stdio cimport SEEK_SET, SEEK_CUR, SEEK_END +from libc.math cimport M_PI_2, asin + _whence_vals = {"FIO_SEEK_SET": SEEK_SET, "FIO_SEEK_CUR": SEEK_CUR, "FIO_SEEK_END": SEEK_END} @@ -263,54 +265,6 @@ cdef class DCDFile: (self.charmm & DCD_HAS_EXTRA_BLOCK)) - def read(self): - if self.reached_eof: - raise IOError('Reached last frame in DCD, seek to 0') - if not self.is_open: - raise IOError("No file open") - if self.mode != 'r': - raise IOError('File opened in mode: {}. Reading only allow ' - 'in mode "r"'.format('self.mode')) - if self.n_frames == 0: - raise IOError("opened empty file. No frames are saved") - - cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=FLOAT, - order='F') - cdef np.ndarray unitcell = np.empty(6, dtype=DOUBLE) - - cdef FLOAT_T[::1] x = xyz[:, 0] - cdef FLOAT_T[::1] y = xyz[:, 1] - cdef FLOAT_T[::1] z = xyz[:, 2] - - first_frame = self.current_frame == 0 - - ok = read_dcdstep(self.fp, self.n_atoms, - &x[0], - &y[0], &z[0], - unitcell.data, self.nfixed, first_frame, - self.freeind, self.fixedcoords, - self.reverse_endian, self.charmm) - if ok != 0 and ok != -4: - raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) - - # we couldn't read any more frames. - if ok == -4: - self.reached_eof = True - raise StopIteration - - self.current_frame += 1 - - _ts_order = [0, 2, 5, 4, 3, 1] - uc = np.take(unitcell, _ts_order) - if np.any(uc < 0.) or np.any(uc[3:] > 180.): - # might be new CHARMM: box matrix vectors - H = unitcell - e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] - uc = triclinic_box(e1, e2, e3) - - unitcell = uc - - return DCDFrame(xyz, unitcell) def seek(self, frame): """Seek to Frame. @@ -398,13 +352,20 @@ cdef class DCDFile: if self.mode != 'w': raise IOError('File opened in mode: {}. Writing only allowed ' 'in mode "w"'.format('self.mode')) + if len(box) != 6: + raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + + # print("box to write ", box) #cdef double [:,:] unitcell = box xyz = np.asarray(xyz, order='F', dtype=np.float32) - cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) - if c_box.size != 6: - raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + # we only support writing charmm format unit cell info + # The DCD unitcell is written as ``[A, gamma, B, beta, alpha, C]`` + _ts_order = [0, 5, 1, 4, 3, 2] + box = np.take(box, _ts_order) + # print("writting box ", box) + cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) if xyz.shape != (natoms, 3): raise ValueError("xyz shape is wrong should be (natoms, 3), got:".format(xyz.shape)) @@ -423,24 +384,86 @@ cdef class DCDFile: # looks like self.nsavc is just 0 all the time step = self.current_frame * self.nsavc - - # we only support writing charmm format unit cell info - alpha = box[3] - beta = box[4] - gamma = box[5] - a = box[0] - b = box[1] - c = box[2] - box[0] = a - box[1] = gamma - box[2] = b - box[3] = beta - box[4] = alpha - box[5] = c - ok = write_dcdstep(self.fp, self.current_frame, step, self.n_atoms, &x[0], &y[0], &z[0], &c_box[0], charmm) self.current_frame += 1 + + def read(self): + if self.reached_eof: + raise IOError('Reached last frame in DCD, seek to 0') + if not self.is_open: + raise IOError("No file open") + if self.mode != 'r': + raise IOError('File opened in mode: {}. Reading only allow ' + 'in mode "r"'.format('self.mode')) + if self.n_frames == 0: + raise IOError("opened empty file. No frames are saved") + + cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=FLOAT, + order='F') + cdef np.ndarray unitcell = np.empty(6, dtype=DOUBLE) + unitcell[0] = unitcell[2] = unitcell[5] = 0.0; + unitcell[4] = unitcell[3] = unitcell[1] = 90.0; + + cdef FLOAT_T[::1] x = xyz[:, 0] + cdef FLOAT_T[::1] y = xyz[:, 1] + cdef FLOAT_T[::1] z = xyz[:, 2] + + first_frame = self.current_frame == 0 + ok = read_dcdstep(self.fp, self.n_atoms, + &x[0], + &y[0], &z[0], + unitcell.data, self.nfixed, first_frame, + self.freeind, self.fixedcoords, + self.reverse_endian, self.charmm) + if ok != 0 and ok != -4: + raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + + # we couldn't read any more frames. + if ok == -4: + self.reached_eof = True + raise StopIteration + + self.current_frame += 1 + + if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and + unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and + unitcell[4] >= -1.0 and unitcell[4] <= 1.0): + # This file was generated by Charmm, or by NAMD > 2.5, with the angle + # cosines of the periodic cell angles written to the DCD file. + # This formulation improves rounding behavior for orthogonal cells + # so that the angles end up at precisely 90 degrees, unlike acos(). + # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; + # see Issue 187) */ + alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; + beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; + gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; + else: + # This file was likely generated by NAMD 2.5 and the periodic cell + # angles are specified in degrees rather than angle cosines. + alpha = unitcell[4]; + beta = unitcell[3]; + gamma = unitcell[1]; + + unitcell[4] = alpha; + unitcell[3] = beta; + unitcell[1] = gamma; + + # The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` + _ts_order = [0, 2, 5, 4, 3, 1] + uc = np.take(unitcell, _ts_order) + + # heuristic sanity check: uc = A,B,C,alpha,beta,gamma + # XXX: should we worry about these comparisons with floats? + if np.any(uc < 0.) or np.any(uc[3:] > 180.): + # might be new CHARMM: box matrix vectors + H = unitcell + e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] + uc = triclinic_box(e1, e2, e3) + + unitcell = uc + + return DCDFrame(xyz, unitcell) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 20061361350..ca56178990c 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -272,8 +272,8 @@ def test_written_unit_cell(self): with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: curr_frame = 0 while curr_frame < test.n_frames: - written_unitcell = test.read()[1] - ref_unitcell = ref.read()[1] + written_unitcell = test.read().unitcell + ref_unitcell = ref.read().unitcell curr_frame += 1 assert_equal(written_unitcell, ref_unitcell) @@ -476,7 +476,7 @@ def setUp(self): dtype=np.float32) -class TestDCDWriteRandom(): +class TestDCDWriteRandom(object): # should only be supported for Charmm24 format writing (for now) def setUp(self): @@ -489,7 +489,8 @@ def setUp(self): self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' np.random.seed(1178083) - self.random_unitcells = np.random.random((self.expected_frames, 6)).astype(np.float64) + self.random_unitcells = np.random.uniform(high=80,size=(self.expected_frames, 6)).astype(np.float64) + with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: for index, frame in enumerate(f_in): box=frame.unitcell.astype(np.float64) @@ -510,23 +511,12 @@ def tearDown(self): del self.tmpdir def test_written_unit_cell_random(self): - # written unit cell dimensions should match for all frames - # using randomly generated unit cells but some processing - # of the cosine data stored in charmm format is needed - # as well as shuffling of the orders in the unitcell - # array based on the prcoessing performed by - # DCDFile read and more generally relating to Issue 187 with DCDFile(self.testfile) as test: curr_frame = 0 while curr_frame < test.n_frames: written_unitcell = test.read()[1] ref_unitcell = self.random_unitcells[curr_frame] - ref_unitcell[1] = math.degrees(math.acos(ref_unitcell[1])) - ref_unitcell[3] = math.degrees(math.acos(ref_unitcell[3])) - ref_unitcell[4] = math.degrees(math.acos(ref_unitcell[4])) - _ts_order = [0, 2, 5, 4, 3, 1] - ref_unitcell = np.take(ref_unitcell, _ts_order) curr_frame += 1 assert_allclose(written_unitcell, ref_unitcell, rtol=1e-05) From 54abc2dbeef52e0e5faf4df22141c53277566178 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 29 May 2017 21:29:06 +0200 Subject: [PATCH 040/101] fix failing tests --- package/MDAnalysis/coordinates/DCD.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 617c7c44f9a..f0a6d152b37 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -101,7 +101,7 @@ class DCDReader(base.ReaderBase): flavor = 'CHARMM' units = {'time': 'AKMA', 'length': 'Angstrom'} - def __init__(self, filename, convert_units=True, **kwargs): + def __init__(self, filename, convert_units=True, dt=None, **kwargs): """Parameters ---------- filename : str @@ -120,8 +120,11 @@ def __init__(self, filename, convert_units=True, **kwargs): delta = mdaunits.convert(self._file.delta, self.units['time'], 'ps') - dt = delta * self._file.nsavc + if dt is None: + dt = delta * self._file.nsavc + self.skip_timestep = self._file.nsavc + self._ts_kwargs['dt'] = dt self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) frame = self._file.read() self._frame = 0 @@ -172,7 +175,8 @@ def _read_next_timestep(self, ts=None): ts = self.ts frame = self._file.read() self._frame += 1 - self._frame_to_ts(frame, ts) + ts = self._frame_to_ts(frame, ts) + self.ts = ts return ts def Writer(self, filename, n_atoms=None, **kwargs): @@ -183,8 +187,8 @@ def Writer(self, filename, n_atoms=None, **kwargs): def _frame_to_ts(self, frame, ts): """convert a trr-frame to a mda TimeStep""" - ts.time = self._file.tell() * self._file.delta ts.frame = self._frame + ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() ts.dimensions = frame.unitcell @@ -208,7 +212,7 @@ class DCDWriter(base.WriterBase): flavor = 'CHARMM' units = {'time': 'AKMA', 'length': 'Angstrom'} - def __init__(self, filename, n_atoms, convert_units=True, **kwargs): + def __init__(self, filename, n_atoms, convert_units=True, step=1, dt=None, **kwargs): """ Parameters ---------- @@ -224,7 +228,9 @@ def __init__(self, filename, n_atoms, convert_units=True, **kwargs): self.filename = filename self._convert_units = convert_units self.n_atoms = n_atoms - self._file = self._file(self.filename, 'w') + self._file = DCDFile(self.filename, 'w') + self.step = step + self.dt = dt def write_next_timestep(self, ts): """Write timestep object into trajectory. @@ -247,8 +253,11 @@ def write_next_timestep(self, ts): xyz = self.convert_pos_to_native(xyz, inplace=False) dimensions = self.convert_dimensions_to_unitcell(ts, inplace=False) + dt = self.dt if self.dt is not None else ts.dt + dt = mdaunits.convert(dt, 'ps', self.units['time']) + self._file.write(xyz=xyz, box=dimensions, step=step, natoms=xyz.shape[0], - charmm=1, time_step=ts.dt * step, ts_between_saves=1, remarks='test') + charmm=1, time_step=dt, ts_between_saves=self.step, remarks='test') def close(self): """close trajectory""" From 473f1c548c144c46e5c407e4474c97604979e955 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 07:58:49 +0200 Subject: [PATCH 041/101] enable new style reader/writer tests --- package/MDAnalysis/coordinates/DCD.py | 8 ++- testsuite/MDAnalysisTests/coordinates/base.py | 7 +-- .../MDAnalysisTests/coordinates/test_dcd.py | 47 ++++++++++++++++-- .../data/coordinates/create_data.py | 1 + .../MDAnalysisTests/data/coordinates/test.dcd | Bin 0 -> 1056 bytes testsuite/MDAnalysisTests/datafiles.py | 2 + 6 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 testsuite/MDAnalysisTests/data/coordinates/test.dcd diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index f0a6d152b37..8de3203e8f8 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -127,11 +127,15 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): self._ts_kwargs['dt'] = dt self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) frame = self._file.read() + # reset trajectory + if self._file.n_frames > 1: + self._file.seek(1) + else: + self._file.seek(0) self._frame = 0 self._frame_to_ts(frame, self.ts) # these should only be initialized once self.ts.dt = dt - self._file.seek(0) self.ts.dimensions = frame.unitcell if self.convert_units: self.convert_pos_from_native(self.ts.dimensions[:3]) @@ -183,7 +187,7 @@ def Writer(self, filename, n_atoms=None, **kwargs): """Return writer for trajectory format""" if n_atoms is None: n_atoms = self.n_atoms - return self._writer(filename, n_atoms=n_atoms, **kwargs) + return DCDWriter(filename, n_atoms=n_atoms, **kwargs) def _frame_to_ts(self, frame, ts): """convert a trr-frame to a mda TimeStep""" diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 9f6838b577f..9128dba3c91 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -243,13 +243,13 @@ def test_get_writer_2(self): assert_equal(W.n_atoms, 100) def test_dt(self): - assert_equal(self.reader.dt, self.ref.dt) + assert_almost_equal(self.reader.dt, self.ref.dt) def test_ts_dt_matches_reader(self): assert_equal(self.reader.ts.dt, self.reader.dt) def test_total_time(self): - assert_equal(self.reader.totaltime, self.ref.totaltime) + assert_almost_equal(self.reader.totaltime, self.ref.totaltime) def test_first_dimensions(self): self.reader.rewind() @@ -1068,7 +1068,8 @@ def assert_timestep_almost_equal(A, B, decimal=6, verbose=True): 'A.frame = {}, B.frame={}'.format( A.frame, B.frame)) - if A.time != B.time: + # if A.time != B.time: + if not np.allclose(A.time, B.time): raise AssertionError('A and B refer to different times: ' 'A.time = {}, B.time={}'.format( A.time, B.time)) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 73263b3fe98..ed99f453cff 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -31,12 +31,49 @@ assert_allclose, dec) from unittest import TestCase -from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, CRD, PRMncdf, NCDF) +from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, CRD, PRMncdf, NCDF, + COORDINATES_TOPOLOGY, COORDINATES_DCD) from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD) -from MDAnalysisTests.coordinates.base import BaseTimestepTest +# from MDAnalysisTests.coordinates.base import BaseTimestepTest + +from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, + BaseWriterTest, + assert_timestep_almost_equal) + from MDAnalysisTests import module_not_found, tempdir -from MDAnalysisTests.plugins.knownfailure import knownfailure +# from MDAnalysisTests.plugins.knownfailure import knownfailure + + +class DCDReference(BaseReference): + def __init__(self): + super(DCDReference, self).__init__() + self.trajectory = COORDINATES_DCD + self.topology = COORDINATES_TOPOLOGY + self.reader = mda.coordinates.DCD.DCDReader + self.writer = mda.coordinates.DCD.DCDWriter + self.ext = 'xtc' + self.prec = 3 + self.changing_dimensions = True + + +class TestDCDReader(MultiframeReaderTest): + def __init__(self, reference=None): + if reference is None: + reference = DCDReference() + super(TestDCDReader, self).__init__(reference) + + +class TestDCDWriter(BaseWriterTest): + def __init__(self, reference=None): + if reference is None: + reference = DCDReference() + super(TestDCDWriter, self).__init__(reference) + + +################ +# Legacy tests # +################ @attr('issue') def TestDCD_Issue32(): @@ -65,7 +102,7 @@ def test_with_statement(self): err_msg="with_statement: DCDReader does not read all frames") -class TestDCDReader(TestCase): +class TestDCDReader_old(TestCase): def setUp(self): self.universe = mda.Universe(PSF, DCD) self.dcd = self.universe.trajectory @@ -193,7 +230,7 @@ def test_DCDReader_set_dt(dt=100., frame=3): assert_almost_equal(u.trajectory.dt, dt, err_msg="trajectory.dt does not match set dt") -class TestDCDWriter(TestCase): +class TestDCDWriter_old(TestCase): def setUp(self): self.universe = mda.Universe(PSF, DCD) ext = ".dcd" diff --git a/testsuite/MDAnalysisTests/data/coordinates/create_data.py b/testsuite/MDAnalysisTests/data/coordinates/create_data.py index d9f8790a502..9a2b3ede853 100644 --- a/testsuite/MDAnalysisTests/data/coordinates/create_data.py +++ b/testsuite/MDAnalysisTests/data/coordinates/create_data.py @@ -32,6 +32,7 @@ def main(): create_test_trj(u, 'test.xtc') create_test_trj(u, 'test.trr') create_test_trj(u, 'test.gro') + create_test_trj(u, 'test.dcd') if __name__ == '__main__': main() diff --git a/testsuite/MDAnalysisTests/data/coordinates/test.dcd b/testsuite/MDAnalysisTests/data/coordinates/test.dcd new file mode 100644 index 0000000000000000000000000000000000000000..1c625476dba98235f12b1dc36c2161e9689e4724 GIT binary patch literal 1056 zcmd^+F;2rU6o!p}P7IOC09%d_DF?u55EBwrNe8wpg`Pm^y Date: Tue, 30 May 2017 08:04:22 +0200 Subject: [PATCH 042/101] fix up docs --- package/MDAnalysis/coordinates/DCD.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 8de3203e8f8..5288aea0564 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -67,8 +67,6 @@ Classes ------- -.. autoclass:: Timestep - :inherited-members: .. autoclass:: DCDReader :inherited-members: .. autoclass:: DCDWriter @@ -108,6 +106,8 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): trajectory filename convert_units : bool (optional) convert units to MDAnalysis units + dt : float (optional) + overwrite time delta stored in DCD **kwargs : dict General reader arguments. @@ -159,22 +159,13 @@ def _reopen(self): def _read_frame(self, i): """read frame i""" self._frame = i - 1 - try: - self._file.seek(i) - timestep = self._read_next_timestep() - except IOError: - warnings.warn('seek failed, recalculating offsets and retrying') - offsets = self._file.calc_offsets() - self._file.set_offsets(offsets) - self._read_offsets(store=True) - self._file.seek(i) - timestep = self._read_next_timestep() - return timestep + self._file.seek(i) + return self._read_next_timestep() def _read_next_timestep(self, ts=None): """copy next frame into timestep""" if self._frame == self.n_frames - 1: - raise IOError(errno.EIO, 'trying to go over trajectory limit') + raise IOError('trying to go over trajectory limit') if ts is None: ts = self.ts frame = self._file.read() @@ -226,6 +217,10 @@ def __init__(self, filename, n_atoms, convert_units=True, step=1, dt=None, **kwa number of atoms to be written convert_units : bool (optional) convert from MDAnalysis units to format specific units + step : int (optional) + number of steps between frames to be written + dt : float (optional) + use this time step in DCD. If ``None`` guess from written TimeStep **kwargs : dict General writer arguments """ From 9e75910afe9c58b8031b15c32a508e71fd89f880 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 08:05:36 +0200 Subject: [PATCH 043/101] remove old C dcd reader --- package/MDAnalysis/coordinates/src/dcd.c | 949 ----------------------- 1 file changed, 949 deletions(-) delete mode 100644 package/MDAnalysis/coordinates/src/dcd.c diff --git a/package/MDAnalysis/coordinates/src/dcd.c b/package/MDAnalysis/coordinates/src/dcd.c deleted file mode 100644 index 782c163b35a..00000000000 --- a/package/MDAnalysis/coordinates/src/dcd.c +++ /dev/null @@ -1,949 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode:nil; -*- */ -/* vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 */ -/* - - MDAnalysis --- http://www.mdanalysis.org - Copyright (c) 2006-2016 The MDAnalysis Development Team and contributors - (see the file AUTHORS for the full list of names) - - Released under the GNU Public Licence, v2 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. - - 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 - -*/ - - -/* I have to fix error handling in these functions */ - -#include -#include - -#include "readdcd.h" - -typedef struct { - fio_fd fd; - fio_size_t header_size; - int natoms; - int nsets; - int setsread; - int istart; - int nsavc; - double delta; - int nfixed; - int *freeind; - float *fixedcoords; - int reverse; - int charmm; - int first; - int with_unitcell; -} dcdhandle; - - -int jump_to_frame(dcdhandle *dcd, int frame); - -static PyObject * -__write_dcd_header(PyObject *self, PyObject *args) -{ - /* At this point we assume the file has been opened for writing */ - PyObject *temp = NULL; - dcdhandle *dcd = NULL; - fio_fd fd; - int rc = 0; - int natoms = 0; - const char *remarks = "DCD"; - int istart = 0; /* starting timestep of DCD file */ - int nsavc = 1; /* number of timesteps between written DCD frames */ - double delta = 1.0; /* length of a timestep */ - int with_unitcell = 1; /* contains unit cell information (charmm format) */ - int charmm = DCD_IS_CHARMM; /* charmm-formatted DCD file */ - if (with_unitcell) - charmm |= DCD_HAS_EXTRA_BLOCK; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "Oi|iids", &self, &natoms, &istart, &nsavc, &delta, &remarks) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "i|iids", &natoms, &istart, &nsavc, &delta, &remarks) ) - return NULL; - } - - // Get the file object from the class - if (!PyObject_HasAttrString(self, "dcdfile")) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "dcdfile is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "dcdfile")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "dcdfile is not an attribute"); - return NULL; - } - - if (!PyFile_CheckExact(temp)) { - // Raise exception - PyErr_SetString(PyExc_TypeError, "dcdfile does not refer to a file object"); - Py_DECREF(temp); - return NULL; - } - fd = fileno(PyFile_AsFile(temp)); - // No longer need the reference to temp - Py_DECREF(temp); - - dcd = (dcdhandle *)malloc(sizeof(dcdhandle)); - memset(dcd, 0, sizeof(dcdhandle)); - dcd->fd = fd; - - if ((rc = write_dcdheader(dcd->fd, remarks, natoms, istart, nsavc, delta, with_unitcell, charmm)) < 0) { - PyErr_SetString(PyExc_IOError, "Cannot write header of DCD file"); - free(dcd); - return NULL; - } - - dcd->natoms = natoms; - dcd->nsets = 0; - dcd->istart = istart; - dcd->nsavc = nsavc; - dcd->delta = delta; - dcd->with_unitcell = with_unitcell; - dcd->charmm = charmm; - temp = PyCObject_FromVoidPtr(dcd, free); // Creates a New Reference - if (PyObject_SetAttrString(self, "_dcd_C_ptr", temp) == -1) { - // Raise exception - who knows what exception to raise?? - PyErr_SetString(PyExc_AttributeError, "Could not create attribute _dcd_C_ptr"); - Py_DECREF(temp); - return NULL; - } - Py_DECREF(temp); - - // For debugging purposes - temp = PyBuffer_FromMemory(dcd, sizeof(dcdhandle)); // Creates a New Reference - if (PyObject_SetAttrString(self, "_dcd_C_str", temp) == -1) { - // Raise exception - who knows what exception to raise?? - PyErr_SetString(PyExc_AttributeError, "Could not create attribute _dcd_C_str"); - Py_DECREF(temp); - return NULL; - } - Py_DECREF(temp); - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -__write_next_frame(PyObject *self, PyObject *args) -{ - dcdhandle *dcd; - PyObject *temp; - PyArrayObject *x, *y, *z, *uc; - int rc, curstep; - float* uc_array; - double unitcell[6]; - - if (!self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "OO!O!O!O!", &self, &PyArray_Type, &x, &PyArray_Type, &y, &PyArray_Type, &z, &PyArray_Type, &uc) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "O!O!O!O!", &PyArray_Type, &x, &PyArray_Type, &y, &PyArray_Type, &z, &PyArray_Type, &uc) ) - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - Py_DECREF(temp); - dcd->nsets++; - curstep = dcd->istart + dcd->nsets * dcd->nsavc; - - uc_array = (float*) uc->data; - unitcell[0] = uc_array[0]; /* A */ - unitcell[2] = uc_array[2]; /* B */ - unitcell[5] = uc_array[5]; /* C */ - /* write angle cosines with NAMD ordering [orbeckst] */ - /* (changed in MDAnalysis 0.9.0) */ - unitcell[4] = sin((M_PI_2 / 90.0 ) * (90.0 - uc_array[4])); /* cos(alpha) */ - unitcell[3] = sin((M_PI_2 / 90.0 ) * (90.0 - uc_array[3])); /* cos(beta) */ - unitcell[1] = sin((M_PI_2 / 90.0 ) * (90.0 - uc_array[1])); /* cos(gamma) */ - - if ((rc = write_dcdstep(dcd->fd, dcd->nsets, curstep, dcd->natoms, (float*)x->data, (float*)y->data, (float*)z->data, - dcd->with_unitcell ? unitcell: NULL, - dcd->charmm)) < 0) - { - PyErr_SetString(PyExc_IOError, "Could not write timestep to dcd file"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -__finish_dcd_write(PyObject *self, PyObject *args) -{ - //PyObject* temp; - //dcdhandle *dcd; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "O", &self) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "") ) - return NULL; - } - - /*if ( !PyObject_HasAttrString(self, "_dcd_C_ptr") ) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - free(dcd); - Py_DECREF(temp);*/ - - if ( PyObject_DelAttrString(self, "_dcd_C_ptr") == -1) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject * -__read_dcd_header(PyObject *self, PyObject *args) -{ - - /* At this point we assume the file has been checked for existence and opened, and the DCD class - * contains a reference to the file (which can be used to retrieve the file descriptor) - */ - PyObject *temp; - fio_fd fd; - int rc; - char *remarks = NULL; - int len_remarks = 0; - dcdhandle *dcd = NULL; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "O", &self) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "") ) - return NULL; - } - - // Get the file object from the class - if (!PyObject_HasAttrString(self, "dcdfile")) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "dcdfile is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "dcdfile")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "dcdfile is not an attribute"); - return NULL; - } - - if (!PyFile_CheckExact(temp)) { - // Raise exception - PyErr_SetString(PyExc_TypeError, "dcdfile does not refer to a file object"); - goto error; - } - fd = fileno(PyFile_AsFile(temp)); - // No longer need the reference to temp - Py_DECREF(temp); - - dcd = (dcdhandle *)malloc(sizeof(dcdhandle)); - memset(dcd, 0, sizeof(dcdhandle)); - dcd->fd = fd; - - if ((rc = read_dcdheader(dcd->fd, &dcd->natoms, &dcd->nsets, &dcd->istart, - &dcd->nsavc, &dcd->delta, &dcd->nfixed, &dcd->freeind, - &dcd->fixedcoords, &dcd->reverse, &dcd->charmm, &remarks, &len_remarks))) { - // Raise exception - PyErr_SetString(PyExc_IOError, "Cannot read DCD header"); - goto error; - } - - /* - * Check that the file is big enough to really hold all the frames it claims to have - */ - { - off_t ndims, firstframesize, framesize, extrablocksize; - off_t filesize; - struct stat stbuf; - - extrablocksize = dcd->charmm & DCD_HAS_EXTRA_BLOCK ? 48 + 8 : 0; - ndims = dcd->charmm & DCD_HAS_4DIMS ? 4 : 3; - firstframesize = (dcd->natoms+2) * ndims * sizeof(float) + extrablocksize; - framesize = (dcd->natoms-dcd->nfixed+2) * ndims * sizeof(float) - + extrablocksize; - - // Get filesize from file descriptor - memset(&stbuf, 0, sizeof(struct stat)); - if (fstat(dcd->fd, &stbuf)) { - PyErr_SetString(PyExc_IOError, "Could not stat file"); - goto error; - } - - // Size of header - dcd->header_size = fio_ftell(dcd->fd); - - /* - * It's safe to use ftell, even though ftell returns a long, because the - * header size is < 4GB. - */ - filesize = stbuf.st_size - fio_ftell(dcd->fd) - firstframesize; - if (filesize < 0) { - PyErr_SetString(PyExc_IOError, "DCD file appears to contain no timesteps"); - goto error; - } - dcd->nsets = filesize / framesize + 1; - dcd->setsread = 0; - } - - // We are at the first frame - dcd->first = 1; - - temp = Py_BuildValue("s#", remarks, len_remarks); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "remarks", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute remarks"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("i", dcd->natoms); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "n_atoms", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute n_atoms"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("i", dcd->nsets); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "n_frames", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute n_frames"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("i", dcd->nfixed); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "fixed", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute fixed"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("i", dcd->istart); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "start_timestep", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute fixed"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("i", dcd->nsavc); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "skip_timestep", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute fixed"); - goto error; - } - Py_DECREF(temp); - temp = Py_BuildValue("d", dcd->delta); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "delta", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute fixed"); - goto error; - } - Py_DECREF(temp); - - temp = PyCObject_FromVoidPtr(dcd, NULL); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "_dcd_C_ptr", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute _dcd_C_ptr"); - goto error; - } - - if ((dcd->charmm & DCD_IS_CHARMM) && (dcd->charmm & DCD_HAS_EXTRA_BLOCK)) { - dcd->with_unitcell = 1; - temp = Py_True; - } else { - temp = Py_False; - } - Py_INCREF(temp); - if (PyObject_SetAttrString(self, "periodic", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute periodic"); - goto error; - } - Py_DECREF(temp); - - // For debugging purposes - temp = PyBuffer_FromMemory(dcd, sizeof(dcdhandle)); - if (temp == NULL) goto error; - if (PyObject_SetAttrString(self, "_dcd_C_str", temp) == -1) { - PyErr_SetString(PyExc_AttributeError, "Could not create attribute _dcd_C_str"); - goto error; - } - Py_DECREF(temp); - - Py_INCREF(Py_None); - return Py_None; - - error: - Py_XDECREF(temp); - if (dcd != NULL) free(dcd); - if (remarks != NULL) free(remarks); - return NULL; -} - -static PyObject * -__read_timeseries(PyObject *self, PyObject *args) -{ - PyObject *temp = NULL; - PyArrayObject *coord = NULL; - PyListObject *atoms = NULL; - int lowerb = 0, upperb = 0, range=0; - float *tempX = NULL, *tempY = NULL, *tempZ = NULL; - int rc; - int i, j, index; - int n_atoms = 0, n_frames = 0; - - /* Stop = -1 is incorrect and causes an error, look for a fix of this in line - 469 */ - int start = 0, stop = -1, step = 1, numskip = 0, remaining_frames=0; - dcdhandle *dcd = NULL; - int *atomlist = NULL; - npy_intp dimensions[3]; - float unitcell[6]; - const char* format = "afc"; - - if (!self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "OO!|iiis", &self, &PyList_Type, &atoms, &start, &stop, &step, &format) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "O!|iiis", &PyList_Type, &atoms, &start, &stop, &step, &format) ) - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - Py_DECREF(temp); - - n_frames = ((stop-start) / step); - if ((stop-start) % step > 0) { n_frames++; } - //n_frames = dcd->nsets / skip; - n_atoms = PyList_Size((PyObject*)atoms); - if (n_atoms == 0) { - PyErr_SetString(PyExc_Exception, "No atoms passed into _read_timeseries function"); - return NULL; - } - atomlist = (int*)malloc(sizeof(int)*n_atoms); - memset(atomlist, 0, sizeof(int)*n_atoms); - - // Get the atom indexes - for (i=0;ifd, dcd->header_size, FIO_SEEK_SET); - dcd->setsread = 0; - dcd->first = 1; - - // Jump to starting frame - jump_to_frame(dcd, start); - - // Now read through trajectory and get the atom pos - tempX = (float*)malloc(sizeof(float)*range); - tempY = (float*)malloc(sizeof(float)*range); - tempZ = (float*)malloc(sizeof(float)*range); - if ((tempX == NULL) | (tempY == NULL) || (tempZ == NULL)) { - PyErr_SetString(PyExc_MemoryError, "Can't allocate temporary space for coordinate arrays"); - goto error; - } - - remaining_frames = stop-start; - for (i=0;i 1 && i>0) { - // Check if we have fixed atoms - // XXX not done - /* Figure out how many steps to step over, if step = n, np array - slicing treats this as skip over n-1, read the nth. */ - numskip = step -1; - /* If the number to skip is greater than the number of frames left - to be jumped over, just take one more step to reflect np slicing - if there is a remainder, guaranteed to have at least one more - frame. - */ - if(remaining_frames < numskip){ - numskip = 1; - } - rc = skip_dcdstep(dcd->fd, dcd->natoms, dcd->nfixed, dcd->charmm, numskip); - if (rc < 0) { - // return an exception - PyErr_SetString(PyExc_IOError, "Error skipping frame from DCD file"); - goto error; - } - } - // on first iteration, numskip == 0, first set is always read. - dcd->setsread += numskip; - //now read from subset - rc = read_dcdsubset(dcd->fd, dcd->natoms, lowerb, upperb, tempX, tempY, tempZ, - unitcell, dcd->nfixed, dcd->first, dcd->freeind, dcd->fixedcoords, - dcd->reverse, dcd->charmm); - dcd->first = 0; - dcd->setsread++; - remaining_frames = stop - dcd->setsread; - if (rc < 0) { - // return an exception - PyErr_SetString(PyExc_IOError, "Error reading frame from DCD file"); - goto error; - - } - - - // Copy into Numeric array only those atoms we are interested in - for (j=0;jdata + a*coord->strides[0] + b*coord->strides[1] + c*coord->strides[2]) - */ - if (strncasecmp(format, "afc", 3) == 0) { - *(double*)(coord->data + j*coord->strides[0] + i*coord->strides[1] + 0*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + j*coord->strides[0] + i*coord->strides[1] + 1*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + j*coord->strides[0] + i*coord->strides[1] + 2*coord->strides[2]) = tempZ[index]; - } else if (strncasecmp(format, "acf", 3) == 0) { - *(double*)(coord->data + j*coord->strides[0] + 0*coord->strides[1] + i*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + j*coord->strides[0] + 1*coord->strides[1] + i*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + j*coord->strides[0] + 2*coord->strides[1] + i*coord->strides[2]) = tempZ[index]; - } else if (strncasecmp(format, "fac", 3) == 0) { - *(double*)(coord->data + i*coord->strides[0] + j*coord->strides[1] + 0*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + i*coord->strides[0] + j*coord->strides[1] + 1*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + i*coord->strides[0] + j*coord->strides[1] + 2*coord->strides[2]) = tempZ[index]; - } else if (strncasecmp(format, "fca", 3) == 0) { - *(double*)(coord->data + i*coord->strides[0] + 0*coord->strides[1] + j*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + i*coord->strides[0] + 1*coord->strides[1] + j*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + i*coord->strides[0] + 2*coord->strides[1] + j*coord->strides[2]) = tempZ[index]; - } else if (strncasecmp(format, "caf", 3) == 0) { - *(double*)(coord->data + 0*coord->strides[0] + j*coord->strides[1] + i*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + 1*coord->strides[0] + j*coord->strides[1] + i*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + 2*coord->strides[0] + j*coord->strides[1] + i*coord->strides[2]) = tempZ[index]; - } else if (strncasecmp(format, "cfa", 3) == 0) { - *(double*)(coord->data + 0*coord->strides[0] + i*coord->strides[1] + j*coord->strides[2]) = tempX[index]; - *(double*)(coord->data + 1*coord->strides[0] + i*coord->strides[1] + j*coord->strides[2]) = tempY[index]; - *(double*)(coord->data + 2*coord->strides[0] + i*coord->strides[1] + j*coord->strides[2]) = tempZ[index]; - } - } - // Check if we've been interupted by the user - if (PyErr_CheckSignals() == 1) goto error; - } - - // Reset trajectory - rc = fio_fseek(dcd->fd, dcd->header_size, FIO_SEEK_SET); - dcd->setsread = 0; - dcd->first = 1; - free(atomlist); - free(tempX); - free(tempY); - free(tempZ); - return PyArray_Return(coord); - - error: - // Reset trajectory - rc = fio_fseek(dcd->fd, dcd->header_size, FIO_SEEK_SET); - dcd->setsread = 0; - dcd->first = 1; - Py_XDECREF(coord); - if (atomlist != NULL) free(atomlist); - if (tempX != NULL) free(tempX); - if (tempY != NULL) free(tempY); - if (tempZ != NULL) free(tempZ); - return NULL; -} - - -static PyObject * -__read_next_frame(PyObject *self, PyObject *args) -{ - dcdhandle *dcd; - PyObject *temp; - PyArrayObject *x, *y, *z, *uc; - int skip=1; - int rc,numskip; - float* unitcell; - float alpha, beta, gamma; - - if (!self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "OO!O!O!O!|i", &self, &PyArray_Type, &x, &PyArray_Type, &y, &PyArray_Type, &z, &PyArray_Type, &uc, &skip) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "O!O!O!O!|i", &PyArray_Type, &x, &PyArray_Type, &y, &PyArray_Type, &z, &PyArray_Type, &uc, &skip) ) - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - Py_DECREF(temp); - - unitcell = (float*) uc->data; - unitcell[0] = unitcell[2] = unitcell[5] = 0.0f; - unitcell[1] = unitcell[3] = unitcell[4] = 90.0f; - - /* Check for EOF here; that way all EOF's encountered later must be errors */ - if (dcd->setsread == dcd->nsets) { - // Is this an exception? - PyErr_SetString(PyExc_IOError, "End of file reached for dcd file"); - return NULL; - //return MOLFILE_EOF; - } - if (skip > 1) { - if (dcd->first && dcd->nfixed) { - /* We can't just skip it because we need the fixed atom coordinates */ - rc = read_dcdstep(dcd->fd, dcd->natoms, (float*)x->data, (float*)y->data, (float*)z->data, - unitcell, dcd->nfixed, dcd->first, dcd->freeind, dcd->fixedcoords, - dcd->reverse, dcd->charmm); - dcd->first = 0; - if (rc < 0) { - // return an exception - PyErr_SetString(PyExc_IOError, "Error reading first frame from DCD file"); - //fprintf(stderr, "read_dcdstep returned %d\n", rc); - return NULL; - //return MOLFILE_ERROR; - } - dcd->setsread++; - temp = Py_BuildValue("i", dcd->setsread); - return temp; - //return rc; /* XXX this needs to be updated */ - } - dcd->first = 0; - // Figure out how many steps to skip - numskip = skip - (dcd->setsread % skip) - 1; - /* XXX this needs to be changed */ - rc = skip_dcdstep(dcd->fd, dcd->natoms, dcd->nfixed, dcd->charmm, numskip); - if (rc < 0) { - // return an exception - PyErr_SetString(PyExc_IOError, "Error skipping frame from DCD file"); - //fprintf(stderr, "read_dcdstep returned %d\n", rc); - return NULL; - //return MOLFILE_ERROR; - } - dcd->setsread+=numskip; - } - rc = read_dcdstep(dcd->fd, dcd->natoms, (float*)x->data, (float*)y->data, (float*)z->data, unitcell, - dcd->nfixed, dcd->first, dcd->freeind, dcd->fixedcoords, - dcd->reverse, dcd->charmm); - dcd->first = 0; - dcd->setsread++; - if (rc < 0) { - // return an exception - PyErr_SetString(PyExc_IOError, "Error reading frame from DCD file"); - //fprintf(stderr, "read_dcdstep returned %d\n", rc); - return NULL; - //return MOLFILE_ERROR; - } - - if (unitcell[1] >= -1.0 && unitcell[1] <= 1.0 && - unitcell[3] >= -1.0 && unitcell[3] <= 1.0 && - unitcell[4] >= -1.0 && unitcell[4] <= 1.0) { - /* This file was generated by Charmm, or by NAMD > 2.5, with the angle */ - /* cosines of the periodic cell angles written to the DCD file. */ - /* This formulation improves rounding behavior for orthogonal cells */ - /* so that the angles end up at precisely 90 degrees, unlike acos(). */ - /* (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; */ - /* see Issue 187) */ - alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; - beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; - gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; - } else { - /* This file was likely generated by NAMD 2.5 and the periodic cell */ - /* angles are specified in degrees rather than angle cosines. */ - alpha = unitcell[4]; - beta = unitcell[3]; - gamma = unitcell[1]; - } - unitcell[4] = alpha; - unitcell[3] = beta; - unitcell[1] = gamma; - - // Return the frame read - temp = Py_BuildValue("i", dcd->setsread); - return temp; -} - -static PyObject * -__finish_dcd_read(PyObject *self, PyObject *args) -{ - PyObject* temp; - dcdhandle *dcd; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "O", &self) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "") ) - return NULL; - } - - if ( !PyObject_HasAttrString(self, "_dcd_C_ptr") ) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - - close_dcd_read(dcd->freeind, dcd->fixedcoords); - free(dcd); - Py_DECREF(temp); - Py_INCREF(Py_None); - return Py_None; -} -int jump_to_frame(dcdhandle *dcd, int frame) -{ - int rc; - if (frame > dcd->nsets) { - return -1; - } - // Calculate file offset - { - off_t extrablocksize, ndims, firstframesize, framesize; - off_t pos; - extrablocksize = dcd->charmm & DCD_HAS_EXTRA_BLOCK ? 48 + 8 : 0; - ndims = dcd->charmm & DCD_HAS_4DIMS ? 4 : 3; - firstframesize = (dcd->natoms+2) * ndims * sizeof(float) + extrablocksize; - framesize = (dcd->natoms-dcd->nfixed+2) * ndims * sizeof(float) - + extrablocksize; - // Use zero indexing - if (frame == 0) { - pos = dcd->header_size; - dcd->first = 1; - } - else { - pos = dcd->header_size + firstframesize + framesize * (frame-1); - dcd->first = 0; - } - rc = fio_fseek(dcd->fd, pos, FIO_SEEK_SET); - } - dcd->setsread = frame; - return rc; -} - -static PyObject * -__jump_to_frame(PyObject *self, PyObject *args) -{ - PyObject* temp; - dcdhandle *dcd; - int frame; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "Oi", &self, &frame) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "i", &frame) ) - return NULL; - } - - if ( !PyObject_HasAttrString(self, "_dcd_C_ptr") ) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - Py_DECREF(temp); - - /*if (frame > dcd->nsets) { - PyErr_SetString(PyExc_IndexError, "Invalid frame"); - return NULL; - } - - // Calculate file offset - { - off_t extrablocksize, ndims, firstframesize, framesize; - off_t pos; - extrablocksize = dcd->charmm & DCD_HAS_EXTRA_BLOCK ? 48 + 8 : 0; - ndims = dcd->charmm & DCD_HAS_4DIMS ? 4 : 3; - firstframesize = (dcd->natoms+2) * ndims * sizeof(float) + extrablocksize; - framesize = (dcd->natoms-dcd->nfixed+2) * ndims * sizeof(float) - + extrablocksize; - // Use zero indexing - if (frame == 0) { - pos = dcd->header_size; - } - else { - pos = dcd->header_size + firstframesize + framesize * (frame-1); - } - rc = fio_fseek(dcd->fd, pos, FIO_SEEK_SET); - } - dcd->setsread = frame; - dcd->first = 0;*/ - jump_to_frame(dcd, frame); - - temp = Py_BuildValue("i", dcd->setsread); - return temp; -} - -static PyObject * -__reset_dcd_read(PyObject *self, PyObject *args) -{ - PyObject* temp; - dcdhandle *dcd; - int rc; - - if (! self) { - /* we were in fact called as a module function, try to retrieve - a matching object from args */ - if( !PyArg_ParseTuple(args, "O", &self) ) - return NULL; - } else { - /* we were obviously called as an object method so args should - only have the int value. */ - if( !PyArg_ParseTuple(args, "") ) - return NULL; - } - - if ( !PyObject_HasAttrString(self, "_dcd_C_ptr") ) { - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - if ((temp = PyObject_GetAttrString(self, "_dcd_C_ptr")) == NULL) { // This gives me a New Reference - // Raise exception - PyErr_SetString(PyExc_AttributeError, "_dcd_C_ptr is not an attribute"); - return NULL; - } - - dcd = (dcdhandle*)PyCObject_AsVoidPtr(temp); - rc = fio_fseek(dcd->fd, dcd->header_size, FIO_SEEK_SET); - dcd->setsread = 0; - dcd->first = 1; - Py_DECREF(temp); - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef DCDMethods[] = { - {"__write_dcd_header", __write_dcd_header, METH_VARARGS, "Write a DCD header."}, - {"__write_next_frame", __write_next_frame, METH_VARARGS, "Write the next timestep."}, - {"__finish_dcd_write", __finish_dcd_write, METH_VARARGS, "Clean up and data for handling dcd file."}, - {"__read_dcd_header", __read_dcd_header, METH_VARARGS, "Read in a DCD header."}, - {"__read_next_frame", __read_next_frame, METH_VARARGS, "Read in the next timestep."}, - {"__jump_to_frame", __jump_to_frame, METH_VARARGS, "Jump to specified timestep."}, - {"__reset_dcd_read", __reset_dcd_read, METH_VARARGS, "Reset dcd file reading."}, - {"__finish_dcd_read", __finish_dcd_read, METH_VARARGS, "Clean up any data for handling dcd file."}, - {"__read_timeseries", __read_timeseries, METH_VARARGS, "Return a Numpy array of the coordinates for a set of atoms for the whole trajectory."}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - -PyMODINIT_FUNC -init_dcdmodule(void) -{ - (void) Py_InitModule("_dcdmodule", DCDMethods); - import_array(); -} From e64127fc30652049e5aa76f02842ddc4f68e2da6 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 08:11:09 +0200 Subject: [PATCH 044/101] remove duplicate tests --- .../MDAnalysisTests/coordinates/test_dcd.py | 130 ++++-------------- 1 file changed, 25 insertions(+), 105 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index ed99f453cff..c43fed19014 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -21,6 +21,7 @@ # from __future__ import absolute_import import MDAnalysis as mda +from MDAnalysis.coordinates.DCD import DCDReader import numpy as np import os from six.moves import zip, range @@ -63,28 +64,12 @@ def __init__(self, reference=None): reference = DCDReference() super(TestDCDReader, self).__init__(reference) + @staticmethod + def test_empty_dcd(): + assert_raises(IOError, mda.Universe, PSF, DCD_empty) -class TestDCDWriter(BaseWriterTest): - def __init__(self, reference=None): - if reference is None: - reference = DCDReference() - super(TestDCDWriter, self).__init__(reference) - - -################ -# Legacy tests # -################ - -@attr('issue') -def TestDCD_Issue32(): - """Test for Issue 32: 0-size dcds lead to a segfault: now caught with - IOError""" - assert_raises(IOError, mda.Universe, PSF, DCD_empty) - - -class TestDCDReaderClass(TestCase): - def test_with_statement(self): - from MDAnalysis.coordinates.DCD import DCDReader + @staticmethod + def test_with_statement(): try: with DCDReader(DCD) as trj: @@ -102,6 +87,22 @@ def test_with_statement(self): err_msg="with_statement: DCDReader does not read all frames") +class TestDCDWriter(BaseWriterTest): + def __init__(self, reference=None): + if reference is None: + reference = DCDReference() + super(TestDCDWriter, self).__init__(reference) + + +################ +# Legacy tests # +################ + + +class TestDCDReaderClass(TestCase): + pass + + class TestDCDReader_old(TestCase): def setUp(self): self.universe = mda.Universe(PSF, DCD) @@ -122,10 +123,6 @@ def test_next_dcd(self): self.dcd.next() assert_equal(self.ts.frame, 1, "loading frame 1") - def test_jump_dcd(self): - self.dcd[15] # index is 0-based and frames are 0-based - assert_equal(self.ts.frame, 15, "jumping to frame 15") - def test_jump_lastframe_dcd(self): self.dcd[-1] assert_equal(self.ts.frame, 97, "indexing last frame with dcd[-1]") @@ -155,45 +152,6 @@ def test_reverse_dcd(self): assert_equal(frames, list(range(20, 5, -1)), "reversing dcd [20:5:-1]") - def test_n_atoms(self): - assert_equal(self.universe.trajectory.n_atoms, 3341, - "wrong number of atoms") - - def test_n_frames(self): - assert_equal(self.universe.trajectory.n_frames, 98, - "wrong number of frames in dcd") - - def test_dt(self): - assert_almost_equal(self.universe.trajectory.dt, - 1.0, - 4, - err_msg="wrong timestep dt") - - def test_totaltime(self): - # test_totaltime(): need to reduce precision because dt is only precise - # to ~4 decimals and accumulating the inaccuracy leads to even lower - # precision in the totaltime (consequence of fixing Issue 64) - assert_almost_equal(self.universe.trajectory.totaltime, - 97.0, - 3, - err_msg="wrong total length of AdK trajectory") - - def test_frame(self): - self.dcd[15] # index is 0-based and frames are 0-based - assert_equal(self.universe.trajectory.frame, 15, "wrong frame number") - - def test_time(self): - self.dcd[15] # index is 0-based and frames are 0-based - assert_almost_equal(self.universe.trajectory.time, - 15.0, - 5, - err_msg="wrong time of frame") - - def test_volume(self): - assert_almost_equal(self.ts.volume, 0.0, 3, - err_msg="wrong volume for unitcell (no unitcell " - "in DCD so this should be 0)") - # def test_timeseries_slicing(self): # # check that slicing behaves correctly # # should before issue #914 resolved @@ -230,7 +188,7 @@ def test_DCDReader_set_dt(dt=100., frame=3): assert_almost_equal(u.trajectory.dt, dt, err_msg="trajectory.dt does not match set dt") -class TestDCDWriter_old(TestCase): +class TestDCDWriter_old(object): def setUp(self): self.universe = mda.Universe(PSF, DCD) ext = ".dcd" @@ -247,26 +205,6 @@ def tearDown(self): del self.Writer del self.tmpdir - @attr('issue') - def test_write_trajectory(self): - """Test writing DCD trajectories (Issue 50)""" - t = self.universe.trajectory - W = self.Writer(self.outfile, t.n_atoms, dt=t.dt, step=t.skip_timestep) - for ts in self.universe.trajectory: - W.write_next_timestep(ts) - W.close() - - uw = mda.Universe(PSF, self.outfile) - - # check that the coordinates are identical for each time step - for orig_ts, written_ts in zip(self.universe.trajectory, - uw.trajectory): - assert_array_almost_equal(written_ts._pos, orig_ts._pos, 3, - err_msg="coordinate mismatch between " - "original and written trajectory at " - "frame %d (orig) vs %d (written)" % ( - orig_ts.frame, written_ts.frame)) - def test_dt(self): DT = 5.0 t = self.universe.trajectory @@ -314,24 +252,8 @@ def test_single_frame(self): 3, err_msg="coordinates do not match") - def test_with_statement(self): - u = mda.Universe(PSF, CRD) - try: - with mda.Writer(self.outfile, u.atoms.n_atoms) as W: - W.write(u.atoms) - except AttributeError: # misses __exit__ - raise AssertionError("DCDWriter: does not support with statement") - w = mda.Universe(PSF, self.outfile) - assert_equal(w.trajectory.n_frames, 1, - "with_statement: single frame trajectory has wrong " - "number of frames") - assert_almost_equal(w.atoms.positions, - u.atoms.positions, - 3, - err_msg="with_statement: coordinates do not match") - -class TestDCDWriter_Issue59(TestCase): +class TestDCDWriter_Issue59(object): def setUp(self): """Generate input xtc.""" self.u = mda.Universe(PSF, DCD) @@ -403,9 +325,7 @@ def test_OtherWriter(self): class _TestDCDReader_TriclinicUnitcell(TestCase): - __test__ = False - def setUp(self): self.u = mda.Universe(self.topology, self.trajectory) self.tempdir = tempdir.TempDir() @@ -458,7 +378,7 @@ class TestDCDReader_NAMD_Unitcell(_TestDCDReader_TriclinicUnitcell, __test__ = True -class TestNCDF2DCD(TestCase): +class TestNCDF2DCD(object): @dec.skipif(module_not_found("netCDF4"), "Test skipped because netCDF is not available.") def setUp(self): From 188813003f13e6e3732221f7f3c98b7c2b97f7cf Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 08:19:33 +0200 Subject: [PATCH 045/101] add header property --- package/MDAnalysis/lib/formats/libdcd.pyx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 7c147944ff9..32e753a85fb 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -133,6 +133,10 @@ cdef class DCDFile: def __dealloc__(self): self.close() + if self.fixedcoords != NULL: + free(self.fixedcoords) + if self.freeind != NULL: + free(self.freeind) def __enter__(self): """Support context manager""" @@ -245,6 +249,18 @@ cdef class DCDFile: return py_remarks + @property + def header(self): + return {'n_atoms': self.n_atoms, + 'nsets': self.nsets, + 'istart': self.istart, + 'nsavc': self.nsacv, + 'delta': self.delta, + 'nfixed': self.nfixed, + 'reverse_endian': self.reverse_endian, + 'charmm': self.charmm, + 'remarks': self.remarks} + def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 self._firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize From 911bff5b3c76d99d2d2c0b4ebaba9230e90e3406 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 17:32:29 +0200 Subject: [PATCH 046/101] remove old module from setup.py --- package/setup.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/package/setup.py b/package/setup.py index 44000be8a43..c0f6b7f2164 100755 --- a/package/setup.py +++ b/package/setup.py @@ -295,11 +295,6 @@ def extensions(config): include_dirs = [get_numpy_include] - dcd = MDAExtension('coordinates._dcdmodule', - ['MDAnalysis/coordinates/src/dcd.c'], - include_dirs=include_dirs + ['MDAnalysis/coordinates/include'], - define_macros=define_macros, - extra_compile_args=extra_compile_args) libdcd = MDAExtension('lib.formats.libdcd', ['MDAnalysis/lib/formats/libdcd' + source_suffix], include_dirs=include_dirs + ['MDAnalysis/lib/formats/include'], @@ -357,7 +352,7 @@ def extensions(config): include_dirs = include_dirs+['MDAnalysis/analysis/encore/dimensionality_reduction/include'], libraries=["m"], extra_compile_args=["-O3", "-ffast-math","-std=c99"]) - pre_exts = [dcd, libdcd, distances, distances_omp, qcprot, + pre_exts = [libdcd, distances, distances_omp, qcprot, transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred] cython_generated = [] From 4ea19bb9fc276aa96047fe6ee5f8d01ba18f13d0 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 17:33:56 +0200 Subject: [PATCH 047/101] move unitcell handling from libdcd to DCDReader - remove unitcell handling from libdcd - add dimensions handling into MDAnalysis DCD reader - fix box tests for libdcd --- package/MDAnalysis/coordinates/DCD.py | 51 ++++++++++++++++++- package/MDAnalysis/lib/formats/libdcd.pyx | 44 ---------------- .../MDAnalysisTests/formats/test_libdcd.py | 14 ++--- 3 files changed, 56 insertions(+), 53 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 5288aea0564..76c22d1c787 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -89,6 +89,10 @@ from ..exceptions import NoDataError from . import base from . import core +from ..lib.formats.libdcd import DCDFile +from ..lib.mdamath import triclinic_box +# dcdtimeseries is implemented with Pyrex - hopefully all dcd reading functionality can move to pyrex +# from . import dcdtimeseries class DCDReader(base.ReaderBase): @@ -186,7 +190,44 @@ def _frame_to_ts(self, frame, ts): ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() - ts.dimensions = frame.unitcell + unitcell = frame.unitcell + M_PI_2 = np.pi / 2 + if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and + unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and + unitcell[4] >= -1.0 and unitcell[4] <= 1.0): + # This file was generated by Charmm, or by NAMD > 2.5, with the angle + # cosines of the periodic cell angles written to the DCD file. + # This formulation improves rounding behavior for orthogonal cells + # so that the angles end up at precisely 90 degrees, unlike acos(). + # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; + # see Issue 187) */ + alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / M_PI_2; + beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / M_PI_2; + gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / M_PI_2; + else: + # This file was likely generated by NAMD 2.5 and the periodic cell + # angles are specified in degrees rather than angle cosines. + alpha = unitcell[4]; + beta = unitcell[3]; + gamma = unitcell[1]; + + unitcell[4] = alpha; + unitcell[3] = beta; + unitcell[1] = gamma; + + # The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` + _ts_order = [0, 2, 5, 4, 3, 1] + uc = np.take(unitcell, _ts_order) + # heuristic sanity check: uc = A,B,C,alpha,beta,gamma + # XXX: should we worry about these comparisons with floats? + if np.any(uc < 0.) or np.any(uc[3:] > 180.): + # might be new CHARMM: box matrix vectors + H = unitcell + e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] + uc = triclinic_box(e1, e2, e3) + unitcell = uc + + ts.dimensions = unitcell ts.positions = frame.x if self.convert_units: @@ -255,7 +296,13 @@ def write_next_timestep(self, ts): dt = self.dt if self.dt is not None else ts.dt dt = mdaunits.convert(dt, 'ps', self.units['time']) - self._file.write(xyz=xyz, box=dimensions, step=step, natoms=xyz.shape[0], + # we only support writing charmm format unit cell info + # The DCD unitcell is written as ``[A, gamma, B, beta, alpha, C]`` + _ts_order = [0, 5, 1, 4, 3, 2] + box = np.take(dimensions, _ts_order) + # print("writting box ", box) + + self._file.write(xyz=xyz, box=box, step=step, natoms=xyz.shape[0], charmm=1, time_step=dt, ts_between_saves=self.step, remarks='test') def close(self): diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 32e753a85fb..84927bff888 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -375,12 +375,6 @@ cdef class DCDFile: #cdef double [:,:] unitcell = box xyz = np.asarray(xyz, order='F', dtype=np.float32) - - # we only support writing charmm format unit cell info - # The DCD unitcell is written as ``[A, gamma, B, beta, alpha, C]`` - _ts_order = [0, 5, 1, 4, 3, 2] - box = np.take(box, _ts_order) - # print("writting box ", box) cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) if xyz.shape != (natoms, 3): @@ -444,42 +438,4 @@ cdef class DCDFile: raise StopIteration self.current_frame += 1 - - if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and - unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and - unitcell[4] >= -1.0 and unitcell[4] <= 1.0): - # This file was generated by Charmm, or by NAMD > 2.5, with the angle - # cosines of the periodic cell angles written to the DCD file. - # This formulation improves rounding behavior for orthogonal cells - # so that the angles end up at precisely 90 degrees, unlike acos(). - # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; - # see Issue 187) */ - alpha = 90.0 - asin(unitcell[4]) * 90.0 / M_PI_2; - beta = 90.0 - asin(unitcell[3]) * 90.0 / M_PI_2; - gamma = 90.0 - asin(unitcell[1]) * 90.0 / M_PI_2; - else: - # This file was likely generated by NAMD 2.5 and the periodic cell - # angles are specified in degrees rather than angle cosines. - alpha = unitcell[4]; - beta = unitcell[3]; - gamma = unitcell[1]; - - unitcell[4] = alpha; - unitcell[3] = beta; - unitcell[1] = gamma; - - # The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` - _ts_order = [0, 2, 5, 4, 3, 1] - uc = np.take(unitcell, _ts_order) - - # heuristic sanity check: uc = A,B,C,alpha,beta,gamma - # XXX: should we worry about these comparisons with floats? - if np.any(uc < 0.) or np.any(uc[3:] > 180.): - # might be new CHARMM: box matrix vectors - H = unitcell - e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] - uc = triclinic_box(e1, e2, e3) - - unitcell = uc - return DCDFrame(xyz, unitcell) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index ca56178990c..251a5069480 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -35,7 +35,7 @@ def setUp(self): self.selected_legacy_frames = [5, 29] self.legacy_data = legacy_DCD_ADK_coords self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self.expected_unit_cell = np.array([ 0., 0., 0., 90., 90., 90.], + self.expected_unit_cell = np.array([ 0., 90., 0., 90., 90., 0.], dtype=np.float32) def test_header_remarks(self): @@ -451,9 +451,9 @@ def setUp(self): self.selected_legacy_frames = [0] self.legacy_data = legacy_DCD_NAMD_coords self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' - # expected unit cell based on previous DCD framework read in: - self.expected_unit_cell = np.array([ 38.42659378, 38.39310074, 44.75979996, - 90. , 90. , 60.02891541], + # expect raw unit cell unprocessed + self.expected_unit_cell = np.array([ 38.42659378, 0.499563, 38.393102, + 0. , 0. , 44.7598], dtype=np.float32) @@ -470,9 +470,9 @@ def setUp(self): self.selected_legacy_frames = [1, 4] self.legacy_data = legacy_DCD_c36_coords self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' - # expected unit cell based on previous DCD framework read in: - self.expected_unit_cell = np.array([ 35.44603729, 35.06156158, 34.15850067, - 91.32801819, 61.73519516, 44.4070282], + # expect raw unit cell unprocessed + self.expected_unit_cell = np.array([ 30.841836, 14.578635, 31.780088, + 9.626323, -2.60815, 32.67009], dtype=np.float32) From 95a58085cb3138da0e64d562ad1dc649bd72d99f Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 1 Jun 2017 15:56:46 +0200 Subject: [PATCH 048/101] add hypothesis to travis tests --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1fdbab25f06..2bac9adee56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ env: - MAIN_CMD="pytest ${PYTEST_LIST} ${PYTEST_FLAGS}; python ./testsuite/MDAnalysisTests/mda_nosetests ${NOSE_TEST_LIST} ${NOSE_FLAGS}" - SETUP_CMD="" - BUILD_CMD="pip install -v package/ && pip install testsuite/" - - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib scipy griddataformats pytest-cov pytest-xdist" - - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy griddataformats seaborn coveralls clustalw=2.1 pytest-cov pytest-xdist" + - CONDA_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib scipy griddataformats pytest-cov pytest-xdist hypothesis" + - CONDA_ALL_DEPENDENCIES="mmtf-python nose=1.3.7 mock six biopython networkx cython joblib nose-timer matplotlib netcdf4 scikit-learn scipy griddataformats seaborn coveralls clustalw=2.1 pytest-cov pytest-xdist hypothesis" # Install griddataformats from PIP so that scipy is only installed in the full build (#1147) - PIP_DEPENDENCIES='pytest-raises' - CONDA_CHANNELS='biobuilds conda-forge' From ccbf0ba44e87de16d906cd008337d3b2514ab1bd Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 30 May 2017 17:56:22 +0200 Subject: [PATCH 049/101] DCD header improvements - add header writing and getter methods - fix headers in libdcd - use header dictionary - make header writing public --- package/MDAnalysis/lib/formats/libdcd.pyx | 81 ++++++------ .../MDAnalysisTests/formats/test_libdcd.py | 116 ++++++++---------- 2 files changed, 86 insertions(+), 111 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 84927bff888..3dbafdf378e 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -17,16 +17,14 @@ from os import path import numpy as np from collections import namedtuple -from MDAnalysis.lib.mdamath import triclinic_box import six import string import sys cimport numpy as np - - from libc.stdio cimport SEEK_SET, SEEK_CUR, SEEK_END -from libc.math cimport M_PI_2, asin +from libc.stdint cimport uintptr_t +from libc.stdlib cimport free _whence_vals = {"FIO_SEEK_SET": SEEK_SET, "FIO_SEEK_CUR": SEEK_CUR, @@ -46,9 +44,6 @@ ctypedef np.float64_t DOUBLE_T FLOAT = np.float32 DOUBLE = np.float64 -from libc.stdint cimport uintptr_t -from libc.stdlib cimport free - cdef enum: FIO_READ = 0x01 FIO_WRITE = 0x02 @@ -104,11 +99,11 @@ cdef class DCDFile: cdef fio_fd fp cdef readonly fname cdef int is_open - cdef readonly int n_atoms + cdef int n_atoms cdef int nsets - cdef readonly int istart - cdef readonly int nsavc - cdef readonly double delta + cdef int istart + cdef int nsavc + cdef double delta cdef readonly int nfixed cdef int *freeind cdef float *fixedcoords @@ -119,24 +114,22 @@ cdef class DCDFile: cdef readonly int n_frames cdef bint b_read_header cdef int current_frame - cdef readonly remarks + cdef remarks cdef int reached_eof cdef readonly int _firstframesize cdef readonly int _framesize cdef readonly int _header_size + cdef int wrote_header def __cinit__(self, fname, mode='r'): self.fname = fname.encode('utf-8') self.n_atoms = 0 self.is_open = False + self.wrote_header = False self.open(self.fname, mode) def __dealloc__(self): self.close() - if self.fixedcoords != NULL: - free(self.fixedcoords) - if self.freeind != NULL: - free(self.freeind) def __enter__(self): """Support context manager""" @@ -187,6 +180,7 @@ cdef class DCDFile: self.is_open = True self.current_frame = 0 self.reached_eof = False + self.wrote_header = False # Has to come last since it checks the reached_eof flag if self.mode == 'r': self.remarks = self._read_header() @@ -249,18 +243,6 @@ cdef class DCDFile: return py_remarks - @property - def header(self): - return {'n_atoms': self.n_atoms, - 'nsets': self.nsets, - 'istart': self.istart, - 'nsavc': self.nsacv, - 'delta': self.delta, - 'nfixed': self.nfixed, - 'reverse_endian': self.reverse_endian, - 'charmm': self.charmm, - 'remarks': self.remarks} - def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 self._firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize @@ -315,9 +297,18 @@ cdef class DCDFile: raise IOError("DCD seek failed with system errno={}".format(ok)) self.current_frame = frame - def _write_header(self, remarks, int n_atoms, int starting_step, - int ts_between_saves, double time_step, - int charmm): + @property + def header(self): + return {'n_atoms': self.n_atoms, + 'istart': self.istart, + 'nsavc': self.nsavc, + 'delta': self.delta, + 'charmm': self.charmm, + 'remarks': self.remarks} + + def write_header(self, remarks, n_atoms, istart, + nsavc, delta, + charmm): if not self.is_open: raise IOError("No file open") @@ -325,6 +316,9 @@ cdef class DCDFile: if not self.mode=='w': raise IOError("Incorrect file mode for writing.") + if self.wrote_header: + raise IOError("Header already written") + #cdef char c_remarks cdef int len_remarks = 0 cdef int with_unitcell = 1 @@ -336,14 +330,17 @@ cdef class DCDFile: except UnicodeDecodeError: remarks = bytearray(remarks) - ok = write_dcdheader(self.fp, remarks, n_atoms, starting_step, - ts_between_saves, time_step, with_unitcell, + ok = write_dcdheader(self.fp, remarks, n_atoms, istart, + nsavc, delta, with_unitcell, charmm) + self.charmm = charmm + self.n_atoms = n_atoms if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) - def write(self, xyz, box, int step, int natoms, - int ts_between_saves, int charmm, double time_step, remarks): + self.wrote_header = True + + def write(self, xyz, box): """write one frame into DCD file. Parameters @@ -371,13 +368,16 @@ cdef class DCDFile: if len(box) != 6: raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + if not self.wrote_header: + raise IOError("write header first before frames can be written") + # print("box to write ", box) #cdef double [:,:] unitcell = box xyz = np.asarray(xyz, order='F', dtype=np.float32) cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) - if xyz.shape != (natoms, 3): + if xyz.shape != (self.n_atoms, 3): raise ValueError("xyz shape is wrong should be (natoms, 3), got:".format(xyz.shape)) cdef FLOAT_T[::1] x = xyz[:, 0] @@ -385,19 +385,12 @@ cdef class DCDFile: cdef FLOAT_T[::1] z = xyz[:, 2] cdef float alpha, beta, gamma, a, b, c; - if self.current_frame == 0: - self._write_header(remarks=remarks, n_atoms=xyz.shape[0], starting_step=step, - ts_between_saves=ts_between_saves, - time_step=time_step, - charmm=charmm) - self.n_atoms = xyz.shape[0] - # looks like self.nsavc is just 0 all the time step = self.current_frame * self.nsavc ok = write_dcdstep(self.fp, self.current_frame, step, self.n_atoms, &x[0], &y[0], &z[0], - &c_box[0], charmm) + &c_box[0], self.charmm) self.current_frame += 1 diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 251a5069480..bc1ab6002db 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from nose.tools import raises -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_almost_equal from numpy.testing import assert_allclose from MDAnalysis.lib.formats.libdcd import DCDFile @@ -41,7 +41,7 @@ def setUp(self): def test_header_remarks(self): # confirm correct header remarks section reading with DCDFile(self.dcdfile) as f: - assert_equal(len(f.remarks), len(self.expected_remarks)) + assert_equal(len(f.header['remarks']), len(self.expected_remarks)) def test_read_coords(self): # confirm shape of coordinate data against result from previous @@ -120,7 +120,7 @@ def test_raise_not_existing(self): def test_n_atoms(self): with DCDFile(self.dcdfile) as dcd: - assert_equal(dcd.n_atoms, self.natoms) + assert_equal(dcd.header['n_atoms'], self.natoms) @raises(IOError) @run_in_tempdir() @@ -158,47 +158,43 @@ def tearDown(self): pass del self.tmpdir - def test_write_header_crude(self): + def test_write_header(self): # test that _write_header() can produce a very crude # header for a new / empty file with DCDFile(self.testfile, 'w') as dcd: - dcd._write_header(remarks='Crazy!', n_atoms=22, - starting_step=12, ts_between_saves=10, - time_step=0.02, - charmm=1) + dcd.write_header(remarks='Crazy!', n_atoms=22, + istart=12, nsavc=10, + delta=0.02, + charmm=1) # we're not actually asserting anything, yet # run with: nosetests test_libdcd.py --nocapture # to see printed output from nose with DCDFile(self.testfile) as dcd: - assert_equal(dcd.remarks, 'Crazy!') - assert_equal(dcd.n_atoms, 22) - - @raises(IOError) - def test_write_header_only(self): - # test that _write_header() can produce a very crude - # header for a new / empty file - with DCDFile(self.testfile, 'w') as dcd: - dcd._write_header(remarks='Crazy!', n_atoms=22, - starting_step=12, ts_between_saves=10, - time_step=0.02, - charmm=1) - - # we're not actually asserting anything, yet - # run with: nosetests test_libdcd.py --nocapture - # to see printed output from nose - with DCDFile(self.testfile) as dcd: - dcd.read() + header = dcd.header + assert_equal(header['remarks'], 'Crazy!') + assert_equal(header['n_atoms'], 22) + assert_equal(header['istart'], 12) + assert_equal(header['charmm'], 5) + assert_equal(header['nsavc'], 10) + assert_almost_equal(header['delta'], .02) + + # @raises(IOError) + # def test_write_no_header(self): + # # test that _write_header() can produce a very crude + # # header for a new / empty file + # with DCDFile(self.testfile, 'w') as dcd: + # dcd.write(remarks='Crazy!', n_atoms=22, @raises(IOError) def test_write_header_mode_sensitivy(self): # an exception should be raised on any attempt to use # _write_header with a DCDFile object in 'r' mode with DCDFile(self.dcdfile) as dcd: - dcd._write_header(remarks='Crazy!', n_atoms=22, - starting_step=12, ts_between_saves=10, - time_step=0.02, - charmm=1) + dcd.write_header(remarks='Crazy!', n_atoms=22, + istart=12, nsavc=10, + delta=0.02, + charmm=1) @@ -219,20 +215,17 @@ def setUp(self): def _write_files(self, testfile, remarks_setting): with DCDFile(self.readfile) as f_in, DCDFile(testfile, 'w') as f_out: + header = f_in.header + if remarks_setting == 'input': + remarks = header['remarks'] + else: # accept the random remarks strings from hypothesis + remarks = remarks_setting + header['remarks'] = remarks + f_out.write_header(**header) for frame in f_in: - if remarks_setting == 'input': - remarks = f_in.remarks - else: # accept the random remarks strings from hypothesis - remarks = remarks_setting box=frame.unitcell.astype(np.float64) f_out.write(xyz=frame.x, - box=box, - step=f_in.istart, - natoms=frame.x.shape[0], - charmm=1, # DCD should be CHARMM - time_step=f_in.delta, - ts_between_saves=f_in.nsavc, - remarks=remarks) + box=box) def tearDown(self): try: @@ -247,13 +240,7 @@ def test_write_mode(self): # opened files with DCDFile(self.readfile) as dcd: dcd.write(xyz=np.zeros((3,3)), - box=np.zeros(6, dtype=np.float64), - step=0, - natoms=330, - charmm=0, - time_step=22.2, - ts_between_saves=3, - remarks='') + box=np.zeros(6, dtype=np.float64)) def test_written_dcd_coordinate_data_shape(self): # written coord shape should match for all frames @@ -297,7 +284,7 @@ def test_written_remarks(self): # ensure that the REMARKS field *can be* preserved exactly # in the written DCD file with DCDFile(self.testfile) as f: - assert_equal(f.remarks, self.expected_remarks) + assert_equal(f.header['remarks'], self.expected_remarks) # @given(st.text()) # handle the full unicode range of strings # def test_written_remarks_property(self, remarks_str): @@ -313,19 +300,19 @@ def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written # to file, is preserved in the written DCD file with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.nsavc, dcd_r.nsavc) + assert_equal(dcd.header['nsavc'], dcd_r.header['nsavc']) def test_written_istart(self): # ensure that istart, the starting timestep, is preserved # in the written DCD file with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.istart, dcd_r.istart) + assert_equal(dcd.header['istart'], dcd_r.header['istart']) def test_written_delta(self): # ensure that delta, the trajectory timestep, is preserved in # the written DCD file with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.delta, dcd_r.delta) + assert_equal(dcd.header['delta'], dcd_r.header['delta']) def test_coord_match(self): # ensure that all coordinates match in each frame for the @@ -345,8 +332,8 @@ def test_write_wrong_dtype(self): natoms = 10 xyz = np.ones((natoms, 3), dtype=dtype) box = np.ones(6, dtype=dtype) - out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, - ts_between_saves=1, remarks='test') + out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write(xyz=xyz, box=box) def test_write_array_like(self): """we should allow passing a range of dtypes""" @@ -355,8 +342,8 @@ def test_write_array_like(self): natoms = 10 xyz = array_like([[1, 1, 1] for i in range(natoms)]) box = array_like([i for i in range(6)]) - out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, - ts_between_saves=1, remarks='test') + out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write(xyz=xyz, box=box) @raises(ValueError) def test_write_wrong_shape_xyz(self): @@ -364,8 +351,8 @@ def test_write_wrong_shape_xyz(self): natoms = 10 xyz = np.ones((natoms+1, 3)) box = np.ones(6) - out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, - ts_between_saves=1, remarks='test') + out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write(xyz=xyz, box=box) @raises(ValueError) def test_write_wrong_shape_box(self): @@ -373,8 +360,7 @@ def test_write_wrong_shape_box(self): natoms = 10 xyz = np.ones((natoms, 3)) box = np.ones(8) - out.write(xyz=xyz, box=box, step=1, natoms=natoms, charmm=1, time_step=0, - ts_between_saves=1, remarks='test') + out.write(xyz=xyz, box=box) class TestDCDWriteNAMD(TestDCDWrite): @@ -492,16 +478,12 @@ def setUp(self): self.random_unitcells = np.random.uniform(high=80,size=(self.expected_frames, 6)).astype(np.float64) with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: + in_header = f_in.header + f_out.write_header(**in_header) for index, frame in enumerate(f_in): box=frame.unitcell.astype(np.float64) f_out.write(xyz=frame.x, - box=self.random_unitcells[index], - step=f_in.istart, - natoms=frame.x.shape[0], - charmm=1, # DCD should be CHARMM - time_step=f_in.delta, - ts_between_saves=f_in.nsavc, - remarks=f_in.remarks) + box=self.random_unitcells[index]) def tearDown(self): try: From b409b20898e5ecd845279eabccee56069e35de2c Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 1 Jun 2017 21:18:17 +0200 Subject: [PATCH 050/101] general clean up --- package/MDAnalysis/coordinates/DCD.py | 70 ++++--- package/MDAnalysis/lib/formats/libdcd.pyx | 91 +++++---- .../MDAnalysisTests/formats/test_libdcd.py | 176 +++++++++++------- 3 files changed, 187 insertions(+), 150 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 76c22d1c787..dc65191e0aa 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -19,8 +19,6 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # - - """DCD trajectory I/O --- :mod:`MDAnalysis.coordinates.DCD` ============================================================ @@ -91,6 +89,7 @@ from . import core from ..lib.formats.libdcd import DCDFile from ..lib.mdamath import triclinic_box + # dcdtimeseries is implemented with Pyrex - hopefully all dcd reading functionality can move to pyrex # from . import dcdtimeseries @@ -116,17 +115,16 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): General reader arguments. """ - super(DCDReader, self).__init__(filename, - convert_units=convert_units, - **kwargs) + super(DCDReader, self).__init__( + filename, convert_units=convert_units, **kwargs) self._file = DCDFile(self.filename) - self.n_atoms = self._file.n_atoms + self.n_atoms = self._file.header['natoms'] - - delta = mdaunits.convert(self._file.delta, self.units['time'], 'ps') + delta = mdaunits.convert(self._file.header['delta'], + self.units['time'], 'ps') if dt is None: - dt = delta * self._file.nsavc - self.skip_timestep = self._file.nsavc + dt = delta * self._file.header['nsavc'] + self.skip_timestep = self._file.header['nsavc'] self._ts_kwargs['dt'] = dt self.ts = self._Timestep(self.n_atoms, **self._ts_kwargs) @@ -182,7 +180,12 @@ def Writer(self, filename, n_atoms=None, **kwargs): """Return writer for trajectory format""" if n_atoms is None: n_atoms = self.n_atoms - return DCDWriter(filename, n_atoms=n_atoms, **kwargs) + return DCDWriter( + filename, + n_atoms=n_atoms, + dt=self.ts.dt, + convert_units=self.convert_units, + **kwargs) def _frame_to_ts(self, frame, ts): """convert a trr-frame to a mda TimeStep""" @@ -193,27 +196,27 @@ def _frame_to_ts(self, frame, ts): unitcell = frame.unitcell M_PI_2 = np.pi / 2 if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and - unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and - unitcell[4] >= -1.0 and unitcell[4] <= 1.0): + unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and + unitcell[4] >= -1.0 and unitcell[4] <= 1.0): # This file was generated by Charmm, or by NAMD > 2.5, with the angle # cosines of the periodic cell angles written to the DCD file. # This formulation improves rounding behavior for orthogonal cells # so that the angles end up at precisely 90 degrees, unlike acos(). # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; # see Issue 187) */ - alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / M_PI_2; - beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / M_PI_2; - gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / M_PI_2; + alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / M_PI_2 + beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / M_PI_2 + gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / M_PI_2 else: # This file was likely generated by NAMD 2.5 and the periodic cell # angles are specified in degrees rather than angle cosines. - alpha = unitcell[4]; - beta = unitcell[3]; - gamma = unitcell[1]; + alpha = unitcell[4] + beta = unitcell[3] + gamma = unitcell[1] - unitcell[4] = alpha; - unitcell[3] = beta; - unitcell[1] = gamma; + unitcell[4] = alpha + unitcell[3] = beta + unitcell[1] = gamma # The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` _ts_order = [0, 2, 5, 4, 3, 1] @@ -223,7 +226,7 @@ def _frame_to_ts(self, frame, ts): if np.any(uc < 0.) or np.any(uc[3:] > 180.): # might be new CHARMM: box matrix vectors H = unitcell - e1, e2, e3 = H[[0,1,3]], H[[1,2,4]], H[[3,4,5]] + e1, e2, e3 = H[[0, 1, 3]], H[[1, 2, 4]], H[[3, 4, 5]] uc = triclinic_box(e1, e2, e3) unitcell = uc @@ -248,7 +251,15 @@ class DCDWriter(base.WriterBase): flavor = 'CHARMM' units = {'time': 'AKMA', 'length': 'Angstrom'} - def __init__(self, filename, n_atoms, convert_units=True, step=1, dt=None, **kwargs): + def __init__(self, + filename, + n_atoms, + convert_units=True, + step=1, + dt=1, + remarks='', + nsavc=1, + **kwargs): """ Parameters ---------- @@ -271,6 +282,10 @@ def __init__(self, filename, n_atoms, convert_units=True, step=1, dt=None, **kwa self._file = DCDFile(self.filename, 'w') self.step = step self.dt = dt + dt = self.dt if self.dt is not None else ts.dt + dt = mdaunits.convert(dt, 'ps', self.units['time']) + self._file.write_header( + remarks=remarks, natoms=n_atoms, nsavc=nsavc, delta=float(dt), charmm=1, istart=0) def write_next_timestep(self, ts): """Write timestep object into trajectory. @@ -293,17 +308,12 @@ def write_next_timestep(self, ts): xyz = self.convert_pos_to_native(xyz, inplace=False) dimensions = self.convert_dimensions_to_unitcell(ts, inplace=False) - dt = self.dt if self.dt is not None else ts.dt - dt = mdaunits.convert(dt, 'ps', self.units['time']) - # we only support writing charmm format unit cell info # The DCD unitcell is written as ``[A, gamma, B, beta, alpha, C]`` _ts_order = [0, 5, 1, 4, 3, 2] box = np.take(dimensions, _ts_order) - # print("writting box ", box) - self._file.write(xyz=xyz, box=box, step=step, natoms=xyz.shape[0], - charmm=1, time_step=dt, ts_between_saves=self.step, remarks='test') + self._file.write(xyz=xyz, box=box) def close(self): """close trajectory""" diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 3dbafdf378e..d20e37a94ee 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -72,16 +72,16 @@ cdef extern from 'include/fastio.h': fio_size_t fio_fseek(fio_fd fd, fio_size_t offset, int whence) cdef extern from 'include/readdcd.h': - int read_dcdheader(fio_fd fd, int *n_atoms, int *nsets, int *istart, + int read_dcdheader(fio_fd fd, int *natoms, int *nsets, int *istart, int *nsavc, double *delta, int *nfixed, int **freeind, float **fixedcoords, int *reverse_endian, int *charmm, char **remarks, int *len_remarks) void close_dcd_read(int *freeind, float *fixedcoords) - int read_dcdstep(fio_fd fd, int n_atoms, float *X, float *Y, float *Z, + int read_dcdstep(fio_fd fd, int natoms, float *X, float *Y, float *Z, double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) - int read_dcdsubset(fio_fd fd, int n_atoms, int lowerb, int upperb, + int read_dcdsubset(fio_fd fd, int natoms, int lowerb, int upperb, float *X, float *Y, float *Z, double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, @@ -98,32 +98,31 @@ DCDFrame = namedtuple('DCDFrame', 'x unitcell') cdef class DCDFile: cdef fio_fd fp cdef readonly fname - cdef int is_open - cdef int n_atoms - cdef int nsets cdef int istart cdef int nsavc cdef double delta - cdef readonly int nfixed + cdef int natoms + cdef int nfixed cdef int *freeind cdef float *fixedcoords cdef int reverse_endian cdef int charmm + cdef remarks cdef str mode cdef readonly int n_dims cdef readonly int n_frames cdef bint b_read_header cdef int current_frame - cdef remarks - cdef int reached_eof cdef readonly int _firstframesize cdef readonly int _framesize cdef readonly int _header_size + cdef int is_open + cdef int reached_eof cdef int wrote_header def __cinit__(self, fname, mode='r'): self.fname = fname.encode('utf-8') - self.n_atoms = 0 + self.natoms = 0 self.is_open = False self.wrote_header = False self.open(self.fname, mode) @@ -196,7 +195,7 @@ cdef class DCDFile: self.is_open = False if ok != 0: raise IOError("couldn't close file: {}\n" - "ErrorCode: {}".format(self.fname, ok)) + "ErrorCode: {}".format(self.fname, ok)) def _read_header(self): @@ -205,8 +204,9 @@ cdef class DCDFile: cdef char* c_remarks cdef int len_remarks = 0 + cdef int nsets - ok = read_dcdheader(self.fp, &self.n_atoms, &self.nsets, &self.istart, + ok = read_dcdheader(self.fp, &self.natoms, &nsets, &self.istart, &self.nsavc, &self.delta, &self.nfixed, &self.freeind, &self.fixedcoords, &self.reverse_endian, &self.charmm, &c_remarks, &len_remarks) @@ -245,25 +245,21 @@ cdef class DCDFile: def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 - self._firstframesize = (self.n_atoms + 2) * self.n_dims * sizeof(float) + extrablocksize - self._framesize = ((self.n_atoms - self.nfixed + 2) * self.n_dims * sizeof(float) + + self._firstframesize = (self.natoms + 2) * self.n_dims * sizeof(float) + extrablocksize + self._framesize = ((self.natoms - self.nfixed + 2) * self.n_dims * sizeof(float) + extrablocksize) filesize = path.getsize(self.fname) # It's safe to use ftell, even though ftell returns a long, because the # header size is < 4GB. self._header_size = fio_ftell(self.fp) - # TODO: check that nframessize is larger then 0, the c-implementation - # used to do that. nframessize = filesize - self._header_size - self._firstframesize return nframessize / self._framesize + 1 @property - def periodic(self): + def is_periodic(self): return bool((self.charmm & DCD_IS_CHARMM) and (self.charmm & DCD_HAS_EXTRA_BLOCK)) - - def seek(self, frame): """Seek to Frame. @@ -299,45 +295,53 @@ cdef class DCDFile: @property def header(self): - return {'n_atoms': self.n_atoms, + return {'natoms': self.natoms, 'istart': self.istart, 'nsavc': self.nsavc, 'delta': self.delta, 'charmm': self.charmm, 'remarks': self.remarks} - def write_header(self, remarks, n_atoms, istart, + def write_header(self, remarks, natoms, istart, nsavc, delta, charmm): + """write DCD header + Parameters + ---------- + remarks : str + remarks of DCD file. Shouldn't be more then 240 characters (ASCII) + natoms : int + number of atoms to write + istart : int + nsavc : int + delta : float + charmm : int + """ if not self.is_open: raise IOError("No file open") - if not self.mode=='w': raise IOError("Incorrect file mode for writing.") - if self.wrote_header: raise IOError("Header already written") - #cdef char c_remarks cdef int len_remarks = 0 cdef int with_unitcell = 1 - if isinstance(remarks, six.string_types): try: remarks = bytearray(remarks, 'ascii') except UnicodeDecodeError: remarks = bytearray(remarks) - ok = write_dcdheader(self.fp, remarks, n_atoms, istart, + ok = write_dcdheader(self.fp, remarks, natoms, istart, nsavc, delta, with_unitcell, charmm) - self.charmm = charmm - self.n_atoms = n_atoms if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) + self.charmm = charmm + self.natoms = natoms self.wrote_header = True def write(self, xyz, box): @@ -345,7 +349,7 @@ cdef class DCDFile: Parameters ---------- - xyz : array_like, shape=(n_atoms, 3) + xyz : array_like, shape=(natoms, 3) cartesion coordinates box : array_like, shape=(6) Box vectors for this frame @@ -364,33 +368,25 @@ cdef class DCDFile: raise IOError("No file open") if self.mode != 'w': raise IOError('File opened in mode: {}. Writing only allowed ' - 'in mode "w"'.format('self.mode')) + 'in mode "w"'.format('self.mode')) if len(box) != 6: raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) - if not self.wrote_header: raise IOError("write header first before frames can be written") - - # print("box to write ", box) - - #cdef double [:,:] unitcell = box xyz = np.asarray(xyz, order='F', dtype=np.float32) - cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) - - if xyz.shape != (self.n_atoms, 3): + if xyz.shape != (self.natoms, 3): raise ValueError("xyz shape is wrong should be (natoms, 3), got:".format(xyz.shape)) + cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) cdef FLOAT_T[::1] x = xyz[:, 0] cdef FLOAT_T[::1] y = xyz[:, 1] cdef FLOAT_T[::1] z = xyz[:, 2] - cdef float alpha, beta, gamma, a, b, c; - # looks like self.nsavc is just 0 all the time - step = self.current_frame * self.nsavc - ok = write_dcdstep(self.fp, self.current_frame, step, - self.n_atoms, &x[0], - &y[0], &z[0], - &c_box[0], self.charmm) + step = self.istart + self.current_frame * self.nsavc + ok = write_dcdstep(self.fp, self.current_frame + 1, step, + self.natoms, &x[0], + &y[0], &z[0], + &c_box[0], self.charmm) self.current_frame += 1 @@ -405,8 +401,7 @@ cdef class DCDFile: if self.n_frames == 0: raise IOError("opened empty file. No frames are saved") - cdef np.ndarray xyz = np.empty((self.n_atoms, 3), dtype=FLOAT, - order='F') + cdef np.ndarray xyz = np.empty((self.natoms, 3), dtype=FLOAT, order='F') cdef np.ndarray unitcell = np.empty(6, dtype=DOUBLE) unitcell[0] = unitcell[2] = unitcell[5] = 0.0; unitcell[4] = unitcell[3] = unitcell[1] = 90.0; @@ -416,7 +411,7 @@ cdef class DCDFile: cdef FLOAT_T[::1] z = xyz[:, 2] first_frame = self.current_frame == 0 - ok = read_dcdstep(self.fp, self.n_atoms, + ok = read_dcdstep(self.fp, self.natoms, &x[0], &y[0], &z[0], unitcell.data, self.nfixed, first_frame, diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index bc1ab6002db..147c82ddaf1 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -3,28 +3,22 @@ from nose.tools import raises from numpy.testing import assert_equal, assert_almost_equal -from numpy.testing import assert_allclose +from numpy.testing import assert_allclose, assert_array_almost_equal from MDAnalysis.lib.formats.libdcd import DCDFile -from MDAnalysisTests.datafiles import (PSF, DCD, DCD_NAMD_TRICLINIC, - PSF_NAMD_TRICLINIC, - legacy_DCD_ADK_coords, - legacy_DCD_NAMD_coords, - legacy_DCD_c36_coords, - PSF_TRICLINIC, - DCD_TRICLINIC) +from MDAnalysisTests.datafiles import ( + DCD, DCD_NAMD_TRICLINIC, legacy_DCD_ADK_coords, legacy_DCD_NAMD_coords, + legacy_DCD_c36_coords, DCD_TRICLINIC) from MDAnalysisTests.tempdir import run_in_tempdir from MDAnalysisTests import tempdir import numpy as np import os -import math from hypothesis import given import hypothesis.strategies as st -class TestDCDReadFrame(): - +class TestDCDReadFrame(object): def setUp(self): self.dcdfile = DCD self.natoms = 3341 @@ -33,16 +27,21 @@ def setUp(self): self.context_frame = 22 self.num_iters = 3 self.selected_legacy_frames = [5, 29] + self.is_periodic = False self.legacy_data = legacy_DCD_ADK_coords self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self.expected_unit_cell = np.array([ 0., 90., 0., 90., 90., 0.], - dtype=np.float32) + self.expected_unit_cell = np.array( + [0., 90., 0., 90., 90., 0.], dtype=np.float32) def test_header_remarks(self): # confirm correct header remarks section reading with DCDFile(self.dcdfile) as f: assert_equal(len(f.header['remarks']), len(self.expected_remarks)) + def test_is_periodic(self): + with DCDFile(self.dcdfile) as f: + assert_equal(f.is_periodic, self.is_periodic) + def test_read_coords(self): # confirm shape of coordinate data against result from previous # MDAnalysis implementation of DCD file handling @@ -54,7 +53,6 @@ def test_read_coords(self): def test_read_coord_values(self): # test the actual values of coordinates read in versus # stored values read in by the legacy DCD handling framework - # to reduce repo storage burden, we only compare for a few # randomly selected frames legacy_DCD_frame_data = np.load(self.legacy_data) @@ -64,17 +62,14 @@ def test_read_coord_values(self): dcd.seek(frame_num) actual_coords = dcd.read()[0] desired_coords = legacy_DCD_frame_data[index] - assert_equal(actual_coords, - desired_coords) - + assert_equal(actual_coords, desired_coords) def test_read_unit_cell(self): # confirm unit cell read against result from previous # MDAnalysis implementation of DCD file handling with DCDFile(self.dcdfile) as dcd: dcd_frame = dcd.read() - unitcell = dcd_frame[1] - assert_allclose(unitcell, self.expected_unit_cell, rtol=1e-05) + assert_array_almost_equal(dcd_frame.unitcell, self.expected_unit_cell) @raises(IOError) def test_seek_over_max(self): @@ -118,9 +113,9 @@ def test_open_wrong_mode(self): def test_raise_not_existing(self): DCDFile('foo') - def test_n_atoms(self): + def test_natoms(self): with DCDFile(self.dcdfile) as dcd: - assert_equal(dcd.header['n_atoms'], self.natoms) + assert_equal(dcd.header['natoms'], self.natoms) @raises(IOError) @run_in_tempdir() @@ -145,7 +140,6 @@ def test_iteration_2(self): class TestDCDWriteHeader(): - def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' @@ -162,10 +156,13 @@ def test_write_header(self): # test that _write_header() can produce a very crude # header for a new / empty file with DCDFile(self.testfile, 'w') as dcd: - dcd.write_header(remarks='Crazy!', n_atoms=22, - istart=12, nsavc=10, - delta=0.02, - charmm=1) + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + charmm=1) # we're not actually asserting anything, yet # run with: nosetests test_libdcd.py --nocapture @@ -173,33 +170,54 @@ def test_write_header(self): with DCDFile(self.testfile) as dcd: header = dcd.header assert_equal(header['remarks'], 'Crazy!') - assert_equal(header['n_atoms'], 22) + assert_equal(header['natoms'], 22) assert_equal(header['istart'], 12) assert_equal(header['charmm'], 5) assert_equal(header['nsavc'], 10) assert_almost_equal(header['delta'], .02) - # @raises(IOError) - # def test_write_no_header(self): - # # test that _write_header() can produce a very crude - # # header for a new / empty file - # with DCDFile(self.testfile, 'w') as dcd: - # dcd.write(remarks='Crazy!', n_atoms=22, + @raises(IOError) + def test_write_no_header(self): + # test that _write_header() can produce a very crude + # header for a new / empty file + with DCDFile(self.testfile, 'w') as dcd: + dcd.write(np.ones(3), np.ones(6)) + + @raises(IOError) + def test_write_header_twice(self): + # test that _write_header() can produce a very crude + # header for a new / empty file + with DCDFile(self.testfile, 'w') as dcd: + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + charmm=1) + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + charmm=1) @raises(IOError) def test_write_header_mode_sensitivy(self): # an exception should be raised on any attempt to use # _write_header with a DCDFile object in 'r' mode with DCDFile(self.dcdfile) as dcd: - dcd.write_header(remarks='Crazy!', n_atoms=22, - istart=12, nsavc=10, - delta=0.02, - charmm=1) - + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + charmm=1) class TestDCDWrite(): - def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' @@ -209,8 +227,7 @@ def setUp(self): self.expected_frames = 98 self.seek_frame = 91 self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self._write_files(testfile=self.testfile, - remarks_setting='input') + self._write_files(testfile=self.testfile, remarks_setting='input') def _write_files(self, testfile, remarks_setting): @@ -218,14 +235,13 @@ def _write_files(self, testfile, remarks_setting): header = f_in.header if remarks_setting == 'input': remarks = header['remarks'] - else: # accept the random remarks strings from hypothesis + else: # accept the random remarks strings from hypothesis remarks = remarks_setting header['remarks'] = remarks f_out.write_header(**header) for frame in f_in: - box=frame.unitcell.astype(np.float64) - f_out.write(xyz=frame.x, - box=box) + box = frame.unitcell.astype(np.float64) + f_out.write(xyz=frame.x, box=box) def tearDown(self): try: @@ -239,8 +255,7 @@ def test_write_mode(self): # ensure that writing of DCD files only occurs with properly # opened files with DCDFile(self.readfile) as dcd: - dcd.write(xyz=np.zeros((3,3)), - box=np.zeros(6, dtype=np.float64)) + dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) def test_written_dcd_coordinate_data_shape(self): # written coord shape should match for all frames @@ -332,7 +347,13 @@ def test_write_wrong_dtype(self): natoms = 10 xyz = np.ones((natoms, 3), dtype=dtype) box = np.ones(6, dtype=dtype) - out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) out.write(xyz=xyz, box=box) def test_write_array_like(self): @@ -342,16 +363,28 @@ def test_write_array_like(self): natoms = 10 xyz = array_like([[1, 1, 1] for i in range(natoms)]) box = array_like([i for i in range(6)]) - out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) out.write(xyz=xyz, box=box) @raises(ValueError) def test_write_wrong_shape_xyz(self): with DCDFile(self.testfile, 'w') as out: natoms = 10 - xyz = np.ones((natoms+1, 3)) + xyz = np.ones((natoms + 1, 3)) box = np.ones(6) - out.write_header(remarks='test', n_atoms=natoms, charmm=1, delta=1, nsavc=1, istart=1) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) out.write(xyz=xyz, box=box) @raises(ValueError) @@ -363,7 +396,7 @@ def test_write_wrong_shape_box(self): out.write(xyz=xyz, box=box) -class TestDCDWriteNAMD(TestDCDWrite): +class TestDCDWriteNAMD(TestDCDWrite): # repeat writing tests for NAMD format DCD def setUp(self): @@ -375,8 +408,7 @@ def setUp(self): self.expected_frames = 1 self.seek_frame = 0 self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' - self._write_files(testfile=self.testfile, - remarks_setting='input') + self._write_files(testfile=self.testfile, remarks_setting='input') def test_written_unit_cell(self): # there's no expectation that we can write unit cell @@ -397,8 +429,7 @@ def setUp(self): self.expected_frames = 10 self.seek_frame = 7 self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' - self._write_files(testfile=self.testfile, - remarks_setting='input') + self._write_files(testfile=self.testfile, remarks_setting='input') def test_written_unit_cell(self): # there's no expectation that we can write unit cell @@ -433,14 +464,15 @@ def setUp(self): self.traj_length = 1 self.new_frame = 0 self.context_frame = 0 + self.is_periodic = True self.num_iters = 0 self.selected_legacy_frames = [0] self.legacy_data = legacy_DCD_NAMD_coords self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' # expect raw unit cell unprocessed - self.expected_unit_cell = np.array([ 38.42659378, 0.499563, 38.393102, - 0. , 0. , 44.7598], - dtype=np.float32) + self.expected_unit_cell = np.array( + [38.42659378, 0.499563, 38.393102, 0., 0., 44.7598], + dtype=np.float32) class TestDCDReadFrameTestCharmm36(TestDCDReadFrame): @@ -453,13 +485,14 @@ def setUp(self): self.new_frame = 2 self.context_frame = 5 self.num_iters = 7 + self.is_periodic = True self.selected_legacy_frames = [1, 4] self.legacy_data = legacy_DCD_c36_coords self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' # expect raw unit cell unprocessed - self.expected_unit_cell = np.array([ 30.841836, 14.578635, 31.780088, - 9.626323, -2.60815, 32.67009], - dtype=np.float32) + self.expected_unit_cell = np.array( + [30.841836, 14.578635, 31.780088, 9.626323, -2.60815, 32.67009], + dtype=np.float32) class TestDCDWriteRandom(object): @@ -475,15 +508,16 @@ def setUp(self): self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' np.random.seed(1178083) - self.random_unitcells = np.random.uniform(high=80,size=(self.expected_frames, 6)).astype(np.float64) + self.random_unitcells = np.random.uniform( + high=80, size=(self.expected_frames, 6)).astype(np.float64) - with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, 'w') as f_out: + with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, + 'w') as f_out: in_header = f_in.header f_out.write_header(**in_header) for index, frame in enumerate(f_in): - box=frame.unitcell.astype(np.float64) - f_out.write(xyz=frame.x, - box=self.random_unitcells[index]) + box = frame.unitcell.astype(np.float64) + f_out.write(xyz=frame.x, box=self.random_unitcells[index]) def tearDown(self): try: @@ -500,12 +534,10 @@ def test_written_unit_cell_random(self): ref_unitcell = self.random_unitcells[curr_frame] curr_frame += 1 - assert_allclose(written_unitcell, ref_unitcell, - rtol=1e-05) + assert_allclose(written_unitcell, ref_unitcell, rtol=1e-05) class TestDCDByteArithmetic(object): - def setUp(self): self.dcdfile = DCD self._filesize = os.path.getsize(DCD) @@ -526,7 +558,8 @@ def test_file_size_breakdown(self): # frame expected = self._filesize with DCDFile(self.dcdfile) as dcd: - actual = dcd._header_size + dcd._firstframesize + ((dcd.n_frames - 1) * dcd._framesize) + actual = dcd._header_size + dcd._firstframesize + ( + (dcd.n_frames - 1) * dcd._framesize) assert_equal(actual, expected) def test_nframessize_int(self): @@ -538,7 +571,6 @@ def test_nframessize_int(self): assert_equal(float(nframessize) % float(dcd._framesize), 0) - class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): # repeat byte arithmetic tests for NAMD format DCD From fcfbf5da36e5e7c03d6ba2fc037822390ebab25f Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 1 Jun 2017 22:21:59 +0200 Subject: [PATCH 051/101] reactivate hypothesis tests --- .../MDAnalysisTests/formats/test_libdcd.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 147c82ddaf1..928d273c75c 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -14,8 +14,9 @@ from MDAnalysisTests import tempdir import numpy as np import os -from hypothesis import given +from hypothesis import given, example import hypothesis.strategies as st +import string class TestDCDReadFrame(object): @@ -301,15 +302,18 @@ def test_written_remarks(self): with DCDFile(self.testfile) as f: assert_equal(f.header['remarks'], self.expected_remarks) - # @given(st.text()) # handle the full unicode range of strings - # def test_written_remarks_property(self, remarks_str): - # # property based testing for writing of a wide range of string - # # values to REMARKS field - # self._write_files(testfile=self.testfile2, - # remarks_setting=remarks_str) - # expected_remarks = remarks_str - # with DCDFile(self.testfile2) as f: - # assert_equal(f.remarks, expected_remarks) + @given(st.text(alphabet=string.printable, + min_size=0, + max_size=240)) # handle the printable ASCII strings + @example('') + def test_written_remarks_property(self, remarks_str): + # property based testing for writing of a wide range of string + # values to REMARKS field + self._write_files(testfile=self.testfile2, + remarks_setting=remarks_str) + expected_remarks = remarks_str[:240] + with DCDFile(self.testfile2) as f: + assert_equal(f.header['remarks'], expected_remarks) def test_written_nsavc(self): # ensure that nsavc, the timesteps between frames written From e651f598613c8408389b6758db13cb7b6dc0cd2d Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 1 Jun 2017 22:52:09 +0200 Subject: [PATCH 052/101] use timeseries doesn't work right now --- package/MDAnalysis/coordinates/DCD.py | 47 +++++++++++++++++++ package/MDAnalysis/lib/formats/libdcd.pyx | 15 +++++- .../MDAnalysisTests/coordinates/test_dcd.py | 24 +++++----- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index dc65191e0aa..9107a0478bd 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -243,6 +243,53 @@ def _frame_to_ts(self, frame, ts): def dt(self): return self.ts.dt + def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, + format='afc'): + """Return a subset of coordinate data for an AtomGroup + Parameters + ---------- + asel : :class:`~MDAnalysis.core.groups.AtomGroup` + The :class:`~MDAnalysis.core.groups.AtomGroup` to read the + coordinates from. Defaults to None, in which case the full set of + coordinate data is returned. + start : int (optional) + Begin reading the trajectory at frame index `start` (where 0 is the index + of the first frame in the trajectory); the default ``None`` starts + at the beginning. + stop : int (optional) + End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. + The trajectory is read to the end with the default ``None``. + step : int (optional) + Step size for reading; the default ``None`` is equivalent to 1 and means to + read every frame. + format : str (optional) + the order/shape of the return data array, corresponding + to (a)tom, (f)rame, (c)oordinates all six combinations + of 'a', 'f', 'c' are allowed ie "fac" - return array + where the shape is (frame, number of atoms, + coordinates) + .. deprecated:: 0.16.0 + `skip` has been deprecated in favor of the standard keyword `step`. + """ + if skip is not None: + step = skip + warnings.warn("Skip is deprecated and will be removed in" + "in 1.0. Use step instead.", + category=DeprecationWarning) + + start, stop, step = self.check_slice_indices(start, stop, step) + + if asel is not None: + if len(asel) == 0: + raise NoDataError("Timeseries requires at least one atom to analyze") + atom_numbers = list(asel.indices) + else: + atom_numbers = list(range(self.n_atoms)) + + if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: + raise ValueError("Invalid timeseries format") + frames = self._file.read_nframes(self.n_frames) + return frames.xyz[start:stop:step, atom_numbers] class DCDWriter(base.WriterBase): """Base class for libmdaxdr file formats xtc and trr""" diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index d20e37a94ee..c098eeda13b 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -401,7 +401,7 @@ cdef class DCDFile: if self.n_frames == 0: raise IOError("opened empty file. No frames are saved") - cdef np.ndarray xyz = np.empty((self.natoms, 3), dtype=FLOAT, order='F') + cdef np.ndarray xyz = np.empty((self.natoms, self.ndims), dtype=FLOAT, order='F') cdef np.ndarray unitcell = np.empty(6, dtype=DOUBLE) unitcell[0] = unitcell[2] = unitcell[5] = 0.0; unitcell[4] = unitcell[3] = unitcell[1] = 90.0; @@ -427,3 +427,16 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) + + def read_nframes(self, n): + + xyz = np.empty((n, self.natoms, self.ndims)) + box = np.empty((n, 6)) + cdef int i + + for i in range(n): + f = self.read() + xyz[i] = f.xyz + box[i] = f.unitcell + + return DCDFrame(xyz, box) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index c43fed19014..2d4001fbf34 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -152,24 +152,24 @@ def test_reverse_dcd(self): assert_equal(frames, list(range(20, 5, -1)), "reversing dcd [20:5:-1]") - # def test_timeseries_slicing(self): - # # check that slicing behaves correctly - # # should before issue #914 resolved - # x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), - # (0, 5, 5), (3, 5, 1), (None, None, None)] - # for start, stop, step in x: - # yield self._slice_generation_test, start, stop, step + def test_timeseries_slicing(self): + # check that slicing behaves correctly + # should before issue #914 resolved + x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), + (0, 5, 5), (3, 5, 1), (None, None, None)] + for start, stop, step in x: + yield self._slice_generation_test, start, stop, step # def test_backwards_stepping(self): # x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] # for start, stop, step in x: # yield self._failed_slices_test, start, stop, step - # def _slice_generation_test(self, start, stop, step): - # self.u = mda.Universe(PSF, DCD) - # ts = self.u.trajectory.timeseries(self.u.atoms) - # ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - # assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) + def _slice_generation_test(self, start, stop, step): + self.u = mda.Universe(PSF, DCD) + ts = self.u.trajectory.timeseries(self.u.atoms) + ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) + assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) # @knownfailure # def _failed_slices_test(self, start, stop, step): From 64098d8986cc121799a55fe66427e20e7e3bdbab Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 08:05:33 +0200 Subject: [PATCH 053/101] fix ndims DCD reader error --- package/MDAnalysis/lib/formats/libdcd.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index c098eeda13b..c64a8355d81 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -109,7 +109,7 @@ cdef class DCDFile: cdef int charmm cdef remarks cdef str mode - cdef readonly int n_dims + cdef readonly int ndims cdef readonly int n_frames cdef bint b_read_header cdef int current_frame @@ -219,7 +219,7 @@ cdef class DCDFile: else: py_remarks = "" - self.n_dims = 3 if not self.charmm & DCD_HAS_4DIMS else 4 + self.ndims = 3 if not self.charmm & DCD_HAS_4DIMS else 4 self.n_frames = self._estimate_n_frames() self.b_read_header = True @@ -245,8 +245,8 @@ cdef class DCDFile: def _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 - self._firstframesize = (self.natoms + 2) * self.n_dims * sizeof(float) + extrablocksize - self._framesize = ((self.natoms - self.nfixed + 2) * self.n_dims * sizeof(float) + + self._firstframesize = (self.natoms + 2) * self.ndims * sizeof(float) + extrablocksize + self._framesize = ((self.natoms - self.nfixed + 2) * self.ndims * sizeof(float) + extrablocksize) filesize = path.getsize(self.fname) # It's safe to use ftell, even though ftell returns a long, because the From 65ea67b4058a7a7d365c6c4b80198d4611b44ef1 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 08:05:41 +0200 Subject: [PATCH 054/101] fix timeseries --- package/MDAnalysis/coordinates/DCD.py | 4 ++-- package/MDAnalysis/lib/formats/libdcd.pyx | 6 +++--- testsuite/MDAnalysisTests/coordinates/test_dcd.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 9107a0478bd..ec311af75a4 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -288,8 +288,8 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: raise ValueError("Invalid timeseries format") - frames = self._file.read_nframes(self.n_frames) - return frames.xyz[start:stop:step, atom_numbers] + frames = self._file.readframes(self.n_frames) + return frames.x[start:stop:step, atom_numbers] class DCDWriter(base.WriterBase): """Base class for libmdaxdr file formats xtc and trr""" diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index c64a8355d81..8ef8611b83c 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -428,15 +428,15 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) - def read_nframes(self, n): - + def readframes(self, n): + self.seek(0) xyz = np.empty((n, self.natoms, self.ndims)) box = np.empty((n, 6)) cdef int i for i in range(n): f = self.read() - xyz[i] = f.xyz + xyz[i] = f.x box[i] = f.unitcell return DCDFrame(xyz, box) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 2d4001fbf34..02956705d15 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -169,7 +169,7 @@ def _slice_generation_test(self, start, stop, step): self.u = mda.Universe(PSF, DCD) ts = self.u.trajectory.timeseries(self.u.atoms) ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) + assert_array_almost_equal(ts[start:stop:step], ts_skip, 5) # @knownfailure # def _failed_slices_test(self, start, stop, step): From f1895105498f37a00c02f63f468542ebc5282a69 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 08:23:19 +0200 Subject: [PATCH 055/101] have readframes with start,stop,step this will support slices now. Saves memory --- package/MDAnalysis/coordinates/DCD.py | 4 +-- package/MDAnalysis/lib/formats/libdcd.pyx | 29 +++++++++++++++---- .../MDAnalysisTests/coordinates/test_dcd.py | 2 +- .../MDAnalysisTests/formats/test_libdcd.py | 17 +++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index ec311af75a4..400bf9e556b 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -288,8 +288,8 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: raise ValueError("Invalid timeseries format") - frames = self._file.readframes(self.n_frames) - return frames.x[start:stop:step, atom_numbers] + frames = self._file.readframes(start, stop, step) + return frames.x[:, atom_numbers] class DCDWriter(base.WriterBase): """Base class for libmdaxdr file formats xtc and trr""" diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 8ef8611b83c..16674d49f32 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -428,15 +428,32 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) - def readframes(self, n): + def readframes(self, start=None, stop=None, step=None): + cdef int i, cstart, cstop, cstep, n, counter self.seek(0) + cstop = stop if not stop is None else self.n_frames + cstart = start if not start is None else 0 + cstep = step if not step is None else 1 + #n = (cstop - cstart) / cstep + n = len(range(cstart, cstop, cstep)) + print("n = ", n) + xyz = np.empty((n, self.natoms, self.ndims)) box = np.empty((n, 6)) - cdef int i - for i in range(n): - f = self.read() - xyz[i] = f.x - box[i] = f.unitcell + if cstart == 0 and cstep == 1 and cstop == self.n_frames: + for i in range(n): + f = self.read() + xyz[i] = f.x + box[i] = f.unitcell + else: + counter = 0 + for i in range(cstart, cstop, cstep): + print("i = ", i) + self.seek(i) + f = self.read() + xyz[counter] = f.x + box[counter] = f.unitcell + counter += 1 return DCDFrame(xyz, box) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 02956705d15..3358cc2c901 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -155,7 +155,7 @@ def test_reverse_dcd(self): def test_timeseries_slicing(self): # check that slicing behaves correctly # should before issue #914 resolved - x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), + x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 4, 2), (1, 4, 4), (0, 5, 5), (3, 5, 1), (None, None, None)] for start, stop, step in x: yield self._slice_generation_test, start, stop, step diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 928d273c75c..15cfb3a9d17 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -65,6 +65,23 @@ def test_read_coord_values(self): desired_coords = legacy_DCD_frame_data[index] assert_equal(actual_coords, desired_coords) + def test_readframes(self): + legacy_DCD_frame_data = np.load(self.legacy_data) + with DCDFile(self.dcdfile) as dcd: + frames = dcd.readframes() + xyz = frames.x + assert_equal(len(xyz), len(dcd)) + for index, frame_num in enumerate(self.selected_legacy_frames): + assert_array_almost_equal(xyz[frame_num], legacy_DCD_frame_data[index]) + + def test_readframes_slice(self): + with DCDFile(self.dcdfile) as dcd: + if len(dcd) > 6: + frames = dcd.readframes(start=2, stop=6, step=2) + xyz = frames.x + assert_equal(len(xyz), 2) + + def test_read_unit_cell(self): # confirm unit cell read against result from previous # MDAnalysis implementation of DCD file handling From 979c8dd4bc28ea6aff200d43f910085e6f7e4593 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 13:41:36 +0200 Subject: [PATCH 056/101] fix linter problems --- package/MDAnalysis/coordinates/DCD.py | 51 ++++++++++--------- .../MDAnalysisTests/coordinates/test_dcd.py | 2 +- .../MDAnalysisTests/formats/test_libdcd.py | 4 +- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 400bf9e556b..5ef3f9d3546 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -246,30 +246,33 @@ def dt(self): def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, format='afc'): """Return a subset of coordinate data for an AtomGroup - Parameters - ---------- - asel : :class:`~MDAnalysis.core.groups.AtomGroup` - The :class:`~MDAnalysis.core.groups.AtomGroup` to read the - coordinates from. Defaults to None, in which case the full set of - coordinate data is returned. - start : int (optional) - Begin reading the trajectory at frame index `start` (where 0 is the index - of the first frame in the trajectory); the default ``None`` starts - at the beginning. - stop : int (optional) - End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. - The trajectory is read to the end with the default ``None``. - step : int (optional) - Step size for reading; the default ``None`` is equivalent to 1 and means to - read every frame. - format : str (optional) - the order/shape of the return data array, corresponding - to (a)tom, (f)rame, (c)oordinates all six combinations - of 'a', 'f', 'c' are allowed ie "fac" - return array - where the shape is (frame, number of atoms, - coordinates) - .. deprecated:: 0.16.0 - `skip` has been deprecated in favor of the standard keyword `step`. + + Parameters + ---------- + asel : :class:`~MDAnalysis.core.groups.AtomGroup` + The :class:`~MDAnalysis.core.groups.AtomGroup` to read the + coordinates from. Defaults to None, in which case the full set of + coordinate data is returned. + start : int (optional) + Begin reading the trajectory at frame index `start` (where 0 is the index + of the first frame in the trajectory); the default ``None`` starts + at the beginning. + stop : int (optional) + End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. + The trajectory is read to the end with the default ``None``. + step : int (optional) + Step size for reading; the default ``None`` is equivalent to 1 and means to + read every frame. + format : str (optional) + the order/shape of the return data array, corresponding + to (a)tom, (f)rame, (c)oordinates all six combinations + of 'a', 'f', 'c' are allowed ie "fac" - return array + where the shape is (frame, number of atoms, + coordinates) + + + .. deprecated:: 0.16.0 + `skip` has been deprecated in favor of the standard keyword `step`. """ if skip is not None: step = skip diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 3358cc2c901..94c57517d32 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -19,7 +19,7 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from __future__ import absolute_import +from __future__ import absolute_import, print_function import MDAnalysis as mda from MDAnalysis.coordinates.DCD import DCDReader import numpy as np diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 15cfb3a9d17..6c2913028fb 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -157,7 +157,7 @@ def test_iteration_2(self): assert_equal(i + 1, f.tell()) -class TestDCDWriteHeader(): +class TestDCDWriteHeader(object): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' @@ -235,7 +235,7 @@ def test_write_header_mode_sensitivy(self): charmm=1) -class TestDCDWrite(): +class TestDCDWrite(object): def setUp(self): self.tmpdir = tempdir.TempDir() self.testfile = self.tmpdir.name + '/test.dcd' From 8ba9f19ff05f95e79ea3f08edbf6b2a1f104874c Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 13:51:31 +0200 Subject: [PATCH 057/101] remove dcd from timestep API test --- testsuite/MDAnalysisTests/coordinates/test_timestep_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_timestep_api.py b/testsuite/MDAnalysisTests/coordinates/test_timestep_api.py index 25b608ebe19..eee666dbb84 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_timestep_api.py +++ b/testsuite/MDAnalysisTests/coordinates/test_timestep_api.py @@ -52,8 +52,7 @@ def test_other_timestep(self): # can't do TRR Timestep here as it always has vels and forces # so isn't actually equal to a position only timestep - for otherTS in [mda.coordinates.DCD.Timestep, - mda.coordinates.TRJ.Timestep, + for otherTS in [mda.coordinates.TRJ.Timestep, mda.coordinates.DMS.Timestep, mda.coordinates.GRO.Timestep, mda.coordinates.TRZ.Timestep, From 4c308c7ecb488a4cb2a6a8c08b94aa3e7204fffe Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 14:21:21 +0200 Subject: [PATCH 058/101] fix unitcell initialization --- package/MDAnalysis/coordinates/DCD.py | 8 ++++---- testsuite/MDAnalysisTests/coordinates/base.py | 2 +- testsuite/MDAnalysisTests/coordinates/test_memory.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 5ef3f9d3546..dfeab7d2874 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -135,10 +135,9 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): else: self._file.seek(0) self._frame = 0 - self._frame_to_ts(frame, self.ts) + self.ts = self._frame_to_ts(frame, self.ts) # these should only be initialized once self.ts.dt = dt - self.ts.dimensions = frame.unitcell if self.convert_units: self.convert_pos_from_native(self.ts.dimensions[:3]) @@ -196,8 +195,8 @@ def _frame_to_ts(self, frame, ts): unitcell = frame.unitcell M_PI_2 = np.pi / 2 if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and - unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and - unitcell[4] >= -1.0 and unitcell[4] <= 1.0): + unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and + unitcell[4] >= -1.0 and unitcell[4] <= 1.0): # This file was generated by Charmm, or by NAMD > 2.5, with the angle # cosines of the periodic cell angles written to the DCD file. # This formulation improves rounding behavior for orthogonal cells @@ -294,6 +293,7 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, frames = self._file.readframes(start, stop, step) return frames.x[:, atom_numbers] + class DCDWriter(base.WriterBase): """Base class for libmdaxdr file formats xtc and trr""" diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 9128dba3c91..3693be5bb61 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -249,7 +249,7 @@ def test_ts_dt_matches_reader(self): assert_equal(self.reader.ts.dt, self.reader.dt) def test_total_time(self): - assert_almost_equal(self.reader.totaltime, self.ref.totaltime) + assert_almost_equal(self.reader.totaltime, self.ref.totaltime, decimal=5) def test_first_dimensions(self): self.reader.rewind() diff --git a/testsuite/MDAnalysisTests/coordinates/test_memory.py b/testsuite/MDAnalysisTests/coordinates/test_memory.py index 672680f8d56..499fa61200e 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_memory.py +++ b/testsuite/MDAnalysisTests/coordinates/test_memory.py @@ -37,7 +37,7 @@ class MemoryReference(BaseReference): 'DCD parser not available. Are you using python 3?') def __init__(self): super(MemoryReference, self).__init__() - + self.topology = PSF self.trajectory = DCD self.universe = mda.Universe(PSF, DCD) @@ -103,7 +103,7 @@ def test_default_memory_layout(self): universe2 = mda.Universe(PSF, DCD, in_memory=True, order='fac') assert_equal(universe1.trajectory.get_array().shape, universe2.trajectory.get_array().shape) - + def test_iteration(self): frames = 0 for i, frame in enumerate(self.reader): From 4d0a44f1147cd6a69b6dc4766c0d7934247e8081 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 19:26:17 +0200 Subject: [PATCH 059/101] support ordered readframes --- package/MDAnalysis/coordinates/DCD.py | 10 ++++-- package/MDAnalysis/lib/formats/libdcd.pyx | 39 ++++++++++++++++++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index dfeab7d2874..48a4500317b 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -290,8 +290,14 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: raise ValueError("Invalid timeseries format") - frames = self._file.readframes(start, stop, step) - return frames.x[:, atom_numbers] + frames = self._file.readframes(start, stop, step, order=format) + + if format == 'fac' or format == 'caf': + return frames.x[:, atom_numbers] + elif format == 'fca' or format == 'fca': + return frames.x[:, :, atom_numbers] + elif format == 'afc' or format == 'acf': + return frames.x[atom_numbers] class DCDWriter(base.WriterBase): diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 16674d49f32..286f77327b9 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -428,7 +428,7 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) - def readframes(self, start=None, stop=None, step=None): + def readframes(self, start=None, stop=None, step=None, order='fac'): cdef int i, cstart, cstop, cstep, n, counter self.seek(0) cstop = stop if not stop is None else self.n_frames @@ -438,13 +438,29 @@ cdef class DCDFile: n = len(range(cstart, cstop, cstep)) print("n = ", n) - xyz = np.empty((n, self.natoms, self.ndims)) + shape = [] + if order == 'fac': + shape = (n, self.natoms, self.ndims) + elif order == 'fca': + shape = (n, self.ndims, self.natoms) + elif order == 'afc': + shape = (self.natoms, n, self.ndims) + elif order == 'acf': + shape = (self.natoms, self.ndims, n) + elif order == 'caf': + shape = (self.ndims, self.natoms, n) + elif order == 'cfa': + shape = (self.ndims, n, self.natoms) + else: + raise ValueError("unkown order '{}'".format(order)) + + xyz = np.empty(shape) box = np.empty((n, 6)) if cstart == 0 and cstep == 1 and cstop == self.n_frames: for i in range(n): f = self.read() - xyz[i] = f.x + copy_in_order(f.x, xyz, order, i) box[i] = f.unitcell else: counter = 0 @@ -452,8 +468,23 @@ cdef class DCDFile: print("i = ", i) self.seek(i) f = self.read() - xyz[counter] = f.x + copy_in_order(f.x, xyz, order, counter) box[counter] = f.unitcell counter += 1 return DCDFrame(xyz, box) + + +def copy_in_order(source, target, order, index): + if order == 'fac': + target[index] = source + elif order == 'fca': + target[index] = source.T + elif order == 'afc': + target[:, index] = source + elif order == 'acf': + target[:, :, index] = source + elif order == 'caf': + target[:, :, index] = source.T + elif order == 'cfa': + target[:, index] = source.T From 51ba896c92ad20c4a2487f1678f397be88a53f97 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 19:30:38 +0200 Subject: [PATCH 060/101] allow subset reading in readframes --- package/MDAnalysis/coordinates/DCD.py | 11 +++------ package/MDAnalysis/lib/formats/libdcd.pyx | 28 ++++++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 48a4500317b..6b74947804d 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -290,14 +290,9 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: raise ValueError("Invalid timeseries format") - frames = self._file.readframes(start, stop, step, order=format) - - if format == 'fac' or format == 'caf': - return frames.x[:, atom_numbers] - elif format == 'fca' or format == 'fca': - return frames.x[:, :, atom_numbers] - elif format == 'afc' or format == 'acf': - return frames.x[atom_numbers] + frames = self._file.readframes(start, stop, step, + order=format, indices=atom_numbers) + return frames.x class DCDWriter(base.WriterBase): diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 286f77327b9..92168fd991f 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -428,7 +428,7 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) - def readframes(self, start=None, stop=None, step=None, order='fac'): + def readframes(self, start=None, stop=None, step=None, order='fac', indices=None): cdef int i, cstart, cstop, cstep, n, counter self.seek(0) cstop = stop if not stop is None else self.n_frames @@ -436,21 +436,26 @@ cdef class DCDFile: cstep = step if not step is None else 1 #n = (cstop - cstart) / cstep n = len(range(cstart, cstop, cstep)) - print("n = ", n) + + if indices == 'None': + indices = np.arange(self.natoms) + natoms = self.natoms + else: + natoms = len(indices) shape = [] if order == 'fac': - shape = (n, self.natoms, self.ndims) + shape = (n, natoms, self.ndims) elif order == 'fca': - shape = (n, self.ndims, self.natoms) + shape = (n, self.ndims, natoms) elif order == 'afc': - shape = (self.natoms, n, self.ndims) + shape = (natoms, n, self.ndims) elif order == 'acf': - shape = (self.natoms, self.ndims, n) + shape = (natoms, self.ndims, n) elif order == 'caf': - shape = (self.ndims, self.natoms, n) + shape = (self.ndims, natoms, n) elif order == 'cfa': - shape = (self.ndims, n, self.natoms) + shape = (self.ndims, n, natoms) else: raise ValueError("unkown order '{}'".format(order)) @@ -460,15 +465,16 @@ cdef class DCDFile: if cstart == 0 and cstep == 1 and cstop == self.n_frames: for i in range(n): f = self.read() - copy_in_order(f.x, xyz, order, i) + x = f.x[indices] + copy_in_order(x, xyz, order, i) box[i] = f.unitcell else: counter = 0 for i in range(cstart, cstop, cstep): - print("i = ", i) self.seek(i) f = self.read() - copy_in_order(f.x, xyz, order, counter) + x = f.x[indices] + copy_in_order(x, xyz, order, counter) box[counter] = f.unitcell counter += 1 From 9a07c00bf8ce10808671e0aa2de74f70b6901d97 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 19:50:04 +0200 Subject: [PATCH 061/101] speed up --- package/MDAnalysis/lib/formats/libdcd.pyx | 88 ++++++++++++++++------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 92168fd991f..0ea610a7ccf 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -428,69 +428,103 @@ cdef class DCDFile: self.current_frame += 1 return DCDFrame(xyz, unitcell) + def readframes(self, start=None, stop=None, step=None, order='fac', indices=None): - cdef int i, cstart, cstop, cstep, n, counter + if self.reached_eof: + raise IOError('Reached last frame in DCD, seek to 0') + if not self.is_open: + raise IOError("No file open") + if self.mode != 'r': + raise IOError('File opened in mode: {}. Reading only allow ' + 'in mode "r"'.format('self.mode')) + if self.n_frames == 0: + raise IOError("opened empty file. No frames are saved") + self.seek(0) - cstop = stop if not stop is None else self.n_frames - cstart = start if not start is None else 0 - cstep = step if not step is None else 1 - #n = (cstop - cstart) / cstep - n = len(range(cstart, cstop, cstep)) + stop = stop if not stop is None else self.n_frames + start = start if not start is None else 0 + step = step if not step is None else 1 + cdef int n + n = len(range(start, stop, step)) + cdef np.ndarray[np.int64_t, ndim=1] c_indices if indices == 'None': - indices = np.arange(self.natoms) + c_indices = np.arange(self.natoms) natoms = self.natoms else: natoms = len(indices) + c_indices = np.asarray(indices, dtype=np.int64) - shape = [] + cdef int hash_order = -1 if order == 'fac': shape = (n, natoms, self.ndims) + hash_order = 1 elif order == 'fca': shape = (n, self.ndims, natoms) + hash_order = 2 elif order == 'afc': shape = (natoms, n, self.ndims) + hash_order = 3 elif order == 'acf': shape = (natoms, self.ndims, n) + hash_order = 4 elif order == 'caf': shape = (self.ndims, natoms, n) + hash_order = 5 elif order == 'cfa': + hash_order = 6 shape = (self.ndims, n, natoms) else: raise ValueError("unkown order '{}'".format(order)) - xyz = np.empty(shape) - box = np.empty((n, 6)) + cdef np.ndarray[FLOAT_T, ndim=3] xyz = np.empty(shape, dtype=FLOAT) + cdef np.ndarray[DOUBLE_T, ndim=2] box = np.empty((n, 6)) + + + cdef np.ndarray xyz_tmp = np.empty((self.natoms, self.ndims), dtype=FLOAT, order='F') + cdef int ok, i - if cstart == 0 and cstep == 1 and cstop == self.n_frames: + if start == 0 and step == 1 and stop == self.n_frames: for i in range(n): - f = self.read() - x = f.x[indices] - copy_in_order(x, xyz, order, i) - box[i] = f.unitcell + ok = self.c_readframes_helper(xyz_tmp[:, 0], xyz_tmp[:, 1], xyz_tmp[:, 2], box[i], i==0) + if ok != 0 and ok != -4: + raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + copy_in_order(xyz_tmp[c_indices], xyz, hash_order, i) else: counter = 0 - for i in range(cstart, cstop, cstep): + for i in range(start, stop, step): self.seek(i) - f = self.read() - x = f.x[indices] - copy_in_order(x, xyz, order, counter) - box[counter] = f.unitcell + ok = self.c_readframes_helper(xyz_tmp[:, 0], xyz_tmp[:, 1], xyz_tmp[:, 2], box[counter], i==0) + if ok != 0 and ok != -4: + raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + copy_in_order(xyz_tmp[c_indices], xyz, hash_order, counter) counter += 1 return DCDFrame(xyz, box) + cdef int c_readframes_helper(self, FLOAT_T[::1] x, + FLOAT_T[::1] y, FLOAT_T[::1] z, + DOUBLE_T[::1] unitcell, int first_frame): + cdef int ok + ok = read_dcdstep(self.fp, self.natoms, + &x[0], + &y[0], &z[0], + &unitcell[0], self.nfixed, first_frame, + self.freeind, self.fixedcoords, + self.reverse_endian, self.charmm) + return ok + -def copy_in_order(source, target, order, index): - if order == 'fac': +cdef void copy_in_order(FLOAT_T[:, :] source, FLOAT_T[:, :, :] target, int order, int index): + if order == 1: # 'fac': target[index] = source - elif order == 'fca': + elif order == 2: # 'fca': target[index] = source.T - elif order == 'afc': + elif order == 3: # 'afc': target[:, index] = source - elif order == 'acf': + elif order == 4: # 'acf': target[:, :, index] = source - elif order == 'caf': + elif order == 5: # 'caf': target[:, :, index] = source.T - elif order == 'cfa': + elif order == 6: # 'cfa': target[:, index] = source.T From 66d42a2caadbfe76b10642a06c3e1b2c77d98599 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 2 Jun 2017 23:10:19 +0200 Subject: [PATCH 062/101] add docs --- package/MDAnalysis/lib/formats/libdcd.pyx | 176 +++++++++++++++--- .../MDAnalysisTests/formats/test_libdcd.py | 3 +- 2 files changed, 150 insertions(+), 29 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 0ea610a7ccf..ba9039f3d7c 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -13,6 +13,40 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # +"""\ +Low level DCD trajectory reading - :mod:`MDAnalysis.lib.formats.libdcd` +------------------------------------------------------------------------ + +:mod:`libdcd` contains the class :class:`DCDFile` to read and write frames of a +DCD file. The class tries to behave similar to a normal file object. + +:mod:`libdcd` contains the classes :class:`XTCFile` and +:class:`TRRFile`. Both can be used to read and write frames from and to +Gromacs_ XTC and TRR files. These classes are used internally by MDAnalysis in +:mod:`MDAnalysis.coordinates.XTC` and :mod:`MDAnalysis.coordinates.TRR`. They +behave similar to normal file objects. + +For example, one can use a :class:`DCDFile` to directly calculate mean +coordinates (where the coordinates are stored in `x` attribute of the +:class:`namedtuple` `frame`): + +.. code-block:: python + :emphasize-lines: 1,2,5 + + with DCDFile("trajectory.dcd") as dcd: + header = dcd.header + mean = np.zeros((header['natoms'], 3)) + # iterate over trajectory + for frame in dcd: + mean += frame.x + mean /= header['natoms'] + + +Besides iteration one can also seek to arbitrary frames using the +:meth:`~DCDFile.seek` method. + +""" + from os import path import numpy as np @@ -96,6 +130,26 @@ cdef extern from 'include/readdcd.h': DCDFrame = namedtuple('DCDFrame', 'x unitcell') cdef class DCDFile: + """File like wrapper for DCD files + + This class can be similar to the normal file objects in python. The read() + function will return a frame and all information in it instead of a single + line. Additionally the context-manager protocoll is supported as well. + + Parameters + ---------- + fname : str + The filename to open. + mode : ('r', 'w') + The mode in which to open the file, either 'r' read or 'w' write + + Examples + -------- + >>> from MDAnalysis.lib.formats.libdcd import DCDFile + >>> with DCDFile('foo.dcd') as f: + >>> for frame in f: + >>> print(frame.x) + """ cdef fio_fd fp cdef readonly fname cdef int istart @@ -158,9 +212,29 @@ cdef class DCDFile: return self.n_frames def tell(self): + """ + Returns + ------- + current frame + """ return self.current_frame def open(self, filename, mode='r'): + """Open a DCD file + + If another DCD file is currently opened it will be closed + + Parameters + ---------- + fname : str + The filename to open. + mode : ('r', 'w') + The mode in which to open the file, either 'r' read or 'w' write + + Raises + ------ + IOError + """ if self.is_open: self.close() @@ -182,9 +256,15 @@ cdef class DCDFile: self.wrote_header = False # Has to come last since it checks the reached_eof flag if self.mode == 'r': - self.remarks = self._read_header() + self._read_header() def close(self): + """Close the open DCD file + + Raises + ------ + IOError + """ if self.is_open: # In case there are fixed atoms we should free the memory again. # Both pointers are guaranted to be non NULL if either one is. @@ -198,7 +278,8 @@ cdef class DCDFile: "ErrorCode: {}".format(self.fname, ok)) - def _read_header(self): + cdef void _read_header(self): + """read header and populate internal fields""" if not self.is_open: raise IOError("No file open") @@ -241,9 +322,9 @@ cdef class DCDFile: py_remarks = "".join(s for s in py_remarks if s in string.printable) - return py_remarks + self.remarks = py_remarks - def _estimate_n_frames(self): + cdef int _estimate_n_frames(self): extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 self._firstframesize = (self.natoms + 2) * self.ndims * sizeof(float) + extrablocksize self._framesize = ((self.natoms - self.nfixed + 2) * self.ndims * sizeof(float) + @@ -257,8 +338,13 @@ cdef class DCDFile: @property def is_periodic(self): - return bool((self.charmm & DCD_IS_CHARMM) and - (self.charmm & DCD_HAS_EXTRA_BLOCK)) + """ + Returns + ------- + bool if periodic unitcell is available + """ + return bool((self.charmm & DCD_IS_CHARMM) and + (self.charmm & DCD_HAS_EXTRA_BLOCK)) def seek(self, frame): """Seek to Frame. @@ -295,6 +381,11 @@ cdef class DCDFile: @property def header(self): + """ + Returns + ------- + dict of header values needed to write new dcd + """ return {'natoms': self.natoms, 'istart': self.istart, 'nsavc': self.nsavc, @@ -302,10 +393,9 @@ cdef class DCDFile: 'charmm': self.charmm, 'remarks': self.remarks} - def write_header(self, remarks, natoms, istart, - nsavc, delta, - charmm): - """write DCD header + def write_header(self, remarks, natoms, istart, nsavc, delta, charmm): + """write DCD header. This function needs to be called before a frame can be + written. Parameters ---------- @@ -314,9 +404,14 @@ cdef class DCDFile: natoms : int number of atoms to write istart : int + starting frame nsavc : int + number of frames between saves delta : float + timepstep charmm : int + is charmm dcd + """ if not self.is_open: raise IOError("No file open") @@ -353,12 +448,6 @@ cdef class DCDFile: cartesion coordinates box : array_like, shape=(6) Box vectors for this frame - step : int - current step number, 1 indexed - time : float - current time - natoms : int - number of atoms in frame Raises ------ @@ -387,10 +476,26 @@ cdef class DCDFile: self.natoms, &x[0], &y[0], &z[0], &c_box[0], self.charmm) + if ok != 0: + raise IOError("Couldn't write DCD frame") self.current_frame += 1 def read(self): + """ + Read next dcd frame + + Returns + ------- + DCDFrame : namedtuple + positions are in ``x`` and unitcell in ``unitcell`` attribute of DCDFrame + + Notes + ----- + unitcell is read as it from DCD. Post processing depending the program this + DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader + for possible post processing into a common unitcell data structure. + """ if self.reached_eof: raise IOError('Reached last frame in DCD, seek to 0') if not self.is_open: @@ -406,17 +511,8 @@ cdef class DCDFile: unitcell[0] = unitcell[2] = unitcell[5] = 0.0; unitcell[4] = unitcell[3] = unitcell[1] = 90.0; - cdef FLOAT_T[::1] x = xyz[:, 0] - cdef FLOAT_T[::1] y = xyz[:, 1] - cdef FLOAT_T[::1] z = xyz[:, 2] - first_frame = self.current_frame == 0 - ok = read_dcdstep(self.fp, self.natoms, - &x[0], - &y[0], &z[0], - unitcell.data, self.nfixed, first_frame, - self.freeind, self.fixedcoords, - self.reverse_endian, self.charmm) + ok = self.c_readframes_helper(xyz[:, 0], xyz[:, 1], xyz[:, 2], unitcell, first_frame) if ok != 0 and ok != -4: raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) @@ -430,6 +526,30 @@ cdef class DCDFile: def readframes(self, start=None, stop=None, step=None, order='fac', indices=None): + """ + read multiple frames at once + + Parameters + ---------- + start, stop, step : int + range of frames + order : str (optional) + give order of returned array with `f`:frames, `a`:atoms, `c`:coordinates + indices : array_like (optional) + only read selected atoms. In ``None`` read all. + + Returns + ------- + DCDFrame : namedtuple + positions are in ``x`` and unitcell in ``unitcell`` attribute of DCDFrame. + Here the attributes contain the positions for all frames in the given order + + Notes + ----- + unitcell is read as it from DCD. Post processing depending the program this + DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader + for possible post processing into a common unitcell data structure. + """ if self.reached_eof: raise IOError('Reached last frame in DCD, seek to 0') if not self.is_open: @@ -448,7 +568,7 @@ cdef class DCDFile: n = len(range(start, stop, step)) cdef np.ndarray[np.int64_t, ndim=1] c_indices - if indices == 'None': + if indices is None: c_indices = np.arange(self.natoms) natoms = self.natoms else: @@ -502,6 +622,7 @@ cdef class DCDFile: return DCDFrame(xyz, box) + # Helper to read current DCD frame cdef int c_readframes_helper(self, FLOAT_T[::1] x, FLOAT_T[::1] y, FLOAT_T[::1] z, DOUBLE_T[::1] unitcell, int first_frame): @@ -515,6 +636,7 @@ cdef class DCDFile: return ok +# Helper in readframes to copy given a specific memory layout cdef void copy_in_order(FLOAT_T[:, :] source, FLOAT_T[:, :, :] target, int order, int index): if order == 1: # 'fac': target[index] = source diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 6c2913028fb..2a72b5ba23c 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -1,5 +1,4 @@ -from __future__ import print_function -from __future__ import absolute_import +from __future__ import absolute_import, print_function from nose.tools import raises from numpy.testing import assert_equal, assert_almost_equal From 2c754d4da83fd850011669c021dc6c9c7e6a578f Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 4 Jun 2017 17:38:21 +0200 Subject: [PATCH 063/101] copy timestep this avoids that ts points to the same reference in a loop. --- package/MDAnalysis/coordinates/DCD.py | 3 ++- testsuite/MDAnalysisTests/coordinates/test_dcd.py | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 6b74947804d..4f7e545a16a 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -168,7 +168,8 @@ def _read_next_timestep(self, ts=None): if self._frame == self.n_frames - 1: raise IOError('trying to go over trajectory limit') if ts is None: - ts = self.ts + # use a copy to avoid that ts always points to the same reference + ts = self.ts.copy() frame = self._file.read() self._frame += 1 ts = self._frame_to_ts(frame, ts) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 94c57517d32..a37114860dd 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -107,25 +107,23 @@ class TestDCDReader_old(TestCase): def setUp(self): self.universe = mda.Universe(PSF, DCD) self.dcd = self.universe.trajectory - self.ts = self.universe.coord def tearDown(self): del self.universe del self.dcd - del self.ts def test_rewind_dcd(self): self.dcd.rewind() - assert_equal(self.ts.frame, 0, "rewinding to frame 0") + assert_equal(self.dcd.ts.frame, 0, "rewinding to frame 0") def test_next_dcd(self): self.dcd.rewind() self.dcd.next() - assert_equal(self.ts.frame, 1, "loading frame 1") + assert_equal(self.dcd.ts.frame, 1, "loading frame 1") def test_jump_lastframe_dcd(self): self.dcd[-1] - assert_equal(self.ts.frame, 97, "indexing last frame with dcd[-1]") + assert_equal(self.dcd.ts.frame, 97, "indexing last frame with dcd[-1]") def test_slice_dcd(self): frames = [ts.frame for ts in self.dcd[5:17:3]] From 9b1d607146ad7bb0fb77fe62e345c7c5909d6dbf Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 4 Jun 2017 18:10:54 +0200 Subject: [PATCH 064/101] fixed known failure in transition fix unit tests --- .../MDAnalysisTests/coordinates/test_dcd.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index a37114860dd..537c2e9349a 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -158,23 +158,22 @@ def test_timeseries_slicing(self): for start, stop, step in x: yield self._slice_generation_test, start, stop, step - # def test_backwards_stepping(self): - # x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] - # for start, stop, step in x: - # yield self._failed_slices_test, start, stop, step + def test_backwards_stepping(self): + x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] + for start, stop, step in x: + yield self._failed_slices_test, start, stop, step def _slice_generation_test(self, start, stop, step): self.u = mda.Universe(PSF, DCD) ts = self.u.trajectory.timeseries(self.u.atoms) ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[start:stop:step], ts_skip, 5) - - # @knownfailure - # def _failed_slices_test(self, start, stop, step): - # self.u = mda.Universe(PSF, DCD) - # ts = self.u.trajectory.timeseries(self.u.atoms) - # ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - # assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) + assert_array_almost_equal(ts[:,start:stop:step], ts_skip, 5) + + def _failed_slices_test(self, start, stop, step): + self.u = mda.Universe(PSF, DCD) + ts = self.u.trajectory.timeseries(self.u.atoms) + ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) + assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) def test_DCDReader_set_dt(dt=100., frame=3): From f0170be38eaf87f83a807c8846bf244fbc188ba4 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 4 Jun 2017 18:16:38 +0200 Subject: [PATCH 065/101] update changelog --- package/CHANGELOG | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 170dbe84cbb..e13ee60141a 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -18,6 +18,8 @@ mm/dd/17 richardjgowers, rathann, orbeckst, tylerjereddy, mtiberti * 0.17.0 Enhancements + * add low level lib.formats.libdcd module for reading/writing DCD (PR #1372) + * add Python 3 ready DCD reader (Issue #659) Deprecations @@ -39,14 +41,10 @@ Changes writing (if available) or fall back to netcdf (see also Issue #506) -mm/dd/17 richardjgowers, rathann, jbarnoud, orbeckst, utkbansal +mm/dd/17 richardjgowers, rathann, jbarnoud, kain88-de * 0.16.2 -Deprecations - * deprecated core.Timeseries module for 0.17.0 (Issue #1383) - * deprecated instant selectors for 1.0 (Issue #1377) - * deprecated the core.flag registry for 1.0 (Issue #782) Fixes * fixed GROWriter truncating long resids from the wrong end (Issue #1395) From eb1c00181c088012ddc69a28401753dd3051f814 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Tue, 13 Jun 2017 22:49:09 -0600 Subject: [PATCH 066/101] DCD test improvements - test_libdcd self.testfile generation switched to os.path.join based on reviewer comments in PR 1372. - libdcd test_written_unit_cell() adjusted to use assert_almost_equal, as requested by reviewer in PR 1372. - libdcd test_relative_frame_sizes() has simplified assert statement based on reviewer request in PR 1372. - test_libdcd.py adjusted with (commented) knownfailure decorators for appropriate unit cell writing tests as requested in PR 1372. Comments are used because the tests pass, but we do not explicitly support this writing behavior. - Added knownfailure unit tests for unit cell writing in unsupported formats. - Clarified header writing unit test comments as requested by reviewer in PR 1372. --- .../MDAnalysisTests/formats/test_libdcd.py | 143 +++++++++++------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 2a72b5ba23c..727b458270a 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function +from MDAnalysisTests.plugins.knownfailure import knownfailure from nose.tools import raises from numpy.testing import assert_equal, assert_almost_equal -from numpy.testing import assert_allclose, assert_array_almost_equal +from numpy.testing import (assert_allclose, assert_array_almost_equal, + assert_) from MDAnalysis.lib.formats.libdcd import DCDFile from MDAnalysisTests.datafiles import ( @@ -159,7 +161,7 @@ def test_iteration_2(self): class TestDCDWriteHeader(object): def setUp(self): self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') self.dcdfile = DCD def tearDown(self): @@ -195,15 +197,16 @@ def test_write_header(self): @raises(IOError) def test_write_no_header(self): - # test that _write_header() can produce a very crude - # header for a new / empty file + # an IOError should be raised if we + # attempt to write inappropriate header + # data that looks like frame data with DCDFile(self.testfile, 'w') as dcd: dcd.write(np.ones(3), np.ones(6)) @raises(IOError) def test_write_header_twice(self): - # test that _write_header() can produce a very crude - # header for a new / empty file + # an IOError should be raised if a duplicate + # header writing is attempted with DCDFile(self.testfile, 'w') as dcd: dcd.write_header( remarks='Crazy!', @@ -237,8 +240,8 @@ def test_write_header_mode_sensitivy(self): class TestDCDWrite(object): def setUp(self): self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' - self.testfile2 = self.tmpdir.name + '/test2.dcd' + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') + self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') self.readfile = DCD self.natoms = 3341 self.expected_frames = 98 @@ -260,6 +263,16 @@ def _write_files(self, testfile, remarks_setting): box = frame.unitcell.astype(np.float64) f_out.write(xyz=frame.x, box=box) + def _test_written_unit_cell(self): + # written unit cell dimensions should match for all frames + with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: + curr_frame = 0 + while curr_frame < test.n_frames: + written_unitcell = test.read().unitcell + ref_unitcell = ref.read().unitcell + curr_frame += 1 + assert_almost_equal(written_unitcell, ref_unitcell) + def tearDown(self): try: os.unlink(self.testfile) @@ -287,14 +300,7 @@ def test_written_dcd_coordinate_data_shape(self): assert_equal(xyz.shape, expected) def test_written_unit_cell(self): - # written unit cell dimensions should match for all frames - with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: - curr_frame = 0 - while curr_frame < test.n_frames: - written_unitcell = test.read().unitcell - ref_unitcell = ref.read().unitcell - curr_frame += 1 - assert_equal(written_unitcell, ref_unitcell) + self._test_written_unit_cell() def test_written_num_frames(self): with DCDFile(self.testfile) as f: @@ -416,45 +422,8 @@ def test_write_wrong_shape_box(self): out.write(xyz=xyz, box=box) -class TestDCDWriteNAMD(TestDCDWrite): - # repeat writing tests for NAMD format DCD - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' - self.testfile2 = self.tmpdir.name + '/test2.dcd' - self.readfile = DCD_NAMD_TRICLINIC - self.natoms = 5545 - self.expected_frames = 1 - self.seek_frame = 0 - self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' - self._write_files(testfile=self.testfile, remarks_setting='input') - - def test_written_unit_cell(self): - # there's no expectation that we can write unit cell - # data in NAMD format at the moment - pass -class TestDCDWriteCharmm36(TestDCDWrite): - # repeat writing tests for Charmm36 format DCD - # no expectation that we can write unit cell info though (yet) - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' - self.testfile2 = self.tmpdir.name + '/test2.dcd' - self.readfile = DCD_TRICLINIC - self.natoms = 375 - self.expected_frames = 10 - self.seek_frame = 7 - self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' - self._write_files(testfile=self.testfile, remarks_setting='input') - - def test_written_unit_cell(self): - # there's no expectation that we can write unit cell - # data in NAMD format at the moment - pass class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): @@ -462,7 +431,7 @@ class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): def setUp(self): self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') self.dcdfile = DCD_NAMD_TRICLINIC @@ -471,7 +440,7 @@ class TestDCDWriteHeaderCharmm36(TestDCDWriteHeader): def setUp(self): self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') self.dcdfile = DCD_TRICLINIC @@ -520,7 +489,7 @@ class TestDCDWriteRandom(object): def setUp(self): self.tmpdir = tempdir.TempDir() - self.testfile = self.tmpdir.name + '/test.dcd' + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') self.readfile = DCD self.natoms = 3341 self.expected_frames = 98 @@ -546,7 +515,7 @@ def tearDown(self): pass del self.tmpdir - def test_written_unit_cell_random(self): + def _test_written_unit_cell_random(self): with DCDFile(self.testfile) as test: curr_frame = 0 while curr_frame < test.n_frames: @@ -556,6 +525,9 @@ def test_written_unit_cell_random(self): curr_frame += 1 assert_allclose(written_unitcell, ref_unitcell, rtol=1e-05) + def test_written_unit_cell_random(self): + self._test_written_unit_cell_random() + class TestDCDByteArithmetic(object): def setUp(self): @@ -570,7 +542,7 @@ def test_relative_frame_sizes(self): first_frame_size = dcd._firstframesize general_frame_size = dcd._framesize - assert_equal(first_frame_size >= general_frame_size, True) + assert_(first_frame_size >= general_frame_size) def test_file_size_breakdown(self): # the size of a DCD file is equivalent to the sum of the header @@ -605,3 +577,58 @@ class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): def setUp(self): self.dcdfile = DCD_TRICLINIC self._filesize = os.path.getsize(DCD_TRICLINIC) + +class TestDCDWriteNAMD(TestDCDWrite, TestDCDWriteRandom): + # repeat writing tests for NAMD format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') + self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') + self.readfile = DCD_NAMD_TRICLINIC + self.natoms = 5545 + self.expected_frames = 1 + self.seek_frame = 0 + self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' + self._write_files(testfile=self.testfile, remarks_setting='input') + np.random.seed(1178083) + self.random_unitcells = np.random.uniform( + high=80, size=(self.expected_frames, 6)).astype(np.float64) + + #@knownfailure + def test_written_unit_cell(self): + # there's no expectation that we can write unit cell + # data in NAMD format at the moment + self._test_written_unit_cell() + + @knownfailure + def test_written_unit_cell_random(self): + self._test_written_unit_cell_random() + +class TestDCDWriteCharmm36(TestDCDWrite, TestDCDWriteRandom): + # repeat writing tests for Charmm36 format DCD + # no expectation that we can write unit cell info though (yet) + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') + self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') + self.readfile = DCD_TRICLINIC + self.natoms = 375 + self.expected_frames = 10 + self.seek_frame = 7 + self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' + self._write_files(testfile=self.testfile, remarks_setting='input') + np.random.seed(1178083) + self.random_unitcells = np.random.uniform( + high=80, size=(self.expected_frames, 6)).astype(np.float64) + + #@knownfailure + def test_written_unit_cell(self): + # there's no expectation that we can write unit cell + # data in charmm format at the moment + self._test_written_unit_cell() + + @knownfailure + def test_written_unit_cell_random(self): + self._test_written_unit_cell_random() From d3c437dca77213697c3482f4c160b9e340d8b66f Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 15 Jun 2017 19:45:49 +0200 Subject: [PATCH 067/101] update libdcd and DCDReader docs --- package/MDAnalysis/coordinates/DCD.py | 145 ++++++++++++++-------- package/MDAnalysis/lib/formats/libdcd.pyx | 77 +++++++----- 2 files changed, 138 insertions(+), 84 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 4f7e545a16a..f24d277cb3a 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -27,27 +27,14 @@ system-endianness as this is auto-detected. Generally, DCD trajectories produced by any code can be read (with the -:class:`DCDReader`) although there can be issues with the unitcell -(simulation box) representation (see -:attr:`Timestep.dimensions`). DCDs can also be written but the -:class:`DCDWriter` follows recent NAMD/VMD convention for the unitcell -but still writes AKMA time. Reading and writing these trajectories -within MDAnalysis will work seamlessly but if you process those -trajectories with other tools you might need to watch out that time -and unitcell dimensions are correctly interpreted. - -Note ----- -The DCD file format is not well defined. In particular, NAMD and -CHARMM use it differently. Currently, MDAnalysis tries to guess the -correct **format for the unitcell representation** but it can be -wrong. **Check the unitcell dimensions**, especially for triclinic -unitcells (see `Issue 187`_ and :attr:`Timestep.dimensions`). A -second potential issue are the units of time which are AKMA for the -:class:`DCDReader` (following CHARMM) but ps for NAMD. As a -workaround one can employ the configurable -:class:`MDAnalysis.coordinates.LAMMPS.DCDReader` for NAMD -trajectories. +:class:`DCDReader`) although there can be issues with the unitcell (simulation +box) representation (see :attr:`DCDReader.dimensions`). DCDs can also be +written but the :class:`DCDWriter` follows recent NAMD/VMD convention for the +unitcell but still writes AKMA time. Reading and writing these trajectories +within MDAnalysis will work seamlessly but if you process those trajectories +with other tools you might need to watch out that time and unitcell dimensions +are correctly interpreted. + See Also -------- @@ -55,9 +42,6 @@ module provides a more flexible DCD reader/writer. -The classes in this module are the reference implementations for the -Trajectory API. - .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 @@ -85,17 +69,23 @@ from ..core import flags from .. import units as mdaunits # use mdaunits instead of units to avoid a clash from ..exceptions import NoDataError -from . import base -from . import core +from . import base, core from ..lib.formats.libdcd import DCDFile from ..lib.mdamath import triclinic_box -# dcdtimeseries is implemented with Pyrex - hopefully all dcd reading functionality can move to pyrex -# from . import dcdtimeseries - class DCDReader(base.ReaderBase): - """DCD Reader + """Reader for the DCD format. + + DCD is used by NAMD, CHARMM and LAMMPS as the default trajectory format. + The DCD file format is not well defined. In particular, NAMD and CHARMM use + it differently. Currently, MDAnalysis tries to guess the correct **format + for the unitcell representation** but it can be wrong. **Check the unitcell + dimensions**, especially for triclinic unitcells (see `Issue 187`_ and + :attr:`DCDReader.dimensions`). A second potential issue are the units of + time which are AKMA for the :class:`DCDReader` (following CHARMM) but ps + for NAMD. As a workaround one can employ the configurable + :class:`MDAnalysis.coordinates.LAMMPS.DCDReader` for NAMD trajectories. """ format = 'DCD' @@ -114,6 +104,8 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): **kwargs : dict General reader arguments. + .. versionchanged:: 0.17.0 + Changed to use libdcd.pyx library and removed the correl function """ super(DCDReader, self).__init__( filename, convert_units=convert_units, **kwargs) @@ -188,25 +180,23 @@ def Writer(self, filename, n_atoms=None, **kwargs): **kwargs) def _frame_to_ts(self, frame, ts): - """convert a trr-frame to a mda TimeStep""" + """convert a dcd-frame to a mda TimeStep""" ts.frame = self._frame ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() unitcell = frame.unitcell - M_PI_2 = np.pi / 2 - if (unitcell[1] >= -1.0 and unitcell[1] <= 1.0 and - unitcell[3] >= -1.0 and unitcell[3] <= 1.0 and - unitcell[4] >= -1.0 and unitcell[4] <= 1.0): + pi_2 = np.pi / 2 + if (-1.0 <= unitcell[1] <= 1.0) and (-1.0 <= unitcell[3] <= 1.0) and (-1.0 <= unitcell[4] <= 1.0): # This file was generated by Charmm, or by NAMD > 2.5, with the angle # cosines of the periodic cell angles written to the DCD file. # This formulation improves rounding behavior for orthogonal cells # so that the angles end up at precisely 90 degrees, unlike acos(). # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; # see Issue 187) */ - alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / M_PI_2 - beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / M_PI_2 - gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / M_PI_2 + alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / pi_2 + beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / pi_2 + gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / pi_2 else: # This file was likely generated by NAMD 2.5 and the periodic cell # angles are specified in degrees rather than angle cosines. @@ -222,7 +212,6 @@ def _frame_to_ts(self, frame, ts): _ts_order = [0, 2, 5, 4, 3, 1] uc = np.take(unitcell, _ts_order) # heuristic sanity check: uc = A,B,C,alpha,beta,gamma - # XXX: should we worry about these comparisons with floats? if np.any(uc < 0.) or np.any(uc[3:] > 180.): # might be new CHARMM: box matrix vectors H = unitcell @@ -239,8 +228,44 @@ def _frame_to_ts(self, frame, ts): return ts + @property + def dimensions(self): + """unitcell dimensions (*A*, *B*, *C*, *alpha*, *beta*, *gamma*) + + lengths *A*, *B*, *C* are in the MDAnalysis length unit (Å), and + angles are in degrees. + + The ordering of the angles in the unitcell is the same as in recent + versions of VMD's DCDplugin_ (2013), namely the `X-PLOR DCD format`_: + The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` from + the DCD file; If any of these values are < 0 or if any of the angles + are > 180 degrees then it is assumed it is a new-style CHARMM unitcell + (at least since c36b2) in which box vectors were recorded. + + + .. warning:: + The DCD format is not well defined. Check your unit cell + dimensions carefully, especially when using triclinic boxes. + Different software packages implement different conventions and + MDAnalysis is currently implementing the newer NAMD/VMD convention + and tries to guess the new CHARMM one. Old CHARMM trajectories might + give wrong unitcell values. For more details see `Issue 187`_. + + .. versionchanged:: 0.9.0 + Unitcell is now interpreted in the newer NAMD DCD format as ``[A, + gamma, B, beta, alpha, C]`` instead of the old MDAnalysis/CHARMM + ordering ``[A, alpha, B, beta, gamma, C]``. We attempt to detect the + new CHARMM DCD unitcell format (see `Issue 187`_ for a discussion). + + .. _`X-PLOR DCD format`: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/dcdplugin.html + .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 + .. _DCDplugin: http://www.ks.uiuc.edu/Research/vmd/plugins/doxygen/dcdplugin_8c-source.html#l00947 + """ + return self.ts.dimensions + @property def dt(self): + """timestep between frames""" return self.ts.dt def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, @@ -254,15 +279,16 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, coordinates from. Defaults to None, in which case the full set of coordinate data is returned. start : int (optional) - Begin reading the trajectory at frame index `start` (where 0 is the index - of the first frame in the trajectory); the default ``None`` starts - at the beginning. + Begin reading the trajectory at frame index `start` (where 0 is the + index of the first frame in the trajectory); the default ``None`` + starts at the beginning. stop : int (optional) - End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded. - The trajectory is read to the end with the default ``None``. + End reading the trajectory at frame index `stop`-1, i.e, `stop` is + excluded. The trajectory is read to the end with the default + ``None``. step : int (optional) - Step size for reading; the default ``None`` is equivalent to 1 and means to - read every frame. + Step size for reading; the default ``None`` is equivalent to 1 and + means to read every frame. format : str (optional) the order/shape of the return data array, corresponding to (a)tom, (f)rame, (c)oordinates all six combinations @@ -297,7 +323,13 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, class DCDWriter(base.WriterBase): - """Base class for libmdaxdr file formats xtc and trr""" + """DCD Writer class + + The writer follows recent NAMD/VMD convention for the unitcell but still + writes AKMA time. The unitcell will be written as ``[A, gamma, B, beta, + alpha, C]`` + + """ multiframe = True flavor = 'CHARMM' @@ -312,8 +344,7 @@ def __init__(self, remarks='', nsavc=1, **kwargs): - """ - Parameters + """Parameters ---------- filename : str filename of trajectory @@ -324,7 +355,18 @@ def __init__(self, step : int (optional) number of steps between frames to be written dt : float (optional) - use this time step in DCD. If ``None`` guess from written TimeStep + use this time step in DCD. If ``None`` guess from first written + TimeStep + remarks : str (optional) + remarks to be stored in DCD. Shouldn't be more then 240 characters + nsavc : int (optional) + DCD usually saves ``dt`` as the integrator timestep and the + frequency of writes in the nsavc variable separately. Unless you + know what you are doing you don't need to touch this value. + If you plan to use the written DCD with another tool that depends + on this behavior you can adjust it with this variable. The DCD + reader will then interpret the timestep between frames as ``dt * + nsavc``. **kwargs : dict General writer arguments """ @@ -334,7 +376,6 @@ def __init__(self, self._file = DCDFile(self.filename, 'w') self.step = step self.dt = dt - dt = self.dt if self.dt is not None else ts.dt dt = mdaunits.convert(dt, 'ps', self.units['time']) self._file.write_header( remarks=remarks, natoms=n_atoms, nsavc=nsavc, delta=float(dt), charmm=1, istart=0) @@ -352,8 +393,6 @@ def write_next_timestep(self, ts): The normal write() method takes a more general input """ xyz = ts.positions.copy() - time = ts.time - step = ts.frame dimensions = ts.dimensions if self._convert_units: diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index ba9039f3d7c..01c7b7f5d7c 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -20,11 +20,10 @@ Low level DCD trajectory reading - :mod:`MDAnalysis.lib.formats.libdcd` :mod:`libdcd` contains the class :class:`DCDFile` to read and write frames of a DCD file. The class tries to behave similar to a normal file object. -:mod:`libdcd` contains the classes :class:`XTCFile` and -:class:`TRRFile`. Both can be used to read and write frames from and to -Gromacs_ XTC and TRR files. These classes are used internally by MDAnalysis in -:mod:`MDAnalysis.coordinates.XTC` and :mod:`MDAnalysis.coordinates.TRR`. They -behave similar to normal file objects. +:mod:`libdcd` contains the classes :class:`DCDFile`, which can be used to read +and write frames from and to DCD files. These classes are used internally by +MDAnalysis in :mod:`MDAnalysis.coordinates.DCD`. They behave similar to normal +file objects. For example, one can use a :class:`DCDFile` to directly calculate mean coordinates (where the coordinates are stored in `x` attribute of the @@ -88,14 +87,14 @@ cdef enum: DCD_HAS_EXTRA_BLOCK = 0x04 DCD_ERRORS = { - 0: 'No Problem', + 0: 'Success', -1: 'Normal EOF', -2: 'DCD file does not exist', -3: 'Open of DCD file failed', -4: 'read call on DCD file failed', -5: 'premature EOF found in DCD file', -6: 'format of DCD file is wrong', - -7: 'output file already exiss', + -7: 'output file already exists', -8: 'malloc failed' } @@ -134,7 +133,7 @@ cdef class DCDFile: This class can be similar to the normal file objects in python. The read() function will return a frame and all information in it instead of a single - line. Additionally the context-manager protocoll is supported as well. + line. Additionally the context-manager protocol is supported as well. Parameters ---------- @@ -149,6 +148,11 @@ cdef class DCDFile: >>> with DCDFile('foo.dcd') as f: >>> for frame in f: >>> print(frame.x) + + + Raises + ------ + IOError """ cdef fio_fd fp cdef readonly fname @@ -179,7 +183,7 @@ cdef class DCDFile: self.natoms = 0 self.is_open = False self.wrote_header = False - self.open(self.fname, mode) + self.open(mode) def __dealloc__(self): self.close() @@ -187,7 +191,7 @@ cdef class DCDFile: def __enter__(self): """Support context manager""" if not self.is_open: - self.open(self.fname, self.mode) + self.open(self.mode) return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -198,7 +202,7 @@ cdef class DCDFile: def __iter__(self): self.close() - self.open(self.fname, self.mode) + self.open(self.mode) return self def __next__(self): @@ -219,15 +223,13 @@ cdef class DCDFile: """ return self.current_frame - def open(self, filename, mode='r'): + def open(self, mode='r'): """Open a DCD file If another DCD file is currently opened it will be closed Parameters ---------- - fname : str - The filename to open. mode : ('r', 'w') The mode in which to open the file, either 'r' read or 'w' write @@ -249,7 +251,7 @@ cdef class DCDFile: ok = fio_open(self.fname, fio_mode, &self.fp) if ok != 0: raise IOError("couldn't open file: {}\n" - "ErrorCode: {}".format(self.fname, ok)) + "ErrorCode: {}".format(self.fname, DCD_ERRORS[ok])) self.is_open = True self.current_frame = 0 self.reached_eof = False @@ -275,7 +277,7 @@ cdef class DCDFile: self.is_open = False if ok != 0: raise IOError("couldn't close file: {}\n" - "ErrorCode: {}".format(self.fname, ok)) + "ErrorCode: {}".format(self.fname, DCD_ERRORS[ok])) cdef void _read_header(self): @@ -299,8 +301,9 @@ cdef class DCDFile: free(c_remarks) else: py_remarks = "" - self.ndims = 3 if not self.charmm & DCD_HAS_4DIMS else 4 + # This function assumes that the dcd header was already read and + # self.ndims is set. It will only work when called here !!! self.n_frames = self._estimate_n_frames() self.b_read_header = True @@ -308,7 +311,7 @@ cdef class DCDFile: try: self.read() self.seek(0) - except: + except IOError: # if this fails the file is empty. Set flag and warn using during read if self.n_frames != 0: raise IOError("DCD is corrupted") @@ -325,6 +328,8 @@ cdef class DCDFile: self.remarks = py_remarks cdef int _estimate_n_frames(self): + """ Only call this function in _read_header!!! + """ extrablocksize = 48 + 8 if self.charmm & DCD_HAS_EXTRA_BLOCK else 0 self._firstframesize = (self.natoms + 2) * self.ndims * sizeof(float) + extrablocksize self._framesize = ((self.natoms - self.nfixed + 2) * self.ndims * sizeof(float) + @@ -349,10 +354,6 @@ cdef class DCDFile: def seek(self, frame): """Seek to Frame. - Please note that this function will generate internal file offsets if - they haven't been set before. For large file this means the first seek - can be very slow. Later seeks will be very fast. - Parameters ---------- frame : int @@ -365,7 +366,7 @@ cdef class DCDFile: seek fails (the low-level system error is reported). """ if frame >= self.n_frames: - raise IOError('Trying to seek over max number of frames') + raise EOFError('Trying to seek over max number of frames') self.reached_eof = False cdef fio_size_t offset @@ -376,7 +377,7 @@ cdef class DCDFile: ok = fio_fseek(self.fp, offset, _whence_vals["FIO_SEEK_SET"]) if ok != 0: - raise IOError("DCD seek failed with system errno={}".format(ok)) + raise IOError("DCD seek failed with system errno={}".format(DCD_ERRORS[ok])) self.current_frame = frame @property @@ -408,7 +409,7 @@ cdef class DCDFile: nsavc : int number of frames between saves delta : float - timepstep + integrator time step. The time for 1 frame is nsavc * delta charmm : int is charmm dcd @@ -462,11 +463,11 @@ cdef class DCDFile: raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) if not self.wrote_header: raise IOError("write header first before frames can be written") - xyz = np.asarray(xyz, order='F', dtype=np.float32) + xyz = np.asarray(xyz, order='F', dtype=FLOAT) if xyz.shape != (self.natoms, 3): raise ValueError("xyz shape is wrong should be (natoms, 3), got:".format(xyz.shape)) - cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=np.float64) + cdef DOUBLE_T[::1] c_box = np.asarray(box, order='C', dtype=DOUBLE) cdef FLOAT_T[::1] x = xyz[:, 0] cdef FLOAT_T[::1] y = xyz[:, 1] cdef FLOAT_T[::1] z = xyz[:, 2] @@ -477,7 +478,7 @@ cdef class DCDFile: &y[0], &z[0], &c_box[0], self.charmm) if ok != 0: - raise IOError("Couldn't write DCD frame") + raise IOError("Couldn't write DCD frame: reason {}".format(DCD_ERRORS[ok])) self.current_frame += 1 @@ -492,9 +493,14 @@ cdef class DCDFile: Notes ----- - unitcell is read as it from DCD. Post processing depending the program this + unitcell is read as is from DCD. Post processing depending on the program this DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader for possible post processing into a common unitcell data structure. + + Raises + ------ + IOError + StopIteration """ if self.reached_eof: raise IOError('Reached last frame in DCD, seek to 0') @@ -531,8 +537,12 @@ cdef class DCDFile: Parameters ---------- - start, stop, step : int - range of frames + start : int (optional) + starting frame, default to 0 + stop : int (optional) + stop frame, default to ``n_frames`` + step : int (optional) + step between frames read, defaults to 1 order : str (optional) give order of returned array with `f`:frames, `a`:atoms, `c`:coordinates indices : array_like (optional) @@ -549,6 +559,11 @@ cdef class DCDFile: unitcell is read as it from DCD. Post processing depending the program this DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader for possible post processing into a common unitcell data structure. + + Raises + ------ + IOError + ValueError """ if self.reached_eof: raise IOError('Reached last frame in DCD, seek to 0') From f9492eedb7796e0b1920e2a8af4536f30db46fe9 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 15 Jun 2017 19:46:09 +0200 Subject: [PATCH 068/101] yapify DCD --- package/MDAnalysis/coordinates/DCD.py | 35 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index f24d277cb3a..64082084a65 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -187,7 +187,8 @@ def _frame_to_ts(self, frame, ts): unitcell = frame.unitcell pi_2 = np.pi / 2 - if (-1.0 <= unitcell[1] <= 1.0) and (-1.0 <= unitcell[3] <= 1.0) and (-1.0 <= unitcell[4] <= 1.0): + if (-1.0 <= unitcell[1] <= 1.0) and (-1.0 <= unitcell[3] <= 1.0) and ( + -1.0 <= unitcell[4] <= 1.0): # This file was generated by Charmm, or by NAMD > 2.5, with the angle # cosines of the periodic cell angles written to the DCD file. # This formulation improves rounding behavior for orthogonal cells @@ -268,7 +269,12 @@ def dt(self): """timestep between frames""" return self.ts.dt - def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, + def timeseries(self, + asel=None, + start=None, + stop=None, + step=None, + skip=None, format='afc'): """Return a subset of coordinate data for an AtomGroup @@ -302,23 +308,27 @@ def timeseries(self, asel=None, start=None, stop=None, step=None, skip=None, """ if skip is not None: step = skip - warnings.warn("Skip is deprecated and will be removed in" - "in 1.0. Use step instead.", - category=DeprecationWarning) + warnings.warn( + "Skip is deprecated and will be removed in" + "in 1.0. Use step instead.", + category=DeprecationWarning) start, stop, step = self.check_slice_indices(start, stop, step) if asel is not None: if len(asel) == 0: - raise NoDataError("Timeseries requires at least one atom to analyze") + raise NoDataError( + "Timeseries requires at least one atom to analyze") atom_numbers = list(asel.indices) else: atom_numbers = list(range(self.n_atoms)) - if len(format) != 3 and format not in ['afc', 'acf', 'caf', 'cfa', 'fac', 'fca']: + if len(format) != 3 and format not in [ + 'afc', 'acf', 'caf', 'cfa', 'fac', 'fca' + ]: raise ValueError("Invalid timeseries format") - frames = self._file.readframes(start, stop, step, - order=format, indices=atom_numbers) + frames = self._file.readframes( + start, stop, step, order=format, indices=atom_numbers) return frames.x @@ -378,7 +388,12 @@ def __init__(self, self.dt = dt dt = mdaunits.convert(dt, 'ps', self.units['time']) self._file.write_header( - remarks=remarks, natoms=n_atoms, nsavc=nsavc, delta=float(dt), charmm=1, istart=0) + remarks=remarks, + natoms=n_atoms, + nsavc=nsavc, + delta=float(dt), + charmm=1, + istart=0) def write_next_timestep(self, ts): """Write timestep object into trajectory. From 9f790046ec79a7b7fbdc7f30d189ea970e569dcc Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 15 Jun 2017 21:59:01 +0200 Subject: [PATCH 069/101] fix error in DCD Reader and remove confusing comment --- package/MDAnalysis/coordinates/DCD.py | 2 +- package/MDAnalysis/lib/formats/libdcd.pyx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 64082084a65..7ad4325e332 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -147,7 +147,7 @@ def _reopen(self): self.ts.frame = 0 self._frame = -1 self._file.close() - self._file.open(self.filename.encode('utf-8'), 'r') + self._file.open('r') def _read_frame(self, i): """read frame i""" diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 01c7b7f5d7c..5cfe619758d 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -312,7 +312,6 @@ cdef class DCDFile: self.read() self.seek(0) except IOError: - # if this fails the file is empty. Set flag and warn using during read if self.n_frames != 0: raise IOError("DCD is corrupted") From 19316df2b6759e79f791e84c3d0f0496f2235a87 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Thu, 15 Jun 2017 21:59:09 +0200 Subject: [PATCH 070/101] fix test for dcd --- .../MDAnalysisTests/coordinates/test_dcd.py | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 537c2e9349a..2d49016fa22 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -20,30 +20,27 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # from __future__ import absolute_import, print_function +from six.moves import zip, range import MDAnalysis as mda from MDAnalysis.coordinates.DCD import DCDReader import numpy as np import os -from six.moves import zip, range from nose.plugins.attrib import attr from numpy.testing import (assert_equal, assert_array_equal, assert_raises, assert_almost_equal, assert_array_almost_equal, assert_allclose, dec) -from unittest import TestCase from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, CRD, PRMncdf, NCDF, COORDINATES_TOPOLOGY, COORDINATES_DCD) from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD) -# from MDAnalysisTests.coordinates.base import BaseTimestepTest from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, BaseWriterTest, assert_timestep_almost_equal) -from MDAnalysisTests import module_not_found, tempdir -# from MDAnalysisTests.plugins.knownfailure import knownfailure +from MDAnalysisTests import tempdir class DCDReference(BaseReference): @@ -99,11 +96,7 @@ def __init__(self, reference=None): ################ -class TestDCDReaderClass(TestCase): - pass - - -class TestDCDReader_old(TestCase): +class TestDCDReaderOld(TestCase): def setUp(self): self.universe = mda.Universe(PSF, DCD) self.dcd = self.universe.trajectory @@ -153,7 +146,7 @@ def test_reverse_dcd(self): def test_timeseries_slicing(self): # check that slicing behaves correctly # should before issue #914 resolved - x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 4, 2), (1, 4, 4), + x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), (0, 5, 5), (3, 5, 1), (None, None, None)] for start, stop, step in x: yield self._slice_generation_test, start, stop, step @@ -351,12 +344,10 @@ def test_read_triclinic(self): def test_write_triclinic(self): """test writing of triclinic unitcell (Issue 187) for NAMD or new CHARMM format (at least since c36b2)""" - print("writing") with self.u.trajectory.OtherWriter(self.dcd) as w: for ts in self.u.trajectory: w.write(ts) w = mda.Universe(self.topology, self.dcd) - print("reading\n") for ts_orig, ts_copy in zip(self.u.trajectory, w.trajectory): assert_almost_equal(ts_orig.dimensions, ts_copy.dimensions, 4, @@ -376,8 +367,6 @@ class TestDCDReader_NAMD_Unitcell(_TestDCDReader_TriclinicUnitcell, class TestNCDF2DCD(object): - @dec.skipif(module_not_found("netCDF4"), - "Test skipped because netCDF is not available.") def setUp(self): self.u = mda.Universe(PRMncdf, NCDF) # create the DCD @@ -420,5 +409,3 @@ def test_coordinates(self): 3, err_msg="NCDF->DCD: coordinates wrong at frame {0:d}".format( ts_orig.frame)) - - From 11bef65c59d0bb6269648fd877e8d8db13f26ae3 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 15 Jun 2017 17:23:48 -0600 Subject: [PATCH 071/101] test_libdcd test_seek_over_max() now appropriately checks for EOFError instead of IOError. --- testsuite/MDAnalysisTests/formats/test_libdcd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 727b458270a..8dca33934b2 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -90,9 +90,10 @@ def test_read_unit_cell(self): dcd_frame = dcd.read() assert_array_almost_equal(dcd_frame.unitcell, self.expected_unit_cell) - @raises(IOError) + @raises(EOFError) def test_seek_over_max(self): - # should raise IOError if beyond 98th frame + # should raise EOFError if beyond frame + # total with DCDFile(DCD) as dcd: dcd.seek(102) From 3d5e2d73c8152958327b26bd8017751414e21695 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 16 Jun 2017 07:54:01 +0200 Subject: [PATCH 072/101] reorder tests This way the alternatives where we test CHARMM and NAMD are sorted directly after declaring the initial test class --- .../MDAnalysisTests/formats/test_libdcd.py | 270 +++++++++--------- 1 file changed, 139 insertions(+), 131 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 8dca33934b2..0fda300fd06 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -1,23 +1,36 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# +# MDAnalysis --- http://www.MDAnalysis.org +# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver +# Beckstein and contributors (see AUTHORS for the full list) +# +# Released under the GNU Public Licence, v2 or any higher version +# +# Please cite your use of MDAnalysis in published work: +# +# 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 +# from __future__ import absolute_import, print_function -from MDAnalysisTests.plugins.knownfailure import knownfailure -from nose.tools import raises -from numpy.testing import assert_equal, assert_almost_equal -from numpy.testing import (assert_allclose, assert_array_almost_equal, - assert_) +import numpy as np +import os +from hypothesis import given, example +import hypothesis.strategies as st +import string + +from numpy.testing import (assert_, assert_allclose, assert_almost_equal, + assert_array_almost_equal, assert_equal, raises) from MDAnalysis.lib.formats.libdcd import DCDFile + from MDAnalysisTests.datafiles import ( DCD, DCD_NAMD_TRICLINIC, legacy_DCD_ADK_coords, legacy_DCD_NAMD_coords, legacy_DCD_c36_coords, DCD_TRICLINIC) - -from MDAnalysisTests.tempdir import run_in_tempdir +from MDAnalysisTests.plugins.knownfailure import knownfailure from MDAnalysisTests import tempdir -import numpy as np -import os -from hypothesis import given, example -import hypothesis.strategies as st -import string class TestDCDReadFrame(object): @@ -73,7 +86,8 @@ def test_readframes(self): xyz = frames.x assert_equal(len(xyz), len(dcd)) for index, frame_num in enumerate(self.selected_legacy_frames): - assert_array_almost_equal(xyz[frame_num], legacy_DCD_frame_data[index]) + assert_array_almost_equal(xyz[frame_num], + legacy_DCD_frame_data[index]) def test_readframes_slice(self): with DCDFile(self.dcdfile) as dcd: @@ -82,7 +96,6 @@ def test_readframes_slice(self): xyz = frames.x assert_equal(len(xyz), 2) - def test_read_unit_cell(self): # confirm unit cell read against result from previous # MDAnalysis implementation of DCD file handling @@ -138,7 +151,7 @@ def test_natoms(self): assert_equal(dcd.header['natoms'], self.natoms) @raises(IOError) - @run_in_tempdir() + @tempdir.run_in_tempdir() def test_read_write_mode_file(self): with DCDFile('foo', 'w') as f: f.read() @@ -159,6 +172,46 @@ def test_iteration_2(self): assert_equal(i + 1, f.tell()) +class TestDCDReadFrameTestNAMD(TestDCDReadFrame): + # repeat frame reading tests for NAMD format DCD + + def setUp(self): + self.dcdfile = DCD_NAMD_TRICLINIC + self.natoms = 5545 + self.traj_length = 1 + self.new_frame = 0 + self.context_frame = 0 + self.is_periodic = True + self.num_iters = 0 + self.selected_legacy_frames = [0] + self.legacy_data = legacy_DCD_NAMD_coords + self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' + # expect raw unit cell unprocessed + self.expected_unit_cell = np.array( + [38.42659378, 0.499563, 38.393102, 0., 0., 44.7598], + dtype=np.float32) + + +class TestDCDReadFrameTestCharmm36(TestDCDReadFrame): + # repeat frame reading tests for Charmm36 format DCD + + def setUp(self): + self.dcdfile = DCD_TRICLINIC + self.natoms = 375 + self.traj_length = 10 + self.new_frame = 2 + self.context_frame = 5 + self.num_iters = 7 + self.is_periodic = True + self.selected_legacy_frames = [1, 4] + self.legacy_data = legacy_DCD_c36_coords + self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' + # expect raw unit cell unprocessed + self.expected_unit_cell = np.array( + [30.841836, 14.578635, 31.780088, 9.626323, -2.60815, 32.67009], + dtype=np.float32) + + class TestDCDWriteHeader(object): def setUp(self): self.tmpdir = tempdir.TempDir() @@ -238,6 +291,24 @@ def test_write_header_mode_sensitivy(self): charmm=1) +class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): + # repeat header writing tests for NAMD format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') + self.dcdfile = DCD_NAMD_TRICLINIC + + +class TestDCDWriteHeaderCharmm36(TestDCDWriteHeader): + # repeat header writing tests for Charmm36 format DCD + + def setUp(self): + self.tmpdir = tempdir.TempDir() + self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') + self.dcdfile = DCD_TRICLINIC + + class TestDCDWrite(object): def setUp(self): self.tmpdir = tempdir.TempDir() @@ -327,13 +398,12 @@ def test_written_remarks(self): @given(st.text(alphabet=string.printable, min_size=0, - max_size=240)) # handle the printable ASCII strings + max_size=240)) # handle the printable ASCII strings @example('') def test_written_remarks_property(self, remarks_str): # property based testing for writing of a wide range of string # values to REMARKS field - self._write_files(testfile=self.testfile2, - remarks_setting=remarks_str) + self._write_files(testfile=self.testfile2, remarks_setting=remarks_str) expected_remarks = remarks_str[:240] with DCDFile(self.testfile2) as f: assert_equal(f.header['remarks'], expected_remarks) @@ -423,68 +493,6 @@ def test_write_wrong_shape_box(self): out.write(xyz=xyz, box=box) - - - - -class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): - # repeat header writing tests for NAMD format DCD - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.dcdfile = DCD_NAMD_TRICLINIC - - -class TestDCDWriteHeaderCharmm36(TestDCDWriteHeader): - # repeat header writing tests for Charmm36 format DCD - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.dcdfile = DCD_TRICLINIC - - -class TestDCDReadFrameTestNAMD(TestDCDReadFrame): - # repeat frame reading tests for NAMD format DCD - - def setUp(self): - self.dcdfile = DCD_NAMD_TRICLINIC - self.natoms = 5545 - self.traj_length = 1 - self.new_frame = 0 - self.context_frame = 0 - self.is_periodic = True - self.num_iters = 0 - self.selected_legacy_frames = [0] - self.legacy_data = legacy_DCD_NAMD_coords - self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' - # expect raw unit cell unprocessed - self.expected_unit_cell = np.array( - [38.42659378, 0.499563, 38.393102, 0., 0., 44.7598], - dtype=np.float32) - - -class TestDCDReadFrameTestCharmm36(TestDCDReadFrame): - # repeat frame reading tests for Charmm36 format DCD - - def setUp(self): - self.dcdfile = DCD_TRICLINIC - self.natoms = 375 - self.traj_length = 10 - self.new_frame = 2 - self.context_frame = 5 - self.num_iters = 7 - self.is_periodic = True - self.selected_legacy_frames = [1, 4] - self.legacy_data = legacy_DCD_c36_coords - self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' - # expect raw unit cell unprocessed - self.expected_unit_cell = np.array( - [30.841836, 14.578635, 31.780088, 9.626323, -2.60815, 32.67009], - dtype=np.float32) - - class TestDCDWriteRandom(object): # should only be supported for Charmm24 format writing (for now) @@ -530,55 +538,6 @@ def test_written_unit_cell_random(self): self._test_written_unit_cell_random() -class TestDCDByteArithmetic(object): - def setUp(self): - self.dcdfile = DCD - self._filesize = os.path.getsize(DCD) - - def test_relative_frame_sizes(self): - # the first frame of a DCD file should always be >= in size - # to subsequent frames, as the first frame contains the same - # atoms + (optional) fixed atoms - with DCDFile(self.dcdfile) as dcd: - first_frame_size = dcd._firstframesize - general_frame_size = dcd._framesize - - assert_(first_frame_size >= general_frame_size) - - def test_file_size_breakdown(self): - # the size of a DCD file is equivalent to the sum of the header - # size, first frame size, and (N - 1 frames) * size per general - # frame - expected = self._filesize - with DCDFile(self.dcdfile) as dcd: - actual = dcd._header_size + dcd._firstframesize + ( - (dcd.n_frames - 1) * dcd._framesize) - assert_equal(actual, expected) - - def test_nframessize_int(self): - # require that the (nframessize / framesize) value used by DCDFile - # is an integer (because nframessize / framesize + 1 = total frames, - # which must also be an int) - with DCDFile(self.dcdfile) as dcd: - nframessize = self._filesize - dcd._header_size - dcd._firstframesize - assert_equal(float(nframessize) % float(dcd._framesize), 0) - - -class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): - # repeat byte arithmetic tests for NAMD format DCD - - def setUp(self): - self.dcdfile = DCD_NAMD_TRICLINIC - self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) - - -class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): - # repeat byte arithmetic tests for Charmm36 format DCD - - def setUp(self): - self.dcdfile = DCD_TRICLINIC - self._filesize = os.path.getsize(DCD_TRICLINIC) - class TestDCDWriteNAMD(TestDCDWrite, TestDCDWriteRandom): # repeat writing tests for NAMD format DCD @@ -596,7 +555,6 @@ def setUp(self): self.random_unitcells = np.random.uniform( high=80, size=(self.expected_frames, 6)).astype(np.float64) - #@knownfailure def test_written_unit_cell(self): # there's no expectation that we can write unit cell # data in NAMD format at the moment @@ -606,6 +564,7 @@ def test_written_unit_cell(self): def test_written_unit_cell_random(self): self._test_written_unit_cell_random() + class TestDCDWriteCharmm36(TestDCDWrite, TestDCDWriteRandom): # repeat writing tests for Charmm36 format DCD # no expectation that we can write unit cell info though (yet) @@ -624,7 +583,6 @@ def setUp(self): self.random_unitcells = np.random.uniform( high=80, size=(self.expected_frames, 6)).astype(np.float64) - #@knownfailure def test_written_unit_cell(self): # there's no expectation that we can write unit cell # data in charmm format at the moment @@ -633,3 +591,53 @@ def test_written_unit_cell(self): @knownfailure def test_written_unit_cell_random(self): self._test_written_unit_cell_random() + + +class TestDCDByteArithmetic(object): + def setUp(self): + self.dcdfile = DCD + self._filesize = os.path.getsize(DCD) + + def test_relative_frame_sizes(self): + # the first frame of a DCD file should always be >= in size + # to subsequent frames, as the first frame contains the same + # atoms + (optional) fixed atoms + with DCDFile(self.dcdfile) as dcd: + first_frame_size = dcd._firstframesize + general_frame_size = dcd._framesize + + assert_(first_frame_size >= general_frame_size) + + def test_file_size_breakdown(self): + # the size of a DCD file is equivalent to the sum of the header + # size, first frame size, and (N - 1 frames) * size per general + # frame + expected = self._filesize + with DCDFile(self.dcdfile) as dcd: + actual = dcd._header_size + dcd._firstframesize + ( + (dcd.n_frames - 1) * dcd._framesize) + assert_equal(actual, expected) + + def test_nframessize_int(self): + # require that the (nframessize / framesize) value used by DCDFile + # is an integer (because nframessize / framesize + 1 = total frames, + # which must also be an int) + with DCDFile(self.dcdfile) as dcd: + nframessize = self._filesize - dcd._header_size - dcd._firstframesize + assert_equal(float(nframessize) % float(dcd._framesize), 0) + + +class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): + # repeat byte arithmetic tests for NAMD format DCD + + def setUp(self): + self.dcdfile = DCD_NAMD_TRICLINIC + self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) + + +class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): + # repeat byte arithmetic tests for Charmm36 format DCD + + def setUp(self): + self.dcdfile = DCD_TRICLINIC + self._filesize = os.path.getsize(DCD_TRICLINIC) From 3bc9c0296c2eb9504ad07134aafcd7db09620bd6 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 16 Jun 2017 08:15:49 +0200 Subject: [PATCH 073/101] add readframes tests --- .../MDAnalysisTests/formats/test_libdcd.py | 98 +++++++++++++++---- 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 0fda300fd06..5c7dbecd938 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -22,7 +22,8 @@ import string from numpy.testing import (assert_, assert_allclose, assert_almost_equal, - assert_array_almost_equal, assert_equal, raises) + assert_array_almost_equal, assert_array_equal, + assert_equal, raises) from MDAnalysis.lib.formats.libdcd import DCDFile @@ -79,23 +80,6 @@ def test_read_coord_values(self): desired_coords = legacy_DCD_frame_data[index] assert_equal(actual_coords, desired_coords) - def test_readframes(self): - legacy_DCD_frame_data = np.load(self.legacy_data) - with DCDFile(self.dcdfile) as dcd: - frames = dcd.readframes() - xyz = frames.x - assert_equal(len(xyz), len(dcd)) - for index, frame_num in enumerate(self.selected_legacy_frames): - assert_array_almost_equal(xyz[frame_num], - legacy_DCD_frame_data[index]) - - def test_readframes_slice(self): - with DCDFile(self.dcdfile) as dcd: - if len(dcd) > 6: - frames = dcd.readframes(start=2, stop=6, step=2) - xyz = frames.x - assert_equal(len(xyz), 2) - def test_read_unit_cell(self): # confirm unit cell read against result from previous # MDAnalysis implementation of DCD file handling @@ -171,6 +155,16 @@ def test_iteration_2(self): for i, _ in enumerate(f): assert_equal(i + 1, f.tell()) + def test_readframes(self): + legacy_DCD_frame_data = np.load(self.legacy_data) + with DCDFile(self.dcdfile) as dcd: + frames = dcd.readframes() + xyz = frames.x + assert_equal(len(xyz), len(dcd)) + for index, frame_num in enumerate(self.selected_legacy_frames): + assert_array_almost_equal(xyz[frame_num], + legacy_DCD_frame_data[index]) + class TestDCDReadFrameTestNAMD(TestDCDReadFrame): # repeat frame reading tests for NAMD format DCD @@ -641,3 +635,71 @@ class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): def setUp(self): self.dcdfile = DCD_TRICLINIC self._filesize = os.path.getsize(DCD_TRICLINIC) + + +def test_readframes_slices(): + slices = [([None, None, None], 98), + ([0, None, None], 98), + ([None, 98, None], 98), + ([None, None, 1], 98), + ([None, None, -1], 98), + ([2, 6, 2], 2), + ([0, 10, None], 10), + ([2, 10, None], 8), + ([0, 1, 1], 1), + ([1, 1, 1], 0), + ([1, 2, 1], 1), + ([1, 2, 2], 1), + ([1, 4, 2], 2), + ([1, 4, 4], 1), + ([0, 5, 5], 1), + ([3, 5, 1], 2), + ([4, 0, -1], 4), + ([5, 0, -2], 3), + ([5, 0, -4], 2), + ] + with DCDFile(DCD) as dcd: + allframes = dcd.readframes().x + for (start, stop, step), l in slices: + frames = dcd.readframes(start=start, stop=stop, step=step) + xyz = frames.x + assert_equal(len(xyz), l) + assert_array_almost_equal(xyz, allframes[start:stop:step]) + + +def test_readframes_order(): + orders = ('fac', 'fca', 'afc', 'acf', 'caf', 'cfa') + + with DCDFile(DCD) as dcd: + natoms = dcd.header['natoms'] + ndims = 3 + n = dcd.n_frames + + for order in orders: + if order == 'fac': + shape = (n, natoms, ndims) + elif order == 'fca': + shape = (n, ndims, natoms) + elif order == 'afc': + shape = (natoms, n, ndims) + elif order == 'acf': + shape = (natoms, ndims, n) + elif order == 'caf': + shape = (ndims, natoms, n) + elif order == 'cfa': + shape = (ndims, n, natoms) + x = dcd.readframes(order=order).x + assert_array_equal(x.shape, shape) + + +def test_readframes_atomindices(): + indices = [[1, 2, 3, 4], + [5, 10, 15, 19], + [9, 4, 2, 0, 50]] + with DCDFile(DCD) as dcd: + allframes = dcd.readframes(order='afc').x + for idxs in indices: + frames = dcd.readframes(indices=idxs, order='afc') + xyz = frames.x + assert_equal(len(xyz), len(idxs)) + assert_array_almost_equal(xyz, allframes[idxs]) From 64093cb18bee0f4674341edfe20f895fd221a666 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 16 Jun 2017 08:21:53 +0200 Subject: [PATCH 074/101] fix backwards step in readframes --- package/MDAnalysis/lib/formats/libdcd.pyx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 5cfe619758d..b0965773e50 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -575,9 +575,15 @@ cdef class DCDFile: raise IOError("opened empty file. No frames are saved") self.seek(0) + # if we only want to iterate backwards flip start and end + if start is None and stop is None and step is not None and step < 0: + stop = -1 + start = self.n_frames - 1 stop = stop if not stop is None else self.n_frames start = start if not start is None else 0 step = step if not step is None else 1 + + cdef int n n = len(range(start, stop, step)) From 82de16a5b23dbcddb503e7de23a11240d85ff509 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 16 Jun 2017 09:30:06 +0200 Subject: [PATCH 075/101] add new timeseries tests to test_dcd --- .../MDAnalysisTests/coordinates/test_dcd.py | 89 ++++++++++++++----- 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 2d49016fa22..660c1877b2c 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -143,30 +143,73 @@ def test_reverse_dcd(self): assert_equal(frames, list(range(20, 5, -1)), "reversing dcd [20:5:-1]") - def test_timeseries_slicing(self): - # check that slicing behaves correctly - # should before issue #914 resolved - x = [(0, 1, 1), (1,1,1), (1, 2, 1), (1, 2, 2), (1, 4, 2), (1, 4, 4), - (0, 5, 5), (3, 5, 1), (None, None, None)] - for start, stop, step in x: - yield self._slice_generation_test, start, stop, step - - def test_backwards_stepping(self): - x = [(4, 0, -1), (5, 0, -2), (5, 0, -4)] - for start, stop, step in x: - yield self._failed_slices_test, start, stop, step - - def _slice_generation_test(self, start, stop, step): - self.u = mda.Universe(PSF, DCD) - ts = self.u.trajectory.timeseries(self.u.atoms) - ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[:,start:stop:step], ts_skip, 5) - def _failed_slices_test(self, start, stop, step): - self.u = mda.Universe(PSF, DCD) - ts = self.u.trajectory.timeseries(self.u.atoms) - ts_skip = self.u.trajectory.timeseries(self.u.atoms, start, stop, step) - assert_array_almost_equal(ts[:, start:stop:step,:], ts_skip, 5) +def test_timeseries_slices(): + slices = [([None, None, None], 98), + ([0, None, None], 98), + ([None, 98, None], 98), + ([None, None, 1], 98), + ([None, None, -1], 98), + ([2, 6, 2], 2), + ([0, 10, None], 10), + ([2, 10, None], 8), + ([0, 1, 1], 1), + ([1, 1, 1], 0), + ([1, 2, 1], 1), + ([1, 2, 2], 1), + ([1, 4, 2], 2), + ([1, 4, 4], 1), + ([0, 5, 5], 1), + ([3, 5, 1], 2), + ([4, 0, -1], 4), + ([5, 0, -2], 3), + ([5, 0, -4], 2), + ] + u = mda.Universe(PSF, DCD) + allframes = u.trajectory.timeseries(format='fac') + for (start, stop, step), l in slices: + xyz = u.trajectory.timeseries(start=start, stop=stop, step=step, + format='fac') + assert_equal(len(xyz), l) + assert_array_almost_equal(xyz, allframes[start:stop:step]) + + +def test_timeseries_order(): + orders = ('fac', 'fca', 'afc', 'acf', 'caf', 'cfa') + + u = mda.Universe(PSF, DCD) + natoms = u.atoms.n_atoms + ndims = 3 + n = u.trajectory.n_frames + + for order in orders: + if order == 'fac': + shape = (n, natoms, ndims) + elif order == 'fca': + shape = (n, ndims, natoms) + elif order == 'afc': + shape = (natoms, n, ndims) + elif order == 'acf': + shape = (natoms, ndims, n) + elif order == 'caf': + shape = (ndims, natoms, n) + elif order == 'cfa': + shape = (ndims, n, natoms) + x = u.trajectory.timeseries(format=order) + assert_array_equal(x.shape, shape) + + +def test_readframes_atomindices(): + indices = [[1, 2, 3, 4], + [5, 10, 15, 19], + [9, 4, 2, 0, 50]] + u = mda.Universe(PSF, DCD) + allframes = u.trajectory.timeseries(format='afc') + for idxs in indices: + asel = u.atoms[idxs] + xyz = u.trajectory.timeseries(asel=asel, format='afc') + assert_equal(len(xyz), len(idxs)) + assert_array_almost_equal(xyz, allframes[idxs]) def test_DCDReader_set_dt(dt=100., frame=3): From 8ed06ce6929a75196f2dc03c23687ec40d74668c Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 07:03:49 +0200 Subject: [PATCH 076/101] Convert test_libdcd.py to pytest - no more test classes - yapify --- .../MDAnalysisTests/formats/test_libdcd.py | 997 +++++++----------- 1 file changed, 407 insertions(+), 590 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 5c7dbecd938..d50e0ba0e2e 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -14,215 +14,196 @@ # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # from __future__ import absolute_import, print_function +from six.moves import zip -import numpy as np +from collections import namedtuple import os -from hypothesis import given, example -import hypothesis.strategies as st import string -from numpy.testing import (assert_, assert_allclose, assert_almost_equal, - assert_array_almost_equal, assert_array_equal, - assert_equal, raises) +import hypothesis.strategies as st +from hypothesis import example, given +import numpy as np + +from numpy.testing import (assert_array_almost_equal, assert_equal) from MDAnalysis.lib.formats.libdcd import DCDFile from MDAnalysisTests.datafiles import ( DCD, DCD_NAMD_TRICLINIC, legacy_DCD_ADK_coords, legacy_DCD_NAMD_coords, legacy_DCD_c36_coords, DCD_TRICLINIC) -from MDAnalysisTests.plugins.knownfailure import knownfailure -from MDAnalysisTests import tempdir - - -class TestDCDReadFrame(object): - def setUp(self): - self.dcdfile = DCD - self.natoms = 3341 - self.traj_length = 98 - self.new_frame = 91 - self.context_frame = 22 - self.num_iters = 3 - self.selected_legacy_frames = [5, 29] - self.is_periodic = False - self.legacy_data = legacy_DCD_ADK_coords - self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self.expected_unit_cell = np.array( - [0., 90., 0., 90., 90., 0.], dtype=np.float32) - - def test_header_remarks(self): - # confirm correct header remarks section reading - with DCDFile(self.dcdfile) as f: - assert_equal(len(f.header['remarks']), len(self.expected_remarks)) - - def test_is_periodic(self): - with DCDFile(self.dcdfile) as f: - assert_equal(f.is_periodic, self.is_periodic) - - def test_read_coords(self): - # confirm shape of coordinate data against result from previous - # MDAnalysis implementation of DCD file handling - with DCDFile(self.dcdfile) as dcd: - dcd_frame = dcd.read() - xyz = dcd_frame[0] - assert_equal(xyz.shape, (self.natoms, 3)) - - def test_read_coord_values(self): - # test the actual values of coordinates read in versus - # stored values read in by the legacy DCD handling framework - # to reduce repo storage burden, we only compare for a few - # randomly selected frames - legacy_DCD_frame_data = np.load(self.legacy_data) - - with DCDFile(self.dcdfile) as dcd: - for index, frame_num in enumerate(self.selected_legacy_frames): - dcd.seek(frame_num) - actual_coords = dcd.read()[0] - desired_coords = legacy_DCD_frame_data[index] - assert_equal(actual_coords, desired_coords) - - def test_read_unit_cell(self): - # confirm unit cell read against result from previous - # MDAnalysis implementation of DCD file handling - with DCDFile(self.dcdfile) as dcd: - dcd_frame = dcd.read() - assert_array_almost_equal(dcd_frame.unitcell, self.expected_unit_cell) - - @raises(EOFError) - def test_seek_over_max(self): - # should raise EOFError if beyond frame - # total + +import pytest + + +@pytest.mark.parametrize("dcdfile, is_periodic", + [(DCD, False), (DCD_NAMD_TRICLINIC, True), + (DCD_TRICLINIC, True)]) +def test_is_periodic(dcdfile, is_periodic): + with DCDFile(dcdfile) as f: + assert f.is_periodic == is_periodic + + +@pytest.mark.parametrize("dcdfile, natoms", + [(DCD, 3341), (DCD_NAMD_TRICLINIC, 5545), + (DCD_TRICLINIC, 375)]) +def test_read_coordsshape(dcdfile, natoms): + # confirm shape of coordinate data against result from previous + # MDAnalysis implementation of DCD file handling + with DCDFile(dcdfile) as dcd: + dcd_frame = dcd.read() + xyz = dcd_frame[0] + assert xyz.shape == (natoms, 3) + + +@pytest.mark.parametrize( + "dcdfile, unit_cell", + [(DCD, [0., 90., 0., 90., 90., 0.]), + (DCD_NAMD_TRICLINIC, [38.42659378, 0.499563, 38.393102, 0., 0., 44.7598]), + (DCD_TRICLINIC, + [30.841836, 14.578635, 31.780088, 9.626323, -2.60815, 32.67009])]) +def test_read_unit_cell(dcdfile, unit_cell): + # confirm unit cell read against result from previous + # MDAnalysis implementation of DCD file handling + with DCDFile(dcdfile) as dcd: + dcd_frame = dcd.read() + assert_array_almost_equal(dcd_frame.unitcell, unit_cell) + + +def test_seek_over_max(): + with pytest.raises(EOFError): with DCDFile(DCD) as dcd: dcd.seek(102) - def test_seek_normal(self): - # frame seek within range is tested - with DCDFile(self.dcdfile) as dcd: - dcd.seek(self.new_frame) - assert_equal(dcd.tell(), self.new_frame) - @raises(IOError) - def test_seek_negative(self): - # frame seek with negative number - with DCDFile(self.dcdfile) as dcd: +@pytest.mark.parametrize("new_frame", (10, 42, 21)) +def test_seek_normal(new_frame): + # frame seek within range is tested + with DCDFile(DCD) as dcd: + dcd.seek(new_frame) + assert dcd.tell() == new_frame + + +def test_seek_negative(): + with pytest.raises(IOError): + with DCDFile(DCD) as dcd: dcd.seek(-78) - def test_iteration(self): - with DCDFile(self.dcdfile) as dcd: - for _ in range(self.num_iters): - dcd.__next__() - assert_equal(dcd.tell(), self.num_iters) - def test_zero_based_frames(self): - expected_frame = 0 - with DCDFile(self.dcdfile) as dcd: - assert_equal(dcd.tell(), expected_frame) +def test_iteration(): + num_iters = 10 + with DCDFile(DCD) as dcd: + for _ in range(num_iters): + dcd.__next__() + assert dcd.tell() == num_iters + - def test_length_traj(self): - expected = self.traj_length - with DCDFile(self.dcdfile) as dcd: - assert_equal(len(dcd), expected) +def test_open_wrong_mode(): + with pytest.raises(IOError): + DCDFile(DCD, 'e') - @raises(IOError) - def test_open_wrong_mode(self): - DCDFile('foo', 'e') - @raises(IOError) - def test_raise_not_existing(self): +def test_raise_not_existing(): + with pytest.raises(IOError): DCDFile('foo') - def test_natoms(self): - with DCDFile(self.dcdfile) as dcd: - assert_equal(dcd.header['natoms'], self.natoms) - @raises(IOError) - @tempdir.run_in_tempdir() - def test_read_write_mode_file(self): - with DCDFile('foo', 'w') as f: - f.read() +def test_zero_based_frames_counting(): + with DCDFile(DCD) as dcd: + assert dcd.tell() == 0 + + +@pytest.mark.parametrize("dcdfile, natoms", + [(DCD, 3341), (DCD_NAMD_TRICLINIC, 5545), + (DCD_TRICLINIC, 375)]) +def test_natoms(dcdfile, natoms): + with DCDFile(dcdfile) as dcd: + assert dcd.header['natoms'] == natoms - @raises(IOError) - def test_read_closed(self): - with DCDFile(self.dcdfile) as dcd: + +def test_read_closed(): + with pytest.raises(IOError): + with DCDFile(DCD) as dcd: dcd.close() dcd.read() - def test_iteration_2(self): - with DCDFile(self.dcdfile) as dcd: - with dcd as f: - for i, _ in enumerate(f): - assert_equal(i + 1, f.tell()) - # second iteration should work from start again - for i, _ in enumerate(f): - assert_equal(i + 1, f.tell()) - - def test_readframes(self): - legacy_DCD_frame_data = np.load(self.legacy_data) - with DCDFile(self.dcdfile) as dcd: - frames = dcd.readframes() - xyz = frames.x - assert_equal(len(xyz), len(dcd)) - for index, frame_num in enumerate(self.selected_legacy_frames): - assert_array_almost_equal(xyz[frame_num], - legacy_DCD_frame_data[index]) - - -class TestDCDReadFrameTestNAMD(TestDCDReadFrame): - # repeat frame reading tests for NAMD format DCD - - def setUp(self): - self.dcdfile = DCD_NAMD_TRICLINIC - self.natoms = 5545 - self.traj_length = 1 - self.new_frame = 0 - self.context_frame = 0 - self.is_periodic = True - self.num_iters = 0 - self.selected_legacy_frames = [0] - self.legacy_data = legacy_DCD_NAMD_coords - self.expected_remarks = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' - # expect raw unit cell unprocessed - self.expected_unit_cell = np.array( - [38.42659378, 0.499563, 38.393102, 0., 0., 44.7598], - dtype=np.float32) - - -class TestDCDReadFrameTestCharmm36(TestDCDReadFrame): - # repeat frame reading tests for Charmm36 format DCD - - def setUp(self): - self.dcdfile = DCD_TRICLINIC - self.natoms = 375 - self.traj_length = 10 - self.new_frame = 2 - self.context_frame = 5 - self.num_iters = 7 - self.is_periodic = True - self.selected_legacy_frames = [1, 4] - self.legacy_data = legacy_DCD_c36_coords - self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' - # expect raw unit cell unprocessed - self.expected_unit_cell = np.array( - [30.841836, 14.578635, 31.780088, 9.626323, -2.60815, 32.67009], - dtype=np.float32) - - -class TestDCDWriteHeader(object): - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.dcdfile = DCD - - def tearDown(self): - try: - os.unlink(self.testfile) - except OSError: - pass - del self.tmpdir - - def test_write_header(self): - # test that _write_header() can produce a very crude - # header for a new / empty file - with DCDFile(self.testfile, 'w') as dcd: + +@pytest.mark.parametrize("dcdfile, nframes", + [(DCD, 98), (DCD_NAMD_TRICLINIC, 1), + (DCD_TRICLINIC, 10)]) +def test_length_traj(dcdfile, nframes): + with DCDFile(dcdfile) as dcd: + assert len(dcd) == nframes + + +def test_read_write_mode_file(tmpdir): + with pytest.raises(IOError): + with tmpdir.as_cwd(): + with DCDFile('foo', 'w') as f: + f.read() + + +def test_iterating_twice(): + with DCDFile(DCD) as dcd: + with dcd as f: + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) + # second iteration should work from start again + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) + + +DCD_HEADER = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' +DCD_NAMD_TRICLINIC_HEADER = 'Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,' +DCD_TRICLINIC_HEADER = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 * TEST EXTENDED SYSTEM CONSTANT PRESSURE AND TEMPERATURE * DYNAMICS WITH WATER BOX. * DATE: 7/ 7/14 13:59:46 CREATED BY USER: oliver ' + + +@pytest.mark.parametrize("dcdfile, remarks", ( + (DCD, DCD_HEADER), (DCD_NAMD_TRICLINIC, DCD_NAMD_TRICLINIC_HEADER), + (DCD_TRICLINIC, DCD_TRICLINIC_HEADER))) +def test_header_remarks(dcdfile, remarks): + # confirm correct header remarks section reading + with DCDFile(dcdfile) as f: + assert len(f.header['remarks']) == len(remarks) + + +@pytest.mark.parametrize("dcdfile, legacy_data, frames", + ((DCD, legacy_DCD_ADK_coords, [5, 29]), + (DCD_NAMD_TRICLINIC, legacy_DCD_NAMD_coords, [0]), + (DCD_TRICLINIC, legacy_DCD_c36_coords, [1, 4]))) +def test_read_coord_values(dcdfile, legacy_data, frames): + # test the actual values of coordinates read in versus + # stored values read in by the legacy DCD handling framework + # to reduce repo storage burden, we only compare for a few + # randomly selected frames + legacy = np.load(legacy_data) + with DCDFile(dcdfile) as dcd: + for index, frame_num in enumerate(frames): + dcd.seek(frame_num) + actual_coords = dcd.read()[0] + desired_coords = legacy[index] + assert_equal(actual_coords, desired_coords) + + +@pytest.mark.parametrize("dcdfile, legacy_data, frame_idx", + ((DCD, legacy_DCD_ADK_coords, [5, 29]), + (DCD_NAMD_TRICLINIC, legacy_DCD_NAMD_coords, [0]), + (DCD_TRICLINIC, legacy_DCD_c36_coords, [1, 4]))) +def test_readframes(dcdfile, legacy_data, frame_idx): + legacy = np.load(legacy_data) + with DCDFile(dcdfile) as dcd: + frames = dcd.readframes() + xyz = frames.x + assert_equal(len(xyz), len(dcd)) + for index, frame_num in enumerate(frame_idx): + assert_array_almost_equal(xyz[frame_num], legacy[index]) + + +def test_write_header(tmpdir): + # test that _write_header() can produce a very crude + # header for a new / empty file + testfile = 'test.dcd' + with tmpdir.as_cwd(): + with DCDFile(testfile, 'w') as dcd: dcd.write_header( remarks='Crazy!', natoms=22, @@ -231,38 +212,51 @@ def test_write_header(self): delta=0.02, charmm=1) - # we're not actually asserting anything, yet - # run with: nosetests test_libdcd.py --nocapture - # to see printed output from nose - with DCDFile(self.testfile) as dcd: + with DCDFile(testfile) as dcd: header = dcd.header - assert_equal(header['remarks'], 'Crazy!') - assert_equal(header['natoms'], 22) - assert_equal(header['istart'], 12) - assert_equal(header['charmm'], 5) - assert_equal(header['nsavc'], 10) - assert_almost_equal(header['delta'], .02) - - @raises(IOError) - def test_write_no_header(self): - # an IOError should be raised if we - # attempt to write inappropriate header - # data that looks like frame data - with DCDFile(self.testfile, 'w') as dcd: - dcd.write(np.ones(3), np.ones(6)) - - @raises(IOError) - def test_write_header_twice(self): - # an IOError should be raised if a duplicate - # header writing is attempted - with DCDFile(self.testfile, 'w') as dcd: - dcd.write_header( - remarks='Crazy!', - natoms=22, - istart=12, - nsavc=10, - delta=0.02, - charmm=1) + assert header['remarks'] == 'Crazy!' + assert header['natoms'] == 22 + assert header['istart'] == 12 + assert header['charmm'] == 5 + assert header['nsavc'] == 10 + assert np.allclose(header['delta'], .02) + + +def test_write_no_header(tmpdir): + # an IOError should be raised if we + # attempt to write inappropriate header + # data that looks like frame data + with pytest.raises(IOError): + with tmpdir.as_cwd(): + with DCDFile('test.dcd', 'w') as dcd: + dcd.write(np.ones(3), np.ones(6)) + + +def test_write_header_twice(tmpdir): + # an IOError should be raised if a duplicate + # header writing is attempted + + header = { + "remarks": 'Crazy!', + "natoms": 22, + "istart": 12, + "nsavc": 10, + "delta": 0.02, + "charmm": 1 + } + + with pytest.raises(IOError): + with tmpdir.as_cwd(): + with DCDFile('test.dcd', 'w') as dcd: + dcd.write_header(**header) + dcd.write_header(**header) + + +def test_write_header_wrong_mode(): + # an exception should be raised on any attempt to use + # _write_header with a DCDFile object in 'r' mode + with pytest.raises(IOError): + with DCDFile(DCD) as dcd: dcd.write_header( remarks='Crazy!', natoms=22, @@ -271,170 +265,103 @@ def test_write_header_twice(self): delta=0.02, charmm=1) - @raises(IOError) - def test_write_header_mode_sensitivy(self): - # an exception should be raised on any attempt to use - # _write_header with a DCDFile object in 'r' mode - with DCDFile(self.dcdfile) as dcd: - dcd.write_header( - remarks='Crazy!', - natoms=22, - istart=12, - nsavc=10, - delta=0.02, - charmm=1) +def test_write_mode(): + # ensure that writing of DCD files only occurs with properly + # opened files + with pytest.raises(IOError): + with DCDFile(DCD) as dcd: + dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) -class TestDCDWriteHeaderNAMD(TestDCDWriteHeader): - # repeat header writing tests for NAMD format DCD - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.dcdfile = DCD_NAMD_TRICLINIC +def write_dcd(in_name, out_name, remarks='testing', header=None): + with DCDFile(in_name) as f_in, DCDFile(out_name, 'w') as f_out: + if header is None: + header = f_in.header + f_out.write_header(**header) + for frame in f_in: + f_out.write(xyz=frame.x, box=frame.unitcell) + + +@given(remarks=st.text( + alphabet=string.printable, min_size=0, + max_size=240)) # handle the printable ASCII strings +@example(remarks='') +def test_written_remarks_property(remarks, tmpdir): + # property based testing for writing of a wide range of string + # values to REMARKS field + testfile = 'test.dcd' + with DCDFile(DCD) as dcd: + header = dcd.header + with tmpdir.as_cwd(): + header['remarks'] = remarks + write_dcd(DCD, testfile, header=header) + expected_remarks = remarks[:240] + with DCDFile(testfile) as f: + assert f.header['remarks'] == expected_remarks -class TestDCDWriteHeaderCharmm36(TestDCDWriteHeader): - # repeat header writing tests for Charmm36 format DCD - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.dcdfile = DCD_TRICLINIC +@pytest.fixture(scope='session') +def written_dcd(tmpdir_factory): + testfile = 'test.dcd' + with DCDFile(DCD) as dcd: + header = dcd.header + testfile = tmpdir_factory.mktemp('dcd').join('test.dcd') + testfile = str(testfile) + write_dcd(DCD, testfile) + Result = namedtuple("Result", "testfile, header, orgfile") + return Result(testfile, header, DCD) -class TestDCDWrite(object): - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') - self.readfile = DCD - self.natoms = 3341 - self.expected_frames = 98 - self.seek_frame = 91 - self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - self._write_files(testfile=self.testfile, remarks_setting='input') +def test_written_header(written_dcd): + header = written_dcd.header + with DCDFile(written_dcd.testfile) as dcd: + # need to pop charmm header for now. + header.pop('charmm') + dcdheader = dcd.header + dcdheader.pop('charmm') + assert dcdheader == header - def _write_files(self, testfile, remarks_setting): - with DCDFile(self.readfile) as f_in, DCDFile(testfile, 'w') as f_out: - header = f_in.header - if remarks_setting == 'input': - remarks = header['remarks'] - else: # accept the random remarks strings from hypothesis - remarks = remarks_setting - header['remarks'] = remarks - f_out.write_header(**header) - for frame in f_in: - box = frame.unitcell.astype(np.float64) - f_out.write(xyz=frame.x, box=box) - - def _test_written_unit_cell(self): - # written unit cell dimensions should match for all frames - with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: - curr_frame = 0 - while curr_frame < test.n_frames: - written_unitcell = test.read().unitcell - ref_unitcell = ref.read().unitcell - curr_frame += 1 - assert_almost_equal(written_unitcell, ref_unitcell) - - def tearDown(self): - try: - os.unlink(self.testfile) - except OSError: - pass - del self.tmpdir - - @raises(IOError) - def test_write_mode(self): - # ensure that writing of DCD files only occurs with properly - # opened files - with DCDFile(self.readfile) as dcd: - dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) +def test_written_num_frames(written_dcd): + with DCDFile(written_dcd.testfile) as dcd, DCDFile( + written_dcd.orgfile) as other: + assert len(dcd) == len(other) + + +def test_written_dcd_coordinate_data_shape(written_dcd): + with DCDFile(written_dcd.testfile) as dcd, DCDFile( + written_dcd.orgfile) as other: + for frame, other_frame in zip(dcd, other): + assert frame.x.shape == other_frame.x.shape + + +def test_written_seek(written_dcd): + # ensure that we can seek properly on written DCD file + with DCDFile(written_dcd.testfile) as f: + f.seek(40) + assert_equal(f.tell(), 40) - def test_written_dcd_coordinate_data_shape(self): - # written coord shape should match for all frames - expected = (self.natoms, 3) - with DCDFile(self.testfile) as f: - if f.n_frames > 1: - for frame in f: - xyz = f.read()[0] - assert_equal(xyz.shape, expected) - else: - xyz = f.read()[0] - assert_equal(xyz.shape, expected) - - def test_written_unit_cell(self): - self._test_written_unit_cell() - - def test_written_num_frames(self): - with DCDFile(self.testfile) as f: - assert_equal(len(f), self.expected_frames) - - def test_written_seek(self): - # ensure that we can seek properly on written DCD file - with DCDFile(self.testfile) as f: - f.seek(self.seek_frame) - assert_equal(f.tell(), self.seek_frame) - - def test_written_zero_based_frames(self): - # ensure that the first written DCD frame is 0 - expected_frame = 0 - with DCDFile(self.testfile) as f: - assert_equal(f.tell(), expected_frame) - - def test_written_remarks(self): - # ensure that the REMARKS field *can be* preserved exactly - # in the written DCD file - with DCDFile(self.testfile) as f: - assert_equal(f.header['remarks'], self.expected_remarks) - - @given(st.text(alphabet=string.printable, - min_size=0, - max_size=240)) # handle the printable ASCII strings - @example('') - def test_written_remarks_property(self, remarks_str): - # property based testing for writing of a wide range of string - # values to REMARKS field - self._write_files(testfile=self.testfile2, remarks_setting=remarks_str) - expected_remarks = remarks_str[:240] - with DCDFile(self.testfile2) as f: - assert_equal(f.header['remarks'], expected_remarks) - - def test_written_nsavc(self): - # ensure that nsavc, the timesteps between frames written - # to file, is preserved in the written DCD file - with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.header['nsavc'], dcd_r.header['nsavc']) - - def test_written_istart(self): - # ensure that istart, the starting timestep, is preserved - # in the written DCD file - with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.header['istart'], dcd_r.header['istart']) - - def test_written_delta(self): - # ensure that delta, the trajectory timestep, is preserved in - # the written DCD file - with DCDFile(self.readfile) as dcd_r, DCDFile(self.testfile) as dcd: - assert_equal(dcd.header['delta'], dcd_r.header['delta']) - - def test_coord_match(self): - # ensure that all coordinates match in each frame for the - # written DCD file relative to original - with DCDFile(self.testfile) as test, DCDFile(self.readfile) as ref: - curr_frame = 0 - while curr_frame < test.n_frames: - written_coords = test.read()[0] - ref_coords = ref.read()[0] - curr_frame += 1 - assert_equal(written_coords, ref_coords) - - def test_write_wrong_dtype(self): - """we should allow passing a range of dtypes""" + +def test_written_coord_match(written_dcd): + with DCDFile(written_dcd.testfile) as test, DCDFile( + written_dcd.orgfile) as ref: + for frame, o_frame in zip(test, ref): + assert_array_almost_equal(frame.x, o_frame.x) + + +def test_written_unit_cell(written_dcd): + with DCDFile(written_dcd.testfile) as test, DCDFile( + written_dcd.orgfile) as ref: + for frame, o_frame in zip(test, ref): + assert_array_almost_equal(frame.unitcell, o_frame.unitcell) + + +def test_write_all_dtypes(tmpdir): + with tmpdir.as_cwd(): for dtype in (np.int32, np.int64, np.float32, np.float64): - with DCDFile(self.testfile, 'w') as out: + with DCDFile('foo.dcd', 'w') as out: natoms = 10 xyz = np.ones((natoms, 3), dtype=dtype) box = np.ones(6, dtype=dtype) @@ -447,10 +374,11 @@ def test_write_wrong_dtype(self): istart=1) out.write(xyz=xyz, box=box) - def test_write_array_like(self): - """we should allow passing a range of dtypes""" + +def test_write_array_like(tmpdir): + with tmpdir.as_cwd(): for array_like in (np.array, list): - with DCDFile(self.testfile, 'w') as out: + with DCDFile('foo.dcd', 'w') as out: natoms = 10 xyz = array_like([[1, 1, 1] for i in range(natoms)]) box = array_like([i for i in range(6)]) @@ -463,9 +391,10 @@ def test_write_array_like(self): istart=1) out.write(xyz=xyz, box=box) - @raises(ValueError) - def test_write_wrong_shape_xyz(self): - with DCDFile(self.testfile, 'w') as out: + +def test_write_wrong_shape_xyz(tmpdir): + with tmpdir.as_cwd(): + with DCDFile("foo.dcd", 'w') as out: natoms = 10 xyz = np.ones((natoms + 1, 3)) box = np.ones(6) @@ -476,230 +405,118 @@ def test_write_wrong_shape_xyz(self): delta=1, nsavc=1, istart=1) - out.write(xyz=xyz, box=box) + with pytest.raises(ValueError): + out.write(xyz=xyz, box=box) + - @raises(ValueError) - def test_write_wrong_shape_box(self): - with DCDFile(self.testfile, 'w') as out: +def test_write_wrong_shape_box(tmpdir): + with tmpdir.as_cwd(): + with DCDFile("foo.dcd", 'w') as out: natoms = 10 xyz = np.ones((natoms, 3)) - box = np.ones(8) - out.write(xyz=xyz, box=box) - - -class TestDCDWriteRandom(object): - # should only be supported for Charmm24 format writing (for now) - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.readfile = DCD - self.natoms = 3341 - self.expected_frames = 98 - self.seek_frame = 91 - self.expected_remarks = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' - - np.random.seed(1178083) - self.random_unitcells = np.random.uniform( - high=80, size=(self.expected_frames, 6)).astype(np.float64) - - with DCDFile(self.readfile) as f_in, DCDFile(self.testfile, - 'w') as f_out: - in_header = f_in.header - f_out.write_header(**in_header) - for index, frame in enumerate(f_in): - box = frame.unitcell.astype(np.float64) - f_out.write(xyz=frame.x, box=self.random_unitcells[index]) - - def tearDown(self): - try: - os.unlink(self.testfile) - except OSError: - pass - del self.tmpdir - - def _test_written_unit_cell_random(self): - with DCDFile(self.testfile) as test: - curr_frame = 0 - while curr_frame < test.n_frames: - written_unitcell = test.read()[1] - ref_unitcell = self.random_unitcells[curr_frame] - - curr_frame += 1 - assert_allclose(written_unitcell, ref_unitcell, rtol=1e-05) - - def test_written_unit_cell_random(self): - self._test_written_unit_cell_random() - - -class TestDCDWriteNAMD(TestDCDWrite, TestDCDWriteRandom): - # repeat writing tests for NAMD format DCD - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') - self.readfile = DCD_NAMD_TRICLINIC - self.natoms = 5545 - self.expected_frames = 1 - self.seek_frame = 0 - self.expected_remarks = '''Created by DCD pluginREMARKS Created 06 July, 2014 at 17:29Y5~CORD,''' - self._write_files(testfile=self.testfile, remarks_setting='input') - np.random.seed(1178083) - self.random_unitcells = np.random.uniform( - high=80, size=(self.expected_frames, 6)).astype(np.float64) - - def test_written_unit_cell(self): - # there's no expectation that we can write unit cell - # data in NAMD format at the moment - self._test_written_unit_cell() - - @knownfailure - def test_written_unit_cell_random(self): - self._test_written_unit_cell_random() - - -class TestDCDWriteCharmm36(TestDCDWrite, TestDCDWriteRandom): - # repeat writing tests for Charmm36 format DCD - # no expectation that we can write unit cell info though (yet) - - def setUp(self): - self.tmpdir = tempdir.TempDir() - self.testfile = os.path.join(self.tmpdir.name, 'test.dcd') - self.testfile2 = os.path.join(self.tmpdir.name, 'test2.dcd') - self.readfile = DCD_TRICLINIC - self.natoms = 375 - self.expected_frames = 10 - self.seek_frame = 7 - self.expected_remarks = '* CHARMM TRICLINIC BOX TESTING * (OLIVER BECKSTEIN 2014) * BASED ON NPTDYN.INP : SCOTT FELLER, NIH, 7/15/95 ' - self._write_files(testfile=self.testfile, remarks_setting='input') - np.random.seed(1178083) - self.random_unitcells = np.random.uniform( - high=80, size=(self.expected_frames, 6)).astype(np.float64) - - def test_written_unit_cell(self): - # there's no expectation that we can write unit cell - # data in charmm format at the moment - self._test_written_unit_cell() - - @knownfailure - def test_written_unit_cell_random(self): - self._test_written_unit_cell_random() - - -class TestDCDByteArithmetic(object): - def setUp(self): - self.dcdfile = DCD - self._filesize = os.path.getsize(DCD) - - def test_relative_frame_sizes(self): - # the first frame of a DCD file should always be >= in size - # to subsequent frames, as the first frame contains the same - # atoms + (optional) fixed atoms - with DCDFile(self.dcdfile) as dcd: - first_frame_size = dcd._firstframesize - general_frame_size = dcd._framesize - - assert_(first_frame_size >= general_frame_size) - - def test_file_size_breakdown(self): - # the size of a DCD file is equivalent to the sum of the header - # size, first frame size, and (N - 1 frames) * size per general - # frame - expected = self._filesize - with DCDFile(self.dcdfile) as dcd: - actual = dcd._header_size + dcd._firstframesize + ( - (dcd.n_frames - 1) * dcd._framesize) - assert_equal(actual, expected) - - def test_nframessize_int(self): - # require that the (nframessize / framesize) value used by DCDFile - # is an integer (because nframessize / framesize + 1 = total frames, - # which must also be an int) - with DCDFile(self.dcdfile) as dcd: - nframessize = self._filesize - dcd._header_size - dcd._firstframesize - assert_equal(float(nframessize) % float(dcd._framesize), 0) - - -class TestDCDByteArithmeticNAMD(TestDCDByteArithmetic): - # repeat byte arithmetic tests for NAMD format DCD - - def setUp(self): - self.dcdfile = DCD_NAMD_TRICLINIC - self._filesize = os.path.getsize(DCD_NAMD_TRICLINIC) - - -class TestDCDByteArithmeticCharmm36(TestDCDByteArithmetic): - # repeat byte arithmetic tests for Charmm36 format DCD - - def setUp(self): - self.dcdfile = DCD_TRICLINIC - self._filesize = os.path.getsize(DCD_TRICLINIC) - - -def test_readframes_slices(): - slices = [([None, None, None], 98), - ([0, None, None], 98), - ([None, 98, None], 98), - ([None, None, 1], 98), - ([None, None, -1], 98), - ([2, 6, 2], 2), - ([0, 10, None], 10), - ([2, 10, None], 8), - ([0, 1, 1], 1), - ([1, 1, 1], 0), - ([1, 2, 1], 1), - ([1, 2, 2], 1), - ([1, 4, 2], 2), - ([1, 4, 4], 1), - ([0, 5, 5], 1), - ([3, 5, 1], 2), - ([4, 0, -1], 4), - ([5, 0, -2], 3), - ([5, 0, -4], 2), - ] - with DCDFile(DCD) as dcd: - allframes = dcd.readframes().x - for (start, stop, step), l in slices: - frames = dcd.readframes(start=start, stop=stop, step=step) - xyz = frames.x - assert_equal(len(xyz), l) - assert_array_almost_equal(xyz, allframes[start:stop:step]) - + box = np.ones(7) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) + with pytest.raises(ValueError): + out.write(xyz=xyz, box=box) -def test_readframes_order(): - orders = ('fac', 'fca', 'afc', 'acf', 'caf', 'cfa') +@pytest.mark.parametrize("dcdfile", (DCD, DCD_TRICLINIC, DCD_NAMD_TRICLINIC)) +def test_relative_frame_sizes(dcdfile): + # the first frame of a DCD file should always be >= in size + # to subsequent frames, as the first frame contains the same + # atoms + (optional) fixed atoms + with DCDFile(dcdfile) as dcd: + first_frame_size = dcd._firstframesize + general_frame_size = dcd._framesize + + assert first_frame_size >= general_frame_size + + +@pytest.mark.parametrize("dcdfile", (DCD, DCD_TRICLINIC, DCD_NAMD_TRICLINIC)) +def test_file_size_breakdown(dcdfile): + # the size of a DCD file is equivalent to the sum of the header + # size, first frame size, and (N - 1 frames) * size per general + # frame + + expected = os.path.getsize(dcdfile) + with DCDFile(dcdfile) as dcd: + actual = dcd._header_size + dcd._firstframesize + ( + (dcd.n_frames - 1) * dcd._framesize) + assert actual == expected + + +@pytest.mark.parametrize("dcdfile", (DCD, DCD_TRICLINIC, DCD_NAMD_TRICLINIC)) +def test_nframessize_int(dcdfile): + # require that the (nframessize / framesize) value used by DCDFile + # is an integer (because nframessize / framesize + 1 = total frames, + # which must also be an int) + filesize = os.path.getsize(dcdfile) + with DCDFile(dcdfile) as dcd: + nframessize = filesize - dcd._header_size - dcd._firstframesize + assert float(nframessize) % float(dcd._framesize) == 0 + + +@pytest.mark.parametrize( + "slice, length", + [([None, None, None], 98), ([0, None, None], 98), ([None, 98, None], 98), + ([None, None, 1], 98), ([None, None, -1], 98), ([2, 6, 2], 2), + ([0, 10, None], 10), ([2, 10, None], 8), ([0, 1, 1], 1), ([1, 1, 1], 0), + ([1, 2, 1], 1), ([1, 2, 2], 1), ([1, 4, 2], 2), ([1, 4, 4], 1), + ([0, 5, 5], 1), ([3, 5, 1], 2), ([4, 0, -1], 4), ([5, 0, -2], 3), + ([5, 0, -4], 2)]) +def test_readframes_slices(slice, length): + start, stop, step = slice with DCDFile(DCD) as dcd: - natoms = dcd.header['natoms'] - ndims = 3 - n = dcd.n_frames - - for order in orders: - if order == 'fac': - shape = (n, natoms, ndims) - elif order == 'fca': - shape = (n, ndims, natoms) - elif order == 'afc': - shape = (natoms, n, ndims) - elif order == 'acf': - shape = (natoms, ndims, n) - elif order == 'caf': - shape = (ndims, natoms, n) - elif order == 'cfa': - shape = (ndims, n, natoms) - x = dcd.readframes(order=order).x - assert_array_equal(x.shape, shape) - - -def test_readframes_atomindices(): - indices = [[1, 2, 3, 4], - [5, 10, 15, 19], - [9, 4, 2, 0, 50]] + allframes = dcd.readframes().x + frames = dcd.readframes(start=start, stop=stop, step=step) + xyz = frames.x + assert len(xyz) == length + assert_array_almost_equal(xyz, allframes[start:stop:step]) + + +@pytest.mark.parametrize("order, shape", ( + ('fac', (98, 3341, 3)), + ('fca', (98, 3, 3341)), + ('afc', (3341, 98, 3)), + ('acf', (3341, 3, 98)), + ('caf', (3, 3341, 98)), + ('cfa', (3, 98, 3341)), )) +def test_readframes_order(order, shape): + with DCDFile(DCD) as dcd: + x = dcd.readframes(order=order).x + assert x.shape == shape + + +@pytest.mark.parametrize("indices", [[1, 2, 3, 4], [5, 10, 15, 19], + [9, 4, 2, 0, 50]]) +def test_readframes_atomindices(indices): with DCDFile(DCD) as dcd: allframes = dcd.readframes(order='afc').x - for idxs in indices: - frames = dcd.readframes(indices=idxs, order='afc') - xyz = frames.x - assert_equal(len(xyz), len(idxs)) - assert_array_almost_equal(xyz, allframes[idxs]) + frames = dcd.readframes(indices=indices, order='afc') + xyz = frames.x + assert len(xyz) == len(indices) + assert_array_almost_equal(xyz, allframes[indices]) + + +def test_write_random_unitcell(tmpdir): + with tmpdir.as_cwd(): + testname = 'test.dcd' + rstate = np.random.RandomState(1178083) + random_unitcells = rstate.uniform( + high=80, size=(98, 6)).astype(np.float64) + + with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out: + f_out.write_header(**f_in.header) + for index, frame in enumerate(f_in): + f_out.write(xyz=frame.x, box=random_unitcells[index]) + + with DCDFile(testname) as test: + for index, frame in enumerate(test): + assert_array_almost_equal(frame.unitcell, + random_unitcells[index]) From ae5222986a4d8e9cfbe134ece099182f42537620 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 13:48:53 +0200 Subject: [PATCH 077/101] delete old files --- .../MDAnalysis/coordinates/include/correl.h | 195 ----- .../coordinates/include/endianswap.h | 173 ---- .../MDAnalysis/coordinates/include/fastio.h | 454 ----------- .../MDAnalysis/coordinates/include/readdcd.h | 764 ------------------ .../MDAnalysis/lib/formats/include/correl.h | 188 ----- 5 files changed, 1774 deletions(-) delete mode 100644 package/MDAnalysis/coordinates/include/correl.h delete mode 100644 package/MDAnalysis/coordinates/include/endianswap.h delete mode 100644 package/MDAnalysis/coordinates/include/fastio.h delete mode 100644 package/MDAnalysis/coordinates/include/readdcd.h delete mode 100644 package/MDAnalysis/lib/formats/include/correl.h diff --git a/package/MDAnalysis/coordinates/include/correl.h b/package/MDAnalysis/coordinates/include/correl.h deleted file mode 100644 index 7eb8cbae6d6..00000000000 --- a/package/MDAnalysis/coordinates/include/correl.h +++ /dev/null @@ -1,195 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode:nil; -*- */ -/* vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 */ -/* - - MDAnalysis --- http://www.mdanalysis.org - Copyright (c) 2006-2016 The MDAnalysis Development Team and contributors - (see the file AUTHORS for the full list of names) - - Released under the GNU Public Licence, v2 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. - - 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 - -*/ - -#ifndef CORREL_H -#define CORREL_H - -#include -/* Python.h for 'typedef int Py_intptr_t;' (fixes Issue 19) */ -#include - -static void -copyseries(int frame, char *data, const Py_intptr_t *strides, - const float *tempX, const float *tempY, const float *tempZ, - const char* datacode, int numdata, const int* atomlist, const int* atomcounts, - int lowerb, double* aux) -{ - char code; - int index1 = 0, index2 = 0, index3 = 0, index4 = 0; - double x1, x2, y1, y2, z1, z2, x3, y3, z3, aux1, aux2; - int i = 0, j = 0, atomno = 0; - int dataset = 0; - int stride0, stride1; - - stride0 = strides[0]; - stride1 = strides[1]; - - /* If I eventually switch to using frame,property ordering for timeseries data - stride0 = strides[1]; - stride1 = strides[0]; - */ - - for (i=0;i 0) || (aux2 > 0 && aux1 < 0)) { - aux1 *= -1; - } - // Check if the dihedral has wrapped around 2 pi - aux2 = *(double*)(data + dataset*stride0 + (frame-1)*stride1); - if (fabs(aux1-aux2) > M_PI) { - if (aux1 > 0) { aux1 -= 2*M_PI; } - else { aux1 += 2*M_PI; } - } - *(double*)(data + dataset++*stride0 + frame*stride1) = aux1; - break; - case 'w': - /* dipole orientation of 3-site water: ^ d - index1 = oxygen, index2, index3 = hydrogen | - returns d ,O, - d = rO - (rH1 + rH2)/2 H | H - | - */ - index1 = atomlist[atomno++]-lowerb; // O - index2 = atomlist[atomno++]-lowerb; // H1 - index3 = atomlist[atomno++]-lowerb; // H2 - x1 = tempX[index1] - 0.5*(tempX[index2] + tempX[index3]); // dx - y1 = tempY[index1] - 0.5*(tempY[index2] + tempY[index3]); // dy - z1 = tempZ[index1] - 0.5*(tempZ[index2] + tempZ[index3]); // dz - *(double*)(data + dataset++*stride0 + frame*stride1) = x1; - *(double*)(data + dataset++*stride0 + frame*stride1) = y1; - *(double*)(data + dataset++*stride0 + frame*stride1) = z1; - break; - } - } -} - -// This accounts for periodic boundary conditions -// taken from MMTK -#define distance_vector_2(d, r1, r2, data) \ - { \ - double xh = 0.5*(data)[0]; \ - double yh = 0.5*(data)[1]; \ - double zh = 0.5*(data)[2]; \ - d[0] = r2[0]-r1[0]; \ - if (d[0] > xh) d[0] -= (data)[0]; \ - if (d[0] <= -xh) d[0] += (data)[0]; \ - d[1] = r2[1]-r1[1]; \ - if (d[1] > yh) d[1] -= (data)[1]; \ - if (d[1] <= -yh) d[1] += (data)[1]; \ - d[2] = r2[2]-r1[2]; \ - if (d[2] > zh) d[2] -= (data)[2]; \ - if (d[2] <= -zh) d[2] += (data)[2]; \ - } - -#endif diff --git a/package/MDAnalysis/coordinates/include/endianswap.h b/package/MDAnalysis/coordinates/include/endianswap.h deleted file mode 100644 index 6f1ced37483..00000000000 --- a/package/MDAnalysis/coordinates/include/endianswap.h +++ /dev/null @@ -1,173 +0,0 @@ -/*************************************************************************** - *cr - *cr (C) Copyright 1995-2003 The Board of Trustees of the - *cr University of Illinois - *cr All Rights Reserved - *cr - ***************************************************************************/ -/*************************************************************************** - * RCS INFORMATION: - * - * $RCSfile: endianswap.h,v $ - * $Author: eamon $ $Locker: $ $State: Exp $ - * $Revision: 1.3 $ $Date: 2004/04/16 15:37:00 $ - * - *************************************************************************** - * DESCRIPTION: - * Byte swapping routines used in various plugins - * There are two versions of each routine, one that's safe to use in - * all cases (but is slow) and one that is only safe to use on memory - * addresses that are aligned to the word size that's being byte-swapped - * but are much much much faster. Use the aligned versions of these - * routines whenever possible. The 'ndata' length count parameters and - * internal loops should be safe to use on huge memory arrays on 64-bit - * machines. - * - ***************************************************************************/ - -#ifndef ENDIAN_SWAP_H -#define ENDIAN_SWAP_H - -/* works on unaligned 2-byte quantities */ -static void swap2_unaligned(void *v, long ndata) { - long i; - char * dataptr = (char *) v; - char tmp; - - for (i = 0; i < ndata-1; i += 2) { - tmp = dataptr[i]; - dataptr[i] = dataptr[i+1]; - dataptr[i+1] = tmp; - } -} - - -/* works on unaligned 4-byte quantities */ -static void swap4_unaligned(void *v, long ndata) { - long i; - char *dataptr; - char tmp; - - dataptr = (char *) v; - for (i=0; i>8)&0xff) | ((*N&0xff)<<8)); - } -} - - -/* Only works with aligned 4-byte quantities, will cause a bus error */ -/* on some platforms if used on unaligned data. */ -static void swap4_aligned(void *v, long ndata) { - int *data = (int *) v; - long i; - int *N; - for (i=0; i>24)&0xff) | ((*N&0xff)<<24) | - ((*N>>8)&0xff00) | ((*N&0xff00)<<8)); - } -} - - -/* Only works with aligned 8-byte quantities, will cause a bus error */ -/* on some platforms if used on unaligned data. */ -static void swap8_aligned(void *v, long ndata) { - /* Use int* internally to prevent bugs caused by some compilers */ - /* and hardware that would potentially load data into an FP reg */ - /* and hose everything, such as the old "jmemcpy()" bug in NAMD */ - int *data = (int *) v; - long i; - int *N; - int t0, t1; - - for (i=0; i>24)&0xff) | ((t0&0xff)<<24) | - ((t0>>8)&0xff00) | ((t0&0xff00)<<8)); - - t1 = N[1]; - t1=(((t1>>24)&0xff) | ((t1&0xff)<<24) | - ((t1>>8)&0xff00) | ((t1&0xff00)<<8)); - - N[0] = t1; - N[1] = t0; - } -} - -#if 0 -/* Other implementations that might be faster in some cases */ - -/* swaps the endianism of an eight byte word. */ -void mdio_swap8(double *i) { - char c; - char *n; - n = (char *) i; - c = n[0]; - n[0] = n[7]; - n[7] = c; - c = n[1]; - n[1] = n[6]; - n[6] = c; - c = n[2]; - n[2] = n[5]; - n[5] = c; - c = n[3]; - n[3] = n[4]; - n[4] = c; -} - -#endif - -#endif - diff --git a/package/MDAnalysis/coordinates/include/fastio.h b/package/MDAnalysis/coordinates/include/fastio.h deleted file mode 100644 index 998b5ebd5fb..00000000000 --- a/package/MDAnalysis/coordinates/include/fastio.h +++ /dev/null @@ -1,454 +0,0 @@ -/*************************************************************************** - *cr - *cr (C) Copyright 1995-2009 The Board of Trustees of the - *cr University of Illinois - *cr All Rights Reserved - *cr - ***************************************************************************/ -/*************************************************************************** - * RCS INFORMATION: - * - * $RCSfile: fastio.h,v $ - * $Author: johns $ $Locker: $ $State: Exp $ - * $Revision: 1.20 $ $Date: 2009/04/29 15:45:29 $ - * - *************************************************************************** - * DESCRIPTION: - * This is a simple abstraction layer for system-dependent I/O calls - * that allow plugins to do binary I/O using the fastest possible method. - * - * This code is intended for use by binary trajectory reader plugins that - * work with multi-gigabyte data sets, reading only binary data. - * - ***************************************************************************/ - -/* Compiling on windows */ -#if defined(_MSC_VER) - -#if 1 -/* use native Windows I/O calls */ -#define FASTIO_NATIVEWIN32 1 - -#include -#include - -typedef HANDLE fio_fd; -typedef LONGLONG fio_size_t; -typedef void * fio_caddr_t; - -typedef struct { - fio_caddr_t iov_base; - int iov_len; -} fio_iovec; - -#define FIO_READ 0x01 -#define FIO_WRITE 0x02 - -#define FIO_SEEK_CUR FILE_CURRENT -#define FIO_SEEK_SET FILE_BEGIN -#define FIO_SEEK_END FILE_END - -static int fio_win32convertfilename(const char *filename, char *newfilename, int maxlen) { - int i; - int len=strlen(filename); - - if ((len + 1) >= maxlen) - return -1; - - for (i=0; i - -typedef FILE * fio_fd; -typedef size_t fio_size_t; /* MSVC doesn't uinversally support ssize_t */ -typedef void * fio_caddr_t; /* MSVC doesn't universally support caddr_t */ - -typedef struct { - fio_caddr_t iov_base; - int iov_len; -} fio_iovec; - -#define FIO_READ 0x01 -#define FIO_WRITE 0x02 - -#define FIO_SEEK_CUR SEEK_CUR -#define FIO_SEEK_SET SEEK_SET -#define FIO_SEEK_END SEEK_END - -static int fio_open(const char *filename, int mode, fio_fd *fd) { - char * modestr; - FILE *fp; - - if (mode == FIO_READ) - modestr = "rb"; - - if (mode == FIO_WRITE) - modestr = "wb"; - - fp = fopen(filename, modestr); - if (fp == NULL) { - return -1; - } else { - *fd = fp; - return 0; - } -} - -static int fio_fclose(fio_fd fd) { - return fclose(fd); -} - -static fio_size_t fio_fread(void *ptr, fio_size_t size, - fio_size_t nitems, fio_fd fd) { - return fread(ptr, size, nitems, fd); -} - -static fio_size_t fio_readv(fio_fd fd, const fio_iovec * iov, int iovcnt) { - int i; - fio_size_t len = 0; - - for (i=0; i -#include -#include -#include - -typedef int fio_fd; -typedef off_t fio_size_t; /* off_t is 64-bits with LFS builds */ - -/* enable use of kernel readv() if available */ -#if defined(__sun) || defined(__APPLE_CC__) || defined(__linux) -#define USE_KERNEL_READV 1 -#endif - -typedef void * fio_caddr_t; - -#if defined(USE_KERNEL_READV) -#include -typedef struct iovec fio_iovec; -#else - -typedef struct { - fio_caddr_t iov_base; - int iov_len; -} fio_iovec; -#endif - - -#define FIO_READ 0x01 -#define FIO_WRITE 0x02 - -#define FIO_SEEK_CUR SEEK_CUR -#define FIO_SEEK_SET SEEK_SET -#define FIO_SEEK_END SEEK_END - -static int fio_open(const char *filename, int mode, fio_fd *fd) { - int nfd; - int oflag = 0; - - if (mode == FIO_READ) - oflag = O_RDONLY; - - if (mode == FIO_WRITE) - oflag = O_WRONLY | O_CREAT | O_TRUNC; - - nfd = open(filename, oflag, 0666); - if (nfd < 0) { - return -1; - } else { - *fd = nfd; - return 0; - } -} - -static int fio_fclose(fio_fd fd) { - return close(fd); -} - -static fio_size_t fio_fread(void *ptr, fio_size_t size, - fio_size_t nitems, fio_fd fd) { - int i; - fio_size_t len = 0; - int cnt = 0; - - for (i=0; i= 0) - return 0; /* success (emulate behavior of fseek) */ - else - return -1; /* failure (emulate behavior of fseek) */ -} - -static fio_size_t fio_ftell(fio_fd fd) { - return lseek(fd, 0, SEEK_CUR); -} - -#endif - - -/* higher level routines that are OS independent */ - -static int fio_write_int32(fio_fd fd, int i) { - return (fio_fwrite(&i, 4, 1, fd) != 1); -} - -static int fio_read_int32(fio_fd fd, int *i) { - return (fio_fread(i, 4, 1, fd) != 1); -} - -static int fio_write_str(fio_fd fd, const char *str) { - int len = strlen(str); - return (fio_fwrite((void *) str, len, 1, fd) != 1); -} - diff --git a/package/MDAnalysis/coordinates/include/readdcd.h b/package/MDAnalysis/coordinates/include/readdcd.h deleted file mode 100644 index c9d0f9999c9..00000000000 --- a/package/MDAnalysis/coordinates/include/readdcd.h +++ /dev/null @@ -1,764 +0,0 @@ -/*************************************************************************** - *cr - *cr (C) Copyright 1995-2003 The Board of Trustees of the - *cr University of Illinois - *cr All Rights Reserved - *cr - ***************************************************************************/ - -/*************************************************************************** - * RCS INFORMATION: - * - * $RCSfile: readdcd.h,v $ - * $Author: johns $ $Locker: $ $State: Exp $ - * $Revision: 1.32 $ $Date: 2004/09/21 20:52:37 $ - * - ***************************************************************************/ - -#ifndef READ_DCD_H -#define READ_DCD_H - -#include -#include -#include -#include -#include "endianswap.h" -#include "fastio.h" - -/* DEFINE ERROR CODES THAT MAY BE RETURNED BY DCD ROUTINES */ -#define DCD_SUCCESS 0 /* No problems */ -#define DCD_EOF -1 /* Normal EOF */ -#define DCD_DNE -2 /* DCD file does not exist */ -#define DCD_OPENFAILED -3 /* Open of DCD file failed */ -#define DCD_BADREAD -4 /* read call on DCD file failed */ -#define DCD_BADEOF -5 /* premature EOF found in DCD file */ -#define DCD_BADFORMAT -6 /* format of DCD file is wrong */ -#define DCD_FILEEXISTS -7 /* output file already exists */ -#define DCD_BADMALLOC -8 /* malloc failed */ - -/* - * Read the header information from a dcd file. - * Input: fd - a file struct opened for binary reading. - * Output: 0 on success, negative error code on failure. - * Side effects: *natoms set to number of atoms per frame - * *nsets set to number of frames in dcd file - * *istart set to starting timestep of dcd file - * *nsavc set to timesteps between dcd saves - * *delta set to value of trajectory timestep - * *nfixed set to number of fixed atoms - * *freeind may be set to heap-allocated space - * *reverse set to one if reverse-endian, zero if not. - * *charmm set to internal code for handling charmm data. - */ -static int read_dcdheader(fio_fd fd, int *natoms, int *nsets, int *istart, int *nsavc, - double *delta, int *nfixed, int **freeind, - float **fixedcoords, int *reverse, int *charmm, - char **remarks, int *len_remarks); - -/* - * Read a dcd timestep from a dcd file - * Input: fd - a file struct opened for binary reading, from which the - * header information has already been read. - * natoms, nfixed, first, *freeind, reverse, charmm - the corresponding - * items as set by read_dcdheader - * first - true if this is the first frame we are reading. - * x, y, z: space for natoms each of floats. - * unitcell - space for six floats to hold the unit cell data. - * Not set if no unit cell data is present. - * Output: 0 on success, negative error code on failure. - * Side effects: x, y, z contain the coordinates for the timestep read. - * unitcell holds unit cell data if present. - */ -static int read_dcdstep(fio_fd fd, int natoms, float *x, float *y, float *z, - float *unitcell, int nfixed, int first, int *freeind, - float *fixedcoords, int reverse, int charmm); - -/* - * Read a subset of a timestep from a dcd file - * Input: fd - a file struct opened for binary reading, from which the - * header information has already been read - * natoms, nfixed, first, *freeind, reverse, charmm - the corresponding - * items as set by read_dcdheader - * first - true if this is the first frame we are reading. - * lowerb, upperb - the range of atoms to read data for - * x, y, z: space for upperb-lowerb+1 each of floats - * unitcell - space for six floats to hold the unit cell data. - * Not set if no unit cell data is present. - * Ouput: 0 on success, negative error code on failure. - * Side effects: x, y, z contain coordinates for the range of atoms - * unitcell holds unit cell data if present. - */ -static int read_dcdsubset(fio_fd fd, int natoms, int lowerb, int upperb, float *x, float *y, float *z, - float *unitcell, int nfixed, int first, int *freeind, - float *fixedcoords, int reverse, int charmm); - -/* - * Skip past a timestep. If there are fixed atoms, this cannot be used with - * the first timestep. - * Input: fd - a file struct from which the header has already been read - * natoms - number of atoms per timestep - * nfixed - number of fixed atoms - * charmm - charmm flags as returned by read_dcdheader - * Output: 0 on success, negative error code on failure. - * Side effects: One timestep will be skipped; fd will be positioned at the - * next timestep. - */ -static int skip_dcdstep(fio_fd fd, int natoms, int nfixed, int charmm, int numstep); - -/* - * clean up dcd data - * Input: nfixed, freeind - elements as returned by read_dcdheader - * Output: None - * Side effects: Space pointed to by freeind is freed if necessary. - */ -static void close_dcd_read(int *freeind, float *fixedcoords); - -/* - * Write a header for a new dcd file - * Input: fd - file struct opened for binary writing - * remarks - string to be put in the remarks section of the header. - * The string will be truncated to 70 characters. - * natoms, istart, nsavc, delta - see comments in read_dcdheader - * Output: 0 on success, negative error code on failure. - * Side effects: Header information is written to the dcd file. - */ -static int write_dcdheader(fio_fd fd, const char *remarks, int natoms, - int istart, int nsavc, double delta, int with_unitcell, - int charmm); - -/* - * Write a timestep to a dcd file - * Input: fd - a file struct for which a dcd header has already been written - * curframe: Count of frames written to this file, starting with 1. - * curstep: Count of timesteps elapsed = istart + curframe * nsavc. - * natoms - number of elements in x, y, z arrays - * x, y, z: pointers to atom coordinates - * Output: 0 on success, negative error code on failure. - * Side effects: coordinates are written to the dcd file. - */ -static int write_dcdstep(fio_fd fd, int curstep, int curframe, - int natoms, const float *x, const float *y, const float *z, - const double *unitcell, int charmm); - - - -#define DCD_IS_CHARMM 0x01 -#define DCD_HAS_4DIMS 0x02 -#define DCD_HAS_EXTRA_BLOCK 0x04 - -/* READ Macro to make porting easier */ -#define READ(fd, buf, size) \ - fio_fread(((void *) buf), (size), 1, (fd)) - - -/* WRITE Macro to make porting easier */ -#define WRITE(fd, buf, size) \ - fio_fwrite(((void *) buf), (size), 1, (fd)) - -/* XXX This is broken - fread never returns -1 */ -#define CHECK_FREAD(X, msg) if (X==-1) \ - { \ - return(DCD_BADREAD); \ - } - -#define CHECK_FEOF(X, msg) if (X==0) \ - { \ - return(DCD_BADEOF); \ - } - -static int read_dcdheader(fio_fd fd, int *N, int *NSET, int *ISTART, - int *NSAVC, double *DELTA, int *NAMNF, - int **FREEINDEXES, float **fixedcoords, int *reverseEndian, - int *charmm, char **remarks, int *len_remarks) -{ - int input_integer; /* buffer space */ - int ret_val; - char hdrbuf[84]; /* char buffer used to store header */ - int NTITLE; - - /* First thing in the file should be an 84 */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading first int from dcd file"); - CHECK_FEOF(ret_val, "reading first int from dcd file"); - - /* Check magic number in file header and determine byte order*/ - if (input_integer != 84) { - /* check to see if its merely reversed endianism */ - /* rather than a totally incorrect file magic number */ - swap4_aligned(&input_integer, 1); - - if (input_integer == 84) { - *reverseEndian=1; - } else { - /* not simply reversed endianism, but something rather more evil */ - return DCD_BADFORMAT; - } - } else { - *reverseEndian=0; - } - - /* Buffer the entire header for random access */ - ret_val = READ(fd, hdrbuf, 84); - CHECK_FREAD(ret_val, "buffering header"); - CHECK_FEOF(ret_val, "buffering header"); - - /* Check for the ID string "COORD" */ - if (hdrbuf[0] != 'C' || hdrbuf[1] != 'O' || - hdrbuf[2] != 'R' || hdrbuf[3] != 'D') { - return DCD_BADFORMAT; - } - - /* CHARMm-genereate DCD files set the last integer in the */ - /* header, which is unused by X-PLOR, to its version number. */ - /* Checking if this is nonzero tells us this is a CHARMm file */ - /* and to look for other CHARMm flags. */ - if (*((int *) (hdrbuf + 80)) != 0) { - (*charmm) = DCD_IS_CHARMM; - if (*((int *) (hdrbuf + 44)) != 0) - (*charmm) |= DCD_HAS_EXTRA_BLOCK; - - if (*((int *) (hdrbuf + 48)) == 1) - (*charmm) |= DCD_HAS_4DIMS; - } else { - (*charmm) = 0; - } - - /* Store the number of sets of coordinates (NSET) */ - (*NSET) = *((int *) (hdrbuf + 4)); - if (*reverseEndian) swap4_unaligned(NSET, 1); - - /* Store ISTART, the starting timestep */ - (*ISTART) = *((int *) (hdrbuf + 8)); - if (*reverseEndian) swap4_unaligned(ISTART, 1); - - /* Store NSAVC, the number of timesteps between dcd saves */ - (*NSAVC) = *((int *) (hdrbuf + 12)); - if (*reverseEndian) swap4_unaligned(NSAVC, 1); - - /* Store NAMNF, the number of fixed atoms */ - (*NAMNF) = *((int *) (hdrbuf + 36)); - if (*reverseEndian) swap4_unaligned(NAMNF, 1); - - /* Read in the timestep, DELTA */ - /* Note: DELTA is stored as a double with X-PLOR but as a float with CHARMm */ - if ((*charmm) & DCD_IS_CHARMM) { - float ftmp; - ftmp = *((float *)(hdrbuf+40)); /* is this safe on Alpha? */ - if (*reverseEndian) - swap4_aligned(&ftmp, 1); - - *DELTA = (double)ftmp; - } else { - (*DELTA) = *((double *)(hdrbuf + 40)); - if (*reverseEndian) swap8_unaligned(DELTA, 1); - } - - /* Get the end size of the first block */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading second 84 from dcd file"); - CHECK_FEOF(ret_val, "reading second 84 from dcd file"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (input_integer != 84) { - return DCD_BADFORMAT; - } - - /* Read in the size of the next block */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading size of title block"); - CHECK_FEOF(ret_val, "reading size of title block"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (((input_integer-4) % 80) == 0) { - /* Read NTITLE, the number of 80 character title strings there are */ - ret_val = READ(fd, &NTITLE, sizeof(int)); - CHECK_FREAD(ret_val, "reading NTITLE"); - CHECK_FEOF(ret_val, "reading NTITLE"); - if (*reverseEndian) swap4_aligned(&NTITLE, 1); - *len_remarks = NTITLE*80; - *remarks = (char*)malloc(*len_remarks); - ret_val = fio_fread(*remarks, *len_remarks, 1, fd); - CHECK_FEOF(ret_val, "reading TITLE"); - - /* Get the ending size for this block */ - ret_val = READ(fd, &input_integer, sizeof(int)); - - CHECK_FREAD(ret_val, "reading size of title block"); - CHECK_FEOF(ret_val, "reading size of title block"); - } else { - return DCD_BADFORMAT; - } - - /* Read in an integer '4' */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading a '4'"); - CHECK_FEOF(ret_val, "reading a '4'"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (input_integer != 4) { - return DCD_BADFORMAT; - } - - /* Read in the number of atoms */ - ret_val = READ(fd, N, sizeof(int)); - CHECK_FREAD(ret_val, "reading number of atoms"); - CHECK_FEOF(ret_val, "reading number of atoms"); - if (*reverseEndian) swap4_aligned(N, 1); - - /* Read in an integer '4' */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading a '4'"); - CHECK_FEOF(ret_val, "reading a '4'"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (input_integer != 4) { - return DCD_BADFORMAT; - } - - *FREEINDEXES = NULL; - *fixedcoords = NULL; - if (*NAMNF != 0) { - (*FREEINDEXES) = (int *) calloc(((*N)-(*NAMNF)), sizeof(int)); - if (*FREEINDEXES == NULL) - return DCD_BADMALLOC; - - *fixedcoords = (float *) calloc((*N)*4 - (*NAMNF), sizeof(float)); - if (*fixedcoords == NULL) - return DCD_BADMALLOC; - - /* Read in index array size */ - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading size of index array"); - CHECK_FEOF(ret_val, "reading size of index array"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (input_integer != ((*N)-(*NAMNF))*4) { - return DCD_BADFORMAT; - } - - ret_val = READ(fd, (*FREEINDEXES), ((*N)-(*NAMNF))*sizeof(int)); - CHECK_FREAD(ret_val, "reading size of index array"); - CHECK_FEOF(ret_val, "reading size of index array"); - - if (*reverseEndian) - swap4_aligned((*FREEINDEXES), ((*N)-(*NAMNF))); - - ret_val = READ(fd, &input_integer, sizeof(int)); - CHECK_FREAD(ret_val, "reading size of index array"); - CHECK_FEOF(ret_val, "reading size of index array"); - if (*reverseEndian) swap4_aligned(&input_integer, 1); - - if (input_integer != ((*N)-(*NAMNF))*4) { - return DCD_BADFORMAT; - } - } - - return DCD_SUCCESS; -} - -static int read_charmm_extrablock(fio_fd fd, int charmm, int reverseEndian, - float *unitcell) { - int i, input_integer; - - if ((charmm & DCD_IS_CHARMM) && (charmm & DCD_HAS_EXTRA_BLOCK)) { - /* Leading integer must be 48 */ - if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) - return DCD_BADREAD; - if (reverseEndian) swap4_aligned(&input_integer, 1); - if (input_integer == 48) { - double tmp[6]; - if (fio_fread(tmp, 48, 1, fd) != 1) return DCD_BADREAD; - if (reverseEndian) - swap8_aligned(tmp, 6); - for (i=0; i<6; i++) unitcell[i] = (float)tmp[i]; - } else { - /* unrecognized block, just skip it */ - if (fio_fseek(fd, input_integer, FIO_SEEK_CUR)) return DCD_BADREAD; - } - if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) return DCD_BADREAD; - } - - return DCD_SUCCESS; -} - -static int read_fixed_atoms(fio_fd fd, int N, int num_free, const int *indexes, - int reverseEndian, const float *fixedcoords, - float *freeatoms, float *pos) { - int i, input_integer; - - /* Read leading integer */ - if (fio_fread(&input_integer, sizeof(int), 1, fd) != 1) return DCD_BADREAD; - if (reverseEndian) swap4_aligned(&input_integer, 1); - if (input_integer != 4*num_free) return DCD_BADFORMAT; - - /* Read free atom coordinates */ - if (fio_fread(freeatoms, 4*num_free, 1, fd) != 1) return DCD_BADREAD; - if (reverseEndian) - swap4_aligned(freeatoms, num_free); - - /* Copy fixed and free atom coordinates into position buffer */ - memcpy(pos, fixedcoords, 4*N); - for (i=0; i 1) { - seekoffset *= numsteps; - } - - rc = fio_fseek(fd, seekoffset, FIO_SEEK_CUR); - if (rc == -1) return DCD_BADEOF; - - return DCD_SUCCESS; -} - -static int jump_to_dcdstep(fio_fd fd, int natoms, int nsets, int nfixed, int charmm, int header_size, int step) { - int rc; - if (step > nsets) { - return DCD_BADEOF; - } - // Calculate file offset - off_t extrablocksize, ndims, firstframesize, framesize; - off_t pos; - extrablocksize = charmm & DCD_HAS_EXTRA_BLOCK ? 48 + 8 : 0; - ndims = charmm & DCD_HAS_4DIMS ? 4 : 3; - firstframesize = (natoms+2) * ndims * sizeof(float) + extrablocksize; - framesize = (natoms-nfixed+2) * ndims * sizeof(float) + extrablocksize; - // Use zero indexing - if (step == 0) { - pos = header_size; - } - else { - pos = header_size + firstframesize + framesize * (step-1); - } - rc = fio_fseek(fd, pos, FIO_SEEK_SET); - if (rc == -1) return DCD_BADEOF; - return DCD_SUCCESS; -} - -#define NFILE_POS 8L -#define NSTEP_POS 20L - -static int write_dcdstep(fio_fd fd, int curframe, int curstep, int N, - const float *X, const float *Y, const float *Z, - const double *unitcell, int charmm) { - int out_integer; - - if (charmm) { - /* write out optional unit cell */ - if (unitcell != NULL) { - out_integer = 48; /* 48 bytes (6 doubles) */ - fio_write_int32(fd, out_integer); - WRITE(fd, unitcell, out_integer); - fio_write_int32(fd, out_integer); - } - } - - /* write out coordinates */ - out_integer = N*4; /* N*4 bytes per X/Y/Z array (N floats per array) */ - fio_write_int32(fd, out_integer); - WRITE(fd, X, out_integer); - fio_write_int32(fd, out_integer); - fio_write_int32(fd, out_integer); - WRITE(fd, Y, out_integer); - fio_write_int32(fd, out_integer); - fio_write_int32(fd, out_integer); - WRITE(fd, Z, out_integer); - fio_write_int32(fd, out_integer); - - /* update the DCD header information */ - fio_fseek(fd, NFILE_POS, FIO_SEEK_SET); - fio_write_int32(fd, curframe); - fio_fseek(fd, NSTEP_POS, FIO_SEEK_SET); - fio_write_int32(fd, curstep); - fio_fseek(fd, 0, FIO_SEEK_END); - - return DCD_SUCCESS; -} - -static int write_dcdheader(fio_fd fd, const char *remarks, int N, - int ISTART, int NSAVC, double DELTA, int with_unitcell, - int charmm) { - int out_integer; - float out_float; - char title_string[200]; - time_t cur_time; - struct tm *tmbuf; - char time_str[81]; - - out_integer = 84; - WRITE(fd, (char *) & out_integer, sizeof(int)); - strcpy(title_string, "CORD"); - WRITE(fd, title_string, 4); - fio_write_int32(fd, 0); /* Number of frames in file, none written yet */ - fio_write_int32(fd, ISTART); /* Starting timestep */ - fio_write_int32(fd, NSAVC); /* Timesteps between frames written to the file */ - fio_write_int32(fd, 0); /* Number of timesteps in simulation */ - fio_write_int32(fd, 0); /* NAMD writes NSTEP or ISTART - NSAVC here? */ - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - if (charmm) { - out_float = DELTA; - WRITE(fd, (char *) &out_float, sizeof(float)); - if (with_unitcell) { - fio_write_int32(fd, 1); - } else { - fio_write_int32(fd, 0); - } - } else { - WRITE(fd, (char *) &DELTA, sizeof(double)); - } - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - fio_write_int32(fd, 0); - if (charmm) { - fio_write_int32(fd, 24); /* Pretend to be Charmm version 24 */ - } else { - fio_write_int32(fd, 0); - } - fio_write_int32(fd, 84); - fio_write_int32(fd, 164); - fio_write_int32(fd, 2); - - strncpy(title_string, remarks, 80); - title_string[79] = '\0'; - WRITE(fd, title_string, 80); - - cur_time=time(NULL); - tmbuf=localtime(&cur_time); - strftime(time_str, 80, "REMARKS Created %d %B, %Y at %R", tmbuf); - WRITE(fd, time_str, 80); - - fio_write_int32(fd, 164); - fio_write_int32(fd, 4); - fio_write_int32(fd, N); - fio_write_int32(fd, 4); - - return DCD_SUCCESS; -} - -static void close_dcd_read(int *indexes, float *fixedcoords) { - free(indexes); - free(fixedcoords); -} - -#endif - diff --git a/package/MDAnalysis/lib/formats/include/correl.h b/package/MDAnalysis/lib/formats/include/correl.h deleted file mode 100644 index f3dacb042e7..00000000000 --- a/package/MDAnalysis/lib/formats/include/correl.h +++ /dev/null @@ -1,188 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode:nil; -*- */ -/* vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 */ -/* - MDAnalysis --- http://mdanalysis.googlecode.com - Copyright (c) 2006-2014 Naveen Michaud-Agrawal, - Elizabeth J. Denning, Oliver Beckstein, - and contributors (see AUTHORS for the full list) - Released under the GNU Public Licence, v2 or any higher version - - Please cite your use of MDAnalysis in published work: - - 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, - in press. -*/ - -#ifndef CORREL_H -#define CORREL_H - -#include -/* Python.h for 'typedef int Py_intptr_t;' (fixes Issue 19) */ -#include - -static void -copyseries(int frame, char *data, const Py_intptr_t *strides, - const float *tempX, const float *tempY, const float *tempZ, - const char* datacode, int numdata, const int* atomlist, const int* atomcounts, - int lowerb, double* aux) -{ - char code; - int index1 = 0, index2 = 0, index3 = 0, index4 = 0; - double x1, x2, y1, y2, z1, z2, x3, y3, z3, aux1, aux2; - int i = 0, j = 0, atomno = 0; - int dataset = 0; - int stride0, stride1; - - stride0 = strides[0]; - stride1 = strides[1]; - - /* If I eventually switch to using frame,property ordering for timeseries data - stride0 = strides[1]; - stride1 = strides[0]; - */ - - for (i=0;i 0) || (aux2 > 0 && aux1 < 0)) { - aux1 *= -1; - } - // Check if the dihedral has wrapped around 2 pi - aux2 = *(double*)(data + dataset*stride0 + (frame-1)*stride1); - if (fabs(aux1-aux2) > M_PI) { - if (aux1 > 0) { aux1 -= 2*M_PI; } - else { aux1 += 2*M_PI; } - } - *(double*)(data + dataset++*stride0 + frame*stride1) = aux1; - break; - case 'w': - /* dipole orientation of 3-site water: ^ d - index1 = oxygen, index2, index3 = hydrogen | - returns d ,O, - d = rO - (rH1 + rH2)/2 H | H - | - */ - index1 = atomlist[atomno++]-lowerb; // O - index2 = atomlist[atomno++]-lowerb; // H1 - index3 = atomlist[atomno++]-lowerb; // H2 - x1 = tempX[index1] - 0.5*(tempX[index2] + tempX[index3]); // dx - y1 = tempY[index1] - 0.5*(tempY[index2] + tempY[index3]); // dy - z1 = tempZ[index1] - 0.5*(tempZ[index2] + tempZ[index3]); // dz - *(double*)(data + dataset++*stride0 + frame*stride1) = x1; - *(double*)(data + dataset++*stride0 + frame*stride1) = y1; - *(double*)(data + dataset++*stride0 + frame*stride1) = z1; - break; - } - } -} - -// This accounts for periodic boundary conditions -// taken from MMTK -#define distance_vector_2(d, r1, r2, data) \ - { \ - double xh = 0.5*(data)[0]; \ - double yh = 0.5*(data)[1]; \ - double zh = 0.5*(data)[2]; \ - d[0] = r2[0]-r1[0]; \ - if (d[0] > xh) d[0] -= (data)[0]; \ - if (d[0] <= -xh) d[0] += (data)[0]; \ - d[1] = r2[1]-r1[1]; \ - if (d[1] > yh) d[1] -= (data)[1]; \ - if (d[1] <= -yh) d[1] += (data)[1]; \ - d[2] = r2[2]-r1[2]; \ - if (d[2] > zh) d[2] -= (data)[2]; \ - if (d[2] <= -zh) d[2] += (data)[2]; \ - } - -#endif From 36105ae6d2255ef63814cd87317907071b9e6a80 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 13:51:17 +0200 Subject: [PATCH 078/101] fixup CHANGELOG & setup.py with rebase cleaning --- package/CHANGELOG | 8 ++++++-- package/setup.py | 1 + testsuite/MDAnalysisTests/coordinates/base.py | 1 - testsuite/MDAnalysisTests/datafiles.py | 2 -- testsuite/MDAnalysisTests/util.py | 6 +----- testsuite/setup.py | 2 +- 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index e13ee60141a..03529f67b49 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -13,7 +13,7 @@ The rules for this file: * release numbers follow "Semantic Versioning" http://semver.org ------------------------------------------------------------------------------ -mm/dd/17 richardjgowers, rathann, orbeckst, tylerjereddy, mtiberti +mm/dd/17 richardjgowers, rathann, orbeckst, tylerjereddy, mtiberti, kain88-de * 0.17.0 @@ -41,10 +41,14 @@ Changes writing (if available) or fall back to netcdf (see also Issue #506) -mm/dd/17 richardjgowers, rathann, jbarnoud, kain88-de +mm/dd/17 richardjgowers, rathann, jbarnoud, orbeckst, utkbansal * 0.16.2 +Deprecations + * deprecated core.Timeseries module for 0.17.0 (Issue #1383) + * deprecated instant selectors for 1.0 (Issue #1377) + * deprecated the core.flag registry for 1.0 (Issue #782) Fixes * fixed GROWriter truncating long resids from the wrong end (Issue #1395) diff --git a/package/setup.py b/package/setup.py index c0f6b7f2164..e1004ea40ed 100755 --- a/package/setup.py +++ b/package/setup.py @@ -355,6 +355,7 @@ def extensions(config): pre_exts = [libdcd, distances, distances_omp, qcprot, transformation, libmdaxdr, util, encore_utils, ap_clustering, spe_dimred] + cython_generated = [] if use_cython: extensions = cythonize(pre_exts) diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index 3693be5bb61..45bea93541a 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -1068,7 +1068,6 @@ def assert_timestep_almost_equal(A, B, decimal=6, verbose=True): 'A.frame = {}, B.frame={}'.format( A.frame, B.frame)) - # if A.time != B.time: if not np.allclose(A.time, B.time): raise AssertionError('A and B refer to different times: ' 'A.time = {}, B.time={}'.format( diff --git a/testsuite/MDAnalysisTests/datafiles.py b/testsuite/MDAnalysisTests/datafiles.py index 201f0ac2831..c47da37fcc4 100644 --- a/testsuite/MDAnalysisTests/datafiles.py +++ b/testsuite/MDAnalysisTests/datafiles.py @@ -151,8 +151,6 @@ "legacy_DCD_ADK_coords", # frames 5 and 29 read in for adk_dims.dcd using legacy DCD reader "legacy_DCD_NAMD_coords", # frame 0 read in for SiN_tric_namd.dcd using legacy DCD reader "legacy_DCD_c36_coords", # frames 1 and 4 read in for tip125_tric_C36.dcd using legacy DCD reader - "ALIGN_BOUND", # two component bound system - "ALIGN_UNBOUND", # two component unbound system ] from pkg_resources import resource_filename diff --git a/testsuite/MDAnalysisTests/util.py b/testsuite/MDAnalysisTests/util.py index 3f72e96b78c..fc78dd31671 100644 --- a/testsuite/MDAnalysisTests/util.py +++ b/testsuite/MDAnalysisTests/util.py @@ -37,15 +37,11 @@ from contextlib import contextmanager from functools import wraps import importlib -try: - import mock -except ImportError: # python 3 - from unittest import mock +import mock import os from numpy.testing import assert_warns - def block_import(package): """Block import of a given package diff --git a/testsuite/setup.py b/testsuite/setup.py index 1bfd6a857f1..006e0a4c3f3 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -206,7 +206,7 @@ def dynamic_author_list(): 'data/*.xml', 'data/coordinates/*', 'data/*xvg', - 'data/*.mmtf', 'data/*.mmtf.gz', 'data/analysis/*', + 'data/*.mmtf', 'data/*.mmtf.gz', 'data/analysis/*' ], }, classifiers=CLASSIFIERS, From 49f3e54880e0e7335016d49475160bb76ef44ac6 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 14:20:30 +0200 Subject: [PATCH 079/101] fix DCD writer errors --- package/MDAnalysis/coordinates/DCD.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 7ad4325e332..8ed7ef33b38 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -340,7 +340,7 @@ class DCDWriter(base.WriterBase): alpha, C]`` """ - + format = 'DCD' multiframe = True flavor = 'CHARMM' units = {'time': 'AKMA', 'length': 'Angstrom'} @@ -382,6 +382,8 @@ def __init__(self, """ self.filename = filename self._convert_units = convert_units + if n_atoms is None: + raise ValueError("n_atoms argument is required") self.n_atoms = n_atoms self._file = DCDFile(self.filename, 'w') self.step = step @@ -389,7 +391,7 @@ def __init__(self, dt = mdaunits.convert(dt, 'ps', self.units['time']) self._file.write_header( remarks=remarks, - natoms=n_atoms, + natoms=self.n_atoms, nsavc=nsavc, delta=float(dt), charmm=1, From 76290ee5542bd9393c71b8d1de2c8013583f0bfd Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 14:20:36 +0200 Subject: [PATCH 080/101] add random unit cell test to test_dcd --- .../MDAnalysisTests/coordinates/test_dcd.py | 507 +++++++----------- 1 file changed, 186 insertions(+), 321 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 660c1877b2c..fb520310653 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -21,12 +21,12 @@ # from __future__ import absolute_import, print_function from six.moves import zip, range -import MDAnalysis as mda -from MDAnalysis.coordinates.DCD import DCDReader import numpy as np import os -from nose.plugins.attrib import attr +import MDAnalysis as mda +from MDAnalysis.coordinates.DCD import DCDReader + from numpy.testing import (assert_equal, assert_array_equal, assert_raises, assert_almost_equal, assert_array_almost_equal, assert_allclose, dec) @@ -36,11 +36,14 @@ from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD) -from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, +from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, + BaseReference, BaseWriterTest, assert_timestep_almost_equal) from MDAnalysisTests import tempdir +from unittest import TestCase +import pytest class DCDReference(BaseReference): @@ -91,364 +94,226 @@ def __init__(self, reference=None): super(TestDCDWriter, self).__init__(reference) +def test_write_random_unitcell(tmpdir): + with tmpdir.as_cwd(): + testname = 'test.dcd' + rstate = np.random.RandomState(1178083) + random_unitcells = rstate.uniform( + high=80, size=(98, 6)).astype(np.float64) + + u = mda.Universe(PSF, DCD) + with mda.Writer(testname, n_atoms=u.atoms.n_atoms) as w: + for index, ts in enumerate(u.trajectory): + u.atoms.dimensions = random_unitcells[index] + w.write(u.atoms) + + u2 = mda.Universe(PSF, testname) + for index, ts in enumerate(u2.trajectory): + assert_array_almost_equal(ts.dimensions, random_unitcells[index], + decimal=5) + + ################ # Legacy tests # ################ +@pytest.fixture(scope='module') +def universe_dcd(): + return mda.Universe(PSF, DCD) -class TestDCDReaderOld(TestCase): - def setUp(self): - self.universe = mda.Universe(PSF, DCD) - self.dcd = self.universe.trajectory - - def tearDown(self): - del self.universe - del self.dcd - - def test_rewind_dcd(self): - self.dcd.rewind() - assert_equal(self.dcd.ts.frame, 0, "rewinding to frame 0") - - def test_next_dcd(self): - self.dcd.rewind() - self.dcd.next() - assert_equal(self.dcd.ts.frame, 1, "loading frame 1") - - def test_jump_lastframe_dcd(self): - self.dcd[-1] - assert_equal(self.dcd.ts.frame, 97, "indexing last frame with dcd[-1]") - - def test_slice_dcd(self): - frames = [ts.frame for ts in self.dcd[5:17:3]] - assert_equal(frames, [5, 8, 11, 14], "slicing dcd [5:17:3]") - - def test_list_trajectory(self): - frames = [ts.frame for ts in self.dcd[[0, 3, 4, 5]]] - assert_equal(frames, [0, 3, 4, 5]) - - def test_array_trajectory(self): - frames = [ts.frame for ts in self.dcd[np.array([0, 3, 4, 5])]] - assert_equal(frames, [0, 3, 4, 5]) - - def test_list_reverse_trajectory(self): - frames = [ts.frame for ts in self.dcd[[0, 4, 2, 3, 0, 1]]] - assert_equal(frames, [0, 4, 2, 3, 0, 1]) - - def test_list_repeated_trajectory(self): - frames = [ts.frame for ts in self.dcd[[0, 0, 1, 1, 2, 1, 1]]] - assert_equal(frames, [0, 0, 1, 1, 2, 1, 1]) - - def test_reverse_dcd(self): - frames = [ts.frame for ts in self.dcd[20:5:-1]] - assert_equal(frames, list(range(20, 5, -1)), - "reversing dcd [20:5:-1]") - - -def test_timeseries_slices(): - slices = [([None, None, None], 98), - ([0, None, None], 98), - ([None, 98, None], 98), - ([None, None, 1], 98), - ([None, None, -1], 98), - ([2, 6, 2], 2), - ([0, 10, None], 10), - ([2, 10, None], 8), - ([0, 1, 1], 1), - ([1, 1, 1], 0), - ([1, 2, 1], 1), - ([1, 2, 2], 1), - ([1, 4, 2], 2), - ([1, 4, 4], 1), - ([0, 5, 5], 1), - ([3, 5, 1], 2), - ([4, 0, -1], 4), - ([5, 0, -2], 3), - ([5, 0, -4], 2), - ] - u = mda.Universe(PSF, DCD) - allframes = u.trajectory.timeseries(format='fac') - for (start, stop, step), l in slices: - xyz = u.trajectory.timeseries(start=start, stop=stop, step=step, - format='fac') - assert_equal(len(xyz), l) - assert_array_almost_equal(xyz, allframes[start:stop:step]) - - -def test_timeseries_order(): - orders = ('fac', 'fca', 'afc', 'acf', 'caf', 'cfa') - - u = mda.Universe(PSF, DCD) - natoms = u.atoms.n_atoms - ndims = 3 - n = u.trajectory.n_frames - - for order in orders: - if order == 'fac': - shape = (n, natoms, ndims) - elif order == 'fca': - shape = (n, ndims, natoms) - elif order == 'afc': - shape = (natoms, n, ndims) - elif order == 'acf': - shape = (natoms, ndims, n) - elif order == 'caf': - shape = (ndims, natoms, n) - elif order == 'cfa': - shape = (ndims, n, natoms) - x = u.trajectory.timeseries(format=order) - assert_array_equal(x.shape, shape) - - -def test_readframes_atomindices(): - indices = [[1, 2, 3, 4], - [5, 10, 15, 19], - [9, 4, 2, 0, 50]] - u = mda.Universe(PSF, DCD) - allframes = u.trajectory.timeseries(format='afc') - for idxs in indices: - asel = u.atoms[idxs] - xyz = u.trajectory.timeseries(asel=asel, format='afc') - assert_equal(len(xyz), len(idxs)) - assert_array_almost_equal(xyz, allframes[idxs]) - - -def test_DCDReader_set_dt(dt=100., frame=3): + +def test_rewind(universe_dcd): + universe_dcd.trajectory.rewind() + assert universe_dcd.trajectory.ts.frame == 0 + + +def test_next(universe_dcd): + universe_dcd.trajectory.rewind() + universe_dcd.trajectory.next() + assert universe_dcd.trajectory.ts.frame == 1 + + +def test_jump_last_frame(universe_dcd): + universe_dcd.trajectory[-1] + assert universe_dcd.trajectory.ts.frame == 97 + + +@pytest.mark.parametrize("start, stop, step", ((5, 17, 3), + (20, 5, -1))) +def test_slice(universe_dcd, start, stop, step): + frames = [ts.frame for ts in universe_dcd.trajectory[start:stop:step]] + assert frames == list(range(start, stop, step)) + + +@pytest.mark.parametrize("array_like", [list, np.array]) +def test_array_like(universe_dcd, array_like): + ar = array_like([0, 3, 4, 5]) + frames = [ts.frame for ts in universe_dcd.trajectory[ar]] + assert_array_equal(frames, ar) + + +@pytest.mark.parametrize("indices", ([0, 4, 2, 3, 0, 1], + [0, 0, 1, 1, 2, 1, 1])) +def test_list_indices(universe_dcd, indices): + frames = [ts.frame for ts in universe_dcd.trajectory[indices]] + assert frames == indices + + +@pytest.mark.parametrize( + "slice, length", + [([None, None, None], 98), ([0, None, None], 98), ([None, 98, None], 98), + ([None, None, 1], 98), ([None, None, -1], 98), ([2, 6, 2], 2), + ([0, 10, None], 10), ([2, 10, None], 8), ([0, 1, 1], 1), ([1, 1, 1], 0), + ([1, 2, 1], 1), ([1, 2, 2], 1), ([1, 4, 2], 2), ([1, 4, 4], 1), + ([0, 5, 5], 1), ([3, 5, 1], 2), ([4, 0, -1], 4), ([5, 0, -2], 3), + ([5, 0, -4], 2)]) +def test_timeseries_slices(slice, length, universe_dcd): + start, stop, step = slice + allframes = universe_dcd.trajectory.timeseries(format='fac') + xyz = universe_dcd.trajectory.timeseries(start=start, stop=stop, step=step, + format='fac') + assert len(xyz) == length + assert_array_almost_equal(xyz, allframes[start:stop:step]) + + +@pytest.mark.parametrize("order, shape", ( + ('fac', (98, 3341, 3)), + ('fca', (98, 3, 3341)), + ('afc', (3341, 98, 3)), + ('acf', (3341, 3, 98)), + ('caf', (3, 3341, 98)), + ('cfa', (3, 98, 3341)), )) +def test_timeseries_order(order, shape, universe_dcd): + x = universe_dcd.trajectory.timeseries(format=order) + assert x.shape == shape + + +@pytest.mark.parametrize("indices", [[1, 2, 3, 4], [5, 10, 15, 19], + [9, 4, 2, 0, 50]]) +def test_timeseries_atomindices(indices, universe_dcd): + allframes = universe_dcd.trajectory.timeseries(format='afc') + asel = universe_dcd.atoms[indices] + xyz = universe_dcd.trajectory.timeseries(asel=asel, format='afc') + assert len(xyz) == len(indices) + assert_array_almost_equal(xyz, allframes[indices]) + + +def test_reader_set_dt(): + dt = 100 + frame = 3 u = mda.Universe(PSF, DCD, dt=dt) assert_almost_equal(u.trajectory[frame].time, frame*dt, err_msg="setting time step dt={0} failed: " "actually used dt={1}".format( - dt, u.trajectory._ts_kwargs['dt'])) + dt, u.trajectory._ts_kwargs['dt'])) assert_almost_equal(u.trajectory.dt, dt, err_msg="trajectory.dt does not match set dt") -class TestDCDWriter_old(object): - def setUp(self): - self.universe = mda.Universe(PSF, DCD) - ext = ".dcd" - self.tmpdir = tempdir.TempDir() - self.outfile = self.tmpdir.name + '/dcd-writer' + ext - self.Writer = mda.coordinates.DCD.DCDWriter - def tearDown(self): - try: - os.unlink(self.outfile) - except OSError: - pass - del self.universe - del self.Writer - del self.tmpdir - - def test_dt(self): - DT = 5.0 - t = self.universe.trajectory - with self.Writer(self.outfile, - t.n_atoms, - dt=DT) as W: # set time step to 5 ps - for ts in self.universe.trajectory: - W.write_next_timestep(ts) +@pytest.mark.parametrize("ext, decimal", (("dcd", 5), + ("xtc", 3))) +def test_writer_dt(tmpdir, ext, decimal): + dt = 5.0 # set time step to 5 ps + universe_dcd = mda.Universe(PSF, DCD, dt=dt) + t = universe_dcd.trajectory + outfile = "test.{}".format(ext) + with tmpdir.as_cwd(): + with mda.Writer(outfile, n_atoms=t.n_atoms, dt=dt) as W: + for ts in universe_dcd.trajectory: + W.write(universe_dcd.atoms) - uw = mda.Universe(PSF, self.outfile) + uw = mda.Universe(PSF, outfile) assert_almost_equal(uw.trajectory.totaltime, - (uw.trajectory.n_frames - 1) * DT, 5) + (uw.trajectory.n_frames - 1) * dt, decimal) times = np.array([uw.trajectory.time for ts in uw.trajectory]) frames = np.array([ts.frame for ts in uw.trajectory]) - assert_array_almost_equal(times, frames * DT, 5) + assert_array_almost_equal(times, frames * dt, decimal) - def test_OtherWriter(self): - t = self.universe.trajectory - W = t.OtherWriter(self.outfile) - for ts in self.universe.trajectory: - W.write_next_timestep(ts) - W.close() - uw = mda.Universe(PSF, self.outfile) +@pytest.mark.parametrize("ext, decimal", (("dcd", 5), + ("xtc", 2))) +def test_other_writer(universe_dcd, tmpdir, ext, decimal): + t = universe_dcd.trajectory + outfile = "test.{}".format(ext) + with tmpdir.as_cwd(): + with t.OtherWriter(outfile) as W: + for ts in universe_dcd.trajectory: + W.write_next_timestep(ts) + uw = mda.Universe(PSF, outfile) # check that the coordinates are identical for each time step - for orig_ts, written_ts in zip(self.universe.trajectory, + for orig_ts, written_ts in zip(universe_dcd.trajectory, uw.trajectory): - assert_array_almost_equal(written_ts._pos, orig_ts._pos, 3, + assert_array_almost_equal(written_ts.positions, orig_ts.positions, + decimal, err_msg="coordinate mismatch between " "original and written trajectory at " "frame %d (orig) vs %d (written)" % ( orig_ts.frame, written_ts.frame)) - def test_single_frame(self): - u = mda.Universe(PSF, CRD) - W = mda.Writer(self.outfile, u.atoms.n_atoms) - W.write(u.atoms) - W.close() - w = mda.Universe(PSF, self.outfile) - assert_equal(w.trajectory.n_frames, 1, - "single frame trajectory has wrong number of frames") + +def test_single_frame(universe_dcd, tmpdir): + u = universe_dcd + outfile = "test.dcd" + with tmpdir.as_cwd(): + with mda.Writer(outfile, u.atoms.n_atoms) as W: + W.write(u.atoms) + w = mda.Universe(PSF, outfile) + assert w.trajectory.n_frames == 1 assert_almost_equal(w.atoms.positions, u.atoms.positions, 3, err_msg="coordinates do not match") -class TestDCDWriter_Issue59(object): - def setUp(self): - """Generate input xtc.""" - self.u = mda.Universe(PSF, DCD) - self.tmpdir = tempdir.TempDir() - self.xtc = self.tmpdir.name + '/dcd-writer-issue59-test.xtc' - wXTC = mda.Writer(self.xtc, self.u.atoms.n_atoms) - for ts in self.u.trajectory: - wXTC.write(ts) - wXTC.close() +@pytest.mark.parametrize("ref", (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD)) +def test_read_unitcell_triclinic(ref): + u = mda.Universe(ref.topology, ref.trajectory) + for ts, box in zip(u.trajectory, ref.ref_dimensions[:, 1:]): + assert_array_almost_equal(ts.dimensions, box, 4, + err_msg="box dimensions A,B,C,alpha," + "beta,gamma not identical at frame " + "{}".format(ts.frame)) - def tearDown(self): - try: - os.unlink(self.xtc) - except OSError: - pass - try: - os.unlink(self.dcd) - except (AttributeError, OSError): - pass - del self.u - del self.tmpdir - - @attr('issue') - def test_issue59(self): - """Test writing of XTC to DCD (Issue 59)""" - xtc = mda.Universe(PSF, self.xtc) - self.dcd = self.tmpdir.name + '/dcd-writer-issue59-test.dcd' - wDCD = mda.Writer(self.dcd, xtc.atoms.n_atoms) - for ts in xtc.trajectory: - wDCD.write(ts) - wDCD.close() - - dcd = mda.Universe(PSF, self.dcd) - - xtc.trajectory.rewind() - dcd.trajectory.rewind() - - assert_array_almost_equal( - xtc.atoms.positions, - dcd.atoms.positions, - 3, - err_msg="XTC -> DCD: DCD coordinates are messed up (Issue 59)") - - def test_OtherWriter(self): - dcd = self.u - wXTC = dcd.trajectory.OtherWriter(self.xtc) - for ts in dcd.trajectory: - wXTC.write(ts) - wXTC.close() - - xtc = mda.Universe(PSF, self.xtc) - xtc.trajectory.rewind() - dcd.trajectory.rewind() - - assert_array_almost_equal( - dcd.atoms.positions, - xtc.atoms.positions, - 2, - err_msg="DCD -> XTC: coordinates are messed up (frame {0:d})".format( - dcd.trajectory.frame)) - xtc.trajectory[3] - dcd.trajectory[3] - assert_array_almost_equal( - dcd.atoms.positions, - xtc.atoms.positions, - 2, - err_msg="DCD -> XTC: coordinates are messed up (frame {0:d})".format( - dcd.trajectory.frame)) - - -class _TestDCDReader_TriclinicUnitcell(TestCase): - __test__ = False - def setUp(self): - self.u = mda.Universe(self.topology, self.trajectory) - self.tempdir = tempdir.TempDir() - self.dcd = self.tempdir.name + '/dcd-reader-triclinic.dcd' - - def tearDown(self): - try: - os.unlink(self.dcd) - except (AttributeError, OSError): - pass - del self.u - del self.tempdir - - @attr('issue') - def test_read_triclinic(self): - """test reading of triclinic unitcell (Issue 187) for NAMD or new - CHARMM format (at least since c36b2)""" - for ts, box in zip(self.u.trajectory, - self.ref_dimensions[:, 1:]): - assert_array_almost_equal(ts.dimensions, box, 4, - err_msg="box dimensions A,B,C,alpha," - "beta,gamma not identical at frame " - "{}".format(ts.frame)) - - @attr('issue') - def test_write_triclinic(self): - """test writing of triclinic unitcell (Issue 187) for NAMD or new - CHARMM format (at least since c36b2)""" - with self.u.trajectory.OtherWriter(self.dcd) as w: - for ts in self.u.trajectory: + +@pytest.mark.parametrize("ref", (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD)) +def test_write_unitcell_triclinic(ref, tmpdir): + u = mda.Universe(ref.topology, ref.trajectory) + outfile = 'triclinic.dcd' + with tmpdir.as_cwd(): + with u.trajectory.OtherWriter(outfile) as w: + for ts in u.trajectory: w.write(ts) - w = mda.Universe(self.topology, self.dcd) - for ts_orig, ts_copy in zip(self.u.trajectory, - w.trajectory): + + w = mda.Universe(ref.topology, outfile) + for ts_orig, ts_copy in zip(u.trajectory, w.trajectory): assert_almost_equal(ts_orig.dimensions, ts_copy.dimensions, 4, err_msg="DCD->DCD: unit cell dimensions wrong " "at frame {0}".format(ts_orig.frame)) - del w -class TestDCDReader_CHARMM_Unitcell(_TestDCDReader_TriclinicUnitcell, - RefCHARMMtriclinicDCD): - __test__ = True +@pytest.fixture(scope='module') +def ncdf2dcd(tmpdir_factory): + testfile = tmpdir_factory.mktemp('dcd').join('ncdf2dcd.dcd') + testfile = str(testfile) + ncdf = mda.Universe(PRMncdf, NCDF) + with mda.Writer(testfile, n_atoms=ncdf.atoms.n_atoms) as w: + for ts in ncdf.trajectory: + w.write(ts) + return ncdf, mda.Universe(PRMncdf, testfile) -class TestDCDReader_NAMD_Unitcell(_TestDCDReader_TriclinicUnitcell, - RefNAMDtriclinicDCD): - __test__ = True +def test_ncdf2dcd_unitcell(ncdf2dcd): + ncdf, dcd = ncdf2dcd + for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): + assert_almost_equal(ts_ncdf.dimensions, + ts_dcd.dimensions, + 3) -class TestNCDF2DCD(object): - def setUp(self): - self.u = mda.Universe(PRMncdf, NCDF) - # create the DCD - self.tmpdir = tempdir.TempDir() - self.dcd = self.tmpdir.name + '/ncdf-2-dcd.dcd' - DCD = mda.Writer(self.dcd, n_atoms=self.u.atoms.n_atoms) - for ts in self.u.trajectory: - DCD.write(ts) - DCD.close() - self.w = mda.Universe(PRMncdf, self.dcd) - - def tearDown(self): - try: - os.unlink(self.dcd) - except (AttributeError, OSError): - pass - del self.u - del self.w - del self.tmpdir - - @attr('issue') - def test_unitcell(self): - """NCDFReader: Test that DCDWriter correctly writes the CHARMM - unit cell""" - for ts_orig, ts_copy in zip(self.u.trajectory, - self.w.trajectory): - assert_almost_equal( - ts_orig.dimensions, - ts_copy.dimensions, - 3, - err_msg="NCDF->DCD: unit cell dimensions wrong at frame {0:d}".format( - ts_orig.frame)) - - def test_coordinates(self): - for ts_orig, ts_copy in zip(self.u.trajectory, - self.w.trajectory): - assert_almost_equal( - self.u.atoms.positions, - self.w.atoms.positions, - 3, - err_msg="NCDF->DCD: coordinates wrong at frame {0:d}".format( - ts_orig.frame)) +def test_ncdf2dcd_coords(ncdf2dcd): + ncdf, dcd = ncdf2dcd + for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): + assert_almost_equal(ts_ncdf.positions, + ts_dcd.positions, + 3) From ad64253a98c74eee0832030bca384900b390d10c Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 15:59:48 +0200 Subject: [PATCH 081/101] prefer to use format over string interpolation --- testsuite/MDAnalysisTests/coordinates/test_dcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index fb520310653..59691c89e45 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -248,7 +248,7 @@ def test_other_writer(universe_dcd, tmpdir, ext, decimal): decimal, err_msg="coordinate mismatch between " "original and written trajectory at " - "frame %d (orig) vs %d (written)" % ( + "frame {} (orig) vs {} (written)".format( orig_ts.frame, written_ts.frame)) From 8ca3fd316ba82b692692bbcbe4938e422b3998c5 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 17:04:26 +0200 Subject: [PATCH 082/101] add information about charm bitfield --- package/MDAnalysis/lib/formats/libdcd.pyx | 30 ++++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index b0965773e50..d4539a2c565 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -152,7 +152,23 @@ cdef class DCDFile: Raises ------ - IOError + IOError, EOFError + + + Notes + ----- + This DCDFile reader can process files written by different MD simulation + programs. For files produced by CHARMM or other programs that follow the + same convention we are reading a special CHARMM bitfield that stores + different flags about additional information that is stored in the dcd. + This field cannot be written. The flags it might have are. + + DCD_IS_CHARMM = 0x01 + DCD_HAS_4DIMS = 0x02 + DCD_HAS_EXTRA_BLOCK = 0x04 + + Here `DCD_HAS_EXTRA_BLOCK` means that unitcell information is stored. + """ cdef fio_fd fp cdef readonly fname @@ -385,6 +401,12 @@ cdef class DCDFile: Returns ------- dict of header values needed to write new dcd + natoms: number of atoms + istart: starting frame number + nsavc: number of frames between saves + delta: integrator time step. + charm: bitfield integer if file contains special CHARMM information + remarks: remark string, max 240 bytes. """ return {'natoms': self.natoms, 'istart': self.istart, @@ -404,13 +426,13 @@ cdef class DCDFile: natoms : int number of atoms to write istart : int - starting frame + starting frame number nsavc : int number of frames between saves delta : float integrator time step. The time for 1 frame is nsavc * delta - charmm : int - is charmm dcd + charmm : bool + write unitcell information. Also pretends that file was written by CHARMM 24 """ if not self.is_open: From 7420bd3fbca659dee66a376140c8a033a8c55b12 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 17:08:23 +0200 Subject: [PATCH 083/101] uniformly use xyz to refer to coordinates in libdcd --- package/MDAnalysis/coordinates/DCD.py | 4 ++-- package/MDAnalysis/lib/formats/libdcd.pyx | 2 +- .../MDAnalysisTests/formats/test_libdcd.py | 20 +++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 8ed7ef33b38..e043e87037a 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -221,7 +221,7 @@ def _frame_to_ts(self, frame, ts): unitcell = uc ts.dimensions = unitcell - ts.positions = frame.x + ts.positions = frame.xyz if self.convert_units: self.convert_pos_from_native(ts.dimensions[:3]) @@ -329,7 +329,7 @@ def timeseries(self, raise ValueError("Invalid timeseries format") frames = self._file.readframes( start, stop, step, order=format, indices=atom_numbers) - return frames.x + return frames.xyz class DCDWriter(base.WriterBase): diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index d4539a2c565..a6b7ac55668 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -126,7 +126,7 @@ cdef extern from 'include/readdcd.h': int natoms, const float *x, const float *y, const float *z, const double *unitcell, int charmm); -DCDFrame = namedtuple('DCDFrame', 'x unitcell') +DCDFrame = namedtuple('DCDFrame', 'xyz unitcell') cdef class DCDFile: """File like wrapper for DCD files diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index d50e0ba0e2e..cfe8bf52f61 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -192,7 +192,7 @@ def test_readframes(dcdfile, legacy_data, frame_idx): legacy = np.load(legacy_data) with DCDFile(dcdfile) as dcd: frames = dcd.readframes() - xyz = frames.x + xyz = frames.xyz assert_equal(len(xyz), len(dcd)) for index, frame_num in enumerate(frame_idx): assert_array_almost_equal(xyz[frame_num], legacy[index]) @@ -280,7 +280,7 @@ def write_dcd(in_name, out_name, remarks='testing', header=None): header = f_in.header f_out.write_header(**header) for frame in f_in: - f_out.write(xyz=frame.x, box=frame.unitcell) + f_out.write(xyz=frame.xyz, box=frame.unitcell) @given(remarks=st.text( @@ -334,7 +334,7 @@ def test_written_dcd_coordinate_data_shape(written_dcd): with DCDFile(written_dcd.testfile) as dcd, DCDFile( written_dcd.orgfile) as other: for frame, other_frame in zip(dcd, other): - assert frame.x.shape == other_frame.x.shape + assert frame.xyz.shape == other_frame.xyz.shape def test_written_seek(written_dcd): @@ -348,7 +348,7 @@ def test_written_coord_match(written_dcd): with DCDFile(written_dcd.testfile) as test, DCDFile( written_dcd.orgfile) as ref: for frame, o_frame in zip(test, ref): - assert_array_almost_equal(frame.x, o_frame.x) + assert_array_almost_equal(frame.xyz, o_frame.xyz) def test_written_unit_cell(written_dcd): @@ -473,9 +473,9 @@ def test_nframessize_int(dcdfile): def test_readframes_slices(slice, length): start, stop, step = slice with DCDFile(DCD) as dcd: - allframes = dcd.readframes().x + allframes = dcd.readframes().xyz frames = dcd.readframes(start=start, stop=stop, step=step) - xyz = frames.x + xyz = frames.xyz assert len(xyz) == length assert_array_almost_equal(xyz, allframes[start:stop:step]) @@ -489,7 +489,7 @@ def test_readframes_slices(slice, length): ('cfa', (3, 98, 3341)), )) def test_readframes_order(order, shape): with DCDFile(DCD) as dcd: - x = dcd.readframes(order=order).x + x = dcd.readframes(order=order).xyz assert x.shape == shape @@ -497,9 +497,9 @@ def test_readframes_order(order, shape): [9, 4, 2, 0, 50]]) def test_readframes_atomindices(indices): with DCDFile(DCD) as dcd: - allframes = dcd.readframes(order='afc').x + allframes = dcd.readframes(order='afc').xyz frames = dcd.readframes(indices=indices, order='afc') - xyz = frames.x + xyz = frames.xyz assert len(xyz) == len(indices) assert_array_almost_equal(xyz, allframes[indices]) @@ -514,7 +514,7 @@ def test_write_random_unitcell(tmpdir): with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out: f_out.write_header(**f_in.header) for index, frame in enumerate(f_in): - f_out.write(xyz=frame.x, box=random_unitcells[index]) + f_out.write(xyz=frame.xyz, box=random_unitcells[index]) with DCDFile(testname) as test: for index, frame in enumerate(test): From 134199ec35db67126f4184dbfbaa1226db6df99c Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 17:15:14 +0200 Subject: [PATCH 084/101] fixup DCD sphinx error --- package/MDAnalysis/coordinates/DCD.py | 4 +++- testsuite/MDAnalysisTests/coordinates/test_dcd.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index e043e87037a..eb30272ffe4 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -93,7 +93,8 @@ class DCDReader(base.ReaderBase): units = {'time': 'AKMA', 'length': 'Angstrom'} def __init__(self, filename, convert_units=True, dt=None, **kwargs): - """Parameters + """ + Parameters ---------- filename : str trajectory filename @@ -104,6 +105,7 @@ def __init__(self, filename, convert_units=True, dt=None, **kwargs): **kwargs : dict General reader arguments. + .. versionchanged:: 0.17.0 Changed to use libdcd.pyx library and removed the correl function """ diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 59691c89e45..31135267498 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -35,14 +35,12 @@ COORDINATES_TOPOLOGY, COORDINATES_DCD) from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD) - from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, BaseWriterTest, assert_timestep_almost_equal) -from MDAnalysisTests import tempdir -from unittest import TestCase +from MDAnalysisTests import module_not_found import pytest @@ -303,6 +301,7 @@ def ncdf2dcd(tmpdir_factory): return ncdf, mda.Universe(PRMncdf, testfile) +@pytest.mark.skipif(module_not_found("netCDF4")) def test_ncdf2dcd_unitcell(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): @@ -311,6 +310,7 @@ def test_ncdf2dcd_unitcell(ncdf2dcd): 3) +@pytest.mark.skipif(module_not_found("netCDF4")) def test_ncdf2dcd_coords(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): From 717346a7783f2666e4b63398ef959df227d3c9b6 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 17:37:28 +0200 Subject: [PATCH 085/101] add Exception names to libdcd docs --- package/MDAnalysis/lib/formats/libdcd.pyx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index a6b7ac55668..657cf05aeeb 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -434,6 +434,10 @@ cdef class DCDFile: charmm : bool write unitcell information. Also pretends that file was written by CHARMM 24 + + Raises + ------ + IOError """ if not self.is_open: raise IOError("No file open") @@ -474,6 +478,7 @@ cdef class DCDFile: Raises ------ IOError + ValueError """ if not self.is_open: raise IOError("No file open") From 366290ee18d7847c494dc7cc073cb67849180535 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 20:33:36 +0200 Subject: [PATCH 086/101] remove unnecessary check --- package/MDAnalysis/coordinates/DCD.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index eb30272ffe4..7957d493795 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -325,10 +325,6 @@ def timeseries(self, else: atom_numbers = list(range(self.n_atoms)) - if len(format) != 3 and format not in [ - 'afc', 'acf', 'caf', 'cfa', 'fac', 'fca' - ]: - raise ValueError("Invalid timeseries format") frames = self._file.readframes( start, stop, step, order=format, indices=atom_numbers) return frames.xyz From bd9c425c1845f1369f5e10634209f807ad2453ec Mon Sep 17 00:00:00 2001 From: Max Linke Date: Sun, 25 Jun 2017 20:19:00 +0200 Subject: [PATCH 087/101] reach 100% coverage of DCD.py --- .../MDAnalysisTests/coordinates/reference.py | 31 ------- .../MDAnalysisTests/coordinates/test_dcd.py | 81 +++++++++++++++---- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/reference.py b/testsuite/MDAnalysisTests/coordinates/reference.py index 7c71d0f08bb..40d9e0084f4 100644 --- a/testsuite/MDAnalysisTests/coordinates/reference.py +++ b/testsuite/MDAnalysisTests/coordinates/reference.py @@ -253,34 +253,3 @@ class RefLAMMPSDataMini(object): vel_atom1 = np.array([-0.005667593, 0.00791380978, -0.00300779533], dtype=np.float32) dimensions = np.array([60., 50., 30., 90., 90., 90.], dtype=np.float32) - - -class RefCHARMMtriclinicDCD(object): - topology = PSF_TRICLINIC - trajectory = DCD_TRICLINIC - # time(ps) A B C alpha beta gamma (length in Angstrome, angles in degrees) - # dcd starts at t = 1ps - ref_dimensions = np.array([ - [1., 35.44604, 35.06156, 34.1585, 91.32802, 61.73521, 44.40703], - [2., 34.65957, 34.22689, 33.09897, 90.56206, 61.79192, 44.14549], - [3., 34.52772, 34.66422, 33.53881, 90.55859, 63.11228, 40.14044], - [4., 34.43749, 33.38432, 34.02133, 88.82457, 64.98057, 36.77397], - [5., 33.73129, 32.47752, 34.18961, 89.88102, 65.89032, 36.10921], - [6., 33.78703, 31.90317, 34.98833, 90.03092, 66.12877, 35.07141], - [7., 33.24708, 31.18271, 34.9654, 93.11122, 68.17743, 35.73643], - [8., 32.92599, 30.31393, 34.99197, 93.89051, 69.3799, 33.48945], - [9., 32.15295, 30.43056, 34.96157, 96.01416, 71.50115, 32.56111], - [10., 31.99748, 30.21518, 35.24292, 95.85821, 71.08429, 31.85939] - ]) - - -class RefNAMDtriclinicDCD(object): - topology = PSF_NAMD_TRICLINIC - trajectory = DCD_NAMD_TRICLINIC - # vmd topology trajectory - # molinfo 0 get {a b c alpha beta gamma} - # time(ps) A B C alpha beta gamma (length in Angstrome, angles in degrees) - ref_dimensions = np.array([ - [1., 38.426594, 38.393101, 44.759800, 90.000000, 90.000000, 60.028915], - ]) - diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 31135267498..8b70d8dde5c 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -19,28 +19,26 @@ # MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations. # J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787 # -from __future__ import absolute_import, print_function +from __future__ import absolute_import, print_function, division from six.moves import zip, range import numpy as np -import os import MDAnalysis as mda from MDAnalysis.coordinates.DCD import DCDReader +from MDAnalysis.exceptions import NoDataError from numpy.testing import (assert_equal, assert_array_equal, assert_raises, - assert_almost_equal, assert_array_almost_equal, - assert_allclose, dec) + assert_almost_equal, assert_array_almost_equal) -from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, CRD, PRMncdf, NCDF, - COORDINATES_TOPOLOGY, COORDINATES_DCD) -from MDAnalysisTests.coordinates.reference import (RefCHARMMtriclinicDCD, - RefNAMDtriclinicDCD) +from MDAnalysisTests.datafiles import (DCD, PSF, DCD_empty, PRMncdf, NCDF, + COORDINATES_TOPOLOGY, COORDINATES_DCD, + PSF_TRICLINIC, DCD_TRICLINIC, + PSF_NAMD_TRICLINIC, DCD_NAMD_TRICLINIC) from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, - BaseWriterTest, - assert_timestep_almost_equal) - + BaseWriterTest) from MDAnalysisTests import module_not_found + import pytest @@ -107,7 +105,8 @@ def test_write_random_unitcell(tmpdir): u2 = mda.Universe(PSF, testname) for index, ts in enumerate(u2.trajectory): - assert_array_almost_equal(ts.dimensions, random_unitcells[index], + assert_array_almost_equal(u2.trajectory.dimensions, + random_unitcells[index], decimal=5) @@ -196,6 +195,18 @@ def test_timeseries_atomindices(indices, universe_dcd): assert_array_almost_equal(xyz, allframes[indices]) +def test_timeseries_empty_selection(universe_dcd): + with pytest.raises(NoDataError): + asel = universe_dcd.select_atoms('name FOO') + universe_dcd.trajectory.timeseries(asel=asel) + + +def test_timeseries_skip(universe_dcd): + with pytest.warns(DeprecationWarning): + xyz = universe_dcd.trajectory.timeseries(skip=2, format='fac') + assert len(xyz) == universe_dcd.trajectory.n_frames / 2 + + def test_reader_set_dt(): dt = 100 frame = 3 @@ -264,6 +275,46 @@ def test_single_frame(universe_dcd, tmpdir): err_msg="coordinates do not match") +def test_write_no_natoms(): + with pytest.raises(ValueError): + mda.Writer('foobar.dcd') + + +def test_writer_trajectory_no_natoms(tmpdir, universe_dcd): + with tmpdir.as_cwd(): + universe_dcd.trajectory.Writer("foo.dcd") + + +class RefCHARMMtriclinicDCD(object): + topology = PSF_TRICLINIC + trajectory = DCD_TRICLINIC + # time(ps) A B C alpha beta gamma (length in Angstrome, angles in degrees) + # dcd starts at t = 1ps + ref_dimensions = np.array([ + [1., 35.44604, 35.06156, 34.1585, 91.32802, 61.73521, 44.40703], + [2., 34.65957, 34.22689, 33.09897, 90.56206, 61.79192, 44.14549], + [3., 34.52772, 34.66422, 33.53881, 90.55859, 63.11228, 40.14044], + [4., 34.43749, 33.38432, 34.02133, 88.82457, 64.98057, 36.77397], + [5., 33.73129, 32.47752, 34.18961, 89.88102, 65.89032, 36.10921], + [6., 33.78703, 31.90317, 34.98833, 90.03092, 66.12877, 35.07141], + [7., 33.24708, 31.18271, 34.9654, 93.11122, 68.17743, 35.73643], + [8., 32.92599, 30.31393, 34.99197, 93.89051, 69.3799, 33.48945], + [9., 32.15295, 30.43056, 34.96157, 96.01416, 71.50115, 32.56111], + [10., 31.99748, 30.21518, 35.24292, 95.85821, 71.08429, 31.85939] + ]) + + +class RefNAMDtriclinicDCD(object): + topology = PSF_NAMD_TRICLINIC + trajectory = DCD_NAMD_TRICLINIC + # vmd topology trajectory + # molinfo 0 get {a b c alpha beta gamma} + # time(ps) A B C alpha beta gamma (length in Angstrome, angles in degrees) + ref_dimensions = np.array([ + [1., 38.426594, 38.393101, 44.759800, 90.000000, 90.000000, 60.028915], + ]) + + @pytest.mark.parametrize("ref", (RefCHARMMtriclinicDCD, RefNAMDtriclinicDCD)) def test_read_unitcell_triclinic(ref): u = mda.Universe(ref.topology, ref.trajectory) @@ -301,7 +352,8 @@ def ncdf2dcd(tmpdir_factory): return ncdf, mda.Universe(PRMncdf, testfile) -@pytest.mark.skipif(module_not_found("netCDF4")) +@pytest.mark.skipif(module_not_found("netCDF4"), + reason="netcdf4 module not installed") def test_ncdf2dcd_unitcell(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): @@ -310,7 +362,8 @@ def test_ncdf2dcd_unitcell(ncdf2dcd): 3) -@pytest.mark.skipif(module_not_found("netCDF4")) +@pytest.mark.skipif(module_not_found("netCDF4"), + reason="netcdf4 module not installed") def test_ncdf2dcd_coords(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): From ce6573bb28e1823d025427911b0752c5e43b0a61 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 26 Jun 2017 09:49:29 +0200 Subject: [PATCH 088/101] add libdcd to rst docs --- package/MDAnalysis/lib/formats/libdcd.pyx | 17 ++++++++++------- .../documentation_pages/lib/formats/libdcd.rst | 3 +++ .../source/documentation_pages/lib_modules.rst | 1 + 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 package/doc/sphinx/source/documentation_pages/lib/formats/libdcd.rst diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 657cf05aeeb..0a519b59409 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -290,6 +290,7 @@ cdef class DCDFile: close_dcd_read(self.freeind, self.fixedcoords); ok = fio_fclose(self.fp) + self.is_open = False if ok != 0: raise IOError("couldn't close file: {}\n" @@ -400,13 +401,15 @@ cdef class DCDFile: """ Returns ------- - dict of header values needed to write new dcd - natoms: number of atoms - istart: starting frame number - nsavc: number of frames between saves - delta: integrator time step. - charm: bitfield integer if file contains special CHARMM information - remarks: remark string, max 240 bytes. + dict of header values needed to write new dcd. + natoms: number of atoms + istart: starting frame number + nsavc: number of frames between saves + delta: integrator time step. + charm: bitfield integer if file contains special CHARMM information + remarks: remark string, max 240 bytes. + + """ return {'natoms': self.natoms, 'istart': self.istart, diff --git a/package/doc/sphinx/source/documentation_pages/lib/formats/libdcd.rst b/package/doc/sphinx/source/documentation_pages/lib/formats/libdcd.rst new file mode 100644 index 00000000000..aaf528c1d63 --- /dev/null +++ b/package/doc/sphinx/source/documentation_pages/lib/formats/libdcd.rst @@ -0,0 +1,3 @@ +.. automodule:: MDAnalysis.lib.formats.libdcd + :members: + :inherited-members: diff --git a/package/doc/sphinx/source/documentation_pages/lib_modules.rst b/package/doc/sphinx/source/documentation_pages/lib_modules.rst index 97368315bc3..5bbe3d041ed 100644 --- a/package/doc/sphinx/source/documentation_pages/lib_modules.rst +++ b/package/doc/sphinx/source/documentation_pages/lib_modules.rst @@ -65,3 +65,4 @@ Python-based projects. :maxdepth: 1 ./lib/formats/libmdaxdr + ./lib/formats/libdcd From ceb7313d516682d8e84911fedc5daa9f2755503a Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 26 Jun 2017 10:24:19 +0200 Subject: [PATCH 089/101] push test loop into pytest parametrize --- .../MDAnalysisTests/coordinates/test_dcd.py | 6 +- .../MDAnalysisTests/formats/test_libdcd.py | 102 +++++++++--------- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 8b70d8dde5c..9d3f2d01f85 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -37,7 +37,6 @@ from MDAnalysisTests.coordinates.base import (MultiframeReaderTest, BaseReference, BaseWriterTest) -from MDAnalysisTests import module_not_found import pytest @@ -343,6 +342,7 @@ def test_write_unitcell_triclinic(ref, tmpdir): @pytest.fixture(scope='module') def ncdf2dcd(tmpdir_factory): + pytest.importorskip("netCDF4") testfile = tmpdir_factory.mktemp('dcd').join('ncdf2dcd.dcd') testfile = str(testfile) ncdf = mda.Universe(PRMncdf, NCDF) @@ -352,8 +352,6 @@ def ncdf2dcd(tmpdir_factory): return ncdf, mda.Universe(PRMncdf, testfile) -@pytest.mark.skipif(module_not_found("netCDF4"), - reason="netcdf4 module not installed") def test_ncdf2dcd_unitcell(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): @@ -362,8 +360,6 @@ def test_ncdf2dcd_unitcell(ncdf2dcd): 3) -@pytest.mark.skipif(module_not_found("netCDF4"), - reason="netcdf4 module not installed") def test_ncdf2dcd_coords(ncdf2dcd): ncdf, dcd = ncdf2dcd for ts_ncdf, ts_dcd in zip(ncdf.trajectory, dcd.trajectory): diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index cfe8bf52f61..22ec9f0a520 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -70,8 +70,8 @@ def test_read_unit_cell(dcdfile, unit_cell): def test_seek_over_max(): - with pytest.raises(EOFError): - with DCDFile(DCD) as dcd: + with DCDFile(DCD) as dcd: + with pytest.raises(EOFError): dcd.seek(102) @@ -84,8 +84,8 @@ def test_seek_normal(new_frame): def test_seek_negative(): - with pytest.raises(IOError): - with DCDFile(DCD) as dcd: + with DCDFile(DCD) as dcd: + with pytest.raises(IOError): dcd.seek(-78) @@ -121,9 +121,9 @@ def test_natoms(dcdfile, natoms): def test_read_closed(): - with pytest.raises(IOError): - with DCDFile(DCD) as dcd: - dcd.close() + with DCDFile(DCD) as dcd: + dcd.close() + with pytest.raises(IOError): dcd.read() @@ -136,9 +136,9 @@ def test_length_traj(dcdfile, nframes): def test_read_write_mode_file(tmpdir): - with pytest.raises(IOError): - with tmpdir.as_cwd(): - with DCDFile('foo', 'w') as f: + with tmpdir.as_cwd(): + with DCDFile('foo', 'w') as f: + with pytest.raises(IOError): f.read() @@ -223,12 +223,9 @@ def test_write_header(tmpdir): def test_write_no_header(tmpdir): - # an IOError should be raised if we - # attempt to write inappropriate header - # data that looks like frame data - with pytest.raises(IOError): - with tmpdir.as_cwd(): - with DCDFile('test.dcd', 'w') as dcd: + with tmpdir.as_cwd(): + with DCDFile('test.dcd', 'w') as dcd: + with pytest.raises(IOError): dcd.write(np.ones(3), np.ones(6)) @@ -245,18 +242,18 @@ def test_write_header_twice(tmpdir): "charmm": 1 } - with pytest.raises(IOError): - with tmpdir.as_cwd(): - with DCDFile('test.dcd', 'w') as dcd: - dcd.write_header(**header) + with tmpdir.as_cwd(): + with DCDFile('test.dcd', 'w') as dcd: + dcd.write_header(**header) + with pytest.raises(IOError): dcd.write_header(**header) def test_write_header_wrong_mode(): # an exception should be raised on any attempt to use # _write_header with a DCDFile object in 'r' mode - with pytest.raises(IOError): - with DCDFile(DCD) as dcd: + with DCDFile(DCD) as dcd: + with pytest.raises(IOError): dcd.write_header( remarks='Crazy!', natoms=22, @@ -269,8 +266,8 @@ def test_write_header_wrong_mode(): def test_write_mode(): # ensure that writing of DCD files only occurs with properly # opened files - with pytest.raises(IOError): - with DCDFile(DCD) as dcd: + with DCDFile(DCD) as dcd: + with pytest.raises(IOError): dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) @@ -358,38 +355,39 @@ def test_written_unit_cell(written_dcd): assert_array_almost_equal(frame.unitcell, o_frame.unitcell) -def test_write_all_dtypes(tmpdir): +@pytest.mark.parametrize( + "dtype", (np.int32, np.int64, np.float32, np.float64, int, float)) +def test_write_all_dtypes(tmpdir, dtype): with tmpdir.as_cwd(): - for dtype in (np.int32, np.int64, np.float32, np.float64): - with DCDFile('foo.dcd', 'w') as out: - natoms = 10 - xyz = np.ones((natoms, 3), dtype=dtype) - box = np.ones(6, dtype=dtype) - out.write_header( - remarks='test', - natoms=natoms, - charmm=1, - delta=1, - nsavc=1, - istart=1) - out.write(xyz=xyz, box=box) + with DCDFile('foo.dcd', 'w') as out: + natoms = 10 + xyz = np.ones((natoms, 3), dtype=dtype) + box = np.ones(6, dtype=dtype) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) + out.write(xyz=xyz, box=box) -def test_write_array_like(tmpdir): +@pytest.mark.parametrize("array_like", (np.array, list)) +def test_write_array_like(tmpdir, array_like): with tmpdir.as_cwd(): - for array_like in (np.array, list): - with DCDFile('foo.dcd', 'w') as out: - natoms = 10 - xyz = array_like([[1, 1, 1] for i in range(natoms)]) - box = array_like([i for i in range(6)]) - out.write_header( - remarks='test', - natoms=natoms, - charmm=1, - delta=1, - nsavc=1, - istart=1) - out.write(xyz=xyz, box=box) + with DCDFile('foo.dcd', 'w') as out: + natoms = 10 + xyz = array_like([[1, 1, 1] for i in range(natoms)]) + box = array_like([i for i in range(6)]) + out.write_header( + remarks='test', + natoms=natoms, + charmm=1, + delta=1, + nsavc=1, + istart=1) + out.write(xyz=xyz, box=box) def test_write_wrong_shape_xyz(tmpdir): From 00970991637a6e0c356e4b742a3b54f268b00655 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 3 Jul 2017 22:08:02 +0200 Subject: [PATCH 090/101] update libdcd --- package/MDAnalysis/lib/formats/libdcd.pyx | 42 +++---------------- .../MDAnalysisTests/formats/test_libdcd.py | 9 ++-- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 0a519b59409..876e74f239b 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -45,6 +45,7 @@ Besides iteration one can also seek to arbitrary frames using the :meth:`~DCDFile.seek` method. """ +from six.moves import range from os import path @@ -150,11 +151,6 @@ cdef class DCDFile: >>> print(frame.x) - Raises - ------ - IOError, EOFError - - Notes ----- This DCDFile reader can process files written by different MD simulation @@ -249,9 +245,6 @@ cdef class DCDFile: mode : ('r', 'w') The mode in which to open the file, either 'r' read or 'w' write - Raises - ------ - IOError """ if self.is_open: self.close() @@ -279,9 +272,6 @@ cdef class DCDFile: def close(self): """Close the open DCD file - Raises - ------ - IOError """ if self.is_open: # In case there are fixed atoms we should free the memory again. @@ -362,7 +352,8 @@ cdef class DCDFile: """ Returns ------- - bool if periodic unitcell is available + bool + ``True`` if periodic unitcell is available """ return bool((self.charmm & DCD_IS_CHARMM) and (self.charmm & DCD_HAS_EXTRA_BLOCK)) @@ -375,11 +366,6 @@ cdef class DCDFile: frame : int seek the file to given frame - Raises - ------ - IOError - If you seek for more frames than are available or if the - seek fails (the low-level system error is reported). """ if frame >= self.n_frames: raise EOFError('Trying to seek over max number of frames') @@ -393,7 +379,7 @@ cdef class DCDFile: ok = fio_fseek(self.fp, offset, _whence_vals["FIO_SEEK_SET"]) if ok != 0: - raise IOError("DCD seek failed with system errno={}".format(DCD_ERRORS[ok])) + raise IOError("DCD seek failed with DCD error={}".format(DCD_ERRORS[ok])) self.current_frame = frame @property @@ -437,10 +423,6 @@ cdef class DCDFile: charmm : bool write unitcell information. Also pretends that file was written by CHARMM 24 - - Raises - ------ - IOError """ if not self.is_open: raise IOError("No file open") @@ -478,10 +460,6 @@ cdef class DCDFile: box : array_like, shape=(6) Box vectors for this frame - Raises - ------ - IOError - ValueError """ if not self.is_open: raise IOError("No file open") @@ -526,13 +504,9 @@ cdef class DCDFile: DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader for possible post processing into a common unitcell data structure. - Raises - ------ - IOError - StopIteration """ if self.reached_eof: - raise IOError('Reached last frame in DCD, seek to 0') + raise EOFError('Reached last frame in DCD, seek to 0') if not self.is_open: raise IOError("No file open") if self.mode != 'r': @@ -589,13 +563,9 @@ cdef class DCDFile: DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader for possible post processing into a common unitcell data structure. - Raises - ------ - IOError - ValueError """ if self.reached_eof: - raise IOError('Reached last frame in DCD, seek to 0') + raise EOFError('Reached last frame in DCD, seek to 0') if not self.is_open: raise IOError("No file open") if self.mode != 'r': diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 22ec9f0a520..de3243ad035 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -20,11 +20,12 @@ import os import string -import hypothesis.strategies as st +import hypothesis.strategies as strategies from hypothesis import example, given import numpy as np -from numpy.testing import (assert_array_almost_equal, assert_equal) +from numpy.testing import (assert_array_almost_equal, assert_equal, + assert_array_equal) from MDAnalysis.lib.formats.libdcd import DCDFile @@ -181,7 +182,7 @@ def test_read_coord_values(dcdfile, legacy_data, frames): dcd.seek(frame_num) actual_coords = dcd.read()[0] desired_coords = legacy[index] - assert_equal(actual_coords, desired_coords) + assert_array_equal(actual_coords, desired_coords) @pytest.mark.parametrize("dcdfile, legacy_data, frame_idx", @@ -280,7 +281,7 @@ def write_dcd(in_name, out_name, remarks='testing', header=None): f_out.write(xyz=frame.xyz, box=frame.unitcell) -@given(remarks=st.text( +@given(remarks=strategies.text( alphabet=string.printable, min_size=0, max_size=240)) # handle the printable ASCII strings @example(remarks='') From 015f15a736bba3b658df52a90b1dc3b7397470ca Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 3 Jul 2017 22:08:09 +0200 Subject: [PATCH 091/101] use assert_array_equal --- package/MDAnalysis/lib/formats/libdcd.pyx | 4 ++-- testsuite/MDAnalysisTests/coordinates/test_dcd.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 876e74f239b..de69fcd32f1 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -628,7 +628,7 @@ cdef class DCDFile: for i in range(n): ok = self.c_readframes_helper(xyz_tmp[:, 0], xyz_tmp[:, 1], xyz_tmp[:, 2], box[i], i==0) if ok != 0 and ok != -4: - raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + raise IOError("Reading DCD frames failed: {}".format(DCD_ERRORS[ok])) copy_in_order(xyz_tmp[c_indices], xyz, hash_order, i) else: counter = 0 @@ -636,7 +636,7 @@ cdef class DCDFile: self.seek(i) ok = self.c_readframes_helper(xyz_tmp[:, 0], xyz_tmp[:, 1], xyz_tmp[:, 2], box[counter], i==0) if ok != 0 and ok != -4: - raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + raise IOError("Reading DCD frames failed: {}".format(DCD_ERRORS[ok])) copy_in_order(xyz_tmp[c_indices], xyz, hash_order, counter) counter += 1 diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 9d3f2d01f85..940166bbc8a 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -138,7 +138,7 @@ def test_jump_last_frame(universe_dcd): (20, 5, -1))) def test_slice(universe_dcd, start, stop, step): frames = [ts.frame for ts in universe_dcd.trajectory[start:stop:step]] - assert frames == list(range(start, stop, step)) + assert_array_equal(frames, np.arange(start, stop, step)) @pytest.mark.parametrize("array_like", [list, np.array]) @@ -152,7 +152,7 @@ def test_array_like(universe_dcd, array_like): [0, 0, 1, 1, 2, 1, 1])) def test_list_indices(universe_dcd, indices): frames = [ts.frame for ts in universe_dcd.trajectory[indices]] - assert frames == indices + assert_array_equal(frames, indices) @pytest.mark.parametrize( From 619526c385f14d7dfd9e4f6e0891e4bc7c00cd00 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 3 Jul 2017 22:15:15 +0200 Subject: [PATCH 092/101] avoid copy in dcd writer --- package/MDAnalysis/coordinates/DCD.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 7957d493795..705c8cec8aa 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -408,11 +408,11 @@ def write_next_timestep(self, ts): The normal write() method takes a more general input """ xyz = ts.positions.copy() - dimensions = ts.dimensions + dimensions = ts.dimensions.copy() if self._convert_units: - xyz = self.convert_pos_to_native(xyz, inplace=False) - dimensions = self.convert_dimensions_to_unitcell(ts, inplace=False) + xyz = self.convert_pos_to_native(xyz, inplace=True) + dimensions = self.convert_dimensions_to_unitcell(ts, inplace=True) # we only support writing charmm format unit cell info # The DCD unitcell is written as ``[A, gamma, B, beta, alpha, C]`` From 2d2ce49e815808adcd7591cb4eee2fcf3a81cf0b Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 4 Jul 2017 09:49:56 +0200 Subject: [PATCH 093/101] modify changelog --- package/CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/CHANGELOG b/package/CHANGELOG index 03529f67b49..03289d0dc30 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -19,7 +19,7 @@ mm/dd/17 richardjgowers, rathann, orbeckst, tylerjereddy, mtiberti, kain88-de Enhancements * add low level lib.formats.libdcd module for reading/writing DCD (PR #1372) - * add Python 3 ready DCD reader (Issue #659) + * replace old DCD reader with a Python 3 ready DCD reader (Issue #659) Deprecations From 23e3d28e76722ed534416eb99a39c7fc9ec2befc Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 4 Jul 2017 09:50:01 +0200 Subject: [PATCH 094/101] refactor libdcd header API We now don't expose the CHARMM bitfield directly anymore in the header. The bitfield is a internal implementation detail of readdcd.h and the wrong value to expose to a user of libdcd. Rather we expose `is_periodic` to enable reading/writing box information. With this change the write/read header test symmetric now. --- package/MDAnalysis/coordinates/DCD.py | 2 +- package/MDAnalysis/lib/formats/libdcd.pyx | 91 +++++++++++-------- .../MDAnalysisTests/formats/test_libdcd.py | 25 +++-- 3 files changed, 66 insertions(+), 52 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 705c8cec8aa..80fa62d999c 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -392,7 +392,7 @@ def __init__(self, natoms=self.n_atoms, nsavc=nsavc, delta=float(dt), - charmm=1, + is_periodic=1, istart=0) def write_next_timestep(self, ts): diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index de69fcd32f1..4ff571f29b7 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -82,10 +82,9 @@ cdef enum: FIO_READ = 0x01 FIO_WRITE = 0x02 -cdef enum: - DCD_IS_CHARMM = 0x01 - DCD_HAS_4DIMS = 0x02 - DCD_HAS_EXTRA_BLOCK = 0x04 +DCD_IS_CHARMM = 0x01 +DCD_HAS_4DIMS = 0x02 +DCD_HAS_EXTRA_BLOCK = 0x04 DCD_ERRORS = { 0: 'Success', @@ -120,12 +119,12 @@ cdef extern from 'include/readdcd.h': double *unitcell, int num_fixed, int first, int *indexes, float *fixedcoords, int reverse_endian, int charmm) - int write_dcdheader(fio_fd fd, const char *remarks, int natoms, - int istart, int nsavc, double delta, int with_unitcell, + int write_dcdheader(fio_fd fd, const char *remarks, int natoms, + int istart, int nsavc, double delta, int with_unitcell, int charmm); - int write_dcdstep(fio_fd fd, int curframe, int curstep, - int natoms, const float *x, const float *y, const float *z, - const double *unitcell, int charmm); + int write_dcdstep(fio_fd fd, int curframe, int curstep, + int natoms, const float *x, const float *y, const float *z, + const double *unitcell, int charmm); DCDFrame = namedtuple('DCDFrame', 'xyz unitcell') @@ -177,6 +176,7 @@ cdef class DCDFile: cdef float *fixedcoords cdef int reverse_endian cdef int charmm + cdef readonly is_periodic cdef remarks cdef str mode cdef readonly int ndims @@ -303,6 +303,9 @@ cdef class DCDFile: if ok != 0: raise IOError("Reading DCD header failed: {}".format(DCD_ERRORS[ok])) + self.is_periodic = bool((self.charmm & DCD_IS_CHARMM) and + (self.charmm & DCD_HAS_EXTRA_BLOCK)) + if c_remarks != NULL: py_remarks = c_remarks[:len_remarks] free(c_remarks) @@ -347,17 +350,6 @@ cdef class DCDFile: nframessize = filesize - self._header_size - self._firstframesize return nframessize / self._framesize + 1 - @property - def is_periodic(self): - """ - Returns - ------- - bool - ``True`` if periodic unitcell is available - """ - return bool((self.charmm & DCD_IS_CHARMM) and - (self.charmm & DCD_HAS_EXTRA_BLOCK)) - def seek(self, frame): """Seek to Frame. @@ -401,12 +393,32 @@ cdef class DCDFile: 'istart': self.istart, 'nsavc': self.nsavc, 'delta': self.delta, - 'charmm': self.charmm, + 'is_periodic': self.is_periodic, 'remarks': self.remarks} - def write_header(self, remarks, natoms, istart, nsavc, delta, charmm): - """write DCD header. This function needs to be called before a frame can be - written. + @property + def charmm_bitfield(self): + """This DCDFile reader can process files written by different MD simulation + programs. For files produced by CHARMM or other programs that follow + the same convention we are reading a special CHARMM bitfield that + stores different flags about additional information that is stored in + the dcd. The bit flags are: + + .. code:: + + DCD_IS_CHARMM = 0x01 + DCD_HAS_4DIMS = 0x02 + DCD_HAS_EXTRA_BLOCK = 0x04 + + Here `DCD_HAS_EXTRA_BLOCK` means that unitcell information is stored. + + """ + return self.charmm + + def write_header(self, remarks, natoms, istart, nsavc, delta, is_periodic): + """Write DCD header + + This function needs to be called before the first frame can be written. Parameters ---------- @@ -420,9 +432,8 @@ cdef class DCDFile: number of frames between saves delta : float integrator time step. The time for 1 frame is nsavc * delta - charmm : bool + is_periodic : bool write unitcell information. Also pretends that file was written by CHARMM 24 - """ if not self.is_open: raise IOError("No file open") @@ -431,8 +442,10 @@ cdef class DCDFile: if self.wrote_header: raise IOError("Header already written") - cdef int len_remarks = 0 - cdef int with_unitcell = 1 + cdef int with_unitcell = is_periodic; + if is_periodic: + self.charmm = DCD_HAS_EXTRA_BLOCK | DCD_IS_CHARMM + self.natoms = natoms if isinstance(remarks, six.string_types): try: @@ -440,25 +453,22 @@ cdef class DCDFile: except UnicodeDecodeError: remarks = bytearray(remarks) - ok = write_dcdheader(self.fp, remarks, natoms, istart, + ok = write_dcdheader(self.fp, remarks, self.natoms, istart, nsavc, delta, with_unitcell, - charmm) + self.charmm) if ok != 0: raise IOError("Writing DCD header failed: {}".format(DCD_ERRORS[ok])) - - self.charmm = charmm - self.natoms = natoms self.wrote_header = True - def write(self, xyz, box): + def write(self, xyz, box=None): """write one frame into DCD file. Parameters ---------- xyz : array_like, shape=(natoms, 3) cartesion coordinates - box : array_like, shape=(6) - Box vectors for this frame + box : array_like, shape=(6) (optional) + Box vectors for this frame. Can be left to skip writing a unitcell """ if not self.is_open: @@ -466,8 +476,13 @@ cdef class DCDFile: if self.mode != 'w': raise IOError('File opened in mode: {}. Writing only allowed ' 'in mode "w"'.format('self.mode')) - if len(box) != 6: - raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + if (self.charmm & DCD_HAS_EXTRA_BLOCK): + if len(box) != 6: + raise ValueError("box size is wrong should be 6, got: {}".format(box.size)) + else: + # use a dummy box. It won't be written anyway in readdcd. + box = np.zeros(6) + if not self.wrote_header: raise IOError("write header first before frames can be written") xyz = np.asarray(xyz, order='F', dtype=FLOAT) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index de3243ad035..6036885b4ae 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -27,7 +27,7 @@ from numpy.testing import (assert_array_almost_equal, assert_equal, assert_array_equal) -from MDAnalysis.lib.formats.libdcd import DCDFile +from MDAnalysis.lib.formats.libdcd import DCDFile, DCD_IS_CHARMM, DCD_HAS_EXTRA_BLOCK from MDAnalysisTests.datafiles import ( DCD, DCD_NAMD_TRICLINIC, legacy_DCD_ADK_coords, legacy_DCD_NAMD_coords, @@ -211,14 +211,14 @@ def test_write_header(tmpdir): istart=12, nsavc=10, delta=0.02, - charmm=1) + is_periodic=1) with DCDFile(testfile) as dcd: header = dcd.header assert header['remarks'] == 'Crazy!' assert header['natoms'] == 22 assert header['istart'] == 12 - assert header['charmm'] == 5 + assert header['is_periodic'] == 1 assert header['nsavc'] == 10 assert np.allclose(header['delta'], .02) @@ -240,7 +240,7 @@ def test_write_header_twice(tmpdir): "istart": 12, "nsavc": 10, "delta": 0.02, - "charmm": 1 + "is_periodic": 1 } with tmpdir.as_cwd(): @@ -261,7 +261,7 @@ def test_write_header_wrong_mode(): istart=12, nsavc=10, delta=0.02, - charmm=1) + is_periodic=1) def test_write_mode(): @@ -315,10 +315,7 @@ def written_dcd(tmpdir_factory): def test_written_header(written_dcd): header = written_dcd.header with DCDFile(written_dcd.testfile) as dcd: - # need to pop charmm header for now. - header.pop('charmm') dcdheader = dcd.header - dcdheader.pop('charmm') assert dcdheader == header @@ -367,7 +364,7 @@ def test_write_all_dtypes(tmpdir, dtype): out.write_header( remarks='test', natoms=natoms, - charmm=1, + is_periodic=1, delta=1, nsavc=1, istart=1) @@ -384,7 +381,7 @@ def test_write_array_like(tmpdir, array_like): out.write_header( remarks='test', natoms=natoms, - charmm=1, + is_periodic=1, delta=1, nsavc=1, istart=1) @@ -400,7 +397,7 @@ def test_write_wrong_shape_xyz(tmpdir): out.write_header( remarks='test', natoms=natoms, - charmm=1, + is_periodic=1, delta=1, nsavc=1, istart=1) @@ -417,7 +414,7 @@ def test_write_wrong_shape_box(tmpdir): out.write_header( remarks='test', natoms=natoms, - charmm=1, + is_periodic=1, delta=1, nsavc=1, istart=1) @@ -511,7 +508,9 @@ def test_write_random_unitcell(tmpdir): high=80, size=(98, 6)).astype(np.float64) with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out: - f_out.write_header(**f_in.header) + header = f_in.header + header['is_periodic'] = True + f_out.write_header(**header) for index, frame in enumerate(f_in): f_out.write(xyz=frame.xyz, box=random_unitcells[index]) From 6f2001f7ecb0f4160e384cf2639a9b4bc4316922 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Tue, 4 Jul 2017 19:02:32 +0200 Subject: [PATCH 095/101] don't change dt when using nsavc for writing --- package/MDAnalysis/coordinates/DCD.py | 42 +++++++++++++++------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 80fa62d999c..aee083c29e9 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -82,10 +82,14 @@ class DCDReader(base.ReaderBase): it differently. Currently, MDAnalysis tries to guess the correct **format for the unitcell representation** but it can be wrong. **Check the unitcell dimensions**, especially for triclinic unitcells (see `Issue 187`_ and - :attr:`DCDReader.dimensions`). A second potential issue are the units of - time which are AKMA for the :class:`DCDReader` (following CHARMM) but ps - for NAMD. As a workaround one can employ the configurable - :class:`MDAnalysis.coordinates.LAMMPS.DCDReader` for NAMD trajectories. + :attr:`DCDReader.dimensions`). DCD trajectories produced by CHARMM and + NAMD( >2.5) record time in AKMA units. If other units have been recorded + (e.g., ps) then employ the configurable + :class:MDAnalysis.coordinates.LAMMPS.DCDReader and set the time unit as a + optional argument. You can find a list of units used in the DCD formats on + the MDAnalysis `wiki`_. + + .. _wiki: https://github.com/MDAnalysis/mdanalysis/wiki/FileFormats#dcd """ format = 'DCD' @@ -182,7 +186,7 @@ def Writer(self, filename, n_atoms=None, **kwargs): **kwargs) def _frame_to_ts(self, frame, ts): - """convert a dcd-frame to a mda TimeStep""" + """convert a dcd-frame to a :class:`TimeStep`""" ts.frame = self._frame ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() @@ -333,14 +337,14 @@ def timeseries(self, class DCDWriter(base.WriterBase): """DCD Writer class - The writer follows recent NAMD/VMD convention for the unitcell but still - writes AKMA time. The unitcell will be written as ``[A, gamma, B, beta, - alpha, C]`` + The writer follows recent NAMD/VMD convention for the unitcell (box lengths + in Å and angle-cosines, ``[A, cos(gamma), B, cos(beta), cos(alpha), C]``) + and writes positions in Å and time in AKMA time units. """ format = 'DCD' multiframe = True - flavor = 'CHARMM' + flavor = 'NAMD' units = {'time': 'AKMA', 'length': 'Angstrom'} def __init__(self, @@ -363,20 +367,19 @@ def __init__(self, step : int (optional) number of steps between frames to be written dt : float (optional) - use this time step in DCD. If ``None`` guess from first written - TimeStep + time between two frames. If ``None`` guess from first written + :class:`TimeStep` remarks : str (optional) remarks to be stored in DCD. Shouldn't be more then 240 characters nsavc : int (optional) - DCD usually saves ``dt`` as the integrator timestep and the - frequency of writes in the nsavc variable separately. Unless you - know what you are doing you don't need to touch this value. - If you plan to use the written DCD with another tool that depends - on this behavior you can adjust it with this variable. The DCD - reader will then interpret the timestep between frames as ``dt * - nsavc``. + DCD files can also store the number of integrator time steps that + correspond to the interval between two frames as nsavc (i.e., every + how many MD steps is a frame saved to the DCD). By default, this + number is just set to one and this should be sufficient for almost + all cases but if required, nsavc can be changed. **kwargs : dict General writer arguments + """ self.filename = filename self._convert_units = convert_units @@ -387,11 +390,12 @@ def __init__(self, self.step = step self.dt = dt dt = mdaunits.convert(dt, 'ps', self.units['time']) + delta = float(dt) / nsavc self._file.write_header( remarks=remarks, natoms=self.n_atoms, nsavc=nsavc, - delta=float(dt), + delta=delta, is_periodic=1, istart=0) From 32efca9169a23febbdb5b5c861572eec38d8edae Mon Sep 17 00:00:00 2001 From: Max Linke Date: Mon, 3 Jul 2017 22:15:04 +0200 Subject: [PATCH 096/101] improve docs for libdcd and DCD --- package/MDAnalysis/coordinates/DCD.py | 67 ++++++++++----------- package/MDAnalysis/lib/formats/libdcd.pyx | 73 +++++++++++++++-------- 2 files changed, 78 insertions(+), 62 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index aee083c29e9..745da3f049d 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -81,16 +81,37 @@ class DCDReader(base.ReaderBase): The DCD file format is not well defined. In particular, NAMD and CHARMM use it differently. Currently, MDAnalysis tries to guess the correct **format for the unitcell representation** but it can be wrong. **Check the unitcell - dimensions**, especially for triclinic unitcells (see `Issue 187`_ and - :attr:`DCDReader.dimensions`). DCD trajectories produced by CHARMM and - NAMD( >2.5) record time in AKMA units. If other units have been recorded - (e.g., ps) then employ the configurable + dimensions**, especially for triclinic unitcells (see `Issue 187`_). DCD + trajectories produced by CHARMM and NAMD( >2.5) record time in AKMA units. + If other units have been recorded (e.g., ps) then employ the configurable :class:MDAnalysis.coordinates.LAMMPS.DCDReader and set the time unit as a optional argument. You can find a list of units used in the DCD formats on the MDAnalysis `wiki`_. - .. _wiki: https://github.com/MDAnalysis/mdanalysis/wiki/FileFormats#dcd + MDAnalysis always uses ``(*A*, *B*, *C*, *alpha*, *beta*, *gamma*)`` to + represent the unit cell. Lengths *A*, *B*, *C* are in the MDAnalysis length + unit (Å), and angles are in degrees. + + The ordering of the angles in the unitcell is the same as in recent + versions of VMD's DCDplugin_ (2013), namely the `X-PLOR DCD format`_: The + original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` from the DCD + file. If any of these values are < 0 or if any of the angles are > 180 + degrees then it is assumed it is a new-style CHARMM unitcell (at least + since c36b2) in which box vectors were recorded. + + .. warning:: + The DCD format is not well defined. Check your unit cell + dimensions carefully, especially when using triclinic boxes. + Different software packages implement different conventions and + MDAnalysis is currently implementing the newer NAMD/VMD convention + and tries to guess the new CHARMM one. Old CHARMM trajectories might + give wrong unitcell values. For more details see `Issue 187`_. + + .. _`X-PLOR DCD format`: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/dcdplugin.html + .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 + .. _DCDplugin: http://www.ks.uiuc.edu/Research/vmd/plugins/doxygen/dcdplugin_8c-source.html#l00947 + .. _wiki: https://github.com/MDAnalysis/mdanalysis/wiki/FileFormats#dcd """ format = 'DCD' flavor = 'CHARMM' @@ -167,6 +188,7 @@ def _read_next_timestep(self, ts=None): raise IOError('trying to go over trajectory limit') if ts is None: # use a copy to avoid that ts always points to the same reference + # removing this breaks lammps reader ts = self.ts.copy() frame = self._file.read() self._frame += 1 @@ -191,7 +213,8 @@ def _frame_to_ts(self, frame, ts): ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() - unitcell = frame.unitcell + unitcell = frame.unitcell.copy() + pi_2 = np.pi / 2 if (-1.0 <= unitcell[1] <= 1.0) and (-1.0 <= unitcell[3] <= 1.0) and ( -1.0 <= unitcell[4] <= 1.0): @@ -238,35 +261,6 @@ def _frame_to_ts(self, frame, ts): @property def dimensions(self): """unitcell dimensions (*A*, *B*, *C*, *alpha*, *beta*, *gamma*) - - lengths *A*, *B*, *C* are in the MDAnalysis length unit (Å), and - angles are in degrees. - - The ordering of the angles in the unitcell is the same as in recent - versions of VMD's DCDplugin_ (2013), namely the `X-PLOR DCD format`_: - The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` from - the DCD file; If any of these values are < 0 or if any of the angles - are > 180 degrees then it is assumed it is a new-style CHARMM unitcell - (at least since c36b2) in which box vectors were recorded. - - - .. warning:: - The DCD format is not well defined. Check your unit cell - dimensions carefully, especially when using triclinic boxes. - Different software packages implement different conventions and - MDAnalysis is currently implementing the newer NAMD/VMD convention - and tries to guess the new CHARMM one. Old CHARMM trajectories might - give wrong unitcell values. For more details see `Issue 187`_. - - .. versionchanged:: 0.9.0 - Unitcell is now interpreted in the newer NAMD DCD format as ``[A, - gamma, B, beta, alpha, C]`` instead of the old MDAnalysis/CHARMM - ordering ``[A, alpha, B, beta, gamma, C]``. We attempt to detect the - new CHARMM DCD unitcell format (see `Issue 187`_ for a discussion). - - .. _`X-PLOR DCD format`: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/dcdplugin.html - .. _Issue 187: https://github.com/MDAnalysis/mdanalysis/issues/187 - .. _DCDplugin: http://www.ks.uiuc.edu/Research/vmd/plugins/doxygen/dcdplugin_8c-source.html#l00947 """ return self.ts.dimensions @@ -408,8 +402,7 @@ def write_next_timestep(self, ts): See Also -------- - .write(AtomGroup/Universe/TimeStep) - The normal write() method takes a more general input + :meth:`DCDWriter.write` takes a more general input """ xyz = ts.positions.copy() dimensions = ts.dimensions.copy() diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 4ff571f29b7..3d781828b97 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -42,7 +42,17 @@ coordinates (where the coordinates are stored in `x` attribute of the Besides iteration one can also seek to arbitrary frames using the -:meth:`~DCDFile.seek` method. +:meth:`~DCDFile.seek` method. Note that instead of seeking to a byte-offset as +for normal Python streams, the seek and tell method of DCDFile operate on +complete trajectory frames. + +.. rubric:: Acknowledgements + +:mod:`libdcd` contains and is originally based on DCD reading and writing code +from VMD's `molfile`_ plugin and `catdcd`_. + +.. _molfile: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/ +.. _catdcd: http://www.ks.uiuc.edu/Development/MDTools/catdcd/ """ from six.moves import range @@ -129,12 +139,20 @@ cdef extern from 'include/readdcd.h': DCDFrame = namedtuple('DCDFrame', 'xyz unitcell') cdef class DCDFile: - """File like wrapper for DCD files + """DCDFile(fname, mode='r') + + File like wrapper for DCD files This class can be similar to the normal file objects in python. The read() function will return a frame and all information in it instead of a single line. Additionally the context-manager protocol is supported as well. + DCDFile can read typical DCD files created by e.g., CHARMM, NAMD, or LAMMPS. It + reads raw data from the trajectory and hence interpretation of, for instance, + different unitcell conventions or time and length units, has to be handled in + higher level code. Reading and writing does not support fixed atoms or 4D + coordinates. + Parameters ---------- fname : str @@ -152,18 +170,16 @@ cdef class DCDFile: Notes ----- - This DCDFile reader can process files written by different MD simulation - programs. For files produced by CHARMM or other programs that follow the - same convention we are reading a special CHARMM bitfield that stores - different flags about additional information that is stored in the dcd. - This field cannot be written. The flags it might have are. - - DCD_IS_CHARMM = 0x01 - DCD_HAS_4DIMS = 0x02 - DCD_HAS_EXTRA_BLOCK = 0x04 - - Here `DCD_HAS_EXTRA_BLOCK` means that unitcell information is stored. - + DCD is not a well defined format. One consequence of this is that different + programs like CHARMM and NAMD are using different convention to store the + unitcell information. The :class:`DCDFile` will read the unitcell information + as is when available. Post processing depending on the program this DCD file + was written with is necessary. Have a look at the MDAnalysis DCD reader for + possible post processing into a common unitcell data structure. You can also + find more information how different programs store unitcell information in DCD + on the `mdawiki`_ . + + .. _mdawiki: https://github.com/MDAnalysis/mdanalysis/wiki/FileFormats#dcd """ cdef fio_fd fp cdef readonly fname @@ -231,12 +247,14 @@ cdef class DCDFile: """ Returns ------- - current frame + current frame (0-based) """ return self.current_frame def open(self, mode='r'): - """Open a DCD file + """open(mode='r') + + Open a DCD file If another DCD file is currently opened it will be closed @@ -351,12 +369,14 @@ cdef class DCDFile: return nframessize / self._framesize + 1 def seek(self, frame): - """Seek to Frame. + """seek(frame) + + Seek to Frame. Parameters ---------- frame : int - seek the file to given frame + seek the file to given frame (0-based) """ if frame >= self.n_frames: @@ -416,7 +436,8 @@ cdef class DCDFile: return self.charmm def write_header(self, remarks, natoms, istart, nsavc, delta, is_periodic): - """Write DCD header + """write_header(remarks, natoms, istart, nsavc, delta, is_periodic) + Write DCD header This function needs to be called before the first frame can be written. @@ -442,7 +463,7 @@ cdef class DCDFile: if self.wrote_header: raise IOError("Header already written") - cdef int with_unitcell = is_periodic; + cdef int with_unitcell = is_periodic if is_periodic: self.charmm = DCD_HAS_EXTRA_BLOCK | DCD_IS_CHARMM self.natoms = natoms @@ -461,7 +482,8 @@ cdef class DCDFile: self.wrote_header = True def write(self, xyz, box=None): - """write one frame into DCD file. + """write(xyz, box=None) + write one frame into DCD file. Parameters ---------- @@ -550,7 +572,7 @@ cdef class DCDFile: def readframes(self, start=None, stop=None, step=None, order='fac', indices=None): - """ + """readframes(start=None, stop=None, step=None, order='fac', indices=None) read multiple frames at once Parameters @@ -574,9 +596,10 @@ cdef class DCDFile: Notes ----- - unitcell is read as it from DCD. Post processing depending the program this - DCD file was written with is necessary. Have a look at the MDAnalysis DCD reader - for possible post processing into a common unitcell data structure. + unitcell is read as it from DCD. Post processing depending the program + this DCD file was written with is necessary. Have a look at the + MDAnalysis DCD reader for possible post processing into a common + unitcell data structure. """ if self.reached_eof: From 25bfb7c0b36e30b9b44834a623fe96299a71cd26 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 7 Jul 2017 09:44:45 +0200 Subject: [PATCH 097/101] improve unitcell decoding --- package/MDAnalysis/coordinates/DCD.py | 51 +++++++++++---------------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/package/MDAnalysis/coordinates/DCD.py b/package/MDAnalysis/coordinates/DCD.py index 745da3f049d..3175c756c6a 100644 --- a/package/MDAnalysis/coordinates/DCD.py +++ b/package/MDAnalysis/coordinates/DCD.py @@ -213,43 +213,34 @@ def _frame_to_ts(self, frame, ts): ts.time = ts.frame * self.ts.dt ts.data['step'] = self._file.tell() - unitcell = frame.unitcell.copy() - - pi_2 = np.pi / 2 - if (-1.0 <= unitcell[1] <= 1.0) and (-1.0 <= unitcell[3] <= 1.0) and ( - -1.0 <= unitcell[4] <= 1.0): - # This file was generated by Charmm, or by NAMD > 2.5, with the angle - # cosines of the periodic cell angles written to the DCD file. - # This formulation improves rounding behavior for orthogonal cells - # so that the angles end up at precisely 90 degrees, unlike acos(). - # (changed in MDAnalysis 0.9.0 to have NAMD ordering of the angles; - # see Issue 187) */ - alpha = 90.0 - np.arcsin(unitcell[4]) * 90.0 / pi_2 - beta = 90.0 - np.arcsin(unitcell[3]) * 90.0 / pi_2 - gamma = 90.0 - np.arcsin(unitcell[1]) * 90.0 / pi_2 - else: - # This file was likely generated by NAMD 2.5 and the periodic cell - # angles are specified in degrees rather than angle cosines. - alpha = unitcell[4] - beta = unitcell[3] - gamma = unitcell[1] - - unitcell[4] = alpha - unitcell[3] = beta - unitcell[1] = gamma - # The original unitcell is read as ``[A, gamma, B, beta, alpha, C]`` _ts_order = [0, 2, 5, 4, 3, 1] - uc = np.take(unitcell, _ts_order) + uc = np.take(frame.unitcell, _ts_order) + + pi_2 = np.pi / 2 + if (-1.0 <= uc[3] <= 1.0) and (-1.0 <= uc[4] <= 1.0) and ( + -1.0 <= uc[5] <= 1.0): + # This file was generated by Charmm, or by NAMD > 2.5, with the + # angle cosines of the periodic cell angles written to the DCD + # file. This formulation improves rounding behavior for orthogonal + # cells so that the angles end up at precisely 90 degrees, unlike + # acos(). (changed in MDAnalysis 0.9.0 to have NAMD ordering of the + # angles; see Issue 187) */ + uc[3] = 90.0 - np.arcsin(uc[3]) * 90.0 / pi_2 + uc[4] = 90.0 - np.arcsin(uc[4]) * 90.0 / pi_2 + uc[5] = 90.0 - np.arcsin(uc[5]) * 90.0 / pi_2 # heuristic sanity check: uc = A,B,C,alpha,beta,gamma - if np.any(uc < 0.) or np.any(uc[3:] > 180.): + elif np.any(uc < 0.) or np.any(uc[3:] > 180.): # might be new CHARMM: box matrix vectors - H = unitcell + H = frame.unitcell.copy() e1, e2, e3 = H[[0, 1, 3]], H[[1, 2, 4]], H[[3, 4, 5]] uc = triclinic_box(e1, e2, e3) - unitcell = uc + else: + # This file was likely generated by NAMD 2.5 and the periodic cell + # angles are specified in degrees rather than angle cosines. + pass - ts.dimensions = unitcell + ts.dimensions = uc ts.positions = frame.xyz if self.convert_units: From 6423abb30249630205915fb31a673646a49be184 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 7 Jul 2017 10:03:05 +0200 Subject: [PATCH 098/101] update test_libdcd with pytest style guides --- .../MDAnalysisTests/formats/test_libdcd.py | 339 +++++++++--------- 1 file changed, 165 insertions(+), 174 deletions(-) diff --git a/testsuite/MDAnalysisTests/formats/test_libdcd.py b/testsuite/MDAnalysisTests/formats/test_libdcd.py index 6036885b4ae..15d127871ff 100644 --- a/testsuite/MDAnalysisTests/formats/test_libdcd.py +++ b/testsuite/MDAnalysisTests/formats/test_libdcd.py @@ -76,26 +76,29 @@ def test_seek_over_max(): dcd.seek(102) +@pytest.fixture +def dcd(): + with DCDFile(DCD) as dcd: + yield dcd + + @pytest.mark.parametrize("new_frame", (10, 42, 21)) -def test_seek_normal(new_frame): +def test_seek_normal(new_frame, dcd): # frame seek within range is tested - with DCDFile(DCD) as dcd: - dcd.seek(new_frame) - assert dcd.tell() == new_frame + dcd.seek(new_frame) + assert dcd.tell() == new_frame -def test_seek_negative(): - with DCDFile(DCD) as dcd: - with pytest.raises(IOError): - dcd.seek(-78) +def test_seek_negative(dcd): + with pytest.raises(IOError): + dcd.seek(-78) -def test_iteration(): +def test_iteration(dcd): num_iters = 10 - with DCDFile(DCD) as dcd: - for _ in range(num_iters): - dcd.__next__() - assert dcd.tell() == num_iters + for _ in range(num_iters): + dcd.__next__() + assert dcd.tell() == num_iters def test_open_wrong_mode(): @@ -108,9 +111,8 @@ def test_raise_not_existing(): DCDFile('foo') -def test_zero_based_frames_counting(): - with DCDFile(DCD) as dcd: - assert dcd.tell() == 0 +def test_zero_based_frames_counting(dcd): + assert dcd.tell() == 0 @pytest.mark.parametrize("dcdfile, natoms", @@ -121,11 +123,10 @@ def test_natoms(dcdfile, natoms): assert dcd.header['natoms'] == natoms -def test_read_closed(): - with DCDFile(DCD) as dcd: - dcd.close() - with pytest.raises(IOError): - dcd.read() +def test_read_closed(dcd): + dcd.close() + with pytest.raises(IOError): + dcd.read() @pytest.mark.parametrize("dcdfile, nframes", @@ -137,20 +138,19 @@ def test_length_traj(dcdfile, nframes): def test_read_write_mode_file(tmpdir): - with tmpdir.as_cwd(): - with DCDFile('foo', 'w') as f: - with pytest.raises(IOError): - f.read() + fname = str(tmpdir.join('foo')) + with DCDFile(fname, 'w') as f: + with pytest.raises(IOError): + f.read() -def test_iterating_twice(): - with DCDFile(DCD) as dcd: - with dcd as f: - for i, _ in enumerate(f): - assert_equal(i + 1, f.tell()) - # second iteration should work from start again - for i, _ in enumerate(f): - assert_equal(i + 1, f.tell()) +def test_iterating_twice(dcd): + with dcd as f: + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) + # second iteration should work from start again + for i, _ in enumerate(f): + assert_equal(i + 1, f.tell()) DCD_HEADER = '''* DIMS ADK SEQUENCE FOR PORE PROGRAM * WRITTEN BY LIZ DENNING (6.2008) * DATE: 6/ 6/ 8 17:23:56 CREATED BY USER: denniej0 ''' @@ -202,32 +202,31 @@ def test_readframes(dcdfile, legacy_data, frame_idx): def test_write_header(tmpdir): # test that _write_header() can produce a very crude # header for a new / empty file - testfile = 'test.dcd' - with tmpdir.as_cwd(): - with DCDFile(testfile, 'w') as dcd: - dcd.write_header( - remarks='Crazy!', - natoms=22, - istart=12, - nsavc=10, - delta=0.02, - is_periodic=1) - - with DCDFile(testfile) as dcd: - header = dcd.header - assert header['remarks'] == 'Crazy!' - assert header['natoms'] == 22 - assert header['istart'] == 12 - assert header['is_periodic'] == 1 - assert header['nsavc'] == 10 - assert np.allclose(header['delta'], .02) + testfile = str(tmpdir.join('test.dcd')) + with DCDFile(testfile, 'w') as dcd: + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + is_periodic=1) + + with DCDFile(testfile) as dcd: + header = dcd.header + assert header['remarks'] == 'Crazy!' + assert header['natoms'] == 22 + assert header['istart'] == 12 + assert header['is_periodic'] == 1 + assert header['nsavc'] == 10 + assert np.allclose(header['delta'], .02) def test_write_no_header(tmpdir): - with tmpdir.as_cwd(): - with DCDFile('test.dcd', 'w') as dcd: - with pytest.raises(IOError): - dcd.write(np.ones(3), np.ones(6)) + fname = str(tmpdir.join('test.dcd')) + with DCDFile(fname, 'w') as dcd: + with pytest.raises(IOError): + dcd.write(np.ones(3), np.ones(6)) def test_write_header_twice(tmpdir): @@ -243,33 +242,31 @@ def test_write_header_twice(tmpdir): "is_periodic": 1 } - with tmpdir.as_cwd(): - with DCDFile('test.dcd', 'w') as dcd: + fname = str(tmpdir.join('test.dcd')) + with DCDFile(fname, 'w') as dcd: + dcd.write_header(**header) + with pytest.raises(IOError): dcd.write_header(**header) - with pytest.raises(IOError): - dcd.write_header(**header) -def test_write_header_wrong_mode(): +def test_write_header_wrong_mode(dcd): # an exception should be raised on any attempt to use - # _write_header with a DCDFile object in 'r' mode - with DCDFile(DCD) as dcd: - with pytest.raises(IOError): - dcd.write_header( - remarks='Crazy!', - natoms=22, - istart=12, - nsavc=10, - delta=0.02, - is_periodic=1) + # write_header with a DCDFile object in 'r' mode + with pytest.raises(IOError): + dcd.write_header( + remarks='Crazy!', + natoms=22, + istart=12, + nsavc=10, + delta=0.02, + is_periodic=1) -def test_write_mode(): +def test_write_mode(dcd): # ensure that writing of DCD files only occurs with properly # opened files - with DCDFile(DCD) as dcd: - with pytest.raises(IOError): - dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) + with pytest.raises(IOError): + dcd.write(xyz=np.zeros((3, 3)), box=np.zeros(6, dtype=np.float64)) def write_dcd(in_name, out_name, remarks='testing', header=None): @@ -285,19 +282,16 @@ def write_dcd(in_name, out_name, remarks='testing', header=None): alphabet=string.printable, min_size=0, max_size=240)) # handle the printable ASCII strings @example(remarks='') -def test_written_remarks_property(remarks, tmpdir): +def test_written_remarks_property(remarks, tmpdir, dcd): # property based testing for writing of a wide range of string # values to REMARKS field - testfile = 'test.dcd' - with DCDFile(DCD) as dcd: - header = dcd.header - - with tmpdir.as_cwd(): - header['remarks'] = remarks - write_dcd(DCD, testfile, header=header) - expected_remarks = remarks[:240] - with DCDFile(testfile) as f: - assert f.header['remarks'] == expected_remarks + testfile = str(tmpdir.join('test.dcd')) + header = dcd.header + header['remarks'] = remarks + write_dcd(DCD, testfile, header=header) + expected_remarks = remarks[:240] + with DCDFile(testfile) as f: + assert f.header['remarks'] == expected_remarks @pytest.fixture(scope='session') @@ -356,70 +350,70 @@ def test_written_unit_cell(written_dcd): @pytest.mark.parametrize( "dtype", (np.int32, np.int64, np.float32, np.float64, int, float)) def test_write_all_dtypes(tmpdir, dtype): - with tmpdir.as_cwd(): - with DCDFile('foo.dcd', 'w') as out: - natoms = 10 - xyz = np.ones((natoms, 3), dtype=dtype) - box = np.ones(6, dtype=dtype) - out.write_header( - remarks='test', - natoms=natoms, - is_periodic=1, - delta=1, - nsavc=1, - istart=1) - out.write(xyz=xyz, box=box) + fname = str(tmpdir.join('foo.dcd')) + with DCDFile(fname, 'w') as out: + natoms = 10 + xyz = np.ones((natoms, 3), dtype=dtype) + box = np.ones(6, dtype=dtype) + out.write_header( + remarks='test', + natoms=natoms, + is_periodic=1, + delta=1, + nsavc=1, + istart=1) + out.write(xyz=xyz, box=box) @pytest.mark.parametrize("array_like", (np.array, list)) def test_write_array_like(tmpdir, array_like): - with tmpdir.as_cwd(): - with DCDFile('foo.dcd', 'w') as out: - natoms = 10 - xyz = array_like([[1, 1, 1] for i in range(natoms)]) - box = array_like([i for i in range(6)]) - out.write_header( - remarks='test', - natoms=natoms, - is_periodic=1, - delta=1, - nsavc=1, - istart=1) - out.write(xyz=xyz, box=box) + fname = str(tmpdir.join('foo.dcd')) + with DCDFile(fname, 'w') as out: + natoms = 10 + xyz = array_like([[1, 1, 1] for i in range(natoms)]) + box = array_like([i for i in range(6)]) + out.write_header( + remarks='test', + natoms=natoms, + is_periodic=1, + delta=1, + nsavc=1, + istart=1) + out.write(xyz=xyz, box=box) def test_write_wrong_shape_xyz(tmpdir): - with tmpdir.as_cwd(): - with DCDFile("foo.dcd", 'w') as out: - natoms = 10 - xyz = np.ones((natoms + 1, 3)) - box = np.ones(6) - out.write_header( - remarks='test', - natoms=natoms, - is_periodic=1, - delta=1, - nsavc=1, - istart=1) - with pytest.raises(ValueError): - out.write(xyz=xyz, box=box) + fname = str(tmpdir.join('foo.dcd')) + with DCDFile(fname, 'w') as out: + natoms = 10 + xyz = np.ones((natoms + 1, 3)) + box = np.ones(6) + out.write_header( + remarks='test', + natoms=natoms, + is_periodic=1, + delta=1, + nsavc=1, + istart=1) + with pytest.raises(ValueError): + out.write(xyz=xyz, box=box) def test_write_wrong_shape_box(tmpdir): - with tmpdir.as_cwd(): - with DCDFile("foo.dcd", 'w') as out: - natoms = 10 - xyz = np.ones((natoms, 3)) - box = np.ones(7) - out.write_header( - remarks='test', - natoms=natoms, - is_periodic=1, - delta=1, - nsavc=1, - istart=1) - with pytest.raises(ValueError): - out.write(xyz=xyz, box=box) + fname = str(tmpdir.join('foo.dcd')) + with DCDFile(fname, 'w') as out: + natoms = 10 + xyz = np.ones((natoms, 3)) + box = np.ones(7) + out.write_header( + remarks='test', + natoms=natoms, + is_periodic=1, + delta=1, + nsavc=1, + istart=1) + with pytest.raises(ValueError): + out.write(xyz=xyz, box=box) @pytest.mark.parametrize("dcdfile", (DCD, DCD_TRICLINIC, DCD_NAMD_TRICLINIC)) @@ -466,14 +460,13 @@ def test_nframessize_int(dcdfile): ([1, 2, 1], 1), ([1, 2, 2], 1), ([1, 4, 2], 2), ([1, 4, 4], 1), ([0, 5, 5], 1), ([3, 5, 1], 2), ([4, 0, -1], 4), ([5, 0, -2], 3), ([5, 0, -4], 2)]) -def test_readframes_slices(slice, length): +def test_readframes_slices(slice, length, dcd): start, stop, step = slice - with DCDFile(DCD) as dcd: - allframes = dcd.readframes().xyz - frames = dcd.readframes(start=start, stop=stop, step=step) - xyz = frames.xyz - assert len(xyz) == length - assert_array_almost_equal(xyz, allframes[start:stop:step]) + allframes = dcd.readframes().xyz + frames = dcd.readframes(start=start, stop=stop, step=step) + xyz = frames.xyz + assert len(xyz) == length + assert_array_almost_equal(xyz, allframes[start:stop:step]) @pytest.mark.parametrize("order, shape", ( @@ -483,38 +476,36 @@ def test_readframes_slices(slice, length): ('acf', (3341, 3, 98)), ('caf', (3, 3341, 98)), ('cfa', (3, 98, 3341)), )) -def test_readframes_order(order, shape): - with DCDFile(DCD) as dcd: - x = dcd.readframes(order=order).xyz - assert x.shape == shape +def test_readframes_order(order, shape, dcd): + x = dcd.readframes(order=order).xyz + assert x.shape == shape @pytest.mark.parametrize("indices", [[1, 2, 3, 4], [5, 10, 15, 19], [9, 4, 2, 0, 50]]) -def test_readframes_atomindices(indices): - with DCDFile(DCD) as dcd: - allframes = dcd.readframes(order='afc').xyz - frames = dcd.readframes(indices=indices, order='afc') - xyz = frames.xyz - assert len(xyz) == len(indices) - assert_array_almost_equal(xyz, allframes[indices]) +def test_readframes_atomindices(indices, dcd): + allframes = dcd.readframes(order='afc').xyz + frames = dcd.readframes(indices=indices, order='afc') + xyz = frames.xyz + assert len(xyz) == len(indices) + assert_array_almost_equal(xyz, allframes[indices]) def test_write_random_unitcell(tmpdir): - with tmpdir.as_cwd(): - testname = 'test.dcd' - rstate = np.random.RandomState(1178083) - random_unitcells = rstate.uniform( - high=80, size=(98, 6)).astype(np.float64) + testname = str(tmpdir.join('test.dcd')) + testname = 'test.dcd' + rstate = np.random.RandomState(1178083) + random_unitcells = rstate.uniform( + high=80, size=(98, 6)).astype(np.float64) + + with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out: + header = f_in.header + header['is_periodic'] = True + f_out.write_header(**header) + for index, frame in enumerate(f_in): + f_out.write(xyz=frame.xyz, box=random_unitcells[index]) - with DCDFile(DCD) as f_in, DCDFile(testname, 'w') as f_out: - header = f_in.header - header['is_periodic'] = True - f_out.write_header(**header) - for index, frame in enumerate(f_in): - f_out.write(xyz=frame.xyz, box=random_unitcells[index]) - - with DCDFile(testname) as test: - for index, frame in enumerate(test): - assert_array_almost_equal(frame.unitcell, - random_unitcells[index]) + with DCDFile(testname) as test: + for index, frame in enumerate(test): + assert_array_almost_equal(frame.unitcell, + random_unitcells[index]) From 9bec7bdcab7ffb80d9adad9fd63ae51994d7df33 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 7 Jul 2017 10:05:45 +0200 Subject: [PATCH 099/101] use pytest style for test_dcd --- .../MDAnalysisTests/coordinates/test_dcd.py | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/testsuite/MDAnalysisTests/coordinates/test_dcd.py b/testsuite/MDAnalysisTests/coordinates/test_dcd.py index 940166bbc8a..550fa5a1e64 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_dcd.py +++ b/testsuite/MDAnalysisTests/coordinates/test_dcd.py @@ -90,23 +90,23 @@ def __init__(self, reference=None): def test_write_random_unitcell(tmpdir): - with tmpdir.as_cwd(): - testname = 'test.dcd' - rstate = np.random.RandomState(1178083) - random_unitcells = rstate.uniform( - high=80, size=(98, 6)).astype(np.float64) + testname = str(tmpdir.join('test.dcd')) + testname = 'test.dcd' + rstate = np.random.RandomState(1178083) + random_unitcells = rstate.uniform( + high=80, size=(98, 6)).astype(np.float64) - u = mda.Universe(PSF, DCD) - with mda.Writer(testname, n_atoms=u.atoms.n_atoms) as w: - for index, ts in enumerate(u.trajectory): - u.atoms.dimensions = random_unitcells[index] - w.write(u.atoms) + u = mda.Universe(PSF, DCD) + with mda.Writer(testname, n_atoms=u.atoms.n_atoms) as w: + for index, ts in enumerate(u.trajectory): + u.atoms.dimensions = random_unitcells[index] + w.write(u.atoms) - u2 = mda.Universe(PSF, testname) - for index, ts in enumerate(u2.trajectory): - assert_array_almost_equal(u2.trajectory.dimensions, - random_unitcells[index], - decimal=5) + u2 = mda.Universe(PSF, testname) + for index, ts in enumerate(u2.trajectory): + assert_array_almost_equal(u2.trajectory.dimensions, + random_unitcells[index], + decimal=5) ################ @@ -224,54 +224,51 @@ def test_writer_dt(tmpdir, ext, decimal): dt = 5.0 # set time step to 5 ps universe_dcd = mda.Universe(PSF, DCD, dt=dt) t = universe_dcd.trajectory - outfile = "test.{}".format(ext) - with tmpdir.as_cwd(): - with mda.Writer(outfile, n_atoms=t.n_atoms, dt=dt) as W: - for ts in universe_dcd.trajectory: - W.write(universe_dcd.atoms) + outfile = str(tmpdir.join("test.{}".format(ext))) + with mda.Writer(outfile, n_atoms=t.n_atoms, dt=dt) as W: + for ts in universe_dcd.trajectory: + W.write(universe_dcd.atoms) - uw = mda.Universe(PSF, outfile) - assert_almost_equal(uw.trajectory.totaltime, - (uw.trajectory.n_frames - 1) * dt, decimal) - times = np.array([uw.trajectory.time for ts in uw.trajectory]) - frames = np.array([ts.frame for ts in uw.trajectory]) - assert_array_almost_equal(times, frames * dt, decimal) + uw = mda.Universe(PSF, outfile) + assert_almost_equal(uw.trajectory.totaltime, + (uw.trajectory.n_frames - 1) * dt, decimal) + times = np.array([uw.trajectory.time for ts in uw.trajectory]) + frames = np.array([ts.frame for ts in uw.trajectory]) + assert_array_almost_equal(times, frames * dt, decimal) @pytest.mark.parametrize("ext, decimal", (("dcd", 5), ("xtc", 2))) def test_other_writer(universe_dcd, tmpdir, ext, decimal): t = universe_dcd.trajectory - outfile = "test.{}".format(ext) - with tmpdir.as_cwd(): - with t.OtherWriter(outfile) as W: - for ts in universe_dcd.trajectory: - W.write_next_timestep(ts) - - uw = mda.Universe(PSF, outfile) - # check that the coordinates are identical for each time step - for orig_ts, written_ts in zip(universe_dcd.trajectory, - uw.trajectory): - assert_array_almost_equal(written_ts.positions, orig_ts.positions, - decimal, - err_msg="coordinate mismatch between " - "original and written trajectory at " - "frame {} (orig) vs {} (written)".format( - orig_ts.frame, written_ts.frame)) + outfile = str(tmpdir.join("test.{}".format(ext))) + with t.OtherWriter(outfile) as W: + for ts in universe_dcd.trajectory: + W.write_next_timestep(ts) + + uw = mda.Universe(PSF, outfile) + # check that the coordinates are identical for each time step + for orig_ts, written_ts in zip(universe_dcd.trajectory, + uw.trajectory): + assert_array_almost_equal(written_ts.positions, orig_ts.positions, + decimal, + err_msg="coordinate mismatch between " + "original and written trajectory at " + "frame {} (orig) vs {} (written)".format( + orig_ts.frame, written_ts.frame)) def test_single_frame(universe_dcd, tmpdir): u = universe_dcd - outfile = "test.dcd" - with tmpdir.as_cwd(): - with mda.Writer(outfile, u.atoms.n_atoms) as W: - W.write(u.atoms) - w = mda.Universe(PSF, outfile) - assert w.trajectory.n_frames == 1 - assert_almost_equal(w.atoms.positions, - u.atoms.positions, - 3, - err_msg="coordinates do not match") + outfile = str(tmpdir.join("test.dcd")) + with mda.Writer(outfile, u.atoms.n_atoms) as W: + W.write(u.atoms) + w = mda.Universe(PSF, outfile) + assert w.trajectory.n_frames == 1 + assert_almost_equal(w.atoms.positions, + u.atoms.positions, + 3, + err_msg="coordinates do not match") def test_write_no_natoms(): From 8cbac38757846443716ceafa97425005842c64a2 Mon Sep 17 00:00:00 2001 From: Max Linke Date: Fri, 7 Jul 2017 22:51:53 +0200 Subject: [PATCH 100/101] update license header --- package/MDAnalysis/lib/formats/libdcd.pyx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/package/MDAnalysis/lib/formats/libdcd.pyx b/package/MDAnalysis/lib/formats/libdcd.pyx index 3d781828b97..d080d8df6b6 100644 --- a/package/MDAnalysis/lib/formats/libdcd.pyx +++ b/package/MDAnalysis/lib/formats/libdcd.pyx @@ -1,14 +1,20 @@ # -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*- -# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 +# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8 # -# MDAnalysis --- http://www.MDAnalysis.org -# Copyright (c) 2006-2015 Naveen Michaud-Agrawal, Elizabeth J. Denning, Oliver -# Beckstein and contributors (see AUTHORS for the full list) +# MDAnalysis --- http://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 GNU Public Licence, v2 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. +# # 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 From 42c71057601f5221904b2fcc2a7f62725fb0b66e Mon Sep 17 00:00:00 2001 From: Oliver Beckstein Date: Sat, 8 Jul 2017 02:04:08 -0700 Subject: [PATCH 101/101] install hypothesis for tests with setup.py (Also added pytest-{cov,xdist} which are not absolutely required but very handy.) --- package/setup.py | 5 ++++- testsuite/setup.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package/setup.py b/package/setup.py index e1004ea40ed..54c10977236 100755 --- a/package/setup.py +++ b/package/setup.py @@ -524,8 +524,11 @@ def dynamic_author_list(): }, test_suite="MDAnalysisTests", tests_require=[ - 'pytest>=3.1.2', 'nose>=1.3.7', + 'pytest>=3.1.2', + 'pytest-cov', + 'pytest-xdist', + 'hypothesis', 'MDAnalysisTests=={0}'.format(RELEASE), # same as this release! ], zip_safe=False, # as a zipped egg the *.so files are not found (at diff --git a/testsuite/setup.py b/testsuite/setup.py index 006e0a4c3f3..a61410bc470 100755 --- a/testsuite/setup.py +++ b/testsuite/setup.py @@ -214,8 +214,11 @@ def dynamic_author_list(): install_requires=[ 'MDAnalysis=={0!s}'.format(RELEASE), # same as this release! 'numpy>=1.10.4', - 'pytest>=3.1.2', 'nose>=1.3.7', + 'pytest>=3.1.2', + 'pytest-cov', + 'pytest-xdist', + 'hypothesis', 'psutil>=4.0.2', 'mock>=2.0.0', ],