mimicpy 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- mimicpy/__init__.py +1 -1
- mimicpy/__main__.py +726 -2
- mimicpy/_authors.py +2 -2
- mimicpy/_version.py +2 -2
- mimicpy/coords/__init__.py +1 -1
- mimicpy/coords/base.py +1 -1
- mimicpy/coords/cpmdgeo.py +1 -1
- mimicpy/coords/gro.py +1 -1
- mimicpy/coords/pdb.py +1 -1
- mimicpy/core/__init__.py +1 -1
- mimicpy/core/prepare.py +3 -3
- mimicpy/core/selector.py +1 -1
- mimicpy/force_matching/__init__.py +34 -0
- mimicpy/force_matching/bonded_forces.py +628 -0
- mimicpy/force_matching/compare_top.py +809 -0
- mimicpy/force_matching/dresp.py +435 -0
- mimicpy/force_matching/nonbonded_forces.py +32 -0
- mimicpy/force_matching/opt_ff.py +2114 -0
- mimicpy/force_matching/qm_region.py +1960 -0
- mimicpy/plugins/__main_installer__.py +76 -0
- mimicpy/{__main_vmd__.py → plugins/__main_vmd__.py} +2 -2
- mimicpy/plugins/pymol.py +56 -0
- mimicpy/plugins/vmd.tcl +78 -0
- mimicpy/scripts/__init__.py +1 -1
- mimicpy/scripts/cpmd.py +1 -1
- mimicpy/scripts/fm_input.py +265 -0
- mimicpy/scripts/fmdata.py +120 -0
- mimicpy/scripts/mdp.py +1 -1
- mimicpy/scripts/ndx.py +1 -1
- mimicpy/scripts/script.py +1 -1
- mimicpy/topology/__init__.py +1 -1
- mimicpy/topology/itp.py +603 -35
- mimicpy/topology/mpt.py +1 -1
- mimicpy/topology/top.py +254 -15
- mimicpy/topology/topol_dict.py +233 -4
- mimicpy/utils/__init__.py +1 -1
- mimicpy/utils/atomic_numbers.py +1 -1
- mimicpy/utils/constants.py +17 -3
- mimicpy/utils/elements.py +1 -1
- mimicpy/utils/errors.py +1 -1
- mimicpy/utils/file_handler.py +1 -1
- mimicpy/utils/strings.py +1 -1
- mimicpy-0.3.0.dist-info/METADATA +156 -0
- mimicpy-0.3.0.dist-info/RECORD +50 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/WHEEL +1 -1
- mimicpy-0.3.0.dist-info/entry_points.txt +4 -0
- mimicpy-0.2.0.dist-info/METADATA +0 -86
- mimicpy-0.2.0.dist-info/RECORD +0 -38
- mimicpy-0.2.0.dist-info/entry_points.txt +0 -3
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING.LESSER +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/top_level.txt +0 -0
- {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/zip-safe +0 -0
mimicpy/topology/mpt.py
CHANGED
mimicpy/topology/top.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# MiMiCPy: Python Based Tools for MiMiC
|
|
3
|
-
# Copyright (C) 2020-
|
|
3
|
+
# Copyright (C) 2020-2023 Bharath Raghavan,
|
|
4
4
|
# Florian Schackert
|
|
5
5
|
#
|
|
6
6
|
# This file is part of MiMiCPy.
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import logging
|
|
25
25
|
from os import environ
|
|
26
26
|
from os.path import basename, join, isfile
|
|
27
|
+
from pathlib import Path
|
|
27
28
|
import pandas as pd
|
|
28
29
|
from .itp import Itp
|
|
29
30
|
from .topol_dict import TopolDict
|
|
@@ -66,7 +67,8 @@ class Top:
|
|
|
66
67
|
self.molecules = None
|
|
67
68
|
self.bonds = None
|
|
68
69
|
self.topol_dict = None
|
|
69
|
-
|
|
70
|
+
self.mol_offsets = None
|
|
71
|
+
self.nrexcl_values = {} # Store nrexcl values for all molecules
|
|
70
72
|
if mode == 'r':
|
|
71
73
|
self.__read()
|
|
72
74
|
elif mode == 'w':
|
|
@@ -82,13 +84,20 @@ class Top:
|
|
|
82
84
|
self.atomtypes = top.atom_types_df
|
|
83
85
|
if get_only_atomtypes: return
|
|
84
86
|
molecule_types = top.molecule_types
|
|
85
|
-
|
|
86
87
|
if self.nonstandard_atomtypes is not None:
|
|
87
88
|
atom_types.update(self.nonstandard_atomtypes)
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
self.atom_types_df = pd.DataFrame()
|
|
89
91
|
atoms = {}
|
|
90
92
|
bonds = {}
|
|
93
|
+
angles = {}
|
|
94
|
+
dihedrals = {}
|
|
95
|
+
pairs = {}
|
|
91
96
|
guessed_elems_history = {}
|
|
97
|
+
source_files = {} # Store source file information in a dictionary
|
|
98
|
+
nrexcl_values = {} # Store nrexcl values from all ITP files
|
|
99
|
+
force_fields = {} # Store force field data
|
|
100
|
+
parameter_definitions = {} # Store parameter definitions (#define statements)
|
|
92
101
|
|
|
93
102
|
for itp_file in top.topology_files:
|
|
94
103
|
itp_file_name = basename(itp_file)
|
|
@@ -99,22 +108,84 @@ class Top:
|
|
|
99
108
|
self.buffer,
|
|
100
109
|
'r',
|
|
101
110
|
self.guess_elements,
|
|
102
|
-
self.gmxdata
|
|
111
|
+
self.gmxdata,
|
|
112
|
+
parameter_definitions)
|
|
113
|
+
_ = itp.atom_types
|
|
114
|
+
if not itp.atom_types_df.empty:
|
|
115
|
+
self.atom_types_df = pd.concat([self.atom_types_df,itp.atom_types_df], axis=0, ignore_index=True)
|
|
116
|
+
|
|
117
|
+
# Collect parameter definitions from all files (not just force field files)
|
|
118
|
+
if itp.parameter_definitions:
|
|
119
|
+
parameter_definitions.update(itp.parameter_definitions)
|
|
120
|
+
logging.debug('Read parameter definitions from %s.', itp_file_name)
|
|
121
|
+
|
|
122
|
+
# Check if this is a force field file (contains force field sections)
|
|
123
|
+
if itp.bondtypes or itp.angletypes or itp.dihedraltypes:
|
|
124
|
+
# Extract force field name from filename
|
|
125
|
+
force_field_name = Path(itp_file_name).stem
|
|
126
|
+
if force_field_name not in force_fields:
|
|
127
|
+
force_fields[force_field_name] = {
|
|
128
|
+
'bondtypes': {},
|
|
129
|
+
'angletypes': {},
|
|
130
|
+
'dihedraltypes': {}
|
|
131
|
+
}
|
|
132
|
+
force_fields[force_field_name]['bondtypes'].update(itp.bondtypes)
|
|
133
|
+
force_fields[force_field_name]['angletypes'].update(itp.angletypes)
|
|
134
|
+
force_fields[force_field_name]['dihedraltypes'].update(itp.dihedraltypes)
|
|
135
|
+
logging.debug('Read force field parameters from %s.', itp_file_name)
|
|
136
|
+
|
|
103
137
|
if itp.topol is not None:
|
|
104
138
|
atoms.update(itp.topol)
|
|
105
139
|
bonds.update(itp.bonds)
|
|
140
|
+
angles.update(itp.angles)
|
|
141
|
+
dihedrals.update(itp.dihedrals)
|
|
142
|
+
pairs.update(itp.pairs)
|
|
106
143
|
guessed_elems_history.update(itp.guessed_elems_history)
|
|
144
|
+
# Store source file information in dictionary
|
|
145
|
+
for mol in itp.topol:
|
|
146
|
+
if hasattr(itp.topol[mol], 'source_file'):
|
|
147
|
+
source_files[mol] = itp.topol[mol].source_file
|
|
148
|
+
# Store nrexcl values
|
|
149
|
+
nrexcl_values.update(itp.nrexcl_values)
|
|
107
150
|
logging.debug('Read atoms from %s.', itp_file_name)
|
|
108
151
|
else:
|
|
109
152
|
logging.debug('No atoms found in %s.', itp_file_name)
|
|
110
153
|
except OSError:
|
|
111
154
|
logging.warning('Could not find %s in local or GROMACS data directory. Skipping.', itp_file_name)
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
|
|
114
156
|
self.molecules = top.molecules
|
|
115
|
-
|
|
157
|
+
|
|
158
|
+
# Create TopolDict with all collected data
|
|
159
|
+
self.topol_dict = TopolDict.from_dict(atoms, bonds, angles, dihedrals, pairs)
|
|
160
|
+
# Add source file information to TopolDict
|
|
161
|
+
for mol, source_file in source_files.items():
|
|
162
|
+
self.topol_dict.add_source_file(mol, source_file)
|
|
163
|
+
# Add nrexcl values to TopolDict
|
|
164
|
+
for mol, nrexcl in nrexcl_values.items():
|
|
165
|
+
self.topol_dict.add_nrexcl_value(mol, nrexcl)
|
|
166
|
+
# Add force field data to TopolDict
|
|
167
|
+
for force_field_name, ff_data in force_fields.items():
|
|
168
|
+
self.topol_dict.add_force_field(
|
|
169
|
+
force_field_name,
|
|
170
|
+
bondtypes=ff_data['bondtypes'],
|
|
171
|
+
angletypes=ff_data['angletypes'],
|
|
172
|
+
dihedraltypes=ff_data['dihedraltypes']
|
|
173
|
+
)
|
|
174
|
+
# Add parameter definitions to TopolDict
|
|
175
|
+
self.topol_dict.add_parameter_definitions(parameter_definitions)
|
|
176
|
+
# Store nrexcl values
|
|
177
|
+
self.nrexcl_values = nrexcl_values
|
|
116
178
|
self.bonds = self.__get_bonds(bonds)
|
|
117
179
|
|
|
180
|
+
mol_offsets = []
|
|
181
|
+
offset = 0
|
|
182
|
+
for mol, n_mols in self.molecules:
|
|
183
|
+
mol_len = len(self.topol_dict[mol])
|
|
184
|
+
for i in range(n_mols):
|
|
185
|
+
mol_offsets.append((mol, offset))
|
|
186
|
+
offset += mol_len
|
|
187
|
+
self.mol_offsets = mol_offsets
|
|
188
|
+
|
|
118
189
|
mols_not_in = self.topol_dict.check_mols(self.molecules)
|
|
119
190
|
|
|
120
191
|
if mols_not_in:
|
|
@@ -140,19 +211,32 @@ class Top:
|
|
|
140
211
|
|
|
141
212
|
return pd.DataFrame(bonds_section_dct)
|
|
142
213
|
|
|
143
|
-
|
|
214
|
+
|
|
215
|
+
def write_atomtypes(self, file=None, delete_atomtypes=None):
|
|
216
|
+
"""Write atomtypes to a file with support for modifying multiple files
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
file (str, optional): Target file to write atomtypes to. If None, uses self.file
|
|
220
|
+
delete_atomtypes (list, optional): List of files to delete atomtypes sections from
|
|
221
|
+
"""
|
|
222
|
+
if file is None:
|
|
223
|
+
file = self.file
|
|
224
|
+
|
|
144
225
|
if self.mode != 'w':
|
|
145
226
|
self.mode = 'w'
|
|
146
227
|
self.__read(True)
|
|
147
228
|
|
|
229
|
+
# Get elements from topology dictionary
|
|
148
230
|
elements = {}
|
|
149
231
|
for k, df in self.topol_dict.todict().items():
|
|
150
232
|
elements.update(dict(zip(df['type'], df['element'])))
|
|
151
233
|
elements = {k:atomic_numbers[v] for k,v in elements.items()}
|
|
152
234
|
|
|
235
|
+
# Create atomtypes string
|
|
153
236
|
itp_str = "; Created by mimicpy fixtop\n[ atomtypes ]\n"
|
|
154
|
-
itp_str += "; {:^11}{:^6}{:^10}{:^10}{:^6} {} {}\n".format(
|
|
155
|
-
|
|
237
|
+
itp_str += "; {:^11}{:^6}{:^10}{:^10}{:^6} {} {}\n".format(
|
|
238
|
+
'name', 'at.num', 'mass', 'charge', 'ptype', 'sigma', 'epsilon')
|
|
239
|
+
|
|
156
240
|
for i, row in self.atomtypes.iterrows():
|
|
157
241
|
lst = [row[c] for c in self.atomtypes.columns]
|
|
158
242
|
if lst[0] in elements:
|
|
@@ -161,10 +245,10 @@ class Top:
|
|
|
161
245
|
lst[1] = int(lst[1])
|
|
162
246
|
itp_str += "{:>11}{:6d}{:11.4f}{:11.4f}{:>6} {:e} {:e}\n".format(*lst)
|
|
163
247
|
|
|
164
|
-
|
|
165
|
-
r = re.compile(r"(\[\s*atomtypes\s*\]\n(?:.+\n)+?)\s*(?:$|\[|#)", re.MULTILINE)
|
|
166
|
-
|
|
248
|
+
# Handle deletion of atomtypes sections from other files
|
|
167
249
|
if delete_atomtypes is not None:
|
|
250
|
+
import re
|
|
251
|
+
r = re.compile(r"(\[\s*atomtypes\s*\]\n(?:.+\n)+?)\s*(?:$|\[|#)", re.MULTILINE)
|
|
168
252
|
for i in delete_atomtypes:
|
|
169
253
|
txt = read(i)
|
|
170
254
|
atm_typs = r.findall(txt)
|
|
@@ -173,6 +257,7 @@ class Top:
|
|
|
173
257
|
write(txt, i)
|
|
174
258
|
logging.info('Deleted atomtypes sections from %s', ', '.join(delete_atomtypes))
|
|
175
259
|
|
|
260
|
+
# Write to target file
|
|
176
261
|
if isfile(file):
|
|
177
262
|
txt = read(file)
|
|
178
263
|
atm_typs = r.findall(txt)
|
|
@@ -183,4 +268,158 @@ class Top:
|
|
|
183
268
|
raise FileExistsError('%s exists and has no [ atomtypes ] section to replace', file)
|
|
184
269
|
else:
|
|
185
270
|
write(itp_str, file)
|
|
186
|
-
logging.info('Fixed [ atomtypes ] section and wrote to %s', file)
|
|
271
|
+
logging.info('Fixed [ atomtypes ] section and wrote to %s', file)
|
|
272
|
+
|
|
273
|
+
def write_topology(self, output_file):
|
|
274
|
+
"""Write complete topology to file including all interactions
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
output_file (str): Path to output file
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
# Write system name
|
|
281
|
+
top_str = "; Created by mimicpy\n"
|
|
282
|
+
top_str += "[ system ]\n"
|
|
283
|
+
top_str += "; Name\n"
|
|
284
|
+
top_str += "System\n\n"
|
|
285
|
+
|
|
286
|
+
# Write molecules section
|
|
287
|
+
top_str += "[ molecules ]\n"
|
|
288
|
+
top_str += "; Compound #mols\n"
|
|
289
|
+
for mol, n_mols in self.molecules:
|
|
290
|
+
top_str += f"{mol:16s} {n_mols:6d}\n"
|
|
291
|
+
top_str += "\n"
|
|
292
|
+
|
|
293
|
+
# Write atomtypes section
|
|
294
|
+
top_str += "[ atomtypes ]\n"
|
|
295
|
+
top_str += "; {:^11}{:^6}{:^10}{:^10}{:^6} {} {}\n".format(
|
|
296
|
+
'name', 'at.num', 'mass', 'charge', 'ptype', 'sigma', 'epsilon')
|
|
297
|
+
|
|
298
|
+
# Get unique atom types from topology
|
|
299
|
+
atom_types = set()
|
|
300
|
+
for mol in self.topol_dict.keys():
|
|
301
|
+
atom_types.update(self.topol_dict[mol]['type'].unique())
|
|
302
|
+
|
|
303
|
+
for atom_type in sorted(atom_types):
|
|
304
|
+
row = self.atom_types_df.loc[atom_type]
|
|
305
|
+
top_str += "{:>11}{:6d}{:11.4f}{:11.4f}{:>6} {:e} {:e}\n".format(
|
|
306
|
+
row['type'], row['X'], row['mass'], row['charge'],
|
|
307
|
+
row['ptype'], row['sigma'], row['epsilon'])
|
|
308
|
+
top_str += "\n"
|
|
309
|
+
|
|
310
|
+
# Write molecule types and their interactions to separate .itp files
|
|
311
|
+
for mol, n_mols in self.molecules:
|
|
312
|
+
# Create .itp file for this molecule
|
|
313
|
+
itp_file = f"{mol}.itp"
|
|
314
|
+
|
|
315
|
+
# Get nrexcl value for this molecule
|
|
316
|
+
nrexcl = self.topol_dict.get_nrexcl_value(mol)
|
|
317
|
+
|
|
318
|
+
itp_str = f"; Created by mimicpy\n[ moleculetype ]\n; Name nrexcl\n{mol:16s} {nrexcl}\n\n"
|
|
319
|
+
|
|
320
|
+
# Write atoms section
|
|
321
|
+
itp_str += "[ atoms ]\n"
|
|
322
|
+
itp_str += "; nr type resnr residu atom cgnr charge mass\n"
|
|
323
|
+
atoms_df = self.topol_dict[mol]
|
|
324
|
+
for idx, row in atoms_df.iterrows():
|
|
325
|
+
itp_str += f"{idx:6d} {row['type']:8s} {row['resid']:6d} {row['resname']:8s} {row['name']:8s} {row['charge']:8.4f} {row['mass']:12.4f}\n"
|
|
326
|
+
itp_str += "\n"
|
|
327
|
+
|
|
328
|
+
# Get interactions for this molecule
|
|
329
|
+
mol_bonds = self.topol_dict.get_bonds(mol)
|
|
330
|
+
mol_angles = self.topol_dict.get_angles(mol)
|
|
331
|
+
mol_dihedrals = self.topol_dict.get_dihedrals(mol)
|
|
332
|
+
mol_pairs = self.topol_dict.get_pairs(mol)
|
|
333
|
+
# Write bonds
|
|
334
|
+
if mol_bonds:
|
|
335
|
+
itp_str += "[ bonds ]\n"
|
|
336
|
+
itp_str += "; ai aj funct param1 param2\n"
|
|
337
|
+
for j in range(len(mol_bonds[0])):
|
|
338
|
+
i_idx = mol_bonds[0][j]
|
|
339
|
+
j_idx = mol_bonds[1][j]
|
|
340
|
+
func = mol_bonds[2][j]
|
|
341
|
+
p1 = mol_bonds[3][j]
|
|
342
|
+
p2 = mol_bonds[4][j]
|
|
343
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {func:6d} {p1:10.5E} {p2:10.5E}\n"
|
|
344
|
+
itp_str += "\n"
|
|
345
|
+
|
|
346
|
+
# Write pairs
|
|
347
|
+
if mol_pairs:
|
|
348
|
+
itp_str += "[ pairs ]\n"
|
|
349
|
+
itp_str += "; ai aj funct\n"
|
|
350
|
+
for j in range(len(mol_pairs[0])):
|
|
351
|
+
i_idx = mol_pairs[0][j]
|
|
352
|
+
j_idx = mol_pairs[1][j]
|
|
353
|
+
func = mol_pairs[2][j]
|
|
354
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {func:6d}\n"
|
|
355
|
+
itp_str += "\n"
|
|
356
|
+
# Write angles
|
|
357
|
+
if mol_angles:
|
|
358
|
+
itp_str += "[ angles ]\n"
|
|
359
|
+
itp_str += "; ai aj ak funct param1 param2\n"
|
|
360
|
+
for j in range(len(mol_angles[0])):
|
|
361
|
+
i_idx = mol_angles[0][j]
|
|
362
|
+
j_idx = mol_angles[1][j]
|
|
363
|
+
k_idx = mol_angles[2][j]
|
|
364
|
+
func = mol_angles[3][j]
|
|
365
|
+
p1 = mol_angles[4][j]
|
|
366
|
+
p2 = mol_angles[5][j]
|
|
367
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {k_idx:6d} {func:6d} {p1:10.5E} {p2:10.5E}\n"
|
|
368
|
+
itp_str += "\n"
|
|
369
|
+
|
|
370
|
+
# Write dihedrals
|
|
371
|
+
if mol_dihedrals:
|
|
372
|
+
# Group dihedrals by function type
|
|
373
|
+
dihedrals_by_func = {}
|
|
374
|
+
for j in range(len(mol_dihedrals[0])):
|
|
375
|
+
func = mol_dihedrals[4][j]
|
|
376
|
+
if func not in dihedrals_by_func:
|
|
377
|
+
dihedrals_by_func[func] = []
|
|
378
|
+
dihedrals_by_func[func].append(j)
|
|
379
|
+
|
|
380
|
+
# Write each function type in a separate section
|
|
381
|
+
for func, indices in dihedrals_by_func.items():
|
|
382
|
+
itp_str += "[ dihedrals ]\n"
|
|
383
|
+
# Write header based on function type
|
|
384
|
+
if func in [1, 4, 9]: # Format 1
|
|
385
|
+
itp_str += "; ai aj ak al funct phi0 cp mult\n"
|
|
386
|
+
elif func == 2: # Format 2
|
|
387
|
+
itp_str += "; ai aj ak al funct param1 param2\n"
|
|
388
|
+
elif func == 3: # Format 3
|
|
389
|
+
itp_str += "; ai aj ak al funct C0 C1 C2 C3 C4 C5\n"
|
|
390
|
+
|
|
391
|
+
for j in indices:
|
|
392
|
+
i_idx = mol_dihedrals[0][j]
|
|
393
|
+
j_idx = mol_dihedrals[1][j]
|
|
394
|
+
k_idx = mol_dihedrals[2][j]
|
|
395
|
+
l_idx = mol_dihedrals[3][j]
|
|
396
|
+
|
|
397
|
+
if func in [1, 4, 9]: # Format 1
|
|
398
|
+
phi0 = mol_dihedrals[5][j]
|
|
399
|
+
cp = mol_dihedrals[6][j]
|
|
400
|
+
mult = mol_dihedrals[7][j]
|
|
401
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {k_idx:6d} {l_idx:6d} {func:6d} {phi0:8.1f} {cp:8.2f} {mult:6d}\n"
|
|
402
|
+
elif func == 2: # Format 2
|
|
403
|
+
p1 = mol_dihedrals[8][j]
|
|
404
|
+
p2 = mol_dihedrals[9][j]
|
|
405
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {k_idx:6d} {l_idx:6d} {func:6d} {p1:8.3f} {p2:10.5E}\n"
|
|
406
|
+
elif func == 3: # Format 3
|
|
407
|
+
c0, c1, c2, c3, c4, c5 = [mol_dihedrals[k][j] for k in range(10, 16)]
|
|
408
|
+
itp_str += f"{i_idx:6d} {j_idx:6d} {k_idx:6d} {l_idx:6d} {func:6d} {c0:8.5f} {c1:8.5f} {c2:8.5f} {c3:8.5f} {c4:8.5f} {c5:8.5f}\n"
|
|
409
|
+
itp_str += "\n"
|
|
410
|
+
|
|
411
|
+
# Write the .itp file
|
|
412
|
+
with open(itp_file, 'w') as f:
|
|
413
|
+
f.write(itp_str)
|
|
414
|
+
|
|
415
|
+
# Include the .itp file in the main topology
|
|
416
|
+
top_str += f'#include "{itp_file}"\n'
|
|
417
|
+
|
|
418
|
+
# Write the main topology file
|
|
419
|
+
with open(output_file, 'w') as f:
|
|
420
|
+
f.write(top_str)
|
|
421
|
+
|
|
422
|
+
logging.info(f'Successfully wrote topology to {output_file}')
|
|
423
|
+
return True
|
|
424
|
+
|
|
425
|
+
|
mimicpy/topology/topol_dict.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# MiMiCPy: Python Based Tools for MiMiC
|
|
3
|
-
# Copyright (C) 2020-
|
|
3
|
+
# Copyright (C) 2020-2023 Bharath Raghavan,
|
|
4
4
|
# Florian Schackert
|
|
5
5
|
#
|
|
6
6
|
# This file is part of MiMiCPy.
|
|
@@ -21,14 +21,21 @@
|
|
|
21
21
|
|
|
22
22
|
"""Module for MiMiCPy-specific molecule:topology dictionary"""
|
|
23
23
|
|
|
24
|
+
import logging
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
class TopolDict:
|
|
26
28
|
"""provides a dictionary with non-repeating topology information"""
|
|
27
29
|
|
|
28
30
|
@classmethod
|
|
29
|
-
def from_dict(cls, dict_df):
|
|
31
|
+
def from_dict(cls, dict_df, bonds=None, angles=None, dihedrals=None, pairs=None):
|
|
30
32
|
keys = list(dict_df.keys())
|
|
31
33
|
repeating = {}
|
|
34
|
+
repeating_bonds = {}
|
|
35
|
+
repeating_angles = {}
|
|
36
|
+
repeating_dihedrals = {}
|
|
37
|
+
repeating_pairs = {}
|
|
38
|
+
|
|
32
39
|
i = 0
|
|
33
40
|
while i < len(keys):
|
|
34
41
|
key_i = keys[i]
|
|
@@ -36,15 +43,209 @@ class TopolDict:
|
|
|
36
43
|
key_j = keys[j]
|
|
37
44
|
if dict_df[key_i].equals(dict_df[key_j]):
|
|
38
45
|
repeating[key_j] = key_i
|
|
46
|
+
if bonds and key_j in bonds:
|
|
47
|
+
repeating_bonds[key_j] = key_i
|
|
48
|
+
if angles and key_j in angles:
|
|
49
|
+
repeating_angles[key_j] = key_i
|
|
50
|
+
if dihedrals and key_j in dihedrals:
|
|
51
|
+
repeating_dihedrals[key_j] = key_i
|
|
52
|
+
if pairs and key_j in pairs:
|
|
53
|
+
repeating_pairs[key_j] = key_i
|
|
39
54
|
del dict_df[key_j]
|
|
40
55
|
i += 1
|
|
41
56
|
keys = list(dict_df.keys())
|
|
42
|
-
return cls(dict_df, repeating
|
|
57
|
+
return cls(dict_df, repeating, bonds, angles, dihedrals, pairs,
|
|
58
|
+
repeating_bonds, repeating_angles, repeating_dihedrals, repeating_pairs)
|
|
43
59
|
|
|
44
|
-
def __init__(self, dict_df, repeating
|
|
60
|
+
def __init__(self, dict_df, repeating, bonds=None, angles=None, dihedrals=None, pairs=None,
|
|
61
|
+
repeating_bonds=None, repeating_angles=None, repeating_dihedrals=None, repeating_pairs=None):
|
|
45
62
|
self.dict_df = dict_df
|
|
46
63
|
self.repeating = repeating
|
|
64
|
+
self.bonds = bonds or {}
|
|
65
|
+
self.angles = angles or {}
|
|
66
|
+
self.dihedrals = dihedrals or {}
|
|
67
|
+
self.pairs = pairs or {}
|
|
68
|
+
self.repeating_bonds = repeating_bonds or {}
|
|
69
|
+
self.repeating_angles = repeating_angles or {}
|
|
70
|
+
self.repeating_dihedrals = repeating_dihedrals or {}
|
|
71
|
+
self.repeating_pairs = repeating_pairs or {}
|
|
72
|
+
# Add source file tracking
|
|
73
|
+
self.source_files = {}
|
|
74
|
+
self.nrexcl_values = {} # Store nrexcl values for molecules
|
|
75
|
+
self.mol_num_atoms = {mol: len(self.dict_df[mol]) for mol in self.dict_df}
|
|
76
|
+
# Add force field storage
|
|
77
|
+
self.force_fields = {} # Store force field parameters by force field name
|
|
78
|
+
self.parameter_definitions = {} # Store parameter definitions (#define statements)
|
|
79
|
+
|
|
80
|
+
def add_source_file(self, mol_name, source_file):
|
|
81
|
+
"""Add source file information for a molecule
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
mol_name (str): Name of the molecule
|
|
85
|
+
source_file (str): Path to the source file
|
|
86
|
+
"""
|
|
87
|
+
self.source_files[mol_name] = source_file
|
|
88
|
+
|
|
89
|
+
def get_source_file(self, mol_name):
|
|
90
|
+
"""Get source file for a molecule
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
mol_name (str): Name of the molecule
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
str: Path to the source file, or None if not found
|
|
97
|
+
"""
|
|
98
|
+
if mol_name in self.source_files:
|
|
99
|
+
return self.source_files[mol_name]
|
|
100
|
+
if mol_name in self.repeating:
|
|
101
|
+
return self.source_files.get(self.repeating[mol_name])
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def add_nrexcl_value(self, mol_name, nrexcl):
|
|
105
|
+
"""Add nrexcl value for a molecule
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
mol_name (str): Name of the molecule
|
|
109
|
+
nrexcl (int): nrexcl value for the molecule
|
|
110
|
+
"""
|
|
111
|
+
self.nrexcl_values[mol_name] = nrexcl
|
|
112
|
+
|
|
113
|
+
def get_nrexcl_value(self, mol_name):
|
|
114
|
+
"""Get nrexcl value for a molecule
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
mol_name (str): Name of the molecule
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
int: nrexcl value for the molecule, or 3 if not found (default)
|
|
121
|
+
"""
|
|
122
|
+
if mol_name in self.nrexcl_values:
|
|
123
|
+
return self.nrexcl_values[mol_name]
|
|
124
|
+
if mol_name in self.repeating:
|
|
125
|
+
return self.nrexcl_values.get(self.repeating[mol_name], 3)
|
|
126
|
+
return 3 # Default value
|
|
127
|
+
|
|
128
|
+
def add_force_field(self, force_field_name, bondtypes=None, angletypes=None, dihedraltypes=None):
|
|
129
|
+
"""Add force field parameters to the topology dictionary
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
force_field_name (str): Name of the force field
|
|
133
|
+
bondtypes (dict): Bond type parameters
|
|
134
|
+
angletypes (dict): Angle type parameters
|
|
135
|
+
dihedraltypes (dict): Dihedral type parameters
|
|
136
|
+
"""
|
|
137
|
+
self.force_fields[force_field_name] = {
|
|
138
|
+
'bondtypes': bondtypes or {},
|
|
139
|
+
'angletypes': angletypes or {},
|
|
140
|
+
'dihedraltypes': dihedraltypes or {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def get_force_field(self, force_field_name):
|
|
144
|
+
"""Get force field parameters by name
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
force_field_name (str): Name of the force field
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
dict: Force field parameters or None if not found
|
|
151
|
+
"""
|
|
152
|
+
return self.force_fields.get(force_field_name)
|
|
153
|
+
|
|
154
|
+
def get_force_field_bondtypes(self, force_field_name):
|
|
155
|
+
"""Get bond types for a specific force field
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
force_field_name (str): Name of the force field
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
dict: Bond type parameters or empty dict if not found
|
|
162
|
+
"""
|
|
163
|
+
ff = self.get_force_field(force_field_name)
|
|
164
|
+
return ff.get('bondtypes', {}) if ff else {}
|
|
165
|
+
|
|
166
|
+
def get_force_field_angletypes(self, force_field_name):
|
|
167
|
+
"""Get angle types for a specific force field
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
force_field_name (str): Name of the force field
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
dict: Angle type parameters or empty dict if not found
|
|
174
|
+
"""
|
|
175
|
+
ff = self.get_force_field(force_field_name)
|
|
176
|
+
return ff.get('angletypes', {}) if ff else {}
|
|
47
177
|
|
|
178
|
+
def get_force_field_dihedraltypes(self, force_field_name):
|
|
179
|
+
"""Get dihedral types for a specific force field
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
force_field_name (str): Name of the force field
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
dict: Dihedral type parameters or empty dict if not found
|
|
186
|
+
"""
|
|
187
|
+
ff = self.get_force_field(force_field_name)
|
|
188
|
+
return ff.get('dihedraltypes', {}) if ff else {}
|
|
189
|
+
|
|
190
|
+
def list_force_fields(self):
|
|
191
|
+
"""Get list of available force fields
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
list: List of force field names
|
|
195
|
+
"""
|
|
196
|
+
return list(self.force_fields.keys())
|
|
197
|
+
|
|
198
|
+
def add_parameter_definitions(self, parameter_definitions):
|
|
199
|
+
"""Add parameter definitions (#define statements) to the topology dictionary
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
parameter_definitions (dict): Dictionary of parameter definitions
|
|
203
|
+
"""
|
|
204
|
+
self.parameter_definitions.update(parameter_definitions)
|
|
205
|
+
|
|
206
|
+
def get_parameter_definitions(self):
|
|
207
|
+
"""Get all parameter definitions
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
dict: Dictionary of parameter definitions
|
|
211
|
+
"""
|
|
212
|
+
return self.parameter_definitions
|
|
213
|
+
|
|
214
|
+
def resolve_parameter_reference(self, param_ref):
|
|
215
|
+
"""Resolve a parameter reference to its actual values
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
param_ref (str): Parameter reference name (e.g., 'torsion_ASN_CA_CB_CG_ND2_mult1')
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
list: List of parameter values or None if not found
|
|
222
|
+
"""
|
|
223
|
+
if param_ref in self.parameter_definitions:
|
|
224
|
+
param_str = self.parameter_definitions[param_ref]
|
|
225
|
+
try:
|
|
226
|
+
# Remove comments and parse the parameter values (e.g., "0.0 -4.376464 1")
|
|
227
|
+
# Split by semicolon and take only the first part
|
|
228
|
+
clean_param_str = param_str.split(';')[0].strip()
|
|
229
|
+
values = [float(x) for x in clean_param_str.split()]
|
|
230
|
+
return values
|
|
231
|
+
except ValueError:
|
|
232
|
+
logging.warning(f'Could not parse parameter definition for {param_ref}: {param_str}')
|
|
233
|
+
return None
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
def get_num_atoms(self, mol_name):
|
|
237
|
+
"""Get the number of atoms in a molecule
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
mol_name (str): Name of the molecule
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
int: Number of atoms in the molecule
|
|
244
|
+
"""
|
|
245
|
+
if mol_name not in self.mol_num_atoms:
|
|
246
|
+
raise KeyError('Molecule {} is not in topology'.format(mol_name))
|
|
247
|
+
return self.mol_num_atoms[mol_name]
|
|
248
|
+
|
|
48
249
|
def __getitem__(self, key):
|
|
49
250
|
if key in self.dict_df:
|
|
50
251
|
return self.dict_df[key]
|
|
@@ -52,6 +253,34 @@ class TopolDict:
|
|
|
52
253
|
return self.dict_df[self.repeating[key]]
|
|
53
254
|
raise KeyError('Molecule {} is not in topology'.format(key))
|
|
54
255
|
|
|
256
|
+
def get_bonds(self, key):
|
|
257
|
+
if key in self.bonds:
|
|
258
|
+
return self.bonds[key]
|
|
259
|
+
if key in self.repeating_bonds:
|
|
260
|
+
return self.bonds[self.repeating_bonds[key]]
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def get_angles(self, key):
|
|
264
|
+
if key in self.angles:
|
|
265
|
+
return self.angles[key]
|
|
266
|
+
if key in self.repeating_angles:
|
|
267
|
+
return self.angles[self.repeating_angles[key]]
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
def get_dihedrals(self, key):
|
|
271
|
+
if key in self.dihedrals:
|
|
272
|
+
return self.dihedrals[key]
|
|
273
|
+
if key in self.repeating_dihedrals:
|
|
274
|
+
return self.dihedrals[self.repeating_dihedrals[key]]
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
def get_pairs(self, key):
|
|
278
|
+
if key in self.pairs:
|
|
279
|
+
return self.pairs[key]
|
|
280
|
+
if key in self.repeating_pairs:
|
|
281
|
+
return self.pairs[self.repeating_pairs[key]]
|
|
282
|
+
return None
|
|
283
|
+
|
|
55
284
|
def __contains__(self, key):
|
|
56
285
|
return key in self.dict_df or key in self.repeating
|
|
57
286
|
|
mimicpy/utils/__init__.py
CHANGED
mimicpy/utils/atomic_numbers.py
CHANGED
mimicpy/utils/constants.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#
|
|
2
2
|
# MiMiCPy: Python Based Tools for MiMiC
|
|
3
|
-
# Copyright (C) 2020-
|
|
3
|
+
# Copyright (C) 2020-2023 Bharath Raghavan,
|
|
4
4
|
# Florian Schackert
|
|
5
5
|
#
|
|
6
6
|
# This file is part of MiMiCPy.
|
|
@@ -19,5 +19,19 @@
|
|
|
19
19
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
20
20
|
#
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
|
|
23
|
+
BOHR_RADIUS = 5.29177210859e-02 # in nm
|
|
24
|
+
ATOMIC_TIME_UNIT = 2.4188843265864E-5 # in ps
|
|
25
|
+
|
|
26
|
+
au_kjm=2.62549962505e+3
|
|
27
|
+
kjm_au = 1 / au_kjm
|
|
28
|
+
|
|
29
|
+
au_to_nm = BOHR_RADIUS
|
|
30
|
+
nm_to_au = 1 / au_to_nm
|
|
31
|
+
au_to_kjmolnm = au_kjm / au_to_nm
|
|
32
|
+
kjmolnm_to_au = 1 / au_to_kjmolnm
|
|
33
|
+
kb_gmx2au = kjm_au / nm_to_au**2
|
|
34
|
+
kb_au2gmx = 1/ kb_gmx2au
|
|
35
|
+
kb_g962au = kjm_au / nm_to_au**4
|
|
36
|
+
kb_au2g96 = 1/ kb_g962au
|
|
37
|
+
electric_conversion_factor = 138.935458
|