Skip to content

Commit

Permalink
Merge pull request #389 from csbrasnett/mods-fix
Browse files Browse the repository at this point in the history
fixed modifcation parsing
  • Loading branch information
fgrunewald authored Oct 14, 2024
2 parents 8d1cab6 + dea287b commit 26165e9
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 31 deletions.
6 changes: 3 additions & 3 deletions bin/polyply
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ def main(): # pylint: disable=too-many-locals,too-many-statements
help='A linear sequence of residue names.')
seq_group.add_argument('-seqf', dest='seq_file', type=Path,
help='A graph input file (JSON|TXT|FASTA|IG)')
dna_group = parser_gen_itp.add_argument_group('DNA specifc options')
dna_group = parser_gen_itp.add_argument_group('DNA specific options')
dna_group.add_argument('-dsdna', dest='dsdna', action='store_true',
help='complement single sequence to dsDNA sequence')
modifications_group = parser_gen_itp.add_argument_group('Modifications group')
modifications_group.add_argument('-mods', dest='mods', action='append',
default=[], type=lambda s: s.split(":"),
modifications_group.add_argument('-mods', dest='mods', nargs='+',
default=[], type=lambda s: [i.split(':') for i in s.split(" ")][0],
help=('Add a modification to a residue. The format is '
'<resname><resid>:modification_name, '
'e.g. ASP1:N-ter')
Expand Down
69 changes: 69 additions & 0 deletions polyply/data/martini3/modifications.ff
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,72 @@ BB {"replace": {"atype": "P1", "charge": 0.0}}
NCAP-ter
[ atoms ]
BB {"replace": {"atype": "P2", "charge": 0.0}}

[ modification ]
GLU-HE1
[ atoms ]
SC1 {"replace": {"atype": "P2", "charge": 0}}

[ modification ]
GLU-HE2
[ atoms ]
SC1 {"replace": {"atype": "P2", "charge": 0}}

[ modification ]
ASP-HD1
[ atoms ]
SC1 {"replace": {"atype": "P2", "charge": 0}}

[ modification ]
ASP-HD2
[ atoms ]
SC1 {"replace": {"atype": "P2", "charge": 0}}

[ modification ]
LYS-LSN
[ atoms ]
SC2 {"resname": "LYS", "replace": {"atype": "SN6d", "charge": 0}}

[ modification ]
LYS-HZ3
[ atoms ]
SC2 {"replace": {"resname": "LYS"}}

[ modification ]
HIS-HP
[ atoms ]
BB {"resname": "HIS", "replace": {"resname": "HIS"}}
SC1 {"resname": "HIS", "replace": {"resname": "HIS"}}
SC2 {"resname": "HIS", "replace": {"atype": "TP1dq", "charge": "0.5", "resname": "HIS"}}
SC3 {"resname": "HIS", "replace": {"atype": "TP1dq", "charge": "0.5", "resname": "HIS"}}
;[ edges ]
;BB SC1
;SC1 SC2
;SC1 SC3
;SC2 SC3

[ modification ]
HIS-HE
[ atoms ]
BB {"resname": "HIS", "replace": {"resname": "HIS"}}
SC1 {"resname": "HIS", "replace": {"resname": "HIS"}}
SC2 {"resname": "HIS", "replace": {"resname": "HIS"}}
SC3 {"resname": "HIS", "replace": {"resname": "HIS"}}
;[ edges ]
;BB SC1
;SC1 SC2
;SC1 SC3
;SC2 SC3

[ modification ]
HIS-HD
[ atoms ]
BB {"resname": "HIS", "replace": {"resname": "HIS"}}
SC1 {"resname": "HIS", "replace": {"resname": "HIS"}}
SC2 {"resname": "HIS", "replace": {"resname": "HIS", "atype": "TN5a"}}
SC3 {"resname": "HIS", "replace": {"resname": "HIS", "atype": "TN6a"}}
;[ edges ]
;BB SC1
;SC1 SC2
;SC1 SC3
;SC2 SC3
16 changes: 6 additions & 10 deletions polyply/src/apply_modifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ def _patch_protein_termini(meta_molecule, ter_mods=['N-ter', 'C-ter']):
protein_termini.append(last_mod)
else:
# if only one mod in ter_mods, apply the mod to both start and end residue
LOGGER.info("Only one terminal modification specified. "
LOGGER.warning("Only one terminal modification specified. "
f"Will apply {ter_mods[0]} to both {meta_molecule.nodes[0]['resname']}1 and {last_resname}{max_resid}")
protein_termini.append(({'resid': max_resid, 'resname': last_resname}, ter_mods[0]))

return protein_termini


Expand All @@ -60,7 +59,7 @@ def apply_mod(meta_molecule, modifications):
molecule = meta_molecule.molecule

if not molecule.force_field.modifications:
LOGGER.warning('No modifications present in forcefield, none will be applied')
LOGGER.info('No modifications present in forcefield, none will be applied')
return meta_molecule

for target, desired_mod in modifications:
Expand All @@ -76,13 +75,13 @@ def apply_mod(meta_molecule, modifications):

target_residue = meta_molecule.nodes[target_resid - 1]
# takes care to skip all residues that come from an itp file
if not target_residue.get('from_itp', 'False'):
LOGGER.warning("meta_molecule has come from itp. Will not attempt to modify.")
if target_residue.get('from_itp'):
LOGGER.info("meta_molecule has come from itp. Will not attempt to modify.")
continue
# checks that the resname is a protein resname as defined above
if not vermouth.molecule.attributes_match(target_residue,
{'resname': vermouth.molecule.Choice(protein_resnames.split("|"))}):
LOGGER.warning("The resname of your target residue is not recognised a protein resname. "
LOGGER.info("The resname of your target residue is not recognised as a protein resname. "
"Will not attempt to modify.")
continue

Expand Down Expand Up @@ -114,13 +113,10 @@ class ApplyModifications(Processor):
"""
def __init__(self, meta_molecule, modifications=[]):
self.target_mods = []
self.target_mods = _patch_protein_termini(meta_molecule)
for resspec, val in modifications:
self.target_mods.append((parse_residue_spec(resspec), val))
if len(self.target_mods) == 0:
self.target_mods = _patch_protein_termini(meta_molecule)

def run_molecule(self, meta_molecule):

apply_mod(meta_molecule, self.target_mods)
return meta_molecule
89 changes: 71 additions & 18 deletions polyply/tests/test_apply_modifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import polyply.src.ff_parser_sub
import networkx as nx
from vermouth.molecule import Interaction
from polyply.src.meta_molecule import MetaMolecule


@pytest.mark.parametrize('input_mods, expected',(
Expand All @@ -37,8 +38,6 @@
[({'resid': 1, 'resname': 'A'}, 'Zwitter'),
({'resid': 3, 'resname': 'A'}, 'Zwitter')]
)
))
def test_annotate_protein(example_meta_molecule, input_mods, expected):
""""
Expand Down Expand Up @@ -75,20 +74,25 @@ def test_mods_in_ff(caplog, ff_files, expected):
with expected:
assert len(ff.modifications) > 0

@pytest.mark.parametrize('input_itp, molname, expected',
@pytest.mark.parametrize('input_itp, molname, expected, text',
(
(
'ALA5.itp',
'pALA',
False),
False,
None),
('PEO.itp',
'PEO',
True)
))
def test_apply_mod(input_itp, molname, expected, caplog):
True,
("The resname of your target residue"
" is not recognised as a protein resname."
" Will not attempt to modify."))
))
def test_apply_mod(input_itp, molname, expected, caplog, text):
"""
test that modifications get applied correctly
"""
caplog.set_level(logging.INFO)
#make the meta molecule from the itp and ff files
file_name = TEST_DATA / "itp" / input_itp

Expand All @@ -107,7 +111,12 @@ def test_apply_mod(input_itp, molname, expected, caplog):
apply_mod(meta_mol, termini)

if expected:
assert any(rec.levelname == 'WARNING' for rec in caplog.records)
for record in caplog.records:
if record.message == text:
assert True
break
else:
assert False

else:
#for each mod applied, check that the mod atom and interactions have been changed correctly
Expand All @@ -134,25 +143,69 @@ def test_apply_mod(input_itp, molname, expected, caplog):
meta=interaction.meta)
assert _interaction in meta_mol.molecule.interactions[interaction_type]

