Skip to content

Commit 20e6fd5

Browse files
ehhartmannEric HartmannKRiedmiller
authored
BREAKING CHANGE: Parameterize tool (#361)
Requires reinstall because it changes cli names. * everything except for gro handling * black * update docs * typing * feat: let del_atom deal with multiple atoms at a time. * black * single del_atom call * typo * add gro treatment (untested) * bug fix * fix gro handling * new black * minor filename fix --------- Co-authored-by: Eric Hartmann <hartmaec@rh05659.villa-bosch.de> Co-authored-by: Kai Riedmiller <kai.riedmiller@web.de>
1 parent 4d9cb9a commit 20e6fd5

16 files changed

+145
-150
lines changed

guide/references/cmd_ref.qmd

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The prefered method of starting a KIMMDY run is via the command line, though [Py
2424
This module builds or restores the example directory in the package.
2525

2626
```{python}
27-
!kimmdy-remove-hydrogen --help
27+
!kimmdy-modify-top --help
2828
```
2929

3030
## Examples

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,4 @@ console_scripts =
7777
kimmdy = kimmdy.cmd:entry_point_kimmdy
7878
kimmdy-analysis = kimmdy.analysis:entry_point_analysis
7979
kimmdy-build-examples = kimmdy.tools:entry_point_build_examples
80-
kimmdy-remove-hydrogen = kimmdy.tools:entry_point_remove_hydrogen
80+
kimmdy-modify-top = kimmdy.tools:entry_point_modify_top

src/kimmdy/analysis.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Analysis tools for KIMMDY runs.
22
For command line usage, run `kimmdy-analysis -h`.
33
"""
4+
45
from typing import Union
56
from pathlib import Path
67
import MDAnalysis as mda

src/kimmdy/cmd.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Functions for starting KIMMDY either from python or the command line.
33
Other entry points such as `kimmdy-analysis` also live here.
44
"""
5+
56
import argparse
67
from os import chmod
78
from pathlib import Path
@@ -107,7 +108,7 @@ def get_cmdline_args() -> argparse.Namespace:
107108
parser = argparse.ArgumentParser(
108109
description="Welcome to KIMMDY. `kimmdy` runs KIMMDY, further tools "
109110
"are available as `kimmdy-...` commands. These are `-analysis`, "
110-
"`-remove-hydrogen` and `-build-examples`. Access their help with "
111+
"`-modify-top` and `-build-examples`. Access their help with "
111112
"`kimmdy-... -h.`"
112113
)
113114
parser.add_argument(

src/kimmdy/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Read and validate kimmdy.yml configuration files
33
and package into a parsed format for internal use.
44
"""
5+
56
from __future__ import annotations
67
import shutil
78
from pprint import pformat, pprint

src/kimmdy/constants.py

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Comstants used throughout KIMMDY
33
"""
44

5-
65
ATOM_ID_FIELDS = {
76
"atoms": [0, 5], # atomnr, chargegroup
87
"bonds": [0, 1],

src/kimmdy/kmc.py

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
because of the fundamental premise of chemical kinetics
88
and because we have one reactant molecule
99
"""
10+
1011
from typing import Optional
1112
import logging
1213
import numpy as np

src/kimmdy/parsing.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
All read_<...> and write_<...> functions.
33
"""
4+
45
import os
56
import logging
67
import json

src/kimmdy/recipe.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Contains the Reaction Recipe, RecipeStep and RecipeCollection.
22
"""
3+
34
from __future__ import annotations
45
from typing import TYPE_CHECKING, Callable, Optional
56

src/kimmdy/runmanager.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
It manages the queue of tasks, communicates with the
55
rest of the program and keeps track of global state.
66
"""
7+
78
from __future__ import annotations
89
import logging
910
from pathlib import Path

src/kimmdy/schema.py

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- type
1111
- required
1212
"""
13+
1314
import json
1415
import importlib.resources as pkg_resources
1516
import logging

src/kimmdy/tools.py

+103-117
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from pathlib import Path
66
import shutil
77
import argparse
8+
from typing import Optional
89

910
from kimmdy.topology.topology import Topology
1011
from kimmdy.parsing import read_top, write_top
11-
from kimmdy.utils import run_gmx, run_shell_cmd
1212
from kimmdy.plugins import parameterization_plugins
1313
from kimmdy.plugins import discover_plugins
1414

@@ -83,163 +83,149 @@ def entry_point_build_examples():
8383
build_examples(args.restore)
8484

8585

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,
9089
parameterize: bool,
91-
equilibrate: bool,
92-
gmx_mdrun_flags: str = "",
90+
removeH: Optional[list[int]],
91+
gro_str: Optional[str],
9392
):
94-
"""Remove one hydrogen from a gro and top file to create a radical.
93+
"""Modify topology in various ways.
9594
9695
Parameters
9796
----------
98-
gro
99-
Path to GROMACS gro file
100-
top
97+
top_path
10198
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
104101
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.
108109
"""
109-
gro_path = Path(gro)
110-
top_path = Path(top)
111110

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+
)
113132

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)
118148

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)
121150

122151
# parameterize with grappa
123152
if parameterize:
153+
# load grappa
124154
discover_plugins()
125-
126155
if "grappa" in parameterization_plugins.keys():
127-
topology.parametrizer = parameterization_plugins["grappa"]()
128-
156+
top.parametrizer = parameterization_plugins["grappa"]()
129157
else:
130158
raise KeyError(
131159
"No grappa in parameterization plugins. Can't continue to parameterize molecule"
132160
)
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+
)
133185

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:
206188
"""
207-
parse cmdline args for remove_hydrogen
189+
parse cmdline args for modify_top
208190
"""
209191
parser = argparse.ArgumentParser(
210-
description="Welcome to the KIMMDY remove hydrogen module",
192+
description="Welcome to the KIMMDY modify top module",
211193
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
212194
)
213-
parser.add_argument("gro", help="GROMACS gro file")
214195
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+
218198
parser.add_argument(
219199
"-p",
220200
"--parameterize",
221201
action="store_true",
222-
help="Parameterize topology with grappa after removing hydrogen.",
202+
help="Parameterize topology with grappa.",
223203
default=False,
224204
)
225205
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,
231217
)
232218
return parser.parse_args()
233219

234220

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()
238224

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)
240226

241227

242-
## dot graphs
228+
# dot graphs
243229
def topology_to_edgelist(top: Topology):
244230
"""Convert a topology to a list of edges for a dot graph."""
245231
ls = []

src/kimmdy/topology/atomic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -634,9 +634,9 @@ def from_section(cls, residue, d: dict[str, list[list[str]]]):
634634
if ls := d.get("dihedrals"):
635635
for l in ls["content"]:
636636
proper = ResidueProperSpec.from_top_line(l)
637-
propers[
638-
(proper.atom1, proper.atom2, proper.atom3, proper.atom4)
639-
] = proper
637+
propers[(proper.atom1, proper.atom2, proper.atom3, proper.atom4)] = (
638+
proper
639+
)
640640
if ls := d.get("impropers"):
641641
for l in ls["content"]:
642642
improper = ResidueImproperSpec.from_top_line(l)

0 commit comments

Comments
 (0)