diff --git a/bin/polyply b/bin/polyply index da833826..61efe8dc 100755 --- a/bin/polyply +++ b/bin/polyply @@ -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 ' ':modification_name, ' 'e.g. ASP1:N-ter') diff --git a/polyply/data/martini3/modifications.ff b/polyply/data/martini3/modifications.ff index 61cade54..82c5b956 100644 --- a/polyply/data/martini3/modifications.ff +++ b/polyply/data/martini3/modifications.ff @@ -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 diff --git a/polyply/src/apply_modifications.py b/polyply/src/apply_modifications.py index 3d460137..84d39ca5 100644 --- a/polyply/src/apply_modifications.py +++ b/polyply/src/apply_modifications.py @@ -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 @@ -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: @@ -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 @@ -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 diff --git a/polyply/tests/test_apply_modifications.py b/polyply/tests/test_apply_modifications.py index 0fa10ddf..f8a40c70 100644 --- a/polyply/tests/test_apply_modifications.py +++ b/polyply/tests/test_apply_modifications.py @@ -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',( @@ -37,8 +38,6 @@ [({'resid': 1, 'resname': 'A'}, 'Zwitter'), ({'resid': 3, 'resname': 'A'}, 'Zwitter')] ) - - )) def test_annotate_protein(example_meta_molecule, input_mods, expected): """" @@ -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 @@ -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 @@ -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) diff --git a/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/HIS5_mods.itp b/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/HIS5_mods.itp new file mode 100644 index 00000000..41617da6 --- /dev/null +++ b/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/HIS5_mods.itp @@ -0,0 +1,169 @@ +; Please cite the following papers: +; Souza, P C T; Alessandri, R; Barnoud, J; Thallmair, S; Faustino, I; Grünewald, F; Patmanidis, I; Abdizadeh, H; Bruininks, B M H; Wassenaar, T A; Kroon, P C; Melcr, J; Nieto, V; Corradi, V; Khan, H M; Domański, J; Javanainen, M; Martinez-Seara, H; Reuter, N; Best, R B; Vattulainen, I; Monticelli, L; Periole, X; Tieleman, D P; de Vries, A H; Marrink, S J; Nature Methods 2021; 10.1038/s41592-021-01098-3 +; Souza, P C T; Araujo, L P B; Brasnett, C; Moreira, R A; Grunewald, F; Park, P; Wang, L; Razmazma, H; Borges-Araujo, A C; Cofas-Vargas, L F; Monticelli, L; Mera-Adasme, R; Melo, M N; Wu, S; Marrink, S J; Poma, A B; Thallmair, S; 2024; 10.1101/2024.04.15.589479 +; Grunewald, F; Alessandri, R; Kroon, P C; Monticelli, L; Souza, P C; Marrink, S J; Nature Communications 2022; 10.1038/s41467-021-27627-4 + +[ moleculetype ] +HIS5_mods 1 + +[ atoms ] + 1 P6 1 HIS BB 1 0.0 + 2 TC4 1 HIS SC1 1 0.0 + 3 TN5a 1 HIS SC2 1 0.0 + 4 TN6a 1 HIS SC3 1 0.0 + 5 VS 1 HIS CA 1 0.0 0.0 + 6 P2 2 HIS BB 2 0.0 + 7 TC4 2 HIS SC1 2 0.0 + 8 TN6d 2 HIS SC2 2 0.0 + 9 TN5a 2 HIS SC3 2 0.0 +10 VS 2 HIS CA 2 0.0 0.0 +11 P2 3 HIS BB 3 0.0 +12 TC4 3 HIS SC1 3 0.0 +13 TN6d 3 HIS SC2 3 0.0 +14 TN5a 3 HIS SC3 3 0.0 +15 VS 3 HIS CA 3 0.0 0.0 +16 P2 4 HIS BB 4 0.0 +17 TC4 4 HIS SC1 4 0.0 +18 TN6d 4 HIS SC2 4 0.0 +19 TN5a 4 HIS SC3 4 0.0 +20 VS 4 HIS CA 4 0.0 0.0 +21 Q5 5 HIS BB 5 -1.0 +22 TC4 5 HIS SC1 5 0.0 +23 TN6d 5 HIS SC2 5 0.0 +24 TN5a 5 HIS SC3 5 0.0 +25 VS 5 HIS CA 5 0.0 0.0 + +[ position_restraints ] +#ifdef POSRES + 1 1 1000 1000 1000 + 6 1 1000 1000 1000 +11 1 1000 1000 1000 +16 1 1000 1000 1000 +21 1 1000 1000 1000 +#endif + +[ bonds ] +; Backbone bonds + 1 6 1 0.350 4000 + 6 11 1 0.350 4000 +11 16 1 0.350 4000 +16 21 1 0.350 4000 + +; Side chain bonds + 1 2 1 0.336 7500 + 6 7 1 0.336 7500 +11 12 1 0.336 7500 +16 17 1 0.336 7500 +21 22 1 0.336 7500 + +#ifdef FLEXIBLE +; Side chain bonds + 2 3 1 0.320 1000000 + 2 4 1 0.300 1000000 + 3 4 1 0.270 1000000 + 7 8 1 0.320 1000000 + 7 9 1 0.300 1000000 + 8 9 1 0.270 1000000 +12 13 1 0.320 1000000 +12 14 1 0.300 1000000 +13 14 1 0.270 1000000 +17 18 1 0.320 1000000 +17 19 1 0.300 1000000 +18 19 1 0.270 1000000 +22 23 1 0.320 1000000 +22 24 1 0.300 1000000 +23 24 1 0.270 1000000 +#endif + +[ constraints ] +#ifndef FLEXIBLE +; Side chain bonds + 2 3 1 0.320 + 2 4 1 0.300 + 3 4 1 0.270 + 7 8 1 0.320 + 7 9 1 0.300 + 8 9 1 0.270 +12 13 1 0.320 +12 14 1 0.300 +13 14 1 0.270 +17 18 1 0.320 +17 19 1 0.300 +18 19 1 0.270 +22 23 1 0.320 +22 24 1 0.300 +23 24 1 0.270 +#endif + +[ virtual_sitesn ] + 5 1 1 +10 1 6 +15 1 11 +20 1 16 +25 1 21 + +[ angles ] +; BBB angles + 1 6 11 10 127 20 + 6 11 16 10 127 20 +11 16 21 10 127 20 + +; Side chain angles + 1 2 3 2 120.000 50.0 + 1 2 4 2 120.000 50.0 + 6 7 8 2 120.000 50.0 + 6 7 9 2 120.000 50.0 +11 12 13 2 120.000 50.0 +11 12 14 2 120.000 50.0 +16 17 18 2 120.000 50.0 +16 17 19 2 120.000 50.0 +21 22 23 2 120.000 50.0 +21 22 24 2 120.000 50.0 + +; idp-fix + 1 6 7 10 85 10 + 6 11 12 10 85 10 +11 16 17 10 85 10 +16 21 22 10 85 10 + 2 1 6 10 85 10 + 7 6 11 10 85 10 +12 11 16 10 85 10 +17 16 21 10 85 10 + +[ dihedrals ] +; idp-fix + 1 6 11 16 9 -120 -1 1 ; BB-BB-BB-BB-v1 + 1 6 11 16 9 -120 -1 2 ; BB-BB-BB-BB-v2 + 6 11 16 21 9 -120 -1 1 ; BB-BB-BB-BB-v1 + 6 11 16 21 9 -120 -1 2 ; BB-BB-BB-BB-v2 + 2 1 6 7 9 -130 -1.5 1 ; SC1-BB-BB-SC1-v1 + 2 1 6 7 9 100 -1.5 2 ; SC1-BB-BB-SC1-v2 + 7 6 11 12 9 -130 -1.5 1 ; SC1-BB-BB-SC1-v1 + 7 6 11 12 9 100 -1.5 2 ; SC1-BB-BB-SC1-v2 +12 11 16 17 9 -130 -1.5 1 ; SC1-BB-BB-SC1-v1 +12 11 16 17 9 100 -1.5 2 ; SC1-BB-BB-SC1-v2 +17 16 21 22 9 -130 -1.5 1 ; SC1-BB-BB-SC1-v1 +17 16 21 22 9 100 -1.5 2 ; SC1-BB-BB-SC1-v2 + +[ exclusions ] + 1 2 3 4 + 2 3 4 + 3 4 + 6 7 8 9 + 7 8 9 + 8 9 +11 12 13 14 +12 13 14 +13 14 +16 17 18 19 +17 18 19 +18 19 +21 22 23 24 +22 23 24 +23 24 + 5 1 ; CA-BB-same +10 6 ; CA-BB-same +15 11 ; CA-BB-same +20 16 ; CA-BB-same +25 21 ; CA-BB-same + diff --git a/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/command b/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/command new file mode 100644 index 00000000..cfa99208 --- /dev/null +++ b/polyply/tests/test_data/library_tests/martini3/PROTMODS/polyply/command @@ -0,0 +1 @@ +polyply gen_params -lib martini3 -seq HIS:5 -name HIS5_mods -o HIS5_mods.itp -mods HIS1:HIS-HD HIS1:NH2-ter \ No newline at end of file diff --git a/polyply/tests/test_lib_files.py b/polyply/tests/test_lib_files.py index c7181e94..b4671f41 100644 --- a/polyply/tests/test_lib_files.py +++ b/polyply/tests/test_lib_files.py @@ -181,6 +181,7 @@ def _interaction_equal(interaction1, interaction2, inter_type): ['oplsaaLigParGen', 'PTMA'], ['martini3', 'PDMS'], ['martini3', 'PROT'], + ['martini3', 'PROTMODS'], ['martini3', 'PEO'], ['martini3', 'PS'], ['martini3', 'PE'],