@pytest.mark.parametrize('modifications, expected',
(
(
[['A1', 'N-ter']],
True
),

@pytest.mark.parametrize('adding, expected',
(
(True,
True),
(False,
False)
))
def test_from_itp(caplog, adding, expected):

caplog.set_level(logging.INFO)
#make the meta molecule from the itp and ff files
file_name = TEST_DATA / "itp" / "ALA5.itp"

ff = vermouth.forcefield.ForceField(name='martini3')

ff_lines = []
for file in ['aminoacids.ff', 'modifications.ff']:
with open(TEST_DATA/ "ff" / file) as f:
ff_lines += f.readlines()
polyply.src.ff_parser_sub.read_ff(ff_lines, ff)

meta_mol = MetaMolecule.from_itp(ff, file_name, "pALA")

if adding:
for node in meta_mol.nodes:
meta_mol.nodes[node]['from_itp'] = 'True'

termini = _patch_protein_termini(meta_mol)
apply_mod(meta_mol, termini)

found = False
expected_msg = "meta_molecule has come from itp. Will not attempt to modify."
for record in caplog.records:
if record.message == expected_msg:
found = True
break
else:
continue

assert found == expected

@pytest.mark.parametrize('modifications, expected, text',
(
(
[],
True
True,
"No modifications present in forcefield, none will be applied"
),
))
def test_ApplyModifications(example_meta_molecule, caplog, modifications, expected):
def test_ApplyModifications(example_meta_molecule, caplog, modifications, expected, text):

caplog.set_level(logging.INFO)

ApplyModifications(modifications=modifications,
meta_molecule=example_meta_molecule).run_molecule(example_meta_molecule)

if expected:
assert any(rec.levelname == 'WARNING' for rec in caplog.records)

for record in caplog.records:
if record.message == text:
assert True
break
else:
assert False
assert any(rec.levelname == 'INFO' for rec in caplog.records)
Loading

0 comments on commit 26165e9

Please sign in to comment.