Skip to content

Commit b711668

Browse files
jmbuhrEric Hartmann
and
Eric Hartmann
authored
fix: restarting (#504)
feat: pass restart flag via config for cli fix: autofildict (files.input) should return None on missing refactor: simplify get_task_directories feat: exit early if run already finished chore: add grappa to dev requirements feat: add config.slurm.runcmd to replace sbatch in jobscript for local testing feat(analysis): get dir from kimmdy.yml input file if not specified in analysis feat: dummy_first kmc method that ignores rates and always chooses the first recipe for debugging chore: fix gh ci (publish workflow is not allowed to be re-usable workflow) fix: fully specify --nodes for slurm fix: silently ignore directories in the kimmdy output folder that don't match the kimmdy task naming scheme fix: clean up cli arguments fix: handle latest files in intermediate tasks like relax and place and improve logging of the filehistory, which is now used in a unit test for said feature fix: do not run grompp for checkpoint restarts --------- Co-authored-by: Eric Hartmann <hartmaec@rh05659.villa-bosch.de>
1 parent f95becc commit b711668

28 files changed

+879
-598
lines changed

.github/workflows/publish.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
name: publish
22

33
on:
4-
workflow_call:
54
workflow_dispatch:
6-
5+
push:
6+
branches: [ main ]
77

88
jobs:
99
build:
10+
if: contains(github.event.head_commit.message, 'release-please--branches--main')
1011
name: Build distribution 📦
1112
runs-on: ubuntu-latest
12-
1313
steps:
1414
- uses: actions/checkout@v4
1515
- name: Set up Python

.github/workflows/release.yml

-6
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,4 @@ jobs:
2929
git push origin :stable || true
3030
git tag -a stable -m "Last Stable Release"
3131
git push origin stable
32-
publish:
33-
needs: release
34-
if: ${{ needs.release.outputs.created }}
35-
permissions:
36-
id-token: write
37-
uses: ./.github/workflows/publish.yml
3832

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ package-lock.json
127127
/.quarto/
128128

129129
/.luarc.json
130+
_modules.sh

README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ pip install kimmdy[reactions,analysis]
3131
However, this is only half the fun!
3232

3333
KIMMDY has two exciting plugins in the making, which properly parameterize your molecules
34-
for radicals using GrAPPa (Graph Attentional Protein Parametrization) and predict
35-
Hydrogen Atom Transfer (HAT) rates.
34+
for radicals using GrAPPa (Graph Attentional Protein Parametrization) and predict Hydrogen Atom Transfer (HAT) rates.
3635

3736
Full installation instructions are available [here](https://graeter-group.github.io/kimmdy/guide/how-to/install-ml-plugins.html)
3837

example/alanine_hat_naive/kimmdy.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
dryrun: false
22
name: 'alanine_hat_000'
3+
max_hours: 23
34
max_tasks: 100
45
gromacs_alias: 'gmx'
56
gmx_mdrun_flags: -maxh 24 -dlb yes
67
ff: 'amber99sb-star-ildnp.ff' # optional, dir endinng with .ff by default
78
top: 'Ala_out.top'
89
gro: 'npt.gro'
910
ndx: 'index.ndx'
10-
kmc: "rfkmc"
11+
kmc: rfkmc
1112
mds:
1213
equilibrium:
1314
mdp: 'md.mdp'

example/alanine_hat_naive/kimmdy_restart.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ top: 'Ala_out.top'
88
gro: 'npt.gro'
99
ndx: 'index.ndx'
1010
kmc: "rfkmc"
11-
restart:
12-
run_directory: 'alanine_hat_000'
11+
restart: true
1312
mds:
1413
equilibrium:
1514
mdp: 'md.mdp'

example/alanine_hat_naive/kimmdy_restart_task.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ top: 'Ala_out.top'
88
gro: 'npt.gro'
99
ndx: 'index.ndx'
1010
kmc: "rfkmc"
11-
restart:
12-
run_directory: 'alanine_hat_000'
11+
restart: true
1312
mds:
1413
equilibrium:
1514
mdp: 'md.mdp'

example/charged_peptide_homolysis_hat_naive/kimmdy.yml

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ reactions:
3030
frequency_factor: 100000000
3131
h_cutoff: 3
3232
polling_rate: 1
33-
plot_rates: true
3433
save_recipes: true
3534
sequence:
3635
- equilibrium

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
# git+ssh://git@github.com/graeter-group/grappa.git
33
## replace with path to your local copy of the plugins
44
## for plugin development and kimmdy testing
5+
grappa-ff --index-url https://download.pytorch.org/whl/cpu
56
-e ../kimmdy-grappa
67
-e ../kimmdy-reactions

src/kimmdy/analysis.py

+82-28
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Optional, Union
1212
from collections import defaultdict
1313

14-
import matplotlib as mpl
14+
import matplotlib.axis
1515
import matplotlib.pyplot as plt
1616
import MDAnalysis as mda
1717
import numpy as np
@@ -20,9 +20,11 @@
2020
import seaborn.objects as so
2121
from seaborn import axes_style
2222

23+
from kimmdy.config import Config
2324
from kimmdy.parsing import read_json, write_json, read_time_marker
25+
from kimmdy.plugins import discover_plugins
2426
from kimmdy.recipe import Bind, Break, DeferredRecipeSteps, Place, RecipeCollection
25-
from kimmdy.utils import run_shell_cmd, get_task_directories
27+
from kimmdy.utils import read_reaction_time_marker, run_shell_cmd, get_task_directories
2628
from kimmdy.constants import MARK_DONE, MARK_STARTED
2729

2830

@@ -68,7 +70,6 @@ def concat_traj(
6870
"""
6971
run_dir = Path(dir).expanduser().resolve()
7072
analysis_dir = get_analysis_dir(run_dir)
71-
7273
directories = get_task_directories(run_dir, steps)
7374
if not directories:
7475
raise ValueError(
@@ -90,33 +91,45 @@ def concat_traj(
9091
output = output_group
9192

9293
## gather trajectories
93-
trajectories = []
94+
trajectories: list[Path] = []
9495
tprs = []
9596
gros = []
9697
for d in directories:
97-
trajectories.extend(d.glob(f"*.{filetype}"))
98+
trjs = list(d.glob(f"*.{filetype}"))
99+
trajectories.extend([t for t in trjs if not ".kimmdytrunc." in t.name])
98100
tprs.extend(d.glob("*.tpr"))
99101
gros.extend(d.glob("*.gro"))
100102

101103
assert (
102104
len(trajectories) > 0
103-
), f"No trrs found to concatenate in {run_dir} with subdirectory names {steps}"
105+
), f"No trajectories found to concatenate in {run_dir} with subdirectory names {steps}"
106+
107+
for i, trj in enumerate(trajectories):
108+
task_dir = trj.parent
109+
time = read_reaction_time_marker(task_dir)
110+
if time is not None:
111+
new_trj = trj.with_suffix(".kimmdytrunc.xtc")
112+
run_shell_cmd(
113+
f"echo '0' | gmx trjconv -f {trj} -s {tprs[i]} -e {time} -o {new_trj}",
114+
cwd=run_dir,
115+
)
116+
trajectories[i] = new_trj
104117

105-
trajectories = [str(t) for t in trajectories]
106-
print(trajectories)
118+
flat_trajectories: list[str] = [str(trj) for trj in trajectories]
107119

108120
## write concatenated trajectory
109121
tmp_xtc = str(out_xtc.with_name("tmp.xtc"))
110122
run_shell_cmd(
111-
f"gmx trjcat -f {' '.join(trajectories)} -o {tmp_xtc} -cat",
123+
rf"gmx trjcat -f {' '.join(flat_trajectories)} -o {tmp_xtc} -cat",
112124
cwd=run_dir,
113125
)
114126
run_shell_cmd(
115-
f"echo 'Protein\n{output}' | gmx trjconv -dt 0 -f {tmp_xtc} -s {tprs[0]} -o {str(out_xtc)} -center -pbc mol",
127+
s=rf"echo -e 'Protein\n{output}' | gmx trjconv -dt 0 -f {tmp_xtc} -s {tprs[0]} -o {str(out_xtc)} -center -pbc mol",
116128
cwd=run_dir,
117129
)
130+
assert out_xtc.exists(), f"Concatenated trajectory {out_xtc} not found."
118131
run_shell_cmd(
119-
f"echo 'Protein\n{output}' | gmx trjconv -dump 0 -f {tmp_xtc} -s {tprs[0]} -o {str(out_gro)} -center -pbc mol",
132+
rf"echo -e 'Protein\n{output}' | gmx trjconv -dt 0 -dump 0 -f {tmp_xtc} -s {tprs[0]} -o {str(out_gro)} -center -pbc mol",
120133
cwd=run_dir,
121134
)
122135
run_shell_cmd(f"rm {tmp_xtc}", cwd=run_dir)
@@ -126,7 +139,11 @@ def concat_traj(
126139

127140

128141
def plot_energy(
129-
dir: str, steps: Union[list[str], str], terms: list[str], open_plot: bool = False
142+
dir: str,
143+
steps: Union[list[str], str],
144+
terms: list[str],
145+
open_plot: bool = False,
146+
truncate: bool = True,
130147
):
131148
"""Plot GROMACS energy for a KIMMDY run.
132149
@@ -139,8 +156,10 @@ def plot_energy(
139156
Default is "all".
140157
terms
141158
Terms from gmx energy that will be plotted. Uses 'Potential' by default.
142-
open_plot :
159+
open_plot
143160
Open plot in default system viewer.
161+
truncate
162+
Truncate energy files to the reaction time marker.
144163
"""
145164
run_dir = Path(dir).expanduser().resolve()
146165
xvg_entries = ["time"] + terms
@@ -153,9 +172,10 @@ def plot_energy(
153172
xvgs_dir.mkdir(exist_ok=True)
154173

155174
## gather energy files
156-
edrs = []
175+
edrs: list[Path] = []
157176
for d in subdirs_matched:
158-
edrs.extend(d.glob("*.edr"))
177+
new_edrs = d.glob("*.edr")
178+
edrs.extend([edr for edr in new_edrs if not ".kimmdytrunc." in edr.name])
159179
assert (
160180
len(edrs) > 0
161181
), f"No GROMACS energy files in {run_dir} with subdirectory names {steps}"
@@ -165,11 +185,19 @@ def plot_energy(
165185
time_offset = 0
166186
for i, edr in enumerate(edrs):
167187
## write energy .xvg file
188+
task_dir = edr.parent
168189
xvg = str(xvgs_dir / edr.parents[0].with_suffix(".xvg").name)
169190
step_name = edr.parents[0].name.split("_")[1]
170191

192+
time = read_reaction_time_marker(task_dir)
193+
if time is not None and truncate:
194+
print(f"Truncating {edr} to {time} ps.")
195+
new_edr = edr.with_suffix(".kimmdytrunc.edr")
196+
run_shell_cmd(f"gmx eneconv -f {edr} -e {time} -o {new_edr}")
197+
edr = new_edr
198+
171199
run_shell_cmd(
172-
f"echo '{terms_str} \n\n' | gmx energy -f {str(edr)} -o {xvg}",
200+
f"echo '{terms_str} \n\n' | gmx energy -f {edr} -o {xvg}",
173201
cwd=run_dir,
174202
)
175203

@@ -217,7 +245,9 @@ def plot_energy(
217245
plt.text(x=t, y=v + 0.5, s=s, fontsize=6)
218246

219247
ax = plt.gca()
220-
steps_y_axis = [c for c in ax.get_children() if isinstance(c, mpl.axis.YAxis)][0]
248+
steps_y_axis = [
249+
c for c in ax.get_children() if isinstance(c, matplotlib.axis.YAxis)
250+
][0]
221251
steps_y_axis.set_visible(False)
222252
output_path = str(run_dir / "analysis" / "energy.png")
223253
plt.savefig(output_path, dpi=300)
@@ -423,7 +453,6 @@ def radical_migration(
423453
out_path = analysis_dir / "radical_migration.json"
424454
with open(out_path, "w") as json_file:
425455
json.dump(unique_migrations, json_file)
426-
print("Done!")
427456

428457

429458
def plot_rates(dir: str, open: bool = False):
@@ -694,7 +723,7 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
694723
name="trjcat", help="Concatenate trajectories of a KIMMDY run"
695724
)
696725
parser_trjcat.add_argument(
697-
"dir", type=str, help="KIMMDY run directory to be analysed."
726+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
698727
)
699728
parser_trjcat.add_argument("--filetype", "-f", default="xtc")
700729
parser_trjcat.add_argument(
@@ -708,11 +737,13 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
708737
)
709738
parser_trjcat.add_argument(
710739
"--open-vmd",
740+
"-o",
711741
action="store_true",
712742
help="Open VMD with the concatenated trajectory.",
713743
)
714744
parser_trjcat.add_argument(
715745
"--output-group",
746+
"-g",
716747
type=str,
717748
help="Index group to include in the output. Default is 'Protein' for xtc and 'System' for trr.",
718749
)
@@ -721,7 +752,7 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
721752
name="energy", help="Plot GROMACS energy for a KIMMDY run"
722753
)
723754
parser_energy.add_argument(
724-
"dir", type=str, help="KIMMDY run directory to be analysed."
755+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
725756
)
726757
parser_energy.add_argument(
727758
"--steps",
@@ -743,17 +774,21 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
743774
)
744775
parser_energy.add_argument(
745776
"--open-plot",
746-
"-p",
777+
"-o",
778+
action="store_true",
779+
help="Open plot in default system viewer.",
780+
)
781+
parser_energy.add_argument(
782+
"--no-truncate",
747783
action="store_true",
748784
help="Open plot in default system viewer.",
749785
)
750-
751786
parser_radical_population = subparsers.add_parser(
752787
name="radical_population",
753788
help="Plot population of radicals for one or multiple KIMMDY run(s)",
754789
)
755790
parser_radical_population.add_argument(
756-
"dir", type=str, help="KIMMDY run directory to be analysed."
791+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
757792
)
758793
parser_radical_population.add_argument(
759794
"--population_type",
@@ -820,7 +855,7 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
820855
help="Plot rates of all possible reactions after a MD run. Rates must have been saved!",
821856
)
822857
parser_rates.add_argument(
823-
"dir", type=str, help="KIMMDY run directory to be analysed."
858+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
824859
)
825860
parser_rates.add_argument(
826861
"--open",
@@ -834,7 +869,7 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
834869
help="Plot runtime of the tasks of a kimmdy run.",
835870
)
836871
parser_runtime.add_argument(
837-
"dir", type=str, help="KIMMDY run directory to be analysed."
872+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
838873
)
839874
parser_runtime.add_argument(
840875
"--open-plot",
@@ -847,27 +882,46 @@ def get_analysis_cmdline_args() -> argparse.Namespace:
847882
help="Plot counts of reaction participation per atom id",
848883
)
849884
parser_reaction_participation.add_argument(
850-
"dir", type=str, help="KIMMDY run directory to be analysed."
885+
"dir", type=str, help="KIMMDY run directory to be analysed.", nargs="?"
851886
)
852887
parser_reaction_participation.add_argument(
853888
"--open-plot",
854889
"-p",
855890
action="store_true",
856891
help="Open plot in default system viewer.",
857892
)
893+
894+
for subparser in subparsers.choices.values():
895+
subparser.add_argument(
896+
"--input",
897+
"-i",
898+
type=str,
899+
help="Kimmdy input file. Default `kimmdy.yml`. Only used to infer the output directory if `dir` is not provided.",
900+
default="kimmdy.yml",
901+
)
902+
858903
return parser.parse_args()
859904

860905

861906
def entry_point_analysis():
862907
"""Analyse existing KIMMDY runs."""
863908
args = get_analysis_cmdline_args()
909+
if hasattr(args, "dir") and args.dir is None:
910+
discover_plugins()
911+
# the restart option is used here to avoid creating a new
912+
# output directory and instead use the one from the config verbatim
913+
# without incrementing a number
914+
config = Config(input_file=args.input, restart=True)
915+
args.dir = str(config.out)
864916

865917
if args.module == "trjcat":
866918
concat_traj(
867919
args.dir, args.filetype, args.steps, args.open_vmd, args.output_group
868920
)
869921
elif args.module == "energy":
870-
plot_energy(args.dir, args.steps, args.terms, args.open_plot)
922+
plot_energy(
923+
args.dir, args.steps, args.terms, args.open_plot, not args.no_truncate
924+
)
871925
elif args.module == "radical_population":
872926
radical_population(
873927
args.dir,
@@ -890,5 +944,5 @@ def entry_point_analysis():
890944
)
891945
else:
892946
print(
893-
"No analysis module specified. Use -h for help and a list of available modules."
947+
"No analysis module specified. Use -h for --help and a list of available modules."
894948
)

0 commit comments

Comments
 (0)