mstk 0.3.3.dev11__tar.gz → 0.3.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. {mstk-0.3.3.dev11 → mstk-0.3.4}/PKG-INFO +9 -1
  2. {mstk-0.3.3.dev11 → mstk-0.3.4}/README.md +8 -0
  3. mstk-0.3.4/mstk/errors.py +14 -0
  4. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/typer/zft.py +1 -1
  5. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/statedatareporter.py +17 -5
  6. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/utils.py +9 -4
  7. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/scheduler/slurm.py +2 -2
  8. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/ommexporter.py +10 -7
  9. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/connectivity.py +35 -4
  10. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/geometry.py +30 -4
  11. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/pdb.py +1 -1
  12. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/molecule.py +51 -17
  13. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/trajectory.py +11 -4
  14. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/utils/__init__.py +9 -0
  15. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk.egg-info/PKG-INFO +9 -1
  16. mstk-0.3.4/scripts/analyze-rdf.py +136 -0
  17. mstk-0.3.4/scripts/logplot.py +212 -0
  18. mstk-0.3.4/scripts/topconv.py +87 -0
  19. mstk-0.3.4/scripts/trjconv.py +87 -0
  20. mstk-0.3.4/scripts/wham-pp.py +115 -0
  21. {mstk-0.3.3.dev11 → mstk-0.3.4}/setup.py +1 -1
  22. mstk-0.3.3.dev11/mstk/errors.py +0 -30
  23. mstk-0.3.3.dev11/scripts/analyze-rdf.py +0 -129
  24. mstk-0.3.3.dev11/scripts/logplot.py +0 -156
  25. mstk-0.3.3.dev11/scripts/topconv.py +0 -65
  26. mstk-0.3.3.dev11/scripts/trjconv.py +0 -66
  27. mstk-0.3.3.dev11/scripts/wham-pp.py +0 -108
  28. {mstk-0.3.3.dev11 → mstk-0.3.4}/LICENSE +0 -0
  29. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/__init__.py +0 -0
  30. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/__init__.py +0 -0
  31. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/canny.py +0 -0
  32. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/energy_kernels.py +0 -0
  33. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/ewald.py +0 -0
  34. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/fitting.py +0 -0
  35. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/neighborlist.py +0 -0
  36. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/series.py +0 -0
  37. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/structure.py +0 -0
  38. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/analyzer/vle.py +0 -0
  39. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/chem/__init__.py +0 -0
  40. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/chem/constant.py +0 -0
  41. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/chem/element.py +0 -0
  42. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/chem/formula.py +0 -0
  43. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/chem/rdkit.py +0 -0
  44. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/data/forcefield/SPICA_v1.zfp +0 -0
  45. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/data/forcefield/primitive.zff +0 -0
  46. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/data/forcefield/primitive.zft +0 -0
  47. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/__init__.py +0 -0
  48. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/dff_utils.py +0 -0
  49. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/errors.py +0 -0
  50. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/ffterm.py +0 -0
  51. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/forcefield.py +0 -0
  52. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/io/__init__.py +0 -0
  53. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/io/padua.py +0 -0
  54. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/io/ppf.py +0 -0
  55. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/io/zff.py +0 -0
  56. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/io/zfp.py +0 -0
  57. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/typer/__init__.py +0 -0
  58. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/forcefield/typer/typer.py +0 -0
  59. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/misc/__init__.py +0 -0
  60. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/misc/docmeta.py +0 -0
  61. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/misc/singleton.py +0 -0
  62. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/__init__.py +0 -0
  63. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/force.py +0 -0
  64. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/grofile.py +0 -0
  65. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/__init__.py +0 -0
  66. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/checkpointreporter.py +0 -0
  67. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/drudetemperaturereporter.py +0 -0
  68. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/groreporter.py +0 -0
  69. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/reporter/viscosityreporter.py +0 -0
  70. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/ommhelper/unit.py +0 -0
  71. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/scheduler/__init__.py +0 -0
  72. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/scheduler/pbsjob.py +0 -0
  73. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/scheduler/remote_slurm.py +0 -0
  74. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/scheduler/scheduler.py +0 -0
  75. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/__init__.py +0 -0
  76. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/gmxexporter.py +0 -0
  77. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/lmpexporter.py +0 -0
  78. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/namdexporter.py +0 -0
  79. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/simsys/system.py +0 -0
  80. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/__init__.py +0 -0
  81. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/atom.py +0 -0
  82. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/__init__.py +0 -0
  83. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/lammps.py +0 -0
  84. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/msd.py +0 -0
  85. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/psf.py +0 -0
  86. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/smi.py +0 -0
  87. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/xyz.py +0 -0
  88. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/io/zmat.py +0 -0
  89. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/residue.py +0 -0
  90. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/topology.py +0 -0
  91. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/unitcell.py +0 -0
  92. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/topology/virtualsite.py +0 -0
  93. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/__init__.py +0 -0
  94. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/frame.py +0 -0
  95. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/handler.py +0 -0
  96. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/__init__.py +0 -0
  97. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/combined_trj.py +0 -0
  98. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/dcd.py +0 -0
  99. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/gro.py +0 -0
  100. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/lammps.py +0 -0
  101. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/xtc.py +0 -0
  102. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/trajectory/io/xyz.py +0 -0
  103. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/wrapper/__init__.py +0 -0
  104. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/wrapper/gauss.py +0 -0
  105. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/wrapper/gmx.py +0 -0
  106. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/wrapper/packmol.py +0 -0
  107. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk/wrapper/panedr.py +0 -0
  108. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk.egg-info/SOURCES.txt +0 -0
  109. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk.egg-info/dependency_links.txt +0 -0
  110. {mstk-0.3.3.dev11 → mstk-0.3.4}/mstk.egg-info/top_level.txt +0 -0
  111. {mstk-0.3.3.dev11 → mstk-0.3.4}/scripts/ffconv.py +0 -0
  112. {mstk-0.3.3.dev11 → mstk-0.3.4}/scripts/quick-sim.py +0 -0
  113. {mstk-0.3.3.dev11 → mstk-0.3.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mstk
3
- Version: 0.3.3.dev11
3
+ Version: 0.3.4
4
4
  Summary: Molecular simulation toolkit
5
5
  Home-page: https://github.com/z-gong/mstk
6
6
  Author: Zheng Gong
@@ -69,3 +69,11 @@ https://mstk.readthedocs.io/en/latest/index.html
69
69
 
70
70
  - [ ] Take bond order into consideration for force field assignment
71
71
  - [ ] Re-organize algorithms scattered in topology and analyzer modules
72
+
73
+ ## Known issue
74
+ `mstk` use `chemfiles` to write `XTC` trajectory. However, lastest `chemfiles` is buggy under WSL for writing binary trajectory format.
75
+ If you are working under WSL, please install `chemfiles 0.10.2`.
76
+
77
+ ```
78
+ conda install chemfiles-lib=0.10.2 chemfiles-python=0.10.2
79
+ ```
@@ -56,3 +56,11 @@ https://mstk.readthedocs.io/en/latest/index.html
56
56
 
57
57
  - [ ] Take bond order into consideration for force field assignment
58
58
  - [ ] Re-organize algorithms scattered in topology and analyzer modules
59
+
60
+ ## Known issue
61
+ `mstk` use `chemfiles` to write `XTC` trajectory. However, lastest `chemfiles` is buggy under WSL for writing binary trajectory format.
62
+ If you are working under WSL, please install `chemfiles 0.10.2`.
63
+
64
+ ```
65
+ conda install chemfiles-lib=0.10.2 chemfiles-python=0.10.2
66
+ ```
@@ -0,0 +1,14 @@
1
+ class PackmolError(Exception):
2
+ pass
3
+
4
+
5
+ class GmxError(Exception):
6
+ pass
7
+
8
+
9
+ class RDKitError(Exception):
10
+ pass
11
+
12
+
13
+ class SchedulerError(Exception):
14
+ pass
@@ -98,7 +98,7 @@ class ZftTyper(Typer):
98
98
 
99
99
  Notes
100
100
  -----
101
- * SMARTS is parsed by using OpenBabel package. Therefore `pybel` module should be installed.
101
+ * SMARTS is parsed by using RDKit package. Make sure it is installed.
102
102
  * In type definition file, empty lines are ignored, and comments should start with ##.
103
103
 
104
104
  '''
@@ -122,7 +122,7 @@ class StateDataReporter(object):
122
122
  '''
123
123
 
124
124
  def __init__(self, file, reportInterval, step=True, time=False, potentialEnergy=True,
125
- kineticEnergy=False, totalEnergy=False, temperature=True, volume=False, box=True,
125
+ kineticEnergy=False, totalEnergy=False, temperature=True, volume=True, box=True,
126
126
  density=True, progress=False, remainingTime=False, speed=True, elapsedTime=False,
127
127
  separator='\t', systemMass=None, totalSteps=None, append=False,
128
128
  cvs=None, pressure=True, pxx=False, pyy=False, pzz=False, extra={}):
@@ -314,8 +314,7 @@ class StateDataReporter(object):
314
314
 
315
315
  bool_press = [self._pxx, self._pyy, self._pzz]
316
316
  if any(bool_press):
317
- indexes = [i for i in range(3) if bool_press[i]]
318
- values.extend(self._compute_anisotropic_pressure(simulation.context, state, indexes))
317
+ values.extend(self._compute_anisotropic_pressure(simulation.context, state, *bool_press))
319
318
 
320
319
  if self._extra:
321
320
  values.extend(self._extra.values())
@@ -467,9 +466,20 @@ class StateDataReporter(object):
467
466
 
468
467
  return p_kinetic + p_virial
469
468
 
470
- def _compute_anisotropic_pressure(self, context: mm.Context, state: mm.State, indexes: [bool]):
469
+ def _compute_anisotropic_pressure(self, context, state, pxx, pyy, pzz):
471
470
  '''
472
471
  Compute the anisotropic pressure of a rectangular system
472
+
473
+ Parameters
474
+ ----------
475
+ context : mm.Context
476
+ state : mm.State
477
+ pxx : bool
478
+ Whether or not the Pxx be calculated
479
+ pyy : bool
480
+ Whether or not the Pyy be calculated
481
+ pzz : bool
482
+ Whether or not the Pzz be calculated
473
483
  '''
474
484
  box = state.getPeriodicBoxVectors(asNumpy=True)
475
485
  positions = state.getPositions(asNumpy=True)
@@ -485,7 +495,9 @@ class StateDataReporter(object):
485
495
 
486
496
  scale = 0.0001
487
497
  pressures = []
488
- for index in indexes:
498
+ for index, _bool in enumerate([pxx, pyy, pzz]):
499
+ if not _bool:
500
+ continue
489
501
  scale_array = np.array([1.0, 1.0, 1.0])
490
502
  scale_array[index] = 1 + scale
491
503
 
@@ -1,7 +1,6 @@
1
- import openmm
2
- import openmm.openmm as mm
3
1
  import numpy as np
4
- from openmm import app
2
+ import openmm
3
+ from openmm import openmm as mm, app
5
4
  from .grofile import GroFile
6
5
  from .unit import kelvin, bar, nm, ps
7
6
 
@@ -68,6 +67,12 @@ def apply_mc_barostat(system, pcoupl, P, T, nsteps=100, logger=None):
68
67
  '''
69
68
  Add a MonteCarlo barostat to the system and return the index of the added barostat
70
69
 
70
+ Parameters
71
+ ----------
72
+ system : mm.System
73
+ pcoupl : str
74
+ The type of barostat. Can be one of the following: iso, semi-iso, aniso, xy, z.
75
+
71
76
  Returns
72
77
  -------
73
78
  idx : index of the added barostat force
@@ -80,7 +85,7 @@ def apply_mc_barostat(system, pcoupl, P, T, nsteps=100, logger=None):
80
85
  force = mm.MonteCarloMembraneBarostat(P * bar, 0 * bar * nm, T * kelvin,
81
86
  mm.MonteCarloMembraneBarostat.XYIsotropic,
82
87
  mm.MonteCarloMembraneBarostat.ZFree, nsteps)
83
- elif pcoupl == 'xyz':
88
+ elif pcoupl == 'aniso':
84
89
  msg = 'Anisotropic barostat'
85
90
  force = mm.MonteCarloAnisotropicBarostat([P * bar] * 3, T * kelvin, True, True, True, nsteps)
86
91
  elif pcoupl == 'xy':
@@ -109,8 +109,8 @@ class Slurm(Scheduler):
109
109
  workdir = Path(workdir).absolute().as_posix()
110
110
  sh = sh or self.sh
111
111
  sh_basename = Path(sh).stem
112
- out = os.path.join(workdir, sh_basename + '.out')
113
- err = os.path.join(workdir, sh_basename + '.err')
112
+ out = sh_basename + '.out'
113
+ err = sh_basename + '.err'
114
114
  node_cmd = f'#SBATCH --nodes={self.n_node}\n' if self.n_node > 0 else ''
115
115
  gpu_cmd = f'#SBATCH --gres=gpu:{self.n_gpu}\n' if self.n_gpu > 0 else ''
116
116
  dep_cmd = f'#SBATCH --dependency=afterok:{id_prior}\n' if id_prior is not None else '' # id_prior can be 0
@@ -143,26 +143,29 @@ class OpenMMExporter:
143
143
  elif angle_class == SDKAngleTerm:
144
144
  logger.debug('Setting up SDK angles...')
145
145
  aforce = mm.CustomCompoundBondForce(
146
- 3, 'k*(theta-theta0)^2+step(rmin-r)*LJ96;'
147
- 'LJ96=6.75*epsilon*((sigma/r)^9-(sigma/r)^6)+epsilon;'
146
+ 3, 'k*(theta-theta0)^2+step(rmin-r)*LJ;'
147
+ 'LJ=C*epsilon*((sigma/r)^n-(sigma/r)^m)+epsilon;'
148
148
  'theta=angle(p1,p2,p3);'
149
149
  'r=distance(p1,p3);'
150
- 'rmin=1.144714*sigma')
150
+ 'C=n/(n-m)*(n/m)^(m/(n-m));'
151
+ 'rmin=(n/m)^(1/(n-m))*sigma')
151
152
  aforce.addPerBondParameter('theta0')
152
153
  aforce.addPerBondParameter('k')
153
154
  aforce.addPerBondParameter('epsilon')
154
155
  aforce.addPerBondParameter('sigma')
156
+ aforce.addPerBondParameter('n')
157
+ aforce.addPerBondParameter('m')
155
158
  for angle in top.angles:
156
159
  if angle in system.constrain_angles:
157
160
  continue
158
161
  aterm = system.angle_terms[angle]
159
162
  if type(aterm) != SDKAngleTerm:
160
163
  continue
161
- vdw = ff.get_vdw_term(ff.atom_types[angle.atom1.type], ff.atom_types[angle.atom2.type])
162
- if type(vdw) != MieTerm or vdw.repulsion != 9 or vdw.attraction != 6:
163
- raise Exception(f'Corresponding 9-6 MieTerm for {aterm} not found in FF')
164
+ vdw = ff.get_vdw_term(ff.atom_types[angle.atom1.type], ff.atom_types[angle.atom3.type])
165
+ if type(vdw) != MieTerm:
166
+ raise Exception(f'Corresponding MieTerm for {aterm} not found in FF')
164
167
  aforce.addBond([angle.atom1.id, angle.atom2.id, angle.atom3.id],
165
- [aterm.theta, aterm.k, vdw.epsilon, vdw.sigma])
168
+ [aterm.theta, aterm.k, vdw.epsilon, vdw.sigma, vdw.repulsion, vdw.attraction])
166
169
  elif angle_class == LinearAngleTerm:
167
170
  logger.debug('Setting up linear angles...')
168
171
  aforce = mm.CustomCompoundBondForce(
@@ -1,5 +1,6 @@
1
1
  import numpy as np
2
2
  from .atom import Atom
3
+ from .geometry import periodic_distance, periodic_angle, periodic_dihedral
3
4
 
4
5
  __all__ = [
5
6
  'Bond',
@@ -115,14 +116,21 @@ class Bond():
115
116
  '''
116
117
  return self.atom1.is_drude or self.atom2.is_drude
117
118
 
118
- def evaluate(self):
119
+ def evaluate(self, cell=None):
119
120
  '''
120
121
  Evaluate the length of this bond
121
122
 
123
+ Parameters
124
+ ----------
125
+ box : UnitCell, Optional
126
+
122
127
  Returns
123
128
  -------
124
129
  value : float
125
130
  '''
131
+ if cell:
132
+ return periodic_distance(self.atom1.position, self.atom2.position, cell.size)
133
+
126
134
  delta = self.atom2.position - self.atom1.position
127
135
  return float(np.sqrt(delta.dot(delta)))
128
136
 
@@ -225,14 +233,21 @@ class Angle():
225
233
 
226
234
  return bond12, bond23
227
235
 
228
- def evaluate(self):
236
+ def evaluate(self, cell=None):
229
237
  '''
230
238
  Evaluate the value of this angle in unit of radian
231
239
 
240
+ Parameters
241
+ ----------
242
+ cell : UnitCell, Optional
243
+
232
244
  Returns
233
245
  -------
234
246
  value : float
235
247
  '''
248
+ if cell:
249
+ return periodic_angle(self.atom1.position, self.atom2.position, self.atom3.position, cell)
250
+
236
251
  vec1 = self.atom1.position - self.atom2.position
237
252
  vec2 = self.atom3.position - self.atom2.position
238
253
  cos = vec1.dot(vec2) / np.sqrt(vec1.dot(vec1) * vec2.dot(vec2))
@@ -361,14 +376,22 @@ class Dihedral():
361
376
 
362
377
  return angle123, angle234
363
378
 
364
- def evaluate(self):
379
+ def evaluate(self, cell=None):
365
380
  '''
366
381
  Evaluate the value of this dihedral in unit of radian
367
382
 
383
+ Parameters
384
+ ----------
385
+ cell : UnitCell, Optional
386
+
368
387
  Returns
369
388
  -------
370
389
  value : float
371
390
  '''
391
+ if cell:
392
+ return periodic_dihedral(self.atom1.position, self.atom2.position, self.atom3.position, self.atom4.position,
393
+ cell.size)
394
+
372
395
  vec1 = self.atom2.position - self.atom1.position
373
396
  vec2 = self.atom3.position - self.atom2.position
374
397
  vec3 = self.atom4.position - self.atom3.position
@@ -450,15 +473,23 @@ class Improper():
450
473
  '''
451
474
  return self.atom1, self.atom2, self.atom3, self.atom4
452
475
 
453
- def evaluate(self):
476
+ def evaluate(self, cell=None):
454
477
  '''
455
478
  Evaluate the value of this improper torsion in unit of radian.
456
479
  The improper is defined as the angle between plane a1-a2-a3 and a2-a3-a4.
457
480
 
481
+ Parameters
482
+ ----------
483
+ cell : UnitCell, Optional
484
+
458
485
  Returns
459
486
  -------
460
487
  value : float
461
488
  '''
489
+ if cell:
490
+ return periodic_dihedral(self.atom1.position, self.atom2.position, self.atom3.position, self.atom4.position,
491
+ cell.size)
492
+
462
493
  vec1 = self.atom2.position - self.atom1.position
463
494
  vec2 = self.atom3.position - self.atom2.position
464
495
  vec3 = self.atom4.position - self.atom3.position
@@ -62,6 +62,22 @@ def grow_particle(pos1, pos2, bond, angle):
62
62
  return pos2 + w * bond
63
63
 
64
64
 
65
+ def relocate_hydrogen(hydrogen):
66
+ '''
67
+ Update the position of a hydrogen atom according to the parent atom and other neighbors of parent
68
+
69
+ Parameters
70
+ ----------
71
+ hydrogen : Atom
72
+ '''
73
+ vector = np.array([0., 0., 0.])
74
+ parent = hydrogen.bond_partners[0]
75
+ for atom in parent.bond_partners:
76
+ if atom is not hydrogen:
77
+ vector += atom.position - parent.position
78
+ hydrogen.position = parent.position - vector / np.sqrt(np.dot(vector, vector)) * 0.1 # bXH = 0.1 nm
79
+
80
+
65
81
  def periodic_distance(pos1, pos2, box, distance_max=None):
66
82
  '''
67
83
  Calculate the distance between two points under periodic boundary condition
@@ -155,7 +171,7 @@ def periodic_dihedral(pos1, pos2, pos3, pos4, box):
155
171
  return sign * value
156
172
 
157
173
 
158
- def find_clusters(elements, func):
174
+ def find_clusters(elements, func, show_progress=False):
159
175
  '''
160
176
  Group elements into clusters
161
177
 
@@ -175,7 +191,12 @@ def find_clusters(elements, func):
175
191
  flag = [0] * n_element
176
192
  n_cluster = 0
177
193
  clusters = []
178
- for i in range(n_element):
194
+ if show_progress:
195
+ from tqdm import tqdm
196
+ _iterator = tqdm(range(n_element))
197
+ else:
198
+ _iterator = range(n_element)
199
+ for i in _iterator:
179
200
  if flag[i]:
180
201
  continue
181
202
  path = {i}
@@ -203,7 +224,7 @@ def find_clusters(elements, func):
203
224
  return clusters
204
225
 
205
226
 
206
- def find_clusters_consecutive(elements, func):
227
+ def find_clusters_consecutive(elements, func, show_progress=False):
207
228
  '''
208
229
  Group elements into clusters. If element i and j are in the same group, all elements between i and j will also be put in the same group.
209
230
 
@@ -223,7 +244,12 @@ def find_clusters_consecutive(elements, func):
223
244
  flag = [0] * n_element
224
245
  n_cluster = 0
225
246
  clusters = []
226
- for i in range(n_element):
247
+ if show_progress:
248
+ from tqdm import tqdm
249
+ _iterator = tqdm(range(n_element))
250
+ else:
251
+ _iterator = range(n_element)
252
+ for i in _iterator:
227
253
  if not flag[i]:
228
254
  n_cluster += 1
229
255
  flag[i] = n_cluster
@@ -176,7 +176,7 @@ class Pdb:
176
176
  atom_name = atom.type if atom_type else atom.name
177
177
  resname = atom.residue.name
178
178
  resid = atom.residue.id + 1
179
- line = 'HETATM%5d %4s %4s %4d %8.3f%8.3f%8.3f %2s\n' % (
179
+ line = 'HETATM%5d %4s %-4s %4d %8.3f%8.3f%8.3f %2s\n' % (
180
180
  (atom.id + 1) % 100000, atom_name[:4], resname[:4], resid % 10000,
181
181
  pos[0], pos[1], pos[2], atom.symbol[:2])
182
182
  string += line
@@ -214,27 +214,22 @@ class Molecule():
214
214
  The `rdkit.Chem.Mol` object associated with this molecule.
215
215
 
216
216
  It is required by ZftTyper typing engine, which performs SMARTS matching on the molecule.
217
- The obmol attribute will be assigned if the molecule is initialized from SMILES or Pybel Molecule.
218
- If this information is not available, an Exception will be raised.
217
+ The `rdmol` attribute will be assigned if the molecule is initialized from SMILES or RDKit Molecule.
218
+ If it is not available, a RDKit molecule will be constructed from atoms and bonds.
219
+ The positions will not be preserved.
219
220
 
220
221
  Returns
221
222
  -------
222
223
  rdmol : rdkit.Chem.Mol
223
224
  '''
224
- if not self._is_rdmol_valid:
225
- self._construct_rdmol()
226
-
227
- return self._rdmol
228
-
229
- def _construct_rdmol(self):
230
- '''
231
- Construct a RDKit molecule from atoms and bonds. The positions will not be preserved.
232
- '''
233
225
  try:
234
226
  from rdkit import Chem
235
227
  except ImportError:
236
228
  raise ImportError('RDKit not found')
237
229
 
230
+ if self._is_rdmol_valid:
231
+ return self._rdmol
232
+
238
233
  if any(b.order == Bond.Order.UNSPECIFIED for b in self.bonds):
239
234
  logger.warning(f'Not all bond orders are specified in {self}')
240
235
 
@@ -256,6 +251,38 @@ class Molecule():
256
251
  self._rdmol = rwmol.GetMol()
257
252
  self._is_rdmol_valid = True
258
253
 
254
+ return self._rdmol
255
+
256
+ def generate_conformers(self, n_conformer=1):
257
+ '''
258
+ Generate several conformers with RDKit.
259
+
260
+ The positions will be generated from only elements and bonds. The chiral center will not be respected.
261
+
262
+ Parameters
263
+ ----------
264
+ n_conformer : int
265
+ How many conformers to generate
266
+
267
+ Returns
268
+ -------
269
+ molecules : list of Molecule
270
+ Each conformer will be a independent molecule object
271
+ '''
272
+ try:
273
+ from rdkit.Chem import AllChem as Chem
274
+ except ImportError:
275
+ raise ImportError('RDKit not found')
276
+
277
+ molecules = []
278
+ rdmol = Chem.Mol(self.rdmol)
279
+ Chem.EmbedMultipleConfs(rdmol, numConfs=n_conformer, clearConfs=True)
280
+ for i in range(rdmol.GetNumConformers()):
281
+ molecules.append(Molecule.from_rdmol(rdmol, name=self.name))
282
+ rdmol.RemoveConformer(i)
283
+
284
+ return molecules
285
+
259
286
  @property
260
287
  def topology(self):
261
288
  '''
@@ -317,6 +344,8 @@ class Molecule():
317
344
  The angle, dihedral and improper involving this atom are untouched.
318
345
  Therefore, you may call `generate_angle_dihedral_improper` to refresh the connectivity.
319
346
 
347
+ # TODO This operation is extremely slow
348
+
320
349
  Parameters
321
350
  ----------
322
351
  atom : Atom
@@ -351,13 +380,14 @@ class Molecule():
351
380
  If update_topology is True, the topology this molecule belongs to will update its atom list and assign id for all atoms and residues.
352
381
  Otherwise, you have to re-init the topology manually so that the topological information is correct.
353
382
 
383
+ # TODO This operation is extremely slow
384
+
354
385
  Returns
355
386
  -------
356
387
  ids_removed : list of int
357
388
  The number of atoms removed
358
389
  '''
359
390
  hydrogens = []
360
- ids_removed = []
361
391
  for atom in self.atoms[:]:
362
392
  if atom.symbol != 'H' or len(atom.bonds) != 1:
363
393
  continue
@@ -366,11 +396,13 @@ class Molecule():
366
396
  continue
367
397
  neigh.mass += atom.mass
368
398
  neigh.charge += atom.charge
369
- for conn in self.bonds[:] + self.angles[:] + self.dihedrals[:] + self.impropers[:]:
370
- if atom in conn.atoms:
371
- self.remove_connectivity(conn)
372
- ids_removed.append(atom.id_in_mol)
373
399
  hydrogens.append(atom)
400
+ ids_hydrogens = [atom.id_in_mol for atom in hydrogens]
401
+
402
+ for conn in self.bonds[:] + self.angles[:] + self.dihedrals[:] + self.impropers[:]:
403
+ ids_set = {atom.id_in_mol for atom in conn.atoms}
404
+ if ids_set.intersection(ids_hydrogens):
405
+ self.remove_connectivity(conn)
374
406
 
375
407
  for atom in hydrogens:
376
408
  self.remove_atom(atom, update_topology=False)
@@ -378,7 +410,7 @@ class Molecule():
378
410
  if self._topology is not None and update_topology:
379
411
  self._topology.update_molecules(self._topology.molecules, deepcopy=False)
380
412
 
381
- return ids_removed
413
+ return ids_hydrogens
382
414
 
383
415
  def add_residue(self, name, atoms, update_topology=True):
384
416
  '''
@@ -563,6 +595,8 @@ class Molecule():
563
595
  Note that when a bond get removed, the relevant angles, dihedrals and impropers are still there.
564
596
  You may call `generate_angle_dihedral_improper` to refresh connectivity.
565
597
 
598
+ # TODO This operation is extremely slow
599
+
566
600
  Parameters
567
601
  ----------
568
602
  connectivity : [Bond, Angle, Dihedral, Improper]
@@ -76,6 +76,9 @@ class Trajectory():
76
76
  def __del__(self):
77
77
  self.close()
78
78
 
79
+ def __repr__(self):
80
+ return f'<Trajectory: {self.n_frame} frames {self.n_atom} atoms>'
81
+
79
82
  def close(self):
80
83
  '''
81
84
  Close the opened trajectory file(s).
@@ -97,7 +100,8 @@ class Trajectory():
97
100
  this method should not be used because all the frames actually point to the same frame.
98
101
  In this case, use :func:`read_frames` instead.
99
102
 
100
- i_frame should be in the range of [0, n_frame), otherwise and Exception will be raised.
103
+ i_frame should be in the range of [-1, n_frame), otherwise and Exception will be raised.
104
+ -1 means the last frame.
101
105
 
102
106
  Parameters
103
107
  ----------
@@ -123,6 +127,8 @@ class Trajectory():
123
127
  # Reset the information in self.frame in case the frames read from different trajectory files pollute each other for CombinedTrajectory
124
128
  self.frame.reset()
125
129
 
130
+ if i_frame == -1:
131
+ i_frame = self.n_frame - 1
126
132
  self._handler.read_frame(i_frame, self.frame)
127
133
  return self.frame
128
134
 
@@ -134,7 +140,8 @@ class Trajectory():
134
140
  Instead, a new Frame object is constructed for each frame.
135
141
  This method should be called when you want to multiprocess several frames in parallel.
136
142
 
137
- All the items in i_frames should be in the range of (0, n_frame), otherwise and Exception will be raised.
143
+ All the items in i_frames should be in the range of [-1, n_frame), otherwise and Exception will be raised.
144
+ -1 means the last frame.
138
145
 
139
146
  Parameters
140
147
  ----------
@@ -153,6 +160,8 @@ class Trajectory():
153
160
 
154
161
  frames = [Frame(self.n_atom) for _ in i_frames]
155
162
  for ii, i_frame in enumerate(i_frames):
163
+ if i_frame == -1:
164
+ i_frame = self.n_frame - 1
156
165
  self._handler.read_frame(i_frame, frames[ii])
157
166
  return frames
158
167
 
@@ -219,8 +228,6 @@ class Trajectory():
219
228
 
220
229
  '''
221
230
  trj = Trajectory(file, 'r')
222
- if i_frame == -1:
223
- i_frame = trj.n_frame - 1
224
231
  frame = trj.read_frame(i_frame)
225
232
  trj.close()
226
233
  return frame
@@ -7,6 +7,7 @@ import subprocess
7
7
  import random
8
8
  import string
9
9
  import numpy as np
10
+ from typing import Iterable
10
11
 
11
12
 
12
13
  def greatest_common_divisor(numbers):
@@ -187,3 +188,11 @@ def align_mpl_axis(ax1, ax2):
187
188
 
188
189
  ax1.set(ylim=ylims1)
189
190
  ax2.set(ylim=ylims2)
191
+
192
+
193
+ def flatten(it):
194
+ for x in it:
195
+ if (isinstance(x, Iterable) and not isinstance(x, str)):
196
+ yield from flatten(x)
197
+ else:
198
+ yield x
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mstk
3
- Version: 0.3.3.dev11
3
+ Version: 0.3.4
4
4
  Summary: Molecular simulation toolkit
5
5
  Home-page: https://github.com/z-gong/mstk
6
6
  Author: Zheng Gong
@@ -69,3 +69,11 @@ https://mstk.readthedocs.io/en/latest/index.html
69
69
 
70
70
  - [ ] Take bond order into consideration for force field assignment
71
71
  - [ ] Re-organize algorithms scattered in topology and analyzer modules
72
+
73
+ ## Known issue
74
+ `mstk` use `chemfiles` to write `XTC` trajectory. However, lastest `chemfiles` is buggy under WSL for writing binary trajectory format.
75
+ If you are working under WSL, please install `chemfiles 0.10.2`.
76
+
77
+ ```
78
+ conda install chemfiles-lib=0.10.2 chemfiles-python=0.10.2
79
+ ```