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
|
@@ -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-
|
|
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
|
|
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]
|
mimicpy/plugins/pymol.py
ADDED
|
@@ -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
|
+
##################################
|
mimicpy/plugins/vmd.tcl
ADDED
|
@@ -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
|
+
}
|
mimicpy/scripts/__init__.py
CHANGED
mimicpy/scripts/cpmd.py
CHANGED
|
@@ -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
mimicpy/scripts/ndx.py
CHANGED
mimicpy/scripts/script.py
CHANGED