From 93df7d8f9aea6ec4efe2fd5a86c9b241746b2011 Mon Sep 17 00:00:00 2001 From: yokochi47 Date: Mon, 29 Jan 2024 12:36:33 +0900 Subject: [PATCH] Permit combinational dihedral angle restraints derived from ambiguous atom name (6sy2) --- wwpdb/utils/nmr/NmrDpReport.py | 5 +- wwpdb/utils/nmr/NmrDpUtility.py | 53 ++++++++++++ wwpdb/utils/nmr/mr/BiosymMRParserListener.py | 43 +++++++--- wwpdb/utils/nmr/mr/CharmmMRParserListener.py | 22 +++-- wwpdb/utils/nmr/mr/CnsMRParserListener.py | 18 +++- wwpdb/utils/nmr/mr/CyanaMRParserListener.py | 73 ++++++++++++---- wwpdb/utils/nmr/mr/DynamoMRParserListener.py | 86 ++++++++++++++----- wwpdb/utils/nmr/mr/RosettaMRParserListener.py | 32 +++++-- wwpdb/utils/nmr/mr/XplorMRParserListener.py | 11 ++- 9 files changed, 273 insertions(+), 70 deletions(-) diff --git a/wwpdb/utils/nmr/NmrDpReport.py b/wwpdb/utils/nmr/NmrDpReport.py index 3b5b068e1..01b9e2a2a 100644 --- a/wwpdb/utils/nmr/NmrDpReport.py +++ b/wwpdb/utils/nmr/NmrDpReport.py @@ -81,6 +81,7 @@ # 12-Jan-2024 M. Yokochi - getNmrSeq1LetterCodeOf() returns '.' for missing residue, instead of whitespace (DAOTHER-9065) # 16-Jan-2024 M. Yokochi - add 'nm-res-ari' file type for ARIA restraint format (DAOTHER-9079, NMR restraint remediation) # 17-Jan-2024 M. Yokochi - add 'coordinate_issue' error (DAOTHER-9084) +# 29-Jan-2024 M. Yokochi - add 'ambiguous_dihedral_angle' warning type (NMR restraint remediation, 6sy2) ## """ Wrapper class for NMR data processing report. @author: Masashi Yokochi @@ -2265,7 +2266,7 @@ def __init__(self, verbose=True, log=sys.stdout): 'disordered_index', 'sequence_mismatch', 'atom_nomenclature_mismatch', 'auth_atom_nomenclature_mismatch', 'ccd_mismatch', 'ambiguity_code_mismatch', 'skipped_saveframe_category', 'skipped_loop_category', - 'anomalous_bond_length', 'anomalous_rdc_vector', + 'anomalous_bond_length', 'ambiguous_dihedral_angle', 'anomalous_rdc_vector', 'anomalous_chemical_shift', 'unusual_chemical_shift', 'complemented_chemical_shift', 'incompletely_assigned_chemical_shift', 'incompletely_assigned_spectral_peak', 'anomalous_data', 'unusual_data', 'unusual/rare_data', 'insufficient_data', @@ -2276,7 +2277,7 @@ def __init__(self, verbose=True, log=sys.stdout): self.group_items = ('sequence_mismatch', 'atom_nomenclature_mismatch', 'auth_atom_nomenclature_mismatch', 'ccd_mismatch', 'ambiguity_code_mismatch', - 'anomalous_bond_length', 'anomalous_rdc_vector', + 'anomalous_bond_length', 'ambiguous_dihedral_angle', 'anomalous_rdc_vector', 'complemented_chemical_shift', 'incompletely_assigned_chemical_shift', 'incompletely_assigned_spectral_peak', 'unusual/rare_data', 'insufficient_data', 'conflicted_data', 'inconsistent_data', 'redundant_data', diff --git a/wwpdb/utils/nmr/NmrDpUtility.py b/wwpdb/utils/nmr/NmrDpUtility.py index d275fce0b..a907ed7bf 100644 --- a/wwpdb/utils/nmr/NmrDpUtility.py +++ b/wwpdb/utils/nmr/NmrDpUtility.py @@ -31154,6 +31154,14 @@ def __validateLegacyMr(self): if self.__verbose: self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Anomalous RDC vector]'): self.report.warning.appendDescription('anomalous_rdc_vector', {'file_name': file_name, 'description': warn}) @@ -31348,6 +31356,14 @@ def __validateLegacyMr(self): if self.__verbose: self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Anomalous RDC vector]'): self.report.warning.appendDescription('anomalous_rdc_vector', {'file_name': file_name, 'description': warn}) @@ -31720,6 +31736,13 @@ def __validateLegacyMr(self): # if self.__verbose: # self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") # """ + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") else: self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ KeyError - " + warn) self.report.setError() @@ -31901,6 +31924,14 @@ def __validateLegacyMr(self): if self.__verbose: self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Unsupported data]'): self.report.warning.appendDescription('unsupported_mr_data', {'file_name': file_name, 'description': warn}) @@ -32072,6 +32103,13 @@ def __validateLegacyMr(self): # if self.__verbose: # self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") # """ + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") else: self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ KeyError - " + warn) self.report.setError() @@ -32370,6 +32408,13 @@ def __validateLegacyMr(self): # if self.__verbose: # self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") # """ + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") else: self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ KeyError - " + warn) self.report.setError() @@ -32871,6 +32916,14 @@ def __validateLegacyMr(self): if self.__verbose: self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Ambiguous dihedral angle]'): + self.report.warning.appendDescription('ambiguous_dihedral_angle', + {'file_name': file_name, 'description': warn}) + self.report.setWarning() + + if self.__verbose: + self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning - {warn}\n") + elif warn.startswith('[Anomalous RDC vector]'): self.report.warning.appendDescription('anomalous_rdc_vector', {'file_name': file_name, 'description': warn}) diff --git a/wwpdb/utils/nmr/mr/BiosymMRParserListener.py b/wwpdb/utils/nmr/mr/BiosymMRParserListener.py index 971b0946c..c833674cd 100644 --- a/wwpdb/utils/nmr/mr/BiosymMRParserListener.py +++ b/wwpdb/utils/nmr/mr/BiosymMRParserListener.py @@ -2044,9 +2044,14 @@ def exitDihedral_angle_restraint(self, ctx: BiosymMRParser.Dihedral_angle_restra if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Dihedral angle'): + """ + if not self.areUniqueCoordAtoms('a dihedral angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a dihedral angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -2074,6 +2079,8 @@ def exitDihedral_angle_restraint(self, ctx: BiosymMRParser.Dihedral_angle_restra _dstFunc += f" {dstFunc4}" print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {_dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 @@ -2083,7 +2090,7 @@ def exitDihedral_angle_restraint(self, ctx: BiosymMRParser.Dihedral_angle_restra dstFunc = self.selectRealisticChi2AngleConstraint(atom1, atom2, atom3, atom4, dstFunc) row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.' if dstFunc2 is None else 1, None, angleName, + combinationId if dstFunc2 is None else 1, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2193,9 +2200,14 @@ def exitDihedral_angle_constraint(self, ctx: BiosymMRParser.Dihedral_angle_const if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Dihedral angle'): + """ + if not self.areUniqueCoordAtoms('a dihedral angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a dihedral angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -2219,13 +2231,15 @@ def exitDihedral_angle_constraint(self, ctx: BiosymMRParser.Dihedral_angle_const if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2346,7 +2360,7 @@ def exitChirality_constraint(self, ctx: BiosymMRParser.Chirality_constraintConte if len(self.atomSelectionSet) < 2: return - if not self.areUniqueCoordAtoms('a Chirality'): + if not self.areUniqueCoordAtoms('a chirality'): return if self.__createSfDict: @@ -2446,13 +2460,17 @@ def exitProchirality_constraint(self, ctx: BiosymMRParser.Prochirality_constrain atom5['chain_id'], atom5['seq_id'], atom5['comp_id'], atom5['atom_id'], sf['list_id']]) - def areUniqueCoordAtoms(self, subtype_name): + def areUniqueCoordAtoms(self, subtype_name, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ for _atomSelectionSet in self.atomSelectionSet: + _lenAtomSelectionSet = len(_atomSelectionSet) + + if _lenAtomSelectionSet == 0: + return False # raised error already - if len(_atomSelectionSet) < 2: + if _lenAtomSelectionSet == 1: continue for (atom1, atom2) in itertools.combinations(_atomSelectionSet, 2): @@ -2460,7 +2478,12 @@ def areUniqueCoordAtoms(self, subtype_name): continue if atom1['seq_id'] != atom2['seq_id']: continue - self.__f.append(f"[Invalid atom selection] {self.__getCurrentRestraint()}" + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue + self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") return False diff --git a/wwpdb/utils/nmr/mr/CharmmMRParserListener.py b/wwpdb/utils/nmr/mr/CharmmMRParserListener.py index 6995f7150..02656dec4 100644 --- a/wwpdb/utils/nmr/mr/CharmmMRParserListener.py +++ b/wwpdb/utils/nmr/mr/CharmmMRParserListener.py @@ -939,11 +939,16 @@ def exitDihedral_angle_restraint(self, ctx: CharmmMRParser.Dihedral_angle_restra if len(self.atomSelectionSet) != 4: return - + """ if not self.areUniqueCoordAtoms('a dihedral angle (DIHE)'): if len(self.__g) > 0: self.__f.extend(self.__g) return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a dihedral angle (DIHE)', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -967,13 +972,15 @@ def exitDihedral_angle_restraint(self, ctx: CharmmMRParser.Dihedral_angle_restra if self.__debug: print(f"subtype={self.__cur_subtype} (DIHE) id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -1631,16 +1638,14 @@ def validateAngleRange(self, weight, misc_dict, return dstFunc - def areUniqueCoordAtoms(self, subtype_name, skip_col=None): + def areUniqueCoordAtoms(self, subtype_name, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ - for col, _atomSelectionSet in enumerate(self.atomSelectionSet): + for _atomSelectionSet in self.atomSelectionSet: _lenAtomSelectionSet = len(_atomSelectionSet) if _lenAtomSelectionSet == 0: - if skip_col is not None and col in skip_col: - continue return False # raised error already if _lenAtomSelectionSet == 1: @@ -1651,6 +1656,11 @@ def areUniqueCoordAtoms(self, subtype_name, skip_col=None): continue if atom1['seq_id'] != atom2['seq_id']: continue + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") diff --git a/wwpdb/utils/nmr/mr/CnsMRParserListener.py b/wwpdb/utils/nmr/mr/CnsMRParserListener.py index 47fab76cd..d6ffcdb50 100644 --- a/wwpdb/utils/nmr/mr/CnsMRParserListener.py +++ b/wwpdb/utils/nmr/mr/CnsMRParserListener.py @@ -1885,11 +1885,16 @@ def exitDihedral_assign(self, ctx: CnsMRParser.Dihedral_assignContext): if not self.__hasPolySeq and not self.__hasNonPolySeq: return - + """ if not self.areUniqueCoordAtoms('a dihedral angle (DIHE)'): if len(self.__g) > 0: self.__f.extend(self.__g) return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a dihedral angle (DIHE)', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -1913,13 +1918,15 @@ def exitDihedral_assign(self, ctx: CnsMRParser.Dihedral_assignContext): if self.__debug: print(f"subtype={self.__cur_subtype} (DIHE) id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2057,7 +2064,7 @@ def validateAngleRange(self, weight, misc_dict, return dstFunc - def areUniqueCoordAtoms(self, subtype_name, skip_col=None): + def areUniqueCoordAtoms(self, subtype_name, skip_col=None, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ @@ -2077,6 +2084,11 @@ def areUniqueCoordAtoms(self, subtype_name, skip_col=None): continue if atom1['seq_id'] != atom2['seq_id']: continue + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") diff --git a/wwpdb/utils/nmr/mr/CyanaMRParserListener.py b/wwpdb/utils/nmr/mr/CyanaMRParserListener.py index f9fa83c65..674b39dcb 100644 --- a/wwpdb/utils/nmr/mr/CyanaMRParserListener.py +++ b/wwpdb/utils/nmr/mr/CyanaMRParserListener.py @@ -1245,7 +1245,7 @@ def exitDistance_restraint(self, ctx: CyanaMRParser.Distance_restraintContext): if len(self.atomSelectionSet) < 2: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return chain_id_1 = self.atomSelectionSet[0][0]['chain_id'] @@ -1771,7 +1771,7 @@ def exitDistance_wo_comp_restraint(self, chainId1, seqId1, atomId1, chainId2, se if len(self.atomSelectionSet) < 2: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return chain_id_1 = self.atomSelectionSet[0][0]['chain_id'] @@ -4057,9 +4057,14 @@ def exitTorsion_angle_restraint(self, ctx: CyanaMRParser.Torsion_angle_restraint if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -4077,10 +4082,12 @@ def exitTorsion_angle_restraint(self, ctx: CyanaMRParser.Torsion_angle_restraint if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -4159,9 +4166,14 @@ def exitTorsion_angle_restraint(self, ctx: CyanaMRParser.Torsion_angle_restraint if len(self.atomSelectionSet) < 5: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -4177,10 +4189,12 @@ def exitTorsion_angle_restraint(self, ctx: CyanaMRParser.Torsion_angle_restraint if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} atom5={atom5} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, None, None, None, None, atom5) @@ -4567,13 +4581,17 @@ def validateRdcRange(self, weight, orientation, target_value, lower_limit, upper return dstFunc - def areUniqueCoordAtoms(self, subtype_name): + def areUniqueCoordAtoms(self, subtype_name, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ for _atomSelectionSet in self.atomSelectionSet: + _lenAtomSelectionSet = len(_atomSelectionSet) - if len(_atomSelectionSet) < 2: + if _lenAtomSelectionSet == 0: + return False # raised error already + + if _lenAtomSelectionSet == 1: continue for (atom1, atom2) in itertools.combinations(_atomSelectionSet, 2): @@ -4581,7 +4599,12 @@ def areUniqueCoordAtoms(self, subtype_name): continue if atom1['seq_id'] != atom2['seq_id']: continue - self.__f.append(f"[Invalid atom selection] {self.__getCurrentRestraint()}" + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue + self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") return False @@ -7510,9 +7533,14 @@ def exitTorsion_angle_w_chain_restraint(self, ctx: CyanaMRParser.Torsion_angle_w if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -7530,10 +7558,12 @@ def exitTorsion_angle_w_chain_restraint(self, ctx: CyanaMRParser.Torsion_angle_w if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -7612,9 +7642,14 @@ def exitTorsion_angle_w_chain_restraint(self, ctx: CyanaMRParser.Torsion_angle_w if len(self.atomSelectionSet) < 5: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -7630,10 +7665,12 @@ def exitTorsion_angle_w_chain_restraint(self, ctx: CyanaMRParser.Torsion_angle_w if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} atom5={atom5} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, None, None, None, None, atom5) @@ -7727,7 +7764,7 @@ def exitCco_restraint(self, ctx: CyanaMRParser.Cco_restraintContext): if len(self.atomSelectionSet) < 2: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return chain_id_1 = self.atomSelectionSet[0][0]['chain_id'] diff --git a/wwpdb/utils/nmr/mr/DynamoMRParserListener.py b/wwpdb/utils/nmr/mr/DynamoMRParserListener.py index 21b7c23ba..b04180a37 100644 --- a/wwpdb/utils/nmr/mr/DynamoMRParserListener.py +++ b/wwpdb/utils/nmr/mr/DynamoMRParserListener.py @@ -2403,9 +2403,14 @@ def exitTorsion_angle_restraint(self, ctx: DynamoMRParser.Torsion_angle_restrain if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(constraintType='backbone chemical shifts', @@ -2431,13 +2436,15 @@ def exitTorsion_angle_restraint(self, ctx: DynamoMRParser.Torsion_angle_restrain if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} (index={index}) angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2522,9 +2529,14 @@ def exitTorsion_angle_restraint_sw_segid(self, ctx: DynamoMRParser.Torsion_angle if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(constraintType='backbone chemical shifts', @@ -2550,13 +2562,15 @@ def exitTorsion_angle_restraint_sw_segid(self, ctx: DynamoMRParser.Torsion_angle if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} (index={index}) angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2641,9 +2655,14 @@ def exitTorsion_angle_restraint_ew_segid(self, ctx: DynamoMRParser.Torsion_angle if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle'): + """ + if not self.areUniqueCoordAtoms('a torsion angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(constraintType='backbone chemical shifts', @@ -2669,13 +2688,15 @@ def exitTorsion_angle_restraint_ew_segid(self, ctx: DynamoMRParser.Torsion_angle if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} (index={index}) angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -3569,13 +3590,17 @@ def validateRdcRange(self, weight, target_value, lower_limit, upper_limit): return dstFunc - def areUniqueCoordAtoms(self, subtype_name): + def areUniqueCoordAtoms(self, subtype_name, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ for _atomSelectionSet in self.atomSelectionSet: + _lenAtomSelectionSet = len(_atomSelectionSet) - if len(_atomSelectionSet) < 2: + if _lenAtomSelectionSet == 0: + return False # raised error already + + if _lenAtomSelectionSet == 1: continue for (atom1, atom2) in itertools.combinations(_atomSelectionSet, 2): @@ -3583,7 +3608,12 @@ def areUniqueCoordAtoms(self, subtype_name): continue if atom1['seq_id'] != atom2['seq_id']: continue - self.__f.append(f"[Invalid atom selection] {self.__getCurrentRestraint()}" + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue + self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") return False @@ -3664,7 +3694,7 @@ def exitCoupling_restraint(self, ctx: DynamoMRParser.Coupling_restraintContext): if len(self.atomSelectionSet) < 4: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return if self.__createSfDict: @@ -3808,7 +3838,7 @@ def exitCoupling_restraint_sw_segid(self, ctx: DynamoMRParser.Coupling_restraint if len(self.atomSelectionSet) < 4: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return if self.__createSfDict: @@ -3952,7 +3982,7 @@ def exitCoupling_restraint_ew_segid(self, ctx: DynamoMRParser.Coupling_restraint if len(self.atomSelectionSet) < 4: return - if not self.areUniqueCoordAtoms('a Scalar coupling'): + if not self.areUniqueCoordAtoms('a scalar coupling'): return if self.__createSfDict: @@ -4238,9 +4268,14 @@ def exitTalos_restraint(self, ctx: DynamoMRParser.Talos_restraintContext): if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle (TALOS)'): + """ + if not self.areUniqueCoordAtoms('a torsion angle (TALOS)'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle (TALOS)', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(constraintType='backbone chemical shifts', @@ -4257,10 +4292,12 @@ def exitTalos_restraint(self, ctx: DynamoMRParser.Talos_restraintContext): if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} className={_class} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -4420,9 +4457,14 @@ def exitTalos_restraint_wo_s2(self, ctx: DynamoMRParser.Talos_restraint_wo_s2Con if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Torsion angle (TALOS)'): + """ + if not self.areUniqueCoordAtoms('a torsion angle (TALOS)'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle (TALOS)', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(constraintType='backbone chemical shifts', @@ -4439,10 +4481,12 @@ def exitTalos_restraint_wo_s2(self, ctx: DynamoMRParser.Talos_restraint_wo_s2Con if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} className={_class} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) diff --git a/wwpdb/utils/nmr/mr/RosettaMRParserListener.py b/wwpdb/utils/nmr/mr/RosettaMRParserListener.py index 1f11051de..105add6d5 100644 --- a/wwpdb/utils/nmr/mr/RosettaMRParserListener.py +++ b/wwpdb/utils/nmr/mr/RosettaMRParserListener.py @@ -1960,7 +1960,7 @@ def exitAngle_restraint(self, ctx: RosettaMRParser.Angle_restraintContext): if len(self.atomSelectionSet) < 3: return - if not self.areUniqueCoordAtoms('an Angle'): + if not self.areUniqueCoordAtoms('an angle'): return isNested = len(self.stackNest) > 0 @@ -2230,9 +2230,14 @@ def exitDihedral_restraint(self, ctx: RosettaMRParser.Dihedral_restraintContext) if len(self.atomSelectionSet) < 4: return - - if not self.areUniqueCoordAtoms('a Dihedral angle'): + """ + if not self.areUniqueCoordAtoms('a dihedral angle'): return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a torsion angle', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -2262,13 +2267,15 @@ def exitDihedral_restraint(self, ctx: RosettaMRParser.Dihedral_restraintContext) if self.__debug: print(f"subtype={self.__cur_subtype} id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item and (not isNested or self.__is_first_nest): sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4) @@ -2346,7 +2353,7 @@ def exitDihedral_pair_restraint(self, ctx: RosettaMRParser.Dihedral_pair_restrai if len(self.atomSelectionSet) < 8: return - if not self.areUniqueCoordAtoms('a Dihedral angle pair'): + if not self.areUniqueCoordAtoms('a dihedral angle pair'): return if self.__createSfDict: @@ -4117,13 +4124,17 @@ def exitRdc_restraint(self, ctx: RosettaMRParser.Rdc_restraintContext): finally: self.numberSelection.clear() - def areUniqueCoordAtoms(self, subtype_name): + def areUniqueCoordAtoms(self, subtype_name, allow_ambig=False, allow_ambig_warn_title=''): """ Check whether atom selection sets are uniquely assigned. """ for _atomSelectionSet in self.atomSelectionSet: + _lenAtomSelectionSet = len(_atomSelectionSet) + + if _lenAtomSelectionSet == 0: + return False # raised error already - if len(_atomSelectionSet) < 2: + if _lenAtomSelectionSet == 1: continue for (atom1, atom2) in itertools.combinations(_atomSelectionSet, 2): @@ -4131,7 +4142,12 @@ def areUniqueCoordAtoms(self, subtype_name): continue if atom1['seq_id'] != atom2['seq_id']: continue - self.__f.append(f"[Invalid atom selection] {self.__getCurrentRestraint()}" + if allow_ambig: + self.__f.append(f"[{allow_ambig_warn_title}] {self.__getCurrentRestraint()}" + f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " + f"{atom2['atom_id']}' found in {subtype_name} restraint.") + continue + self.__f.append(f"[Invalid data] {self.__getCurrentRestraint()}" f"Ambiguous atom selection '{atom1['chain_id']}:{atom1['seq_id']}:{atom1['comp_id']}:{atom1['atom_id']} or " f"{atom2['atom_id']}' is not allowed as {subtype_name} restraint.") return False diff --git a/wwpdb/utils/nmr/mr/XplorMRParserListener.py b/wwpdb/utils/nmr/mr/XplorMRParserListener.py index 0cca26185..34fb18e22 100644 --- a/wwpdb/utils/nmr/mr/XplorMRParserListener.py +++ b/wwpdb/utils/nmr/mr/XplorMRParserListener.py @@ -2344,11 +2344,16 @@ def exitDihedral_assign(self, ctx: XplorMRParser.Dihedral_assignContext): if not self.__hasPolySeq and not self.__hasNonPolySeq: return - + """ if not self.areUniqueCoordAtoms('a dihedral angle (DIHE)'): if len(self.__g) > 0: self.__f.extend(self.__g) return + """ + len_f = len(self.__f) + self.areUniqueCoordAtoms('a dihedral angle (DIHE)', + allow_ambig=True, allow_ambig_warn_title='Ambiguous dihedral angle') + combinationId = '.' if len_f == len(self.__f) else 0 if self.__createSfDict: sf = self.__getSf(potentialType=getPotentialType(self.__file_type, self.__cur_subtype, dstFunc)) @@ -2372,13 +2377,15 @@ def exitDihedral_assign(self, ctx: XplorMRParser.Dihedral_assignContext): if self.__debug: print(f"subtype={self.__cur_subtype} (DIHE) id={self.dihedRestraints} angleName={angleName} " f"atom1={atom1} atom2={atom2} atom3={atom3} atom4={atom4} {dstFunc}") + if isinstance(combinationId, int): + combinationId += 1 if self.__createSfDict and sf is not None: if first_item: sf['id'] += 1 first_item = False sf['index_id'] += 1 row = getRow(self.__cur_subtype, sf['id'], sf['index_id'], - '.', None, angleName, + combinationId, None, angleName, sf['list_id'], self.__entryId, dstFunc, self.__authToStarSeq, self.__authToOrigSeq, self.__authToInsCode, self.__offsetHolder, atom1, atom2, atom3, atom4)