|
5 | 5 | from pathlib import Path
|
6 | 6 | import shutil
|
7 | 7 | import argparse
|
| 8 | +from typing import Optional |
8 | 9 |
|
9 | 10 | from kimmdy.topology.topology import Topology
|
10 | 11 | from kimmdy.parsing import read_top, write_top
|
11 |
| -from kimmdy.utils import run_gmx, run_shell_cmd |
12 | 12 | from kimmdy.plugins import parameterization_plugins
|
13 | 13 | from kimmdy.plugins import discover_plugins
|
14 | 14 |
|
@@ -83,163 +83,149 @@ def entry_point_build_examples():
|
83 | 83 | build_examples(args.restore)
|
84 | 84 |
|
85 | 85 |
|
86 |
| -def remove_hydrogen( |
87 |
| - gro: str, |
88 |
| - top: str, |
89 |
| - nr: str, |
| 86 | +def modify_top( |
| 87 | + top_str: str, |
| 88 | + out_str: str, |
90 | 89 | parameterize: bool,
|
91 |
| - equilibrate: bool, |
92 |
| - gmx_mdrun_flags: str = "", |
| 90 | + removeH: Optional[list[int]], |
| 91 | + gro_str: Optional[str], |
93 | 92 | ):
|
94 |
| - """Remove one hydrogen from a gro and top file to create a radical. |
| 93 | + """Modify topology in various ways. |
95 | 94 |
|
96 | 95 | Parameters
|
97 | 96 | ----------
|
98 |
| - gro |
99 |
| - Path to GROMACS gro file |
100 |
| - top |
| 97 | + top_path |
101 | 98 | Path to GROMACS top file
|
102 |
| - nr |
103 |
| - Atom number as indicated in the GROMACS gro and top file |
| 99 | + out_path |
| 100 | + Output topology file name, stem also used for gro |
104 | 101 | parameterize
|
105 |
| - Parameterize topology with grappa after removing hydrogen. |
106 |
| - equilibrate |
107 |
| - Do a minimization and equilibration with GROMACS. Uses mdp files from kimmdy assets. |
| 102 | + Parameterize topology with grappa after removing hydrogen |
| 103 | + removeH |
| 104 | + Remove one or more hydrogens by atom nrs in the top file. |
| 105 | + One based. |
| 106 | + gro_path |
| 107 | + GROMACS gro input file. Updates structure when deleting H. |
| 108 | + Output named like top output. |
108 | 109 | """
|
109 |
| - gro_path = Path(gro) |
110 |
| - top_path = Path(top) |
111 | 110 |
|
112 |
| - topology = Topology(read_top(top_path)) |
| 111 | + top_path = Path(top_str) |
| 112 | + out_path = top_path.with_name(out_str).with_suffix(".top") |
| 113 | + update_map = {} |
| 114 | + gro_out = None |
| 115 | + |
| 116 | + if gro_str: |
| 117 | + if removeH: |
| 118 | + gro_path = Path(gro_str) |
| 119 | + gro_out = gro_path.with_stem(out_path.stem) |
| 120 | + while gro_out.exists(): |
| 121 | + gro_out = gro_out.with_stem(gro_out.stem + "_mod") |
| 122 | + |
| 123 | + print( |
| 124 | + "Changing topology\n" |
| 125 | + f"top: \t\t\t{top_str}\n" |
| 126 | + f"output: \t\t{out_str}\n" |
| 127 | + f"parameterize: \t\t{parameterize}\n" |
| 128 | + f"remove hydrogen: \t{removeH}\n" |
| 129 | + f"optional gro: \t\t{gro_str}\n" |
| 130 | + f"gro output: \t\t{gro_out}\n" |
| 131 | + ) |
113 | 132 |
|
114 |
| - ## check for input validity |
115 |
| - assert (atom_type := topology.atoms[nr].type).startswith( |
116 |
| - "H" |
117 |
| - ), f"Wrong atom type {atom_type} with nr {nr} for remove hydrogen, should start with 'H'" |
| 133 | + top = Topology(read_top(top_path)) |
| 134 | + |
| 135 | + # remove hydrogen |
| 136 | + if removeH: |
| 137 | + broken_idxs = [] |
| 138 | + # check for input validity |
| 139 | + for i, nr in enumerate(removeH): |
| 140 | + if not (atom_type := top.atoms[str(nr)].type).startswith("H"): |
| 141 | + print( |
| 142 | + f"Wrong atom type {atom_type} with nr {nr} for remove hydrogen, should start with 'H'." |
| 143 | + ) |
| 144 | + broken_idxs.append(i) |
| 145 | + continue |
| 146 | + for broken_idx in sorted(broken_idxs, reverse=True): |
| 147 | + removeH.pop(broken_idx) |
118 | 148 |
|
119 |
| - ## deal with top file, order is important here |
120 |
| - # [heavy_nr] = topology.atoms[nr].bound_to_nrs |
| 149 | + top.del_atom([str(nr) for nr in removeH], parameterize=parameterize) |
121 | 150 |
|
122 | 151 | # parameterize with grappa
|
123 | 152 | if parameterize:
|
| 153 | + # load grappa |
124 | 154 | discover_plugins()
|
125 |
| - |
126 | 155 | if "grappa" in parameterization_plugins.keys():
|
127 |
| - topology.parametrizer = parameterization_plugins["grappa"]() |
128 |
| - |
| 156 | + top.parametrizer = parameterization_plugins["grappa"]() |
129 | 157 | else:
|
130 | 158 | raise KeyError(
|
131 | 159 | "No grappa in parameterization plugins. Can't continue to parameterize molecule"
|
132 | 160 | )
|
| 161 | + # require parameterization when writing topology to dict |
| 162 | + top.needs_parameterization = True |
| 163 | + |
| 164 | + # write top file |
| 165 | + write_top(top.to_dict(), out_path) |
| 166 | + |
| 167 | + # deal with gro file |
| 168 | + if gro_str: |
| 169 | + if removeH: |
| 170 | + with open(gro_path, "r") as f: |
| 171 | + gro_raw = f.readlines() |
| 172 | + |
| 173 | + with open(gro_out, "w") as f: |
| 174 | + f.write(gro_raw[0]) |
| 175 | + f.write(f" {str(int(gro_raw[1])-len(removeH))}\n") |
| 176 | + |
| 177 | + for i, line in enumerate(gro_raw[2:]): |
| 178 | + if i + 1 in removeH: |
| 179 | + continue |
| 180 | + f.write(line) |
| 181 | + else: |
| 182 | + print( |
| 183 | + "Gro file supplied but no action requested that requires changes to it." |
| 184 | + ) |
133 | 185 |
|
134 |
| - update_map = topology.del_atom(nr, parameterize=parameterize) |
135 |
| - |
136 |
| - ## write top file |
137 |
| - top_stem = top_path.stem |
138 |
| - top_outpath = top_path.with_stem(top_stem + f"_d{nr}") |
139 |
| - write_top(topology.to_dict(), top_outpath) |
140 |
| - |
141 |
| - ## deal with gro file |
142 |
| - with open(gro_path, "r") as f: |
143 |
| - gro_raw = f.readlines() |
144 |
| - |
145 |
| - if len(gro_raw[3].split()) > 6: |
146 |
| - print("gro file likely contains velocities. These will be discarded.") |
147 |
| - |
148 |
| - gro_stem = gro_path.stem |
149 |
| - gro_outpath = gro_path.with_stem(gro_stem + f"_d{nr}") |
150 |
| - with open(gro_outpath, "w") as f: |
151 |
| - f.write(gro_raw[0]) |
152 |
| - f.write(f" {str(int(gro_raw[1])-1)}\n") |
153 |
| - for line in gro_raw[2:-1]: |
154 |
| - linesplit = line.split() |
155 |
| - if val := update_map.get(linesplit[2]): |
156 |
| - linesplit[2] = val |
157 |
| - else: |
158 |
| - continue |
159 |
| - f.write("{:>8s} {:>4s} {:>4s} {:>7s} {:>7s} {:>7s}\n".format(*linesplit)) |
160 |
| - # format not exactly as defined by GROMACS but should be good enough |
161 |
| - f.write(gro_raw[-1]) |
162 |
| - |
163 |
| - ## minimize and equilibrate system using GROMACS |
164 |
| - if equilibrate: |
165 |
| - cwd = top_outpath.parent |
166 |
| - run_shell_cmd( |
167 |
| - f"cp {str(Path(__file__).parents[2] / 'tests'/'test_files'/'assets'/'md'/'*')} .", |
168 |
| - cwd=cwd, |
169 |
| - ) |
170 |
| - run_gmx( |
171 |
| - f"gmx editconf -f {gro_outpath.name} -o {gro_outpath.stem}_box.gro -c -d 1.0 -bt dodecahedron", |
172 |
| - cwd=cwd, |
173 |
| - ) |
174 |
| - run_gmx( |
175 |
| - f"gmx solvate -cp {gro_outpath.stem}_box.gro -p {top_outpath.name} -o {gro_outpath.stem}_solv.gro", |
176 |
| - cwd=cwd, |
177 |
| - ) |
178 |
| - run_gmx( |
179 |
| - f"gmx grompp -f ions.mdp -c {gro_outpath.stem}_solv.gro -p {top_outpath.name} -o {gro_outpath.stem}_genion.tpr", |
180 |
| - cwd=cwd, |
181 |
| - ) |
182 |
| - run_gmx( |
183 |
| - f"echo 'SOL' | gmx genion -s {gro_outpath.stem}_genion.tpr -p {top_outpath.name} -o {gro_outpath.stem}_ion.gro -conc 0.15 -neutral", |
184 |
| - cwd=cwd, |
185 |
| - ) |
186 |
| - run_gmx( |
187 |
| - f"gmx grompp -f minim.mdp -c {gro_outpath.stem}_ion.gro -p {top_outpath.name} -o {gro_outpath.stem}_min.tpr -maxwarn 2", |
188 |
| - cwd=cwd, |
189 |
| - ) |
190 |
| - run_gmx(f"gmx mdrun -deffnm {gro_outpath.stem}_min" + gmx_mdrun_flags, cwd=cwd) |
191 |
| - run_gmx( |
192 |
| - f"gmx grompp -f nvt.mdp -c {gro_outpath.stem}_min.gro -p {top_outpath.name} -o {gro_outpath.stem}_nvt.tpr -maxwarn 2", |
193 |
| - cwd=cwd, |
194 |
| - ) |
195 |
| - run_gmx(f"gmx mdrun -deffnm {gro_outpath.stem}_nvt" + gmx_mdrun_flags, cwd=cwd) |
196 |
| - run_gmx( |
197 |
| - f"gmx grompp -f npt.mdp -c {gro_outpath.stem}_nvt.gro -p {top_outpath.name} -o {gro_outpath.stem}_npt.tpr -maxwarn 2", |
198 |
| - cwd=cwd, |
199 |
| - ) |
200 |
| - run_gmx( |
201 |
| - f"gmx mdrun -v -deffnm {gro_outpath.stem}_npt" + gmx_mdrun_flags, cwd=cwd |
202 |
| - ) |
203 |
| - |
204 |
| - |
205 |
| -def get_remove_hydrogen_cmdline_args() -> argparse.Namespace: |
| 186 | + |
| 187 | +def get_modify_top_cmdline_args() -> argparse.Namespace: |
206 | 188 | """
|
207 |
| - parse cmdline args for remove_hydrogen |
| 189 | + parse cmdline args for modify_top |
208 | 190 | """
|
209 | 191 | parser = argparse.ArgumentParser(
|
210 |
| - description="Welcome to the KIMMDY remove hydrogen module", |
| 192 | + description="Welcome to the KIMMDY modify top module", |
211 | 193 | formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
212 | 194 | )
|
213 |
| - parser.add_argument("gro", help="GROMACS gro file") |
214 | 195 | parser.add_argument("top", help="GROMACS top file")
|
215 |
| - parser.add_argument( |
216 |
| - "nr", help="Atom number as indicated in the GROMACS gro and top file" |
217 |
| - ) |
| 196 | + parser.add_argument("out", help="Output top file name") |
| 197 | + |
218 | 198 | parser.add_argument(
|
219 | 199 | "-p",
|
220 | 200 | "--parameterize",
|
221 | 201 | action="store_true",
|
222 |
| - help="Parameterize topology with grappa after removing hydrogen.", |
| 202 | + help="Parameterize topology with grappa.", |
223 | 203 | default=False,
|
224 | 204 | )
|
225 | 205 | parser.add_argument(
|
226 |
| - "-e", |
227 |
| - "--equilibrate", |
228 |
| - action="store_true", |
229 |
| - help="Do a minimization and equilibration with GROMACS. Uses mdp files from kimmdy assets.", |
230 |
| - default=False, |
| 206 | + "-r", |
| 207 | + "--removeH", |
| 208 | + help="Remove one or more hydrogens by atom nrs in the top file.", |
| 209 | + nargs="+", |
| 210 | + type=int, |
| 211 | + ) |
| 212 | + parser.add_argument( |
| 213 | + "-c", |
| 214 | + "--grofile", |
| 215 | + help="If necessary, also apply actions on gro file to create a compatible gro/top file pair.", |
| 216 | + type=str, |
231 | 217 | )
|
232 | 218 | return parser.parse_args()
|
233 | 219 |
|
234 | 220 |
|
235 |
| -def entry_point_remove_hydrogen(): |
236 |
| - """Remove hydrogen by atom nr in a gro and topology file""" |
237 |
| - args = get_remove_hydrogen_cmdline_args() |
| 221 | +def entry_point_modify_top(): |
| 222 | + """Modify topology file in various ways""" |
| 223 | + args = get_modify_top_cmdline_args() |
238 | 224 |
|
239 |
| - remove_hydrogen(args.gro, args.top, args.nr, args.parameterize, args.equilibrate) |
| 225 | + modify_top(args.top, args.out, args.parameterize, args.removeH, args.grofile) |
240 | 226 |
|
241 | 227 |
|
242 |
| -## dot graphs |
| 228 | +# dot graphs |
243 | 229 | def topology_to_edgelist(top: Topology):
|
244 | 230 | """Convert a topology to a list of edges for a dot graph."""
|
245 | 231 | ls = []
|
|
0 commit comments