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.
Files changed (53) hide show
  1. mimicpy/__init__.py +1 -1
  2. mimicpy/__main__.py +726 -2
  3. mimicpy/_authors.py +2 -2
  4. mimicpy/_version.py +2 -2
  5. mimicpy/coords/__init__.py +1 -1
  6. mimicpy/coords/base.py +1 -1
  7. mimicpy/coords/cpmdgeo.py +1 -1
  8. mimicpy/coords/gro.py +1 -1
  9. mimicpy/coords/pdb.py +1 -1
  10. mimicpy/core/__init__.py +1 -1
  11. mimicpy/core/prepare.py +3 -3
  12. mimicpy/core/selector.py +1 -1
  13. mimicpy/force_matching/__init__.py +34 -0
  14. mimicpy/force_matching/bonded_forces.py +628 -0
  15. mimicpy/force_matching/compare_top.py +809 -0
  16. mimicpy/force_matching/dresp.py +435 -0
  17. mimicpy/force_matching/nonbonded_forces.py +32 -0
  18. mimicpy/force_matching/opt_ff.py +2114 -0
  19. mimicpy/force_matching/qm_region.py +1960 -0
  20. mimicpy/plugins/__main_installer__.py +76 -0
  21. mimicpy/{__main_vmd__.py → plugins/__main_vmd__.py} +2 -2
  22. mimicpy/plugins/pymol.py +56 -0
  23. mimicpy/plugins/vmd.tcl +78 -0
  24. mimicpy/scripts/__init__.py +1 -1
  25. mimicpy/scripts/cpmd.py +1 -1
  26. mimicpy/scripts/fm_input.py +265 -0
  27. mimicpy/scripts/fmdata.py +120 -0
  28. mimicpy/scripts/mdp.py +1 -1
  29. mimicpy/scripts/ndx.py +1 -1
  30. mimicpy/scripts/script.py +1 -1
  31. mimicpy/topology/__init__.py +1 -1
  32. mimicpy/topology/itp.py +603 -35
  33. mimicpy/topology/mpt.py +1 -1
  34. mimicpy/topology/top.py +254 -15
  35. mimicpy/topology/topol_dict.py +233 -4
  36. mimicpy/utils/__init__.py +1 -1
  37. mimicpy/utils/atomic_numbers.py +1 -1
  38. mimicpy/utils/constants.py +17 -3
  39. mimicpy/utils/elements.py +1 -1
  40. mimicpy/utils/errors.py +1 -1
  41. mimicpy/utils/file_handler.py +1 -1
  42. mimicpy/utils/strings.py +1 -1
  43. mimicpy-0.3.0.dist-info/METADATA +156 -0
  44. mimicpy-0.3.0.dist-info/RECORD +50 -0
  45. {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/WHEEL +1 -1
  46. mimicpy-0.3.0.dist-info/entry_points.txt +4 -0
  47. mimicpy-0.2.0.dist-info/METADATA +0 -86
  48. mimicpy-0.2.0.dist-info/RECORD +0 -38
  49. mimicpy-0.2.0.dist-info/entry_points.txt +0 -3
  50. {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING +0 -0
  51. {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info/licenses}/COPYING.LESSER +0 -0
  52. {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/top_level.txt +0 -0
  53. {mimicpy-0.2.0.dist-info → mimicpy-0.3.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python
2
+
3
+ #
4
+ # MiMiCPy: Python Based Tools for MiMiC
5
+ # Copyright (C) 2020-2023 Bharath Raghavan,
6
+ # Florian Schackert
7
+ #
8
+ # This file is part of MiMiCPy.
9
+ #
10
+ # MiMiCPy is free software: you can redistribute it and/or modify
11
+ # it under the terms of the GNU Lesser General Public License as
12
+ # published by the Free Software Foundation, either version 3 of
13
+ # the License, or (at your option) any later version.
14
+ #
15
+ # MiMiCPy is distributed in the hope that it will be useful, but
16
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
17
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
+ # GNU Lesser General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU Lesser General Public License
21
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
22
+ #
23
+
24
+ from os import path
25
+ import sys
26
+ import platform
27
+ from shutil import copyfileobj
28
+ import argparse
29
+
30
+ def copy_script(source_script, dest):
31
+ # assume the plugin scripts are in the same directory as this script
32
+ source = path.join(path.dirname(path.realpath(__file__)), source_script)
33
+ if not path.isfile(source):
34
+ print("Cannot find plugin file {}".format(source))
35
+ sys.exit(1)
36
+ else:
37
+ with open(source, 'r') as fsrc, open(dest, 'a') as fdest:
38
+ copyfileobj(fsrc, fdest)
39
+
40
+ def main():
41
+ print("Program to install the PrepQM plugins bundled with MiMiCPy")
42
+ parser = argparse.ArgumentParser(prog='mimicpy_plugin_installer')
43
+ parser.add_argument('-vmddir', help='path to the PrepQM VMD plugin directory')
44
+ parser.add_argument('-pymoldir', help='path to the PrepQM PyMOL plugin directory')
45
+ args = parser.parse_args()
46
+
47
+ pymol_dir = args.pymoldir
48
+ vmd_dir = args.vmddir
49
+
50
+ if pymol_dir:
51
+ if platform.system() == 'Windows':
52
+ pymolrc = path.join(pymol_dir, 'pymolrc.py')
53
+ else:
54
+ pymolrc = path.join(pymol_dir, '.pymolrc.py')
55
+
56
+ copy_script('pymol.py', pymolrc)
57
+ print("Wrote the PrepQM PyMOL plugin to {}".format(pymolrc))
58
+
59
+ if vmd_dir:
60
+ if platform.system() == 'Windows':
61
+ vmdrc = path.join(vmd_dir, 'vmd.rc')
62
+ else:
63
+ vmdrc = path.join(vmd_dir, '.vmdrc')
64
+
65
+ copy_script('vmd.tcl', vmdrc)
66
+ print("Wrote the PrepQM VMD plugin to file {}".format(vmdrc))
67
+
68
+ if not pymol_dir and not vmd_dir:
69
+ print("Error! At least one plugin directory should be specified")
70
+
71
+
72
+ if __name__ == '__main__':
73
+ try:
74
+ main()
75
+ except KeyboardInterrupt:
76
+ sys.exit(1)
@@ -2,7 +2,7 @@
2
2
 
3
3
  #
4
4
  # MiMiCPy: Python Based Tools for MiMiC
5
- # Copyright (C) 2020-2021 Bharath Raghavan,
5
+ # Copyright (C) 2020-2023 Bharath Raghavan,
6
6
  # Florian Schackert
7
7
  #
8
8
  # This file is part of MiMiCPy.
@@ -110,7 +110,7 @@ class MockVMDSelector(mimicpy.VMDSelector):
110
110
 
111
111
  def main():
112
112
  if len(sys.argv) < 31:
113
- print("This program is intented to be used solely by the VMD plugin\n")
113
+ print("This program is intended to be used solely by the PrepQM VMD plugin provided as part of MiMiCPy\n")
114
114
  sys.exit(1)
115
115
 
116
116
  top = sys.argv[1]
@@ -0,0 +1,56 @@
1
+ #
2
+ # MiMiCPy: Python Based Tools for MiMiC
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
+ # Florian Schackert
5
+ #
6
+ # This file is part of MiMiCPy.
7
+ #
8
+ # MiMiCPy is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as
10
+ # published by the Free Software Foundation, either version 3 of
11
+ # the License, or (at your option) any later version.
12
+ #
13
+ # MiMiCPy is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+
22
+ import sys
23
+ import mimicpy
24
+ from pymol import cmd
25
+
26
+ def prepqm(top, selection, bound_selection=None, ndx='index.ndx', out='cpmd.inp', inp=None,
27
+ pad=0, abs=False, qma='qmatoms', path=None, q=None, pp=None, find_bound=False):
28
+ try:
29
+ qm = mimicpy.Preparation(mimicpy.PyMOLSelector(top))
30
+ except FileNotFoundError as e:
31
+ print('\n\nError: Cannot find file {}! Exiting..\n'.format(e.filename))
32
+ sys.exit(1)
33
+ except (mimicpy.utils.errors.ParserError, mimicpy.utils.errors.MiMiCPyError) as e:
34
+ print(e)
35
+ sys.exit(1)
36
+
37
+ try:
38
+ qm.add(selection)
39
+
40
+ if bound_selection != None:
41
+ qm.add(bound_selection, True)
42
+
43
+ if find_bound:
44
+ print("Boundary atoms added automatically.")
45
+ qm.find_bound_atoms()
46
+
47
+ qm.get_mimic_input(inp, ndx, out, pad, abs, qma, path, q, pp)
48
+ except mimicpy.utils.errors.SelectionError as e:
49
+ print(e)
50
+ except FileNotFoundError as e:
51
+ print('\n\nError: Cannot find file {}! Exiting..\n'.format(e.filename))
52
+ sys.exit(1)
53
+
54
+ cmd.extend('prepqm', prepqm)
55
+
56
+ ##################################
@@ -0,0 +1,78 @@
1
+ #
2
+ # MiMiCPy: Python Based Tools for MiMiC
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
+ # Florian Schackert
5
+ #
6
+ # This file is part of MiMiCPy.
7
+ #
8
+ # MiMiCPy is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Lesser General Public License as
10
+ # published by the Free Software Foundation, either version 3 of
11
+ # the License, or (at your option) any later version.
12
+ #
13
+ # MiMiCPy is distributed in the hope that it will be useful, but
14
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Lesser General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Lesser General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+
22
+ proc prepqm {
23
+ args
24
+ } {
25
+
26
+ array set options {
27
+ -sele atomselect0
28
+ -molid 0
29
+ -sele_bound None
30
+ -find_bound False
31
+ -inp None
32
+ -out cpmd.inp
33
+ -ndx index.ndx
34
+ -top topol.top
35
+ -q 0
36
+ -pad 0
37
+ -pp None
38
+ -abs False
39
+ -path None
40
+ -qma qmatoms
41
+ }
42
+
43
+ array set options $args
44
+
45
+ set a [molinfo $options(-molid) get a]
46
+ set b [molinfo $options(-molid) get b]
47
+ set c [molinfo $options(-molid) get c]
48
+ set alpha [molinfo $options(-molid) get alpha]
49
+ set beta [molinfo $options(-molid) get beta]
50
+ set gamma [molinfo $options(-molid) get gamma]
51
+
52
+ set name [$options(-sele) get name]
53
+ set index [$options(-sele) get index]
54
+ set resname [$options(-sele) get resname]
55
+ set x [$options(-sele) get x]
56
+ set y [$options(-sele) get y]
57
+ set z [$options(-sele) get z]
58
+
59
+ if {$options(-sele_bound) eq "None"} {
60
+ set name_ "None"
61
+ set index_ 0
62
+ set resname_ "None"
63
+ set x_ 0
64
+ set y_ 0
65
+ set z_ 0
66
+ } else {
67
+ set name_ [$options(-sele_bound) get name]
68
+ set index_ [$options(-sele_bound) get index]
69
+ set resname_ [$options(-sele_bound) get resname]
70
+ set x_ [$options(-sele_bound) get x]
71
+ set y_ [$options(-sele_bound) get y]
72
+ set z_ [$options(-sele_bound) get z]
73
+ }
74
+
75
+ puts $[exec mimicpy_vmd $options(-top) $options(-inp) $options(-ndx) $options(-out) $options(-molid) $options(-sele_bound) $options(-pad) $options(-abs) $options(-qma) $options(-path) $options(-q) $options(-pp)\
76
+ $options(-find_bound) $name $index $resname $x $y $z $name_ $index_ $resname_ $x_ $y_ $z_\
77
+ $a $b $c $alpha $beta $gamma]
78
+ }
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.
mimicpy/scripts/cpmd.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.
@@ -0,0 +1,265 @@
1
+ """Module for force matching input files"""
2
+
3
+ from .script import Script
4
+ import json
5
+
6
+ class FMInput(Script):
7
+ """Formats force matching input files"""
8
+
9
+ def __init__(self, **kwargs):
10
+ super().__init__()
11
+ if 'title' not in kwargs:
12
+ kwargs['title'] = 'Force matching input'
13
+
14
+ # Default values
15
+ self.eq_atoms = None # Can be either global or local format
16
+ self.stride = None
17
+ self.wv = None
18
+ self.we = None
19
+ self.wh = None
20
+ self.wq = None
21
+ self.qm_total_charge = None
22
+ self.reference_charges = None
23
+ self.num_bonds_away = 2
24
+ self.skip_solvent_optimization = True
25
+ self.solvent_resnames = None
26
+ self.solvent_molecules = None
27
+ self.fixed_charge_indices = None
28
+ self.charge_group_constraints = True # Whether to use charge group constraints
29
+ self.weights_to_fix_charges = 100000.0 # Weight to fix charges
30
+ # New parameters for bonded optimization control
31
+ self.optimize_bond_length = True # Whether to optimize bond lengths
32
+ self.optimize_bond_force = True # Whether to optimize bond force constants
33
+ self.optimize_angle_value = True # Whether to optimize angle values
34
+ self.optimize_angle_force = True # Whether to optimize angle force constants
35
+ self.optimize_dihedral_force = True # Whether to optimize dihedral force constants
36
+
37
+ # New parameters for excluded interactions (using atom indices)
38
+ self.exclude_bonds = None # List of atom pairs [(atom1, atom2), ...] to exclude from fitting
39
+ self.exclude_angles = None # List of atom triplets [(atom1, atom2, atom3), ...] to exclude from fitting
40
+ self.exclude_dihedrals = None # List of atom quartets [(atom1, atom2, atom3, atom4), ...] to exclude from fitting
41
+ # Fine-grained hydrogen exclusion controls
42
+ self.exclude_hydrogen_bonds = False
43
+ self.exclude_hydrogen_angles = False
44
+ self.exclude_hydrogen_dihedrals = False
45
+
46
+ # Regularization parameters
47
+ self.regularization = True
48
+ self.regularization_alpha = 0.1 # L2 regularization strength (α)
49
+ self.regularization_prior_widths = None # Prior widths for regularization (auto-generated if None)
50
+ self.regularization_bond_length_width = None # Prior width for bond lengths
51
+ self.regularization_bond_force_width = None # Prior width for bond force constants
52
+ self.regularization_angle_value_width = None # Prior width for angle values
53
+ self.regularization_angle_force_width = None # Prior width for angle force constants
54
+ self.regularization_dihedral_force_width = None # Prior width for dihedral force constants
55
+
56
+ # Optimization method selection
57
+ self.optimization_method = 'hierarchical' # 'hierarchical', 'simultaneous'
58
+
59
+ # Energy-weighted optimization parameters
60
+ self.bond_energy_weight = 1.0 # Weight for bond contributions
61
+ self.angle_energy_weight = 0.1 # Weight for angle contributions
62
+ self.dihedral_energy_weight = 0.01 # Weight for dihedral contributions
63
+
64
+ # Adaptive regularization parameters
65
+ self.adaptive_base_alpha = 0.1 # Base regularization strength for adaptive method
66
+ self.energy_hierarchy_scale = 10.0 # Scale factor for energy hierarchy in adaptive method
67
+
68
+ # Update with provided kwargs
69
+ for key, value in kwargs.items():
70
+ setattr(self, key, value)
71
+
72
+ def __str__(self):
73
+ """Convert the input parameters to a string format"""
74
+ fm_input = f"; Force matching input parameters for {self.title}\n"
75
+
76
+ # Add each parameter if it's not None
77
+ for param in ['stride', 'wv', 'we', 'wh', 'wq', 'qm_total_charge', 'regularization',
78
+ 'reference_charges', 'num_bonds_away', 'skip_solvent_optimization', 'solvent_resnames', 'solvent_molecules',
79
+ 'fixed_charge_indices', 'charge_group_constraints', 'weights_to_fix_charges',
80
+ 'optimize_bond_length', 'optimize_bond_force',
81
+ 'optimize_angle_value', 'optimize_angle_force',
82
+ 'optimize_dihedral_force',
83
+ 'regularization_alpha',
84
+ 'exclude_hydrogen_bonds', 'exclude_hydrogen_angles', 'exclude_hydrogen_dihedrals']:
85
+ value = getattr(self, param)
86
+ if value is not None:
87
+ value_str = str(value)
88
+ fm_input += f"{param}: {value_str}\n"
89
+
90
+ # Add regularization prior widths if specified
91
+ for param in ['regularization_bond_length_width', 'regularization_bond_force_width',
92
+ 'regularization_angle_value_width', 'regularization_angle_force_width',
93
+ 'regularization_dihedral_force_width']:
94
+ value = getattr(self, param)
95
+ if value is not None:
96
+ fm_input += f"{param}: {value}\n"
97
+
98
+ # Add excluded interactions if specified
99
+ for param in ['exclude_bonds', 'exclude_angles', 'exclude_dihedrals']:
100
+ value = getattr(self, param)
101
+ if value is not None:
102
+ # Convert list of tuples to string representation
103
+ value_str = str(value).replace('[', '(').replace(']', ')')
104
+ fm_input += f"{param}: {value_str}\n"
105
+
106
+ # Special handling for eq_atoms
107
+ if self.eq_atoms is not None:
108
+ if isinstance(self.eq_atoms, dict):
109
+ # Handle either global or local format
110
+ for key, value in self.eq_atoms.items():
111
+ if key in ['global', 'local']:
112
+ # Convert list of tuples to string representation
113
+ value_str = str(value).replace('[', '(').replace(']', ')')
114
+ fm_input += f"eq_atoms_{key}: {value_str}\n"
115
+ else:
116
+ # Backward compatibility for old format
117
+ value_str = str(self.eq_atoms).replace('[', '(').replace(']', ')')
118
+ fm_input += f"eq_atoms: {value_str}\n"
119
+
120
+ return fm_input
121
+
122
+ @classmethod
123
+ def from_file(cls, filename):
124
+ """Create an FMInput instance from a file"""
125
+ with open(filename, 'r') as f:
126
+ return cls.from_string(f.read())
127
+
128
+ @classmethod
129
+ def from_string(cls, string):
130
+ """Create an FMInput instance from a string"""
131
+ kwargs = {}
132
+ eq_atoms = {}
133
+
134
+ for line in string.splitlines():
135
+ line = line.strip()
136
+ if line.startswith(('!', '#')) or not line:
137
+ continue
138
+
139
+ if ':' in line:
140
+ line = line.split('#')[0] # Remove comments
141
+ # Split on first colon only
142
+ key, val = line.split(':', 1)
143
+ key = key.strip()
144
+ val = val.strip()
145
+
146
+ # Handle special cases
147
+ if key == 'eq_atoms':
148
+ if val == 'use_atomtypes':
149
+ eq_atoms = val
150
+ elif key == 'eq_atoms_global':
151
+ if 'local' in eq_atoms:
152
+ raise ValueError("Cannot specify both global and local equivalent atoms")
153
+ eq_atoms['global'] = json.loads(val.replace(')', ']').replace('(', '['))
154
+ elif key == 'eq_atoms_local':
155
+ if 'global' in eq_atoms:
156
+ raise ValueError("Cannot specify both global and local equivalent atoms")
157
+ # Parse local format: "mol_name: [atom_ids]"
158
+ mol_name, atom_ids = val.split(':', 1)
159
+ mol_name = mol_name.strip()
160
+ atom_ids = json.loads(atom_ids.replace(')', ']').replace('(', '['))
161
+ eq_atoms['local'] = {mol_name: atom_ids}
162
+ elif key == 'stride':
163
+ val = json.loads(val)
164
+ elif key in ['wv', 'we', 'wh', 'wq', 'qm_total_charge', 'regularization_alpha', 'num_bonds_away', 'fixed_charge_indices', 'weights_to_fix_charges']:
165
+ # Allow lists for wv, we, wh
166
+ if val.startswith('[') or val.startswith('('):
167
+ val = json.loads(val.replace('(', '[').replace(')', ']'))
168
+ else:
169
+ val = float(val) if '.' in val else int(val)
170
+ elif key == 'reference_charges':
171
+ if val == 'ff_charges':
172
+ val = 'ff_charges'
173
+ else:
174
+ val = float(val)
175
+ # Handle boolean parameters for optimization control
176
+ elif key in ['optimize_bond_length', 'optimize_bond_force',
177
+ 'optimize_angle_value', 'optimize_angle_force', 'charge_group_constraints', 'regularization',
178
+ 'optimize_dihedral_force', 'skip_solvent_optimization',
179
+ 'exclude_hydrogen_bonds', 'exclude_hydrogen_angles', 'exclude_hydrogen_dihedrals']:
180
+ val = val.lower()
181
+ if val in ('true', 'yes', '1', 't', 'y'):
182
+ val = True
183
+ elif val in ('false', 'no', '0', 'f', 'n'):
184
+ val = False
185
+ else:
186
+ raise ValueError(f"Invalid boolean value '{val}' for parameter {key}")
187
+ # Handle regularization prior widths
188
+ elif key in ['regularization_bond_length_width', 'regularization_bond_force_width',
189
+ 'regularization_angle_value_width', 'regularization_angle_force_width',
190
+ 'regularization_dihedral_force_width']:
191
+ val = float(val)
192
+ # Handle excluded interactions (using atom indices)
193
+ elif key in ['exclude_bonds', 'exclude_angles', 'exclude_dihedrals']:
194
+ # Parse list of tuples of atom indices
195
+ val = json.loads(val.replace('(', '[').replace(')', ']'))
196
+ # Convert to list of tuples
197
+ val = [tuple(x) for x in val]
198
+ elif key in ['optimization_method', 'solvent_resnames', 'solvent_molecules']:
199
+ # Keep as string, validation will be done later
200
+ val = val.strip()
201
+ elif key in ['bond_energy_weight', 'angle_energy_weight', 'dihedral_energy_weight',
202
+ 'adaptive_base_alpha', 'energy_hierarchy_scale']:
203
+ val = float(val)
204
+
205
+ if key not in ['eq_atoms', 'eq_atoms_global', 'eq_atoms_local']:
206
+ kwargs[key] = val
207
+
208
+ # Add eq_atoms to kwargs if any were found
209
+ if eq_atoms:
210
+ kwargs['eq_atoms'] = eq_atoms
211
+
212
+ return cls(**kwargs)
213
+
214
+ def to_dict(self):
215
+ """Convert the input parameters to a dictionary"""
216
+ return {
217
+ 'eq_atoms': self.eq_atoms,
218
+ 'stride': self.stride,
219
+ 'wv': self.wv,
220
+ 'we': self.we,
221
+ 'wh': self.wh,
222
+ 'wq': self.wq,
223
+ 'qm_total_charge': self.qm_total_charge,
224
+ 'reference_charges': self.reference_charges,
225
+ 'num_bonds_away': self.num_bonds_away,
226
+ 'skip_solvent_optimization': self.skip_solvent_optimization,
227
+ 'solvent_resnames': self.solvent_resnames,
228
+ 'solvent_molecules': self.solvent_molecules,
229
+ 'fixed_charge_indices': self.fixed_charge_indices,
230
+ 'charge_group_constraints': self.charge_group_constraints,
231
+ 'weights_to_fix_charges': self.weights_to_fix_charges,
232
+ 'optimize_bond_length': self.optimize_bond_length,
233
+ 'optimize_bond_force': self.optimize_bond_force,
234
+ 'optimize_angle_value': self.optimize_angle_value,
235
+ 'optimize_angle_force': self.optimize_angle_force,
236
+ 'optimize_dihedral_force': self.optimize_dihedral_force,
237
+ 'exclude_bonds': self.exclude_bonds,
238
+ 'exclude_angles': self.exclude_angles,
239
+ 'exclude_dihedrals': self.exclude_dihedrals,
240
+ 'regularization': self.regularization,
241
+ 'regularization_alpha': self.regularization_alpha,
242
+ 'regularization_prior_widths': self.regularization_prior_widths,
243
+ 'regularization_bond_length_width': self.regularization_bond_length_width,
244
+ 'regularization_bond_force_width': self.regularization_bond_force_width,
245
+ 'regularization_angle_value_width': self.regularization_angle_value_width,
246
+ 'regularization_angle_force_width': self.regularization_angle_force_width,
247
+ 'regularization_dihedral_force_width': self.regularization_dihedral_force_width,
248
+ 'exclude_hydrogen_bonds': self.exclude_hydrogen_bonds,
249
+ 'exclude_hydrogen_angles': self.exclude_hydrogen_angles,
250
+ 'exclude_hydrogen_dihedrals': self.exclude_hydrogen_dihedrals,
251
+ 'optimization_method': getattr(self, 'optimization_method', 'hierarchical'),
252
+ 'bond_energy_weight': getattr(self, 'bond_energy_weight', 1.0),
253
+ 'angle_energy_weight': getattr(self, 'angle_energy_weight', 0.1),
254
+ 'dihedral_energy_weight': getattr(self, 'dihedral_energy_weight', 0.01),
255
+ 'adaptive_base_alpha': getattr(self, 'adaptive_base_alpha', 0.1),
256
+ 'energy_hierarchy_scale': getattr(self, 'energy_hierarchy_scale', 10.0)
257
+ }
258
+
259
+ def copy(self):
260
+ """Create a copy of the FMInput object"""
261
+ return FMInput(**self.to_dict())
262
+
263
+ def __deepcopy__(self, memo):
264
+ """Implement deep copy for FMInput objects"""
265
+ return self.copy()
@@ -0,0 +1,120 @@
1
+ import numpy as np
2
+ import json
3
+ import h5py
4
+ from pathlib import Path
5
+ from .script import Script
6
+
7
+ class FMDataset(Script):
8
+
9
+ def __init__(self, data_file, h5_file=None) -> None:
10
+ super().__init__()
11
+ self.data_file = Path(data_file)
12
+ self.h5_file = Path(h5_file) if h5_file else self.data_file.with_suffix('.h5')
13
+
14
+ if self.h5_file.exists():
15
+ self._load_h5()
16
+ else:
17
+ self._convert_json_to_h5()
18
+
19
+ def _convert_json_to_h5(self):
20
+ """Convert JSON data to HDF5 format"""
21
+ self.fm_data = []
22
+
23
+ with open(self.data_file) as dt:
24
+ json_str = dt.read()
25
+
26
+ # Parse JSON data
27
+ bracecount = 0
28
+ jsonobj = ''
29
+ for jsonstr in json_str:
30
+ bracecount += jsonstr.count('{')
31
+ bracecount -= jsonstr.count('}')
32
+ jsonobj += jsonstr
33
+ if (bracecount==0 and jsonobj.strip()):
34
+ config_dict = json.loads(jsonobj)
35
+ self.fm_data.append(config_dict)
36
+ jsonobj = ''
37
+
38
+ # Convert to HDF5
39
+ with h5py.File(self.h5_file, 'w') as f:
40
+ # Create groups for each configuration
41
+ for i, config in enumerate(self.fm_data):
42
+ grp = f.create_group(f'config_{i}')
43
+
44
+ # Store atom data as structured arrays
45
+ atoms = config['atoms']
46
+ dtype = [
47
+ ('id', 'i4'), # Add atom ID field
48
+ ('region', 'i4'),
49
+ ('coordinate', 'f8', (3,)),
50
+ ('electric_potential', 'f8'),
51
+ ('electric_field', 'f8', (3,)),
52
+ ('hirshfeld_charge', 'f8'),
53
+ ('force', 'f8', (3,))
54
+ ]
55
+
56
+ data = np.zeros(len(atoms), dtype=dtype)
57
+ for j, atom in enumerate(atoms):
58
+ # Convert coordinates to numpy array if they aren't already
59
+ coords = np.array(atom['coordinate'], dtype=np.float64)
60
+ efield = np.array(atom['electric_field'], dtype=np.float64)
61
+ force = np.array(atom.get('force', [0.0, 0.0, 0.0]), dtype=np.float64)
62
+
63
+ # Handle electric_potential which might be a list
64
+ epot = atom['electric_potential']
65
+ if isinstance(epot, list):
66
+ epot = epot[0] if epot else 0.0
67
+
68
+ data[j] = (
69
+ int(atom['id']), # Add atom ID
70
+ int(atom['region']),
71
+ coords,
72
+ float(epot),
73
+ efield,
74
+ float(atom['hirshfeld_charge']),
75
+ force
76
+ )
77
+
78
+ grp.create_dataset('atoms', data=data, compression='gzip')
79
+
80
+ # Clear the JSON data from memory
81
+ self.fm_data = None
82
+
83
+ def _load_h5(self):
84
+ """Load data from HDF5 file"""
85
+ self.fm_data = None # We don't need to keep the data in memory
86
+
87
+ def get_configuration_properties(self, idx, property, region='all'):
88
+ """Get properties for a specific configuration"""
89
+ with h5py.File(self.h5_file, 'r') as f:
90
+ config = f[f'config_{idx}']['atoms']
91
+
92
+ if region == 'all':
93
+ return config[property][:]
94
+ elif region == 'mm':
95
+ mask = config['region'][:] != 1
96
+ return config[property][mask]
97
+ elif region == 'qm':
98
+ mask = config['region'][:] == 1
99
+ return config[property][mask]
100
+ else:
101
+ raise ValueError(f"Invalid region: {region}")
102
+
103
+ def get_atom_properties(self, idx, atom_id, property):
104
+ """Get properties for a specific atom"""
105
+ with h5py.File(self.h5_file, 'r') as f:
106
+ return f[f'config_{idx}']['atoms'][property][atom_id]
107
+
108
+ def __len__(self):
109
+ """Return the number of configurations in the dataset"""
110
+ with h5py.File(self.h5_file, 'r') as f:
111
+ config_keys = [key for key in f.keys() if key.startswith('config_')]
112
+ return len(config_keys)
113
+
114
+ def __str__(self):
115
+ n_configs = len(self)
116
+ return f"FMDataset with {n_configs} configurations from {self.data_file}"
117
+
118
+ @classmethod
119
+ def from_string(cls, string):
120
+ raise NotImplementedError("FMDataset does not support creation from string")
mimicpy/scripts/mdp.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.
mimicpy/scripts/ndx.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.
mimicpy/scripts/script.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # MiMiCPy: Python Based Tools for MiMiC
3
- # Copyright (C) 2020-2021 Bharath Raghavan,
3
+ # Copyright (C) 2020-2023 Bharath Raghavan,
4
4
  # Florian Schackert
5
5
  #
6
6
  # This file is part of MiMiCPy.