Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
DynaMate
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
DynaMate
Commits
2337c1e2
Unverified
Commit
2337c1e2
authored
Jan 23, 2026
by
Cassandra Masschelein
Committed by
GitHub
Jan 23, 2026
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1 from schwallergroup/dev
DynaMate for multiligand systems
parents
52cf521b
1315866d
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
173 additions
and
28 deletions
+173
-28
src/agents/md_agent.py
+121
-9
src/agents/prep_agent.py
+3
-1
src/scripts/equil_Gromacs.sh
+29
-5
src/tools/ligand_tools.py
+10
-4
src/tools/pdb_tools.py
+10
-9
No files found.
src/agents/md_agent.py
View file @
2337c1e2
...
...
@@ -52,7 +52,71 @@ class MDAgent(BaseAgent):
self
.
completed_steps
=
[]
self
.
completed_summary
=
""
self
.
EXPECTED_FILES
=
[
"md.tpr"
,
"md.xtc"
,
"md.edr"
,
"md.log"
,
"md.gro"
]
self
.
EXPECTED_FILES_PROTEIN_TEMPLATE
=
[
"{pdb_id}.pdb"
,
"{pdb_id}_prepared.pdb"
,
"{pdb_id}_prepared_capped.pdb"
,
"{pdb_id}_prepared_capped_his.pdb"
,
"{pdb_id}.prmtop"
,
"{pdb_id}.inpcrd"
,
"{pdb_id}_tleap.pdb"
,
"topol.top"
,
"{pdb_id}.gro"
,
"topol_without_posre.top"
,
"em.gro"
,
"nvt.gro"
,
"nvt.xtc"
,
"npt.gro"
,
"npt.xtc"
,
"temperature.xvg"
,
"pressure.xvg"
,
"density.xvg"
,
"potential.xvg"
,
"md.gro"
,
"md.xtc"
,
"rmsd.xvg"
,
"rmsd_xtal.xvg"
,
"rmsf.xvg"
,
"gyrate.xvg"
,
"hbnum_prot_wat.xvg"
,
"hbnum_sidechain.xvg"
,
]
self
.
EXPECTED_FILES_TEMPLATE
=
[
"{pdb_id}.pdb"
,
"{pdb_id}_prepared.pdb"
,
"{ligand_name}.pdb"
,
[
"{ligand_name}_h.pdb"
,
"{ligand_name}_1_h.pdb"
],
"{pdb_id}_prepared_capped.pdb"
,
"{pdb_id}_prepared_capped_his.pdb"
,
[
"{ligand_name}_h.prepi"
,
"{ligand_name}_1_h.prepi"
],
"{ligand_name}.frcmod"
,
"complex.pdb"
,
"complex.prmtop"
,
"complex.inpcrd"
,
"complex_tleap.pdb"
,
"topol.top"
,
"complex.gro"
,
"topol_without_posre.top"
,
"{ligand_name}.gro"
,
"em.gro"
,
"nvt.gro"
,
"nvt.xtc"
,
"npt.gro"
,
"npt.xtc"
,
"temperature.xvg"
,
"pressure.xvg"
,
"density.xvg"
,
"potential.xvg"
,
"md.gro"
,
"md.xtc"
,
"rmsd.xvg"
,
"rmsd_xtal.xvg"
,
"rmsf.xvg"
,
"gyrate.xvg"
,
"hbnum_prot_lig.xvg"
,
"hbnum_prot_wat.xvg"
,
"hbnum_sidechain.xvg"
,
]
self
.
logger
.
info
(
f
"MDAgent initialized."
)
...
...
@@ -60,18 +124,22 @@ class MDAgent(BaseAgent):
if
(
tool_name
in
(
"gromacs_production"
,
"gromacs_equil"
,
"gromacs_analysis"
))
and
(
" failed with return code "
in
tool_call
):
self
.
logger
.
error
(
f
"{tool_call}"
)
return
False
raise
ToolOutputError
(
f
"Gromacs tool execution failed: {tool_call}"
)
if
tool_name
in
(
"run_tleap"
,
"run_tleap_ligand"
):
if
tool_name
in
(
"run_tleap"
,
"run_tleap_ligand"
,
"param_ligand"
):
if
"tleap run failed with error:"
in
tool_call
:
self
.
logger
.
error
(
f
"{tool_call}"
)
return
False
if
"Ligand parameterization failed with error:"
in
tool_call
:
self
.
logger
.
error
(
f
"{tool_call}"
)
return
False
raise
ToolOutputError
(
f
"TLEaP run failed {tool_call}"
)
if
"ParmEd failed:"
in
tool_call
:
self
.
logger
.
error
(
f
"{tool_call}"
)
return
False
raise
ToolOutputError
(
f
"ParmEd failed {tool_call}"
)
return
True
def
_reset_pipeline
(
self
):
...
...
@@ -199,13 +267,57 @@ class MDAgent(BaseAgent):
return
[
step
[
"step"
]
for
step
in
self
.
plan
[
"plan"
]]
return
list
(
self
.
ESSENTIAL_STEPS
)
def
_format_expected_files
(
self
,
templates
):
values
=
{
"pdb_id"
:
self
.
pdb_id
.
upper
(),
"ligand_name"
:
self
.
ligand_name
.
upper
(),
}
formatted
=
[]
for
item
in
templates
:
if
isinstance
(
item
,
(
list
,
tuple
)):
formatted
.
append
(
[
s
.
format
(
**
values
)
for
s
in
item
]
)
else
:
formatted
.
append
(
item
.
format
(
**
values
)
)
return
formatted
def
_resolve_file
(
self
,
f
):
if
isinstance
(
f
,
(
list
,
tuple
)):
for
candidate
in
f
:
path
=
self
.
sandbox_dir
/
candidate
if
path
.
exists
():
return
candidate
return
f
[
0
]
return
f
def
_pipeline_successful
(
self
)
->
bool
:
"""Check whether the full MD pipeline completed successfully."""
missing
=
[
f
for
f
in
self
.
EXPECTED_FILES
if
not
(
self
.
sandbox_dir
/
f
)
.
exists
()]
expected_files
=
(
self
.
_format_expected_files
(
self
.
EXPECTED_FILES_TEMPLATE
)
if
self
.
ligand_name
else
self
.
_format_expected_files
(
self
.
EXPECTED_FILES_PROTEIN_TEMPLATE
)
)
missing_or_empty
=
[]
for
f
in
expected_files
:
resolved
=
self
.
_resolve_file
(
f
)
path
=
self
.
sandbox_dir
/
resolved
if
not
path
.
exists
()
or
path
.
stat
()
.
st_size
==
0
:
missing_or_empty
.
append
(
resolved
)
if
missing
:
self
.
logger
.
error
(
f
"Pipeline incomplete: missing final outputs {missing}"
)
if
missing_or_empty
:
self
.
logger
.
error
(
f
"Pipeline incomplete: missing final outputs {missing_or_empty}"
)
return
False
return
True
...
...
src/agents/prep_agent.py
View file @
2337c1e2
...
...
@@ -70,9 +70,11 @@ class PrepAgent(BaseAgent):
def
_get_pdb_file_path
(
self
,
prompt
):
pdb_file_path
=
None
num_calls
=
0
while
pdb_file_path
is
None
:
while
pdb_file_path
is
None
and
num_calls
<
5
:
response
=
self
.
_prompt_llm
(
prompt
)
num_calls
+=
1
self
.
logger
.
info
(
f
"Response: {response}"
)
tool_calls
=
response
.
tool_calls
...
...
src/scripts/equil_Gromacs.sh
View file @
2337c1e2
...
...
@@ -53,7 +53,7 @@ fi
# Step 2: if 1 chain, create posre.itp file
if
[
"
$chains
"
-eq
"1"
]
;
then
if
!
ls
posre.itp 1> /dev/null 2>&1
;
then
echo
""
Protein-H
""
|
$GMX
genrestr
-f
em.gro
-n
index.ndx
-o
posre.itp
-fc
1000 1000 1000
>>
$LOG_FILE
2>&1
echo
""
Protein-H
""
|
$GMX
genrestr
-f
em.gro
-n
index.ndx
-o
posre.itp
-fc
1000 1000 1000
>>
$LOG_FILE
2>&1
fi
else
# Step 2: if more than 1 chain, create posre_chain(i).itp files
...
...
@@ -90,12 +90,33 @@ EOF
if
grep
-E
"system1 +2"
topol.top
;
then
#special case for two identical chains named system1
echo
"You have two identical chains named system1, therefore only one position restraint file for the first chain will be created."
>>
$LOG_FILE
2>&1
echo
"Creating group for residues
${
ranges
[0]
}
"
>>
$LOG_FILE
2>&1
echo
-e
"ri
${
ranges
[0]
}
\n
2 &
\"
r_
${
ranges
[0]
}
\"\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
if
grep
-Fq
"[ r_
${
ranges
[0]
}
]"
index.ndx
;
then
# -F for fixed string (so can use [] without putting "^\[ r_${ranges[0]} \]", -q for quiet)
echo
"Group r_
${
ranges
[0]
}
already exists in index.ndx"
>>
$LOG_FILE
2>&1
else
echo
"Adding group r_
${
ranges
[0]
}
to index.ndx"
>>
$LOG_FILE
2>&1
echo
-e
"ri
${
ranges
[0]
}
\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
fi
if
grep
-Fq
"[ Protein-H_&_r_
${
ranges
[0]
}
]"
index.ndx
;
then
echo
"Group Protein-H_&_r_
${
ranges
[0]
}
already exists in index.ndx"
>>
$LOG_FILE
2>&1
else
echo
"Adding group Protein-H_&_r_
${
ranges
[0]
}
to index.ndx"
>>
$LOG_FILE
2>&1
echo
-e
"2 &
\"
r_
${
ranges
[0]
}
\"\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
fi
else
i
=
1
for
range
in
"
${
ranges
[@]
}
"
;
do
echo
"Creating group for residues
$range
..."
>>
$LOG_FILE
2>&1
echo
-e
"ri
$range
\n
2 &
\"
r_
${
range
}
\"\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
if
grep
-Fq
"[ r_
$range
]"
index.ndx
;
then
echo
"Group r_
$range
already exists in index.ndx"
>>
$LOG_FILE
2>&1
else
echo
"Adding group r_
$range
to index.ndx"
>>
$LOG_FILE
2>&1
echo
-e
"ri
$range
\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
fi
if
grep
-Fq
"[ Protein-H_&_r_
$range
]"
index.ndx
;
then
echo
"Group Protein-H_&_r_
$range
already exists in index.ndx"
>>
$LOG_FILE
2>&1
else
echo
"Adding group Protein-H_&_r_
$range
to index.ndx"
>>
$LOG_FILE
2>&1
echo
-e
"2 &
\"
r_
$range
\"\n
q"
|
$GMX
make_ndx
-f
em.gro
-n
index.ndx
-o
index.ndx
>>
$LOG_FILE
2>&1
fi
((
i++
))
done
fi
...
...
@@ -103,7 +124,10 @@ fi
# Step 6: generate posre.itp for each chain
if
grep
-E
"system1 +2"
topol.top
;
then
#special case for two identical chains named system1
group_name
=
"Protein-H_&_r_
${
ranges
[0]
}
"
echo
"
$group_name
"
|
$GMX
genrestr
-f
em.gro
-n
index.ndx
-o
"posre.itp"
-fc
1000 1000 1000
>>
$LOG_FILE
2>&1
if
!
ls
posre.itp 1> /dev/null 2>&1
;
then
echo
"Generating position restraints for chain1"
>>
$LOG_FILE
2>&1
echo
"
$group_name
"
|
$GMX
genrestr
-f
em.gro
-n
index.ndx
-o
"posre.itp"
-fc
1000 1000 1000
>>
$LOG_FILE
2>&1
fi
else
i
=
1
for
range
in
"
${
ranges
[@]
}
"
;
do
...
...
src/tools/ligand_tools.py
View file @
2337c1e2
...
...
@@ -126,9 +126,15 @@ def param_ligand(sandbox_dir: str, ligand_files: str | list[str], ligand_name: s
logger
.
info
(
f
"Mol2 file for ligand {ligand_stem} created"
)
cmd
=
shlex
.
split
(
f
"sed -i 's/UNL/{ligand_name}/g' {sandbox_dir}/{ligand_stem}.mol2"
)
run_3
=
subprocess
.
run
(
cmd
,
cwd
=
sandbox_dir
,
capture_output
=
True
,
text
=
True
)
if
run_3
.
returncode
!=
0
:
error_text
=
"
\n
"
.
join
(
filter
(
None
,
[
run_3
.
stderr
,
run_3
.
stdout
]))
run_3_UNL
=
subprocess
.
run
(
cmd
,
cwd
=
sandbox_dir
,
capture_output
=
True
,
text
=
True
)
if
run_3_UNL
.
returncode
!=
0
:
error_text
=
"
\n
"
.
join
(
filter
(
None
,
[
run_3_UNL
.
stderr
,
run_3_UNL
.
stdout
]))
return
f
"Ligand parameterization failed with error: {error_text}"
cmd
=
shlex
.
split
(
f
"sed -i 's/UNK/{ligand_name}/g' {sandbox_dir}/{ligand_stem}.mol2"
)
run_3_UNK
=
subprocess
.
run
(
cmd
,
cwd
=
sandbox_dir
,
capture_output
=
True
,
text
=
True
)
if
run_3_UNK
.
returncode
!=
0
:
error_text
=
"
\n
"
.
join
(
filter
(
None
,
[
run_3_UNK
.
stderr
,
run_3_UNK
.
stdout
]))
return
f
"Ligand parameterization failed with error: {error_text}"
# Create prepi file using antechamber
...
...
@@ -143,7 +149,7 @@ def param_ligand(sandbox_dir: str, ligand_files: str | list[str], ligand_name: s
# Create frcmod file using parmchk2
cmd
=
shlex
.
split
(
f
"parmchk2 -i {sandbox_dir}/{ligand_stem}.mol2 -f mol2 -o {sandbox_dir}/{ligand_stem}.frcmod"
)
run_5
=
subprocess
.
run
(
cmd
,
cwd
=
sandbox_dir
,
capture_output
=
True
,
text
=
True
,
check
=
True
)
run_5
=
subprocess
.
run
(
cmd
,
cwd
=
sandbox_dir
,
capture_output
=
True
,
text
=
True
)
if
run_5
.
returncode
!=
0
:
error_text
=
"
\n
"
.
join
(
filter
(
None
,
[
run_5
.
stderr
,
run_5
.
stdout
]))
return
f
"Ligand parameterization failed with error: {error_text}"
...
...
src/tools/pdb_tools.py
View file @
2337c1e2
...
...
@@ -151,14 +151,14 @@ def prepare_pdb_file_ligand(sandbox_dir: str, pdb_id: str, ligand_name: str = No
# Extract ligand
if
(
ligand_name
is
not
None
)
and
(
ligand_name
!=
"XXX"
)
and
(
ligand_name
!=
"None"
)
and
(
ligand_name
!=
"None_h"
):
# Count number of ligands
resnum
s
=
set
()
ligand_key
s
=
set
()
with
open
(
f
"{sandbox_dir}/{pdb_id}.pdb"
)
as
f
:
for
line
in
f
:
if
line
.
startswith
(
"HETATM"
)
and
ligand_name
in
line
:
if
line
.
startswith
(
"HETATM"
)
and
line
[
17
:
20
]
.
strip
()
==
ligand_name
:
chain
=
line
[
21
]
resnum
=
int
(
line
[
22
:
26
])
resnums
.
add
(
resnum
)
print
(
"resnums is: "
,
resnums
)
num_ligands
=
len
(
resnums
)
ligand_keys
.
add
((
chain
,
resnum
))
num_ligands
=
len
(
ligand_keys
)
logger
.
info
(
f
"IMPORTANT: Number of ligands {ligand_name} found: {num_ligands}"
)
if
num_ligands
==
0
:
...
...
@@ -183,12 +183,13 @@ def prepare_pdb_file_ligand(sandbox_dir: str, pdb_id: str, ligand_name: str = No
# Collect HETATM lines by residue number
with
open
(
f
"{sandbox_dir}/{pdb_id}.pdb"
,
"r"
)
as
infile
:
for
line
in
infile
:
if
line
.
startswith
(
"HETATM"
)
and
ligand_name
in
line
:
resnum
=
int
(
line
[
22
:
26
])
# residue number column
ligands
[
resnum
]
.
append
(
line
)
if
line
.
startswith
(
"HETATM"
)
and
line
[
17
:
20
]
.
strip
()
==
ligand_name
:
chain
=
line
[
21
]
.
strip
()
or
"_"
resnum
=
int
(
line
[
22
:
26
])
ligands
[(
chain
,
resnum
)]
.
append
(
line
)
# Write one file per ligand
for
i
,
(
resnum
,
atom_lines
)
in
enumerate
(
ligands
.
items
(),
start
=
1
):
for
i
,
(
(
chain
,
resnum
)
,
atom_lines
)
in
enumerate
(
ligands
.
items
(),
start
=
1
):
ligand_pdb_file
=
f
"{sandbox_dir}/{ligand_name}_{i}.pdb"
ligand_pdb_files_list
.
append
(
ligand_pdb_file
)
with
open
(
ligand_pdb_file
,
"w"
)
as
outfile
:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment