##
# File: NmrDpUtility.py
# Date: 26-Sep-2019
#
# Updates:
# 10-Oct-2019  M. Yokochi - add 'check_mandatory_tag' option to detect missing mandatory tags as errors
# 15-Oct-2019  M. Yokochi - revise criteria on discrepancy in distance restraints using normalized value
# 01-Nov-2019  M. Yokochi - revise error message, instead of Python ValueError message
# 05-Nov-2019  M. Yokochi - revise error messages and detect empty sequence information
# 28-Nov-2019  M. Yokochi - fix saveframe name of nef_molecular_system and add 'nmr-str2nef-deposit' workflow operation
# 29-Nov-2019  M. Yokochi - relax allowable range of weight values in restraint data and support index pointer in auxiliary loops
# 11-Dec-2019  M. Yokochi - fix internal errors while processing NMR-VTF/PDBStat_examples and NMR-VTF/BMRB
# 24-Jan-2020  M. Yokochi - add histogram of distance restraints per residue and distance restraints on contact map
# 27-Jan-2020  M. Yokochi - add contact map for inter-chain distance restraints
# 28-Jan-2020  M. Yokochi - add struct_conf and struct_sheet_range data in dp report
# 29-Jan-2020  M. Yokochi - change plot type of dihedral angle and RDC restraints per residue
# 05-Feb-2020  M. Yokochi - add 'circular-shift' and 'void-zero' constraint for dihedral angle restraint
# 05-Feb-2020  M. Yokochi - move conflicted_data error to warning
# 07-Feb-2020  M. Yokochi - replace 'number_of_potential_types' by 'potential_type_of_constraints' in dp report
# 07-Feb-2020  M. Yokochi - allow multiple values in a data type on per residue plots
# 13-Feb-2020  M. Yokochi - add 'number_of_constraints_per_polymer_type' for apilayer.postModifyNMRRestraint
# 14-Feb-2020  M. Yokochi - add 'spectram_dim' for apilayer.postModifyNMRPeaks
# 21-Feb-2020  M. Yokochi - update content-type definitions and add release mode (nmr-str2nef-release workflow operation)
# 02-Mar-2020  M. Yokochi - add 'nmr-cs-nef-consistency-check' and 'nmr-cs-str-consistency-check' workflow operation (DAOTHER-4515)
# 05-Mar-2020  M. Yokochi - revise warning message (disordered_index) and enumerations (DAOTHER-5485)
# 06-Mar-2020  M. Yokochi - fix invalid ambiguity_code while parsing
# 13-Mar-2020  M. Yokochi - revise error/warning messages
# 17-Mar-2020  M. Yokochi - add 'undefined' value for potential_type (DAOTHER-5508)
# 17-Mar-2020  M. Yokochi - revise warning message about enumeration mismatch for potential_type and restraint_origin (DAOTHER-5508)
# 17-Mar-2020  M. Yokochi - check total number of models (DAOTHER-436)
# 17-Mar-2020  M. Yokochi - check consistency between saveframe name and sf_framecode value
# 18-Mar-2020  M. Yokochi - rename warning type from skipped_sf/lp_category to skipped_saveframe/loop_category
# 18-Mar-2020  M. Yokochi - support 'Saveframe' data type as conventional NMR data (DAOTHER-2737)
# 19-Mar-2020  M. Yokochi - atom nomenclature should not become a blocker (DAOTHER-5527)
# 24-Mar-2020  M. Yokochi - add support for chemical shift reference (DAOTHER-1682)
# 24-Mar-2020  M. Yokochi - revise chain assignment for identical dimer case (DAOTHER-3343)
# 30-Mar-2020  M. Yokochi - preserve original sf_framecode for nef_molecular_system (NEF) or assembly (NMR-STAR)
# 31-Mar-2020  M. Yokochi - enable processing without log file
# 03-Apr-2020  M. Yokochi - preserve case code of atom_name (NEF) and Auth_atom_ID/Original_PDB_atom_name (NMR-STAR)
# 06-Apr-2020  M. Yokochi - synchronize with coordinates' auth_asym_id and auth_seq_id for combined NMR-STAR deposition
# 10-Apr-2020  M. Yokochi - fix crash in case of format issue
# 14-Apr-2020  M. Yokochi - fix dependency on label_seq_id, instead of using auth_seq_id in case (DAOTHER-5584)
# 18-Apr-2020  M. Yokochi - fix no model error in coordinate and allow missing 'sf_framecode' in NMR conventional deposition (DAOTHER-5594)
# 19-Apr-2020  M. Yokochi - support concatenated CS data in NMR conventional deposition (DAOTHER-5594)
# 19-Apr-2020  M. Yokochi - report warning against not superimposed models (DAOTHER-4060)
# 22-Apr-2020  M. Yokochi - convert comp_id in capital letters (DAOTHER-5600)
# 22-Apr-2020  M. Yokochi - fix GLY:HA1/HA2 to GLY:HA2/HA3 (DAOTHER-5600)
# 22-Apr-2020  M. Yokochi - fix ambiguity code mismatch if possible (DAOTHER-5601)
# 22-Apr-2020  M. Yokochi - fix None type object is not iterable error (DAOTHER-5602)
# 23-Apr-2020  M. Yokochi - support conventional atom name for methyl group without wildcard character, e.g. ALA:HB (DAOTHER-5603)
# 23-Apr-2020  M. Yokochi - change missing ambiguity_set_id error to warning (DAOTHER-5609)
# 23-Apr-2020  M. Yokochi - make sure to parse chem_shift_ref saveframe tag (DAOTHER-5610)
# 23-Apr-2020  M. Yokochi - implement automatic format correction (DAOTHER-5603, 5610)
# 24-Apr-2020  M. Yokochi - separate format_issue error and missing_mandatory_content error (DAOTHER-5611)
# 24-Apr-2020  M. Yokochi - support 'QR' pseudo atom name (DAOTHER-5611)
# 24-Apr-2020  M. Yokochi - allow mandatory value is missing in NMR conventional deposition (DAOTHER-5611)
# 25-Apr-2020  M. Yokochi - implement automatic format correction for 6NZN, 6PQF, 6PSI entry (DAOTHE-5611)
# 25-Apr-2020  M. Yokochi - add 'entity' content subtype (DAOTHER-5611)
# 25-Apr-2020  M. Yokochi - add 'corrected_format_issue' warning type (DAOTHER-5611)
# 27-Apr-2020  M. Yokochi - add 'auth_atom_nomenclature_mismatch' warning type (DAOTHER-5611)
# 27-Apr-2020  M. Yokochi - implement recursive format corrections (DAOTHER-5602)
# 28-Apr-2020  M. Yokochi - copy the normalized CS/MR files if output file path list is set (DAOTHER-5611)
# 28-Apr-2020  M. Yokochi - catch 'range-float' error as 'unusual data' warning (DAOTHER-5611)
# 28-Apr-2020  M. Yokochi - extract sequence from CS/MR loop with gap (DAOTHER-5611)
# 29-Apr-2020  M. Yokochi - support diagnostic message of PyNMRSTAR v2.6.5.1 or later (DAOTHER-5611)
# 29-Apr-2020  M. Yokochi - implement more automatic format corrections with PyNMRSTAR v2.6.5.1 (DAOTHER-5611)
# 29-Apr-2020  M. Yokochi - fix different CS warning between NEF and NMR-STAR (DAOTHER-5621)
# 29-Apr-2020  M. Yokochi - add 'number_of_constraint_sets' of experiment data in report (DAOTHER-5622)
# 29-Apr-2020  M. Yokochi - sort 'conflicted_data' and 'inconsistent_data' warning items (DAOTHER-5622)
# 30-Apr-2020  M. Yokochi - allow NMR conventional atom naming scheme in NMR-STAR V3.2 (DAOTHER-5634)
# 01-May-2020  M. Yokochi - allow NMR conventional atom naming scheme in NMR-STAR V3.2 (DAOTHER-5634)
# 02-May-2020  M. Yokochi - additional support for format issue correction while STAR to NEF conversion (DAOTHER-5577)
# 02-May-2020  M. Yokochi - re-implement basic mathematical functions using Numpy library
# 07-May-2020  M. Yokochi - revise warning type (from 'insuffcient_data' to 'encouragement') if total number of models is less than 8 (DAOTHER-5650)
# 07-May-2020  M. Yokochi - add preventive code for infinite loop while format issue correction
# 08-May-2020  M. Yokochi - sync update with wwpdb.utils.nmr.CifReader (DAOTHER-5654)
# 09-May-2020  M. Yokochi - add support for submitted coordinate file (allow missing of pdbx_poly_seq_scheme) (DAOTHER-5654)
# 12-May-2020  M. Yokochi - fix diselenide bond detection
# 14-May-2020  M. Yokochi - fix error detection for missing mandatory content (DAOTHER-5681 and 5682)
# 15-May-2020  M. Yokochi - add 'content_mismatch' error for NMR legacy deposition (DAOTHER-5687)
# 15-May-2020  M. Yokochi - revise encouragement message if total number of models is less than 5 (DAOTHER-5650)
# 16-May-2020  M. Yokochi - block NEF file upload in NMR legacy deposition (DAOTHER-5687)
# 30-May-2020  M. Yokochi - refer to atom_site to get total number of models (DAOTHER-5650)
# 01-Jun-2020  M. Yokochi - let RMSD cutoff value configurable (DAOTHER-4060)
# 05-Jun-2020  M. Yokochi - be compatible with wwpdb.utils.align.alignlib using Python 3 (DAOTHER-5766)
# 06-Jun-2020  M. Yokochi - be compatible with pynmrstar v3 (DAOTHER-5765)
# 12-Jun-2020  M. Yokochi - overall performance improvement by reusing cached data and code revision
# 19-Jun-2020  M. Yokochi - do not generate invalid restraints include self atom
# 26-Jun-2020  M. Yokochi - add support for covalent bond information (_nef_covalent_links and _Bond categories)
# 30-Jun-2020  M. Yokochi - ignore third party loops and items gracefully (DAOTHER-5896)
# 30-Jun-2020  M. Yokochi - prevent pynmrstar's exception due to empty string (DAOTHER-5894)
# 08-Jul-2020  M. Yokochi - bug fix release for DAOTHER-5926
# 09-Jul-2020  M. Yokochi - add support for categories in NMR-STAR specific peak list (DAOTHER-5926)
# 09-Jul-2020  M. Yokochi - adjust arguments of pynmrstar write_to_file() to prevent data losses (v2.6.1, DAOTHER-5926)
# 17-Aug-2020  M. Yokochi - add support for residue variant (DAOTHER-5906)
# 20-Aug-2020  M. Yokochi - add 'leave_intl_note' output parameter decides whether to leave internal commentary note in processed NMR-STAR file, set False for
#                           OneDep environment (DAOTHER-6030)
# 10-Sep-2020  M. Yokochi - add 'transl_pseudo_name' input parameter decides whether to translate conventional pseudo atom nomenclature in combined NMR-STAR file (DAOTHER-6128)
# 16-Sep-2020  M. Yokochi - bug fix release based on internal test using BMRB NMR restraint archive of 6.3k entries (DAOTHER-6128)
# 18-Sep-2020  M. Yokochi - bug fix release for negative sequence numbers (DAOTHER-6128)
# 25-Sep-2020  M. Yokochi - add 'tolerant_seq_align' input parameter which enables tolerant sequence alignment for residue variant, set False for OneDep environment (DAOTHER-6128)
# 09-Oct-2020  M. Yokochi - support circular chain_id re-mapping with seq_id shifts in data loops if it is necessary,
#                           'tolerant_seq_align' input parameter is required (DAOTHER-6128)
# 22-Oct-2020  M. Yokochi - run diagnostic routine for case of sequence mismatch between defined polymer sequence and sequence in data loop (DAOTHER-6128)
# 11-Nov-2020  M. Yokochi - set NEF v1.1 as the default specification
# 12-Nov-2020  M. Yokochi - improve NMR warning messages (DAOTHER-6109, 6167)
# 18-Nov-2020  M. Yokochi - fix calculation of CS completeness, fix empty polymer_sequence_in_loop due to atom_site.pdbx_PDB_ins_code (DAOTHER-6128)
# 20-Nov-2020  M. Yokochi - rename 'remarkable_data' warning category to 'unusual/rare_data' (DAOTHER-6372)
# 26-Nov-2020  M. Yokochi - detect the nearest ferromagnetic atom, in addition to paramagnetic atom (DAOTHER-6366)
# 27-Nov-2020  M. Yokochi - add support for non-IUPAC atom names for standard amino acids, e.g. ARG:HB1/HB2 -> HB2/HB3 (DAOTHER-6373)
# 17-Dec-2020  M. Yokochi - support 'atom_not_found' error with message revision (DAOTHER-6345)
# 25-Jan-2021  M. Yokochi - simplify code for Entity_assemble_ID and chain_code
# 25-Jan-2021  M. Yokochi - add CS validation code about rotameric state of ILE/LEU/VAL residue
# 03-Feb-2021  M. Yokochi - update polymer sequence which shares the same entity and missing in the molecular assembly information if necessary,
#                           e.g. double stranded DNA (DAOTHER-6128, BMRB entry: 16812, PDB ID: 6kae)
# 10-Mar-2021  M. Yokochi - block NEF deposition missing '_nef_sequence' category and turn off salvage routine for the case (DAOTHER-6694)
# 10-Mar-2021  M. Yokochi - add support for audit loop in NEF (DAOTHER-6327)
# 12-Mar-2021  M. Yokochi - add diagnostic routine to fix inconsistent sf_framecode of conventional CS file (DAOTHER-6693)
# 14-May-2021  M. Yokochi - add support for PyNMRSTAR v3.1.1 (DAOTHER-6693)
# 20-May-2021  M. Yokochi - fix duplicating pynmrstar data objects during format issue correction that leads to empty upload summary page (DAOTHER-6834)
# 24-May-2021  M. Yokochi - fix tautomer detection of coordinate (DAOTHER-6809)
# 17-Jun-2021  M. Yokochi - fix error in handling lower/upper linear limits (DAOTHER-6963)
# 17-Jun-2021  M. Yokochi - relax tolerance on chemical shift difference (DAOTHER-6963)
# 23-Jun-2021  M. Yokochi - send back the initial error message when format remediation fails (DAOTHER-6830)
# 25-Jun-2021  M. Yokochi - block restraint files that have no distance restraints (DAOTHER-6830)
# 28-Jun-2021  M. Yokochi - support cif-formatted CS file for reupload without changing CS data (DAOTHER-6830, 7097)
# 29-Jun-2021  M. Yokochi - include auth_asym_id in NMR data processing report (DAOTHER-7108)
# 29-Jun-2021  M. Yokochi - add support for PyNMRSTAR v3.2.0 (DAOTHER-7107)
# 02-Jul-2021  M. Yokochi - detect content type of AMBER restraint file and AMBER auxiliary file (DAOTHER-6830, 1901)
# 12-Jul-2021  M. Yokochi - add RCI validation code for graphical representation of NMR data
# 24-Aug-2021  M. Yokochi - detect content type of XPLOR-NIH planarity restraints (DAOTHER-7265)
# 10-Sep-2021  M. Yokochi - prevent system crash for an empty loop case of CS/MR data (D_1292117593)
# 13-Oct-2021  M. Yokochi - fix/adjust tolerances for spectral peak list (DAOTHER-7389, issue #1 and #2)
# 13-Oct-2021  M. Yokochi - code revision according to PEP8 using Pylint (DAOTHER-7389, issue #5)
# 14-Oct-2021  M. Yokochi - remove unassigned chemical shifts, clear incompletely assigned spectral peaks (DAOTHER-7389, issue #3)
# 27-Oct-2021  M. Yokochi - fix collection of unmapped sequences and utilize Auth_asym_ID* tag for chain_id if Entity_assembly_ID* is not available (DAOTHER-7421)
# 28-Oct-2021  M. Yokochi - resolve case-insensitive saveframe name collision for CIF (DAOTHER-7389, issue #4)
# 16-Nov-2021  M. Yokochi - fix sequence conflict in case that large sequence gap in CS implies multi chain complex (DAOTHER-7465)
# 16-Nov-2021  M. Yokochi - fix server crash with disulfide bond, which is not supported by chemical shifts (DAOTHER-7475)
# 16-Nov-2021  M. Yokochi - revised error message for malformed XPLOR-NIH RDC restraints (DAOTHER-7478)
# 18-Nov-2021  M. Yokochi - detect content type of XPLOR-NIH hydrogen bond geometry restraints (DAOTHER-7478)
# 18-Nov-2021  M. Yokochi - relax detection of distance restraints for nm-res-cya and nm-res-oth (DAOTHER-7491)
# 13-Dec-2021  M. Yokochi - append sequence spacer between large gap to prevent failure of sequence alignment (DAOTHER-7465, issue #2)
# 14-Dec-2021  M. Yokochi - report detailed warning message against not superimposed models and exactly overlaid models (DAOTHER-4060, 7544)
# 15-Dec-2021  M. Yokochi - fix server crash while uploading NMR restraint file in NMR-STAR format (DAOTHER-7545)
# 21-Dec-2021  M. Yokochi - fix wrong missing_mandatory_content error when uploading NMR restraint files in NMR-STAR format (DAOTHER-7545, issue #2)
# 14-Jan-2022  M. Yokochi - report exactly overlaid models in the coordinate file (DAOTHER-7544)
# 17-Feb-2022  M. Yokochi - aware of presence of _atom_site.pdbx_auth_atom_name for N-terminal protonation change while upload-conversion of the coordinate file (DAOTHER-7665)
# 17-Feb-2022  M. Yokochi - do report incompletely assigned chemical shifts for conventional deposition (DAOTHER-7662)
# 21-Feb-2022  M. Yokochi - verify 'onebond' coherence transfer type using CCD (DAOTHER-7681, issue #2)
# 21-Feb-2022  M. Yokochi - verify pseudo atom names in NMR restraints are in assigned chemical shifts (DAOTHER-7681, issue #1)
# 24-Mar-2022  M. Yokochi - utilize software specific MR parsers for sanity check of NMR restraint files (DAOTHER-7690)
# 20-Mar-2022  M. Yokochi - add support for _atom_site.label_alt_id (DAOTHER-4060, 7544, NMR restraint remediation)
# 06-Apr-2022  M. Yokochi - detect other possible MR format if the first parsing fails (DAOTHER-7690)
# 02-May-2022  M. Yokochi - implement recursive MR splitter guided by MR parsers (NMR restraint remediation)
# 17-May-2022  M. Yokochi - add support for BIOSYM MR format (DAOTHER-7825, NMR restraint remediation)
# 01-Jun-2022  M. Yokochi - add support for GROMACS PT/MR format (DAOTHER-7769, NMR restraint remediation)
# 17-Jun-2022  M. Yokochi - add support for DYNAMO/PALES/TALOS MR format (DAOTHER-7872, NMR restraint remediation)
# 06-Jul-2022  M. Yokochi - add support for SYBYL MR format (DAOTHER-7902, NMR restraint remediation)
# 05-Aug-2022  M. Yokochi - do not add a saveframe tag if there is already the tag (DAOTHER-7947)
# 31-Aug-2022  M. Yokochi - separate atom_not_found error and hydrogen_not_instantiated error (NMR restraint remediation)
# 06-Sep-2022  M. Yokochi - add support for branched entity (NMR restraint remediation)
# 13-Sep-2022  M. Yokochi - add 'nm-res-isd' file type for IDS (inference structure determination) restraint format (DAOTHER-8059, NMR restraint remediation)
# 22-Sep-2022  M. Yokochi - add 'nm-res-cha' file type for CHARMM restraint format (DAOTHER-8058, NMR restraint remediation)
# 20-Oct-2022  M. Yokochi - report recommendation message when there is no distance restraints for NMR deposition, instead of blocker (DAOTHER-8088 1.b, 8108)
# 24-Oct-2022  M. Yokochi - add support for floating chiral stereo assignments (NMR restraint remediation)
# 15-Dec-2022  M. Yokochi - merge CS and MR as a single NMR data file in CIF format with comprehensive molecular assembly information (DAOTHER-7407, NMR restraint remediation)
# 13-Jan-2023  M. Yokochi - add support for small angle X-ray scattering restraints (NMR restraint remediation)
# 24-Jan-2023  M. Yokochi - add support for heteronuclear relaxation data (NOE, T1, T2, T1rho, Order parameter) (NMR restraint remediation)
# 23-Feb-2023  M. Yokochi - combine spectral peak lists in any plain text format into single NMR-STAR until Phase 2 release (DAOTHER-7407)
# 24-Mar-2023  M. Yokochi - add 'nmr-nef2cif-deposit' and 'nmr-str2cif-deposit' workflow operations (DAOTHER-7407)
# 22-Jun-2023  M. Yokochi - convert model file when pdbx_poly_seq category is missing for reuploading nmr_data after unlock (DAOTHER-8580)
# 19-Jul-2023  M. Yokochi - fix not to merge restraint id (_Gen_dist_constraint.ID) if lower and upper bounds are different (DAOTHER-8705)
# 20-Jul-2023  M. Yokochi - throw 'format_issue' error when polymer sequence extraction fails (DAOTHER-8644)
# 09-Aug-2023  M. Yokochi - remediate combined nmr_data by default and improve robustness of sequence alignment (DAOTHER-8751)
# 13-Sep-2023  M. Yokochi - construct pseudo CCD from the coordinates (DAOTHER-8817)
# 29-Sep-2023  M. Yokochi - add 'nmr-str2cif-annotation' workflow operation (DAOTHER-8817, 8828)
# 02-Oct-2023  M. Yokochi - do not reorganize _Gen_dist_constraint.ID of native combined NMR data (DAOTHER-8855)
# 10-Nov-2023  M. Yokochi - raise a content mismatch error properly for NMR spectral peak list when the file is irrelevant (DAOTHER-8949)
# 13-Dec-2023  M. Yokochi - add 'hydrogen_non_instantiated' warning (DAOTHER-8945)
# 11-Jan-2024  M. Yokochi - convert RTF to ASCII file if necessary (DAOTHER-9063)
# 12-Jan-2024  M. Yokochi - preserve the original sequence offset of CS loop of UNMAPPED residue (DAOTHER-9065)
# 12-Jan-2024  M. Yokochi - fix sequence merge of entity loop and CS loop (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 - detect coordinate issue (DAOTHER-9084, type_symbol mismatches label_atom_id)
# 24-Jan-2024  M. Yokochi - reconstruct polymer/non-polymer sequence based on pdb_mon_id, instead of auth_mon_id (D_1300043061)
# 21-Feb-2024  M. Yokochi - add support for discontinuous model_id (NMR restraint remediation, 2n6j)
# 07-Mar-2024  M. Yokochi - extract pdbx_poly_seq_scheme.auth_mon_id as alt_cmop_id to prevent sequence mismatch due to 5-letter CCD ID (DAOTHER-9158 vs D_1300043061)
# 22-Mar-2024  M. Yokochi - test tautomeric states of histidine-like residue across models (DAOTHER-9252)
# 01-May-2024  M. Yokochi - merge cs/mr sequence extensions containing unknown residues (e.g UNK, DN, N) if necessary (NMR restraint remediation, 6fw4)
# 22-May-2024  M. Yokochi - block deposition using a peak list file in any binary format and prevent 'nm-pea-any' occasionally matches with 'nm-res-cya' (DAOTHER-9425)
# 11-Jun-2024  M. Yokcohi - add support for ligand remapping in annotation process (DAOTHER-9286)
# 25-Jun-2024  M. Yokochi - strip white spaces in a datablock name derived from the model file (DAOTHER-9511)
# 28-Jun-2024  M. Yokochi - ignore extraneous input value for numeric tags and replace statistics of chemical shifts using remediated loop (DAOTHER-9520)
# 28-Jun-2024  M. Yokochi - convert conventional NMR name of carbonyl carbon 'CO' to valid one 'C' (DAOTHER-9520, 2nd case)
# 19-Aug-2024  M. Yokochi - fill 'Data_file_name' saveframe tag by default (DAOTHER-9520, 4th case)
# 20-Aug-2024  M. Yokochi - support truncated loop sequence in the model (DAOTHER-9644)
##
""" Wrapper class for NMR data processing.
    @author: Masashi Yokochi
"""
import sys
import os
import itertools
import copy
import collections
import re
import math
import codecs
import shutil
import time
import hashlib
import pynmrstar
import chardet
import numpy

from packaging import version
from munkres import Munkres
from operator import itemgetter
from striprtf.striprtf import rtf_to_text

from mmcif.io.IoAdapterPy import IoAdapterPy

try:
    from wwpdb.utils.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
    from wwpdb.utils.nmr.NEFTranslator.NEFTranslator import (NEFTranslator,
                                                             NEF_VERSION,
                                                             altDistanceConstraintType,
                                                             altDihedralAngleConstraintType,
                                                             altRdcConstraintType,
                                                             PARAMAGNETIC_ELEMENTS,
                                                             FERROMAGNETIC_ELEMENTS,
                                                             NON_METAL_ELEMENTS,
                                                             MAX_DIM_NUM_OF_SPECTRA,
                                                             MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK)
    from wwpdb.utils.nmr.NmrDpReport import NmrDpReport
    from wwpdb.utils.nmr.AlignUtil import (LOW_SEQ_COVERAGE,
                                           MIN_SEQ_COVERAGE_W_CONFLICT,
                                           LARGE_ASYM_ID,
                                           emptyValue, trueValue,
                                           monDict3,
                                           protonBeginCode, pseProBeginCode, aminoProtonCode, rdcBbPairCode,
                                           unknownResidue,
                                           hasLargeInnerSeqGap, hasLargeSeqGap,
                                           fillInnerBlankCompId, fillBlankCompId, fillBlankCompIdWithOffset,
                                           beautifyPolySeq,
                                           getMiddleCode, getGaugeCode, getScoreOfSeqAlign,
                                           getOneLetterCodeCan, getOneLetterCodeCanSequence, getOneLetterCodeSequence,
                                           letterToDigit,
                                           getRestraintFormatName,
                                           getRestraintFormatNames,
                                           updatePolySeqRst,
                                           sortPolySeqRst,
                                           alignPolymerSequence,
                                           assignPolymerSequence,
                                           trimSequenceAlignment,
                                           getPrettyJson)
    from wwpdb.utils.nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from wwpdb.utils.nmr.ChemCompUtil import ChemCompUtil
    from wwpdb.utils.nmr.io.CifReader import CifReader, LEN_MAJOR_ASYM_ID
    from wwpdb.utils.nmr.rci.RCI import RCI
    from wwpdb.utils.nmr.CifToNmrStar import CifToNmrStar
    from wwpdb.utils.nmr.NmrVrptUtility import (uncompress_gzip_file, compress_as_gzip_file,
                                                load_from_pickle, write_as_pickle,
                                                to_np_array, distance,
                                                to_unit_vector, dihedral_angle)
#    from wwpdb.utils.nmr.NmrStarToCif import NmrStarToCif
    from wwpdb.utils.nmr.mr.ParserListenerUtil import (translateToStdResName,
                                                       translateToStdAtomName,
                                                       coordAssemblyChecker,
                                                       isIdenticalRestraint,
                                                       isAmbigAtomSelection,
                                                       getTypeOfDihedralRestraint,
                                                       isLikeHis,
                                                       startsWithPdbRecord,
                                                       getRestraintName,
                                                       contentSubtypeOf,
                                                       incListIdCounter,
                                                       getSaveframe,
                                                       getLoop,
                                                       getRow,
                                                       getRowForStrMr,
                                                       assignCoordPolymerSequenceWithChainId,
                                                       selectCoordAtoms,
                                                       getPotentialType,
                                                       getPdbxNmrSoftwareName,
                                                       ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS,
                                                       HALF_SPIN_NUCLEUS,
                                                       ALLOWED_AMBIGUITY_CODES,
                                                       ALLOWED_ISOTOPE_NUMBERS,
                                                       KNOWN_ANGLE_NAMES,
                                                       CS_RESTRAINT_RANGE,
                                                       DIST_RESTRAINT_RANGE,
                                                       ANGLE_RESTRAINT_RANGE,
                                                       RDC_RESTRAINT_RANGE,
                                                       CS_UNCERTAINTY_RANGE,
                                                       DIST_UNCERTAINTY_RANGE,
                                                       ANGLE_UNCERTAINTY_RANGE,
                                                       RDC_UNCERTAINTY_RANGE,
                                                       CSA_RESTRAINT_RANGE,
                                                       CCR_RESTRAINT_RANGE,
                                                       PRE_RESTRAINT_RANGE,
                                                       PROBABILITY_RANGE,
                                                       DIST_AMBIG_LOW,
                                                       DIST_AMBIG_UP,
                                                       WEIGHT_RANGE,
                                                       SCALE_RANGE,
                                                       REPRESENTATIVE_MODEL_ID,
                                                       REPRESENTATIVE_ALT_ID,
                                                       MAX_OFFSET_ATTEMPT,
                                                       CYANA_MR_FILE_EXTS,
                                                       NMR_STAR_LP_KEY_ITEMS,
                                                       NMR_STAR_LP_DATA_ITEMS)
    from wwpdb.utils.nmr.mr.AmberMRReader import AmberMRReader
    from wwpdb.utils.nmr.mr.BiosymMRReader import BiosymMRReader
    from wwpdb.utils.nmr.mr.CnsMRReader import CnsMRReader
    from wwpdb.utils.nmr.mr.CyanaMRReader import CyanaMRReader
    from wwpdb.utils.nmr.mr.GromacsMRReader import GromacsMRReader
    from wwpdb.utils.nmr.mr.RosettaMRReader import RosettaMRReader
    from wwpdb.utils.nmr.mr.XplorMRReader import XplorMRReader
    from wwpdb.utils.nmr.mr.AmberPTReader import AmberPTReader
    from wwpdb.utils.nmr.mr.GromacsPTReader import GromacsPTReader
    from wwpdb.utils.nmr.mr.DynamoMRReader import DynamoMRReader
    from wwpdb.utils.nmr.mr.SybylMRReader import SybylMRReader
    from wwpdb.utils.nmr.mr.IsdMRReader import IsdMRReader
    from wwpdb.utils.nmr.mr.CharmmMRReader import CharmmMRReader
    from wwpdb.utils.nmr.mr.AriaMRReader import AriaMRReader

except ImportError:
    from nmr.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
    from nmr.NEFTranslator.NEFTranslator import (NEFTranslator,
                                                 NEF_VERSION,
                                                 altDistanceConstraintType,
                                                 altDihedralAngleConstraintType,
                                                 altRdcConstraintType,
                                                 PARAMAGNETIC_ELEMENTS,
                                                 FERROMAGNETIC_ELEMENTS,
                                                 NON_METAL_ELEMENTS,
                                                 MAX_DIM_NUM_OF_SPECTRA,
                                                 MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK)
    from nmr.NmrDpReport import NmrDpReport
    from nmr.AlignUtil import (LOW_SEQ_COVERAGE,
                               MIN_SEQ_COVERAGE_W_CONFLICT,
                               LARGE_ASYM_ID,
                               emptyValue, trueValue,
                               monDict3,
                               protonBeginCode, pseProBeginCode, aminoProtonCode, rdcBbPairCode,
                               unknownResidue,
                               hasLargeInnerSeqGap, hasLargeSeqGap,
                               fillInnerBlankCompId, fillBlankCompId, fillBlankCompIdWithOffset,
                               beautifyPolySeq,
                               getMiddleCode, getGaugeCode, getScoreOfSeqAlign,
                               getOneLetterCodeCan, getOneLetterCodeCanSequence, getOneLetterCodeSequence,
                               letterToDigit,
                               getRestraintFormatName,
                               getRestraintFormatNames,
                               updatePolySeqRst,
                               sortPolySeqRst,
                               alignPolymerSequence,
                               assignPolymerSequence,
                               trimSequenceAlignment,
                               getPrettyJson)
    from nmr.BMRBChemShiftStat import BMRBChemShiftStat
    from nmr.ChemCompUtil import ChemCompUtil
    from nmr.io.CifReader import CifReader, LEN_MAJOR_ASYM_ID
    from nmr.rci.RCI import RCI
    from nmr.CifToNmrStar import CifToNmrStar
    from nmr.NmrVrptUtility import (uncompress_gzip_file, compress_as_gzip_file,
                                    load_from_pickle, write_as_pickle,
                                    to_np_array, distance,
                                    to_unit_vector, dihedral_angle)
#    from nmr.NmrStarToCif import NmrStarToCif
    from nmr.mr.ParserListenerUtil import (translateToStdResName,
                                           translateToStdAtomName,
                                           coordAssemblyChecker,
                                           isIdenticalRestraint,
                                           isAmbigAtomSelection,
                                           getTypeOfDihedralRestraint,
                                           isLikeHis,
                                           startsWithPdbRecord,
                                           getRestraintName,
                                           contentSubtypeOf,
                                           incListIdCounter,
                                           getSaveframe,
                                           getLoop,
                                           getRow,
                                           getRowForStrMr,
                                           assignCoordPolymerSequenceWithChainId,
                                           selectCoordAtoms,
                                           getPotentialType,
                                           getPdbxNmrSoftwareName,
                                           ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS,
                                           HALF_SPIN_NUCLEUS,
                                           ALLOWED_AMBIGUITY_CODES,
                                           ALLOWED_ISOTOPE_NUMBERS,
                                           KNOWN_ANGLE_NAMES,
                                           CS_RESTRAINT_RANGE,
                                           DIST_RESTRAINT_RANGE,
                                           ANGLE_RESTRAINT_RANGE,
                                           RDC_RESTRAINT_RANGE,
                                           CS_UNCERTAINTY_RANGE,
                                           DIST_UNCERTAINTY_RANGE,
                                           ANGLE_UNCERTAINTY_RANGE,
                                           RDC_UNCERTAINTY_RANGE,
                                           CSA_RESTRAINT_RANGE,
                                           CCR_RESTRAINT_RANGE,
                                           PRE_RESTRAINT_RANGE,
                                           PROBABILITY_RANGE,
                                           DIST_AMBIG_LOW,
                                           DIST_AMBIG_UP,
                                           WEIGHT_RANGE,
                                           SCALE_RANGE,
                                           REPRESENTATIVE_MODEL_ID,
                                           REPRESENTATIVE_ALT_ID,
                                           MAX_OFFSET_ATTEMPT,
                                           CYANA_MR_FILE_EXTS,
                                           NMR_STAR_LP_KEY_ITEMS,
                                           NMR_STAR_LP_DATA_ITEMS)
    from nmr.mr.AmberMRReader import AmberMRReader
    from nmr.mr.BiosymMRReader import BiosymMRReader
    from nmr.mr.CnsMRReader import CnsMRReader
    from nmr.mr.CyanaMRReader import CyanaMRReader
    from nmr.mr.GromacsMRReader import GromacsMRReader
    from nmr.mr.RosettaMRReader import RosettaMRReader
    from nmr.mr.XplorMRReader import XplorMRReader
    from nmr.mr.AmberPTReader import AmberPTReader
    from nmr.mr.GromacsPTReader import GromacsPTReader
    from nmr.mr.DynamoMRReader import DynamoMRReader
    from nmr.mr.SybylMRReader import SybylMRReader
    from nmr.mr.IsdMRReader import IsdMRReader
    from nmr.mr.CharmmMRReader import CharmmMRReader
    from nmr.mr.AriaMRReader import AriaMRReader


__pynmrstar_v3_3__ = version.parse(pynmrstar.__version__) >= version.parse("3.3.0")
__pynmrstar_v3_2__ = version.parse(pynmrstar.__version__) >= version.parse("3.2.0")
__pynmrstar_v3_1__ = version.parse(pynmrstar.__version__) >= version.parse("3.1.0")
__pynmrstar_v3__ = version.parse(pynmrstar.__version__) >= version.parse("3.0.0")


CS_RANGE_MIN = CS_RESTRAINT_RANGE['min_inclusive']
CS_RANGE_MAX = CS_RESTRAINT_RANGE['max_inclusive']

DIST_RANGE_MIN = DIST_RESTRAINT_RANGE['min_inclusive']
DIST_RANGE_MAX = DIST_RESTRAINT_RANGE['max_inclusive']

ANGLE_RANGE_MIN = ANGLE_RESTRAINT_RANGE['min_inclusive']
ANGLE_RANGE_MAX = ANGLE_RESTRAINT_RANGE['max_inclusive']

RDC_RANGE_MIN = RDC_RESTRAINT_RANGE['min_inclusive']
RDC_RANGE_MAX = RDC_RESTRAINT_RANGE['max_inclusive']

WEIGHT_RANGE_MIN = WEIGHT_RANGE['min_inclusive']
WEIGHT_RANGE_MAX = WEIGHT_RANGE['max_inclusive']

CS_UNCERT_MAX = CS_UNCERTAINTY_RANGE['max_inclusive']

DIST_UNCERT_MAX = DIST_UNCERTAINTY_RANGE['max_inclusive']

ANGLE_UNCERT_MAX = ANGLE_UNCERTAINTY_RANGE['max_inclusive']

RDC_UNCERT_MAX = RDC_UNCERTAINTY_RANGE['max_inclusive']

bmrb_nmr_star_file_name_pattern = re.compile(r'^bmr\d[0-9]{1,5}_3.str$')
mr_file_name_pattern = re.compile(r'^([Pp][Dd][Bb]_)?([0-9]{4})?[0-9][0-9A-Za-z]{3}.mr$')
proc_mr_file_name_pattern = re.compile(r'^D_[0-9]{6,10}_mr(-(upload|upload-convert|deposit|annotate|release|review))?'
                                       r'_P\d+\.(amber|aria|biosym|charmm|cns|cyana|dynamo|gromacs|isd|rosetta|sybyl|xplor-nih)\.V\d+$')
pdb_id_pattern = re.compile(r'^([Pp][Dd][Bb]_)?([0-9]{4})?[0-9][0-9A-Za-z]{3}$')
bmrb_id_pattern = re.compile(r'^(bmr)?([0-9]+)$')
dep_id_pattern = re.compile(r'^D_[0-9]{6,10}$')

datablock_pattern = re.compile(r'\s*data_(\S+)\s*')
sf_anonymous_pattern = re.compile(r'\s*save_\S+\s*')
save_pattern = re.compile(r'\s*save_\s*')
loop_pattern = re.compile(r'\s*loop_\s*')
stop_pattern = re.compile(r'\s*stop_\s*')
cif_stop_pattern = re.compile(r'#\s*')
ws_pattern = re.compile(r'\s+')
comment_pattern = re.compile(r'\s*[#!]+(.*)')
gromacs_comment_pattern = re.compile(r'\s*;+[^0-9]?(.*)')
cyana_unset_info_pattern = re.compile(r'\s*unset\s+info.*')
cyana_print_pattern = re.compile(r'\s*print\s+\".*\".*')

category_pattern = re.compile(r'\s*_(\S*)\..*\s*')
tagvalue_pattern = re.compile(r'\s*_(\S*)\.(\S*)\s+(.*)\s*')
sf_category_pattern = re.compile(r'\s*_\S*\.Sf_category\s*\S+\s*')
sf_framecode_pattern = re.compile(r'\s*_\S*\.Sf_framecode\s*\s+\s*')

onedep_upload_file_pattern = re.compile(r'(.*)\-upload_(.*)\.V(.*)$')
onedep_file_pattern = re.compile(r'(.*)\.V(.*)$')
mr_file_header_pattern = re.compile(r'(.*)# Restraints file (\d+): (\S+)\s*')

pynmrstar_lp_obj_pattern = re.compile(r"\<pynmrstar\.Loop '(.*)'\>")
pdb_first_atom_pattern = re.compile(r'ATOM +1 .*')

amber_a_format_pattern = re.compile(r'%FORMAT\((\d+)a(\d+)\)\s*')
amber_i_format_pattern = re.compile(r'%FORMAT\((\d+)I(\d+)\)\s*')
amber_r_pattern = re.compile(r'r(\d+)=(.*)')

amber_rst_pattern = re.compile(r'\s*&[Rr][Ss][Tt].*')
amber_end_pattern = re.compile(r'\s*(?:&[Ee][Nn][Dd]|\/)\s*')
amber_missing_end_err_msg = "missing END at"  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4
amber_extra_end_err_msg_pattern = re.compile(r"extraneous input '(?:&[Ee][Nn][Dd]|\/)' expecting .*")  # NOTICE: depends on ANTLR v4
amber_expecting_comma_pattern = re.compile("expecting \\{.*Comma.*\\}")  # NOTICE: depends on ANTLR v4 and AmberMRLexer.g4

xplor_any_assi_pattern = re.compile(r'[Aa][Ss][Ss][Ii][Gg]?[Nn]?')
xplor_any_rest_pattern = re.compile(r'[Rr][Ee][Ss][Tt][Rr]?[Aa]?[Ii]?[Nn]?[Tt]?[Ss]?')
xplor_any_set_pattern = re.compile(r'[Ss][Ee][Tt]')
xplor_class_pattern = re.compile(r'\s*[Cc][Ll][Aa][Ss][Ss]?[Ii]?.*')
xplor_assi_pattern = re.compile(r'\s*[Aa][Ss][Ss][Ii][Gg]?[Nn]?.*')
xplor_rest_pattern = re.compile(r'\s*[Rr][Ee][Ss][Tt][Rr]?[Aa]?[Ii]?[Nn]?[Tt]?[Ss]?.*')
xplor_set_pattern = re.compile(r'\s*[Ss][Ee][Tt].*')
xplor_end_pattern = re.compile(r'\s*[Ee][Nn][Dd].*')
xplor_missing_end_err_msg = "missing End at"  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4
xplor_extra_end_err_msg_pattern = re.compile(r"extraneous input '[Ee][Nn][Dd]' expecting .*")  # NOTICE: depends on ANTLR v4
xplor_extra_assi_err_msg_pattern = re.compile(r"extraneous input '[Aa][Ss][Ss][Ii][Gg]?[Nn]?' expecting L_paren")  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4
xplor_extra_ssi_err_msg_pattern = re.compile(r"extraneous input '[Aa]?[Ss][Ss][Ii]\S*' .*")  # NOTICE: depends on ANTLR v4
xplor_extra_l_paren_err_msg_pattern = re.compile(r"extraneous input '\(' expecting .*")  # NOTICE: depends on ANTLR v4
xplor_expecting_symbol_pattern = re.compile("expecting \\{.*Symbol_name.*\\}")  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4
xplor_expecting_equ_op_pattern = re.compile("expecting \\{.*Equ_op.*\\}")  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4
xplor_expecting_seg_id_pattern = re.compile("expecting \\{.*SegIdentifier.*\\}")  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4

seq_mismatch_warning_pattern = re.compile(r"\[Sequence mismatch warning\] \[.*\] The residue '(\d+):([0-9A-Z]+)' is not present "
                                          r"in polymer sequence of chain (\S+) of the coordinates. Please update the sequence in the Macromolecules page.")

gromacs_tag_pattern = re.compile(r'\s*[\s+[a-z0-9_]+\s+\]')

mismatched_input_err_msg = "mismatched input"  # NOTICE: depends on ANTLR v4
extraneous_input_err_msg = "extraneous input"  # NOTICE: depends on ANTLR v4
no_viable_alt_err_msg = "no viable alternative at input"  # NOTICE: depends on ANTLR v4
expecting_l_paren = "expecting L_paren"  # NOTICE: depends on ANTLR v4 and (Xplor|Cns)MRLexer.g4

possible_typo_for_comment_out_pattern = re.compile(r'\s*([13])$')

comment_code_mixed_set = {'#', '!'}


def detect_bom(fPath, default='utf-8'):
    """ Detect BOM of input file.
    """

    with open(fPath, 'rb') as ifh:
        raw = ifh.read(4)

    for enc, boms in \
            ('utf-8-sig', (codecs.BOM_UTF8,)), \
            ('utf-16', (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE)), \
            ('utf-32', (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE)):
        if any(raw.startswith(bom) for bom in boms):
            return enc

    return default


def convert_codec(inPath, outPath, in_codec='utf-8', out_codec='utf-8'):
    """ Convert codec of input file.
    """

    with open(inPath, 'rb') as ifh, \
            open(outPath, 'w+b') as ofh:
        contents = ifh.read()
        ofh.write(contents.decode(in_codec).encode(out_codec))


def convert_rtf_to_ascii(inPath, outPath):
    """ Convert RTF file to ASCII text file.
    """

    with open(inPath, 'r') as ifh, \
            open(outPath, 'w+') as ofh:
        contents = ifh.read()
        ofh.write(rtf_to_text(contents, encoding='ascii', errors='ignore'))


def is_binary_file(fPath):
    """ Check if there are non-ascii or non-printable characters in a file.
    """

    with open(fPath, 'rb') as ifh:
        chunk = ifh.read(1024)
        if b'\0' in chunk:
            return True

    return False


def is_rtf_file(fPath):
    """ Check if there are RTF header characters in a file.
    """

    with open(fPath, 'rb') as ifh:
        chunk = ifh.read(1024)
        if b'\x7b\x5c\x72\x74\x66\x31' in chunk:
            return True

    return False


def detect_encoding(line):
    """ Return encoding of a given string.
    """

    try:
        result = chardet.detect(line.encode('utf-8'))
        return result['encoding']
    except Exception:
        return 'binary'


def get_type_of_star_file(fPath):
    """ Return type of a STAR file.
        @return: 'str' for STAR, 'cif' for CIF, 'other' otherwise
    """

    codec = detect_bom(fPath, 'utf-8')

    _fPath = __fPath = None

    if codec != 'utf-8':
        _fPath = fPath + '~'
        convert_codec(fPath, _fPath, codec, 'utf-8')
        fPath = _fPath

    if is_rtf_file(fPath):
        __fPath = fPath + '.rtf2txt'
        convert_rtf_to_ascii(fPath, __fPath)
        fPath = __fPath

    try:

        is_cif = False

        has_datablock = False
        has_anonymous_saveframe = False
        has_save = False
        has_loop = False
        has_stop = False

        with open(fPath, 'r', encoding='utf-8') as ifh:
            for line in ifh:
                str_syntax = False
                if datablock_pattern.match(line):
                    str_syntax = has_datablock = True
                elif sf_anonymous_pattern.match(line):
                    str_syntax = has_anonymous_saveframe = True
                elif save_pattern.match(line):
                    str_syntax = has_save = True
                elif loop_pattern.match(line):
                    str_syntax = has_loop = True
                elif stop_pattern.match(line):
                    str_syntax = has_stop = True

                if str_syntax:
                    if (has_anonymous_saveframe and has_save) or (has_loop and has_stop):
                        return 'str'
                    if has_datablock and has_loop and not has_stop:
                        is_cif = True

        return 'cif' if is_cif else 'other'

    finally:

        if _fPath is not None:
            try:
                os.remove(_fPath)
            except OSError:
                pass

        if __fPath is not None:
            try:
                os.remove(__fPath)
            except OSError:
                pass


def has_key_value(d=None, key=None):
    """ Return whether a given dictionary has effective value for a key.
        @return: True if d[key] has effective value, False otherwise
    """

    if d is None or key is None:
        return False

    if key in d:
        return d[key] is not None

    return False


def get_lp_tag(lp, tags):
    """ Return the selected loop tags by row as a list of lists.
    """

    return lp.get_tag(tags) if __pynmrstar_v3__ else lp.get_data_by_tag(tags)


def get_first_sf_tag(sf=None, tag=None):
    """ Return the first value of a given saveframe tag.
        @return: The first tag value, empty string otherwise.
    """

    if sf is None or tag is None:
        return ''

    array = sf.get_tag(tag)

    if len(array) == 0:
        return ''

    return array[0] if array[0] is not None else ''


def set_sf_tag(sf, tag, value):
    """ Set saveframe tag.
    """

    tagNames = [t[0] for t in sf.tags]

    if isinstance(value, str) and len(value) == 0:
        value = None

    if tag not in tagNames:
        sf.add_tag(tag, value)
        return

    sf.tags[tagNames.index(tag)][1] = value


def is_non_metal_element(comp_id, atom_id):
    """ Return whether a given atom_id is non metal element.
        @return: True for non metal element, False otherwise
    """

    if comp_id == atom_id:
        return False

    return any(elem for elem in NON_METAL_ELEMENTS if atom_id.startswith(elem))


def is_half_spin_nuclei(atom_id):
    """ Return whether nuclei of a given atom_id has a spin 1/2.
        @return: True for spin 1/2 nuclei, False otherwise
    """

    return any(nucl for nucl in HALF_SPIN_NUCLEUS if atom_id.startswith(nucl))


def probability_density(value, mean, stddev):
    """ Return probability density.
    """

    stddev2 = stddev ** 2.0

    return math.exp(-((value - mean) ** 2.0) / (2.0 * stddev2)) / math.sqrt(2.0 * math.pi * stddev2)


def predict_redox_state_of_cystein(ca_chem_shift, cb_chem_shift):
    """ Return prediction of redox state of Cystein using assigned CA, CB chemical shifts.
        @return: probability of oxidized state, probability of reduced state
        Reference:
          13C NMR chemical shifts can predict disulfide bond formation.
          Sharma, D., Rajarathnam, K.
          J Biomol NMR 18, 165–171 (2000).
          DOI: 10.1023/A:1008398416292
    """

    oxi_ca = {'avr': 55.5, 'std': 2.5}
    oxi_cb = {'avr': 40.7, 'std': 3.8}

    red_ca = {'avr': 59.3, 'std': 3.2}
    red_cb = {'avr': 28.3, 'std': 2.2}

    oxi = 1.0
    red = 1.0

    if ca_chem_shift is not None:
        oxi *= probability_density(ca_chem_shift, oxi_ca['avr'], oxi_ca['std'])
        red *= probability_density(ca_chem_shift, red_ca['avr'], red_ca['std'])

    if cb_chem_shift is not None:
        if cb_chem_shift < 32.0:
            oxi = 0.0
        else:
            oxi *= probability_density(cb_chem_shift, oxi_cb['avr'], oxi_cb['std'])
        if cb_chem_shift > 35.0:
            red = 0.0
        else:
            red *= probability_density(cb_chem_shift, red_cb['avr'], red_cb['std'])

    total = oxi + red

    if total in (0.0, 2.0):
        return 0.0, 0.0

    return oxi / total, red / total


def predict_cis_trans_peptide_of_proline(cb_chem_shift, cg_chem_shift):
    """ Return prediction of cis-trans peptide bond of Proline using assigned CB, CG chemical shifts.
        @return: probability of cis-peptide bond, probability of trans-peptide bond
        Reference:
          A software tool for the prediction of Xaa-Pro peptide bond conformations in proteins based on 13C chemical shift statistics.
          Schubert, M., Labudde, D., Oschkinat, H. et al.
          J Biomol NMR 24, 149–154 (2002)
          DOI: 10.1023/A:1020997118364
    """

    cis_cb = {'avr': 34.16, 'std': 1.15, 'max': 36.23, 'min': 30.74}
    cis_cg = {'avr': 24.52, 'std': 1.09, 'max': 27.01, 'min': 22.10}
    cis_dl = {'avr': 9.64, 'std': 1.27}

    trs_cb = {'avr': 31.75, 'std': 0.98, 'max': 35.83, 'min': 26.30}
    trs_cg = {'avr': 27.26, 'std': 1.05, 'max': 33.39, 'min': 19.31}
    trs_dl = {'avr': 4.51, 'std': 1.17}

    cis = 1.0
    trs = 1.0

    if cb_chem_shift is not None:
        if cb_chem_shift < cis_cb['min'] - cis_cb['std'] or cb_chem_shift > cis_cb['max'] + cis_cb['std']:
            cis = 0.0
        else:
            cis *= probability_density(cb_chem_shift, cis_cb['avr'], cis_cb['std'])
        if cb_chem_shift < trs_cb['min'] - trs_cb['std'] or cb_chem_shift > trs_cb['max'] + trs_cb['std']:
            trs = 0.0
        else:
            trs *= probability_density(cb_chem_shift, trs_cb['avr'], trs_cb['std'])

    if cg_chem_shift is not None:
        if cg_chem_shift < cis_cg['min'] - cis_cg['std'] or cg_chem_shift > cis_cg['max'] + cis_cg['std']:
            cis = 0.0
        else:
            cis *= probability_density(cg_chem_shift, cis_cg['avr'], cis_cg['std'])
        if cg_chem_shift < trs_cg['min'] - trs_cg['std'] or cg_chem_shift > trs_cg['max'] + trs_cg['std']:
            trs = 0.0
        else:
            trs *= probability_density(cg_chem_shift, trs_cg['avr'], trs_cg['std'])

    if (cb_chem_shift is not None) and (cg_chem_shift is not None):
        delta_shift = cb_chem_shift - cg_chem_shift

        cis *= probability_density(delta_shift, cis_dl['avr'], cis_dl['std'])
        trs *= probability_density(delta_shift, trs_dl['avr'], trs_dl['std'])

    total = cis + trs

    if total in (0.0, 2.0):
        return 0.0, 0.0

    return cis / total, trs / total


def predict_tautomer_state_of_histidine(cg_chem_shift, cd2_chem_shift, nd1_chem_shift, ne2_chem_shift):
    """ Return prediction of tautomeric state of Histidine using assigned CG, CD2, ND1, and NE2 chemical shifts.
        @return: probability of biprotonated, probability of tau tautomer, probability of pi tautomer
        Reference:
          Protonation, Tautomerization, and Rotameric Structure of Histidine: A Comprehensive Study by Magic-Angle-Spinning Solid-State NMR.
          Shenhui Li and Mei Hong.
          Journal of the American Chemical Society 2011 133 (5), 1534-1544
          DOI: 10.1021/ja108943n
    """

    bip_cg = {'avr': 131.2, 'std': 0.7}
    bip_cd2 = {'avr': 120.6, 'std': 1.3}
    bip_nd1 = {'avr': 190.0, 'std': 1.9}
    bip_ne2 = {'avr': 176.3, 'std': 1.9}

    tau_cg = {'avr': 135.7, 'std': 2.2}
    tau_cd2 = {'avr': 116.9, 'std': 2.1}
    tau_nd1 = {'avr': 249.4, 'std': 1.9}
    tau_ne2 = {'avr': 171.1, 'std': 1.9}

    pi_cg = {'avr': 125.7, 'std': 2.2}
    pi_cd2 = {'avr': 125.6, 'std': 2.1}
    pi_nd1 = {'avr': 171.8, 'std': 1.9}
    pi_ne2 = {'avr': 248.2, 'std': 1.9}

    bip = 1.0
    tau = 1.0
    pi = 1.0

    if cg_chem_shift is not None:
        bip *= probability_density(cg_chem_shift, bip_cg['avr'], bip_cg['std'])
        tau *= probability_density(cg_chem_shift, tau_cg['avr'], tau_cg['std'])
        pi *= probability_density(cg_chem_shift, pi_cg['avr'], pi_cg['std'])

    if cd2_chem_shift is not None:
        bip *= probability_density(cd2_chem_shift, bip_cd2['avr'], bip_cd2['std'])
        tau *= probability_density(cd2_chem_shift, tau_cd2['avr'], tau_cd2['std'])
        pi *= probability_density(cd2_chem_shift, pi_cd2['avr'], pi_cd2['std'])

    if nd1_chem_shift is not None:
        bip *= probability_density(nd1_chem_shift, bip_nd1['avr'], bip_nd1['std'])
        tau *= probability_density(nd1_chem_shift, tau_nd1['avr'], tau_nd1['std'])
        pi *= probability_density(nd1_chem_shift, pi_nd1['avr'], pi_nd1['std'])

    if ne2_chem_shift is not None:
        bip *= probability_density(ne2_chem_shift, bip_ne2['avr'], bip_ne2['std'])
        tau *= probability_density(ne2_chem_shift, tau_ne2['avr'], tau_ne2['std'])
        pi *= probability_density(ne2_chem_shift, pi_ne2['avr'], pi_ne2['std'])

    total = bip + tau + pi

    if total in (0.0, 3.0):
        return 0.0, 0.0, 0.0

    return bip / total, tau / total, pi / total


def predict_rotamer_state_of_leucine(cd1_chem_shift, cd2_chem_shift):
    """ Return prediction of rotermeric state of Leucine using assigned CD1 and CD2 chemical shifts.
        @return: probability of gauche+, trans, gauche-
        Reference:
          Dependence of Amino Acid Side Chain 13C Shifts on Dihedral Angle: Application to Conformational Analysis.
          Robert E. London, Brett D. Wingad, and Geoffrey A. Mueller.
          Journal of the American Chemical Society 2008 130 (33), 11097-11105
          DOI: 10.1021/ja802729t
    """

    if cd1_chem_shift is not None and cd2_chem_shift is not None:

        delta = cd1_chem_shift - cd2_chem_shift

        pt = (delta + 5.0) / 10.0

        if 0.0 <= pt <= 1.0:
            return 1.0 - pt, pt, 0.0

    gp_cd1 = {'avr': 24.45, 'std': 1.58}
    gp_cd2 = {'avr': 25.79, 'std': 1.68}

    t_cd1 = {'avr': 25.17, 'std': 1.58}
    t_cd2 = {'avr': 23.84, 'std': 1.68}

    gp = 1.0
    t = 1.0

    if cd1_chem_shift is not None:
        gp *= probability_density(cd1_chem_shift, gp_cd1['avr'], gp_cd1['std'])
        t *= probability_density(cd1_chem_shift, t_cd1['avr'], t_cd1['std'])

    if cd2_chem_shift is not None:
        gp *= probability_density(cd2_chem_shift, gp_cd2['avr'], gp_cd2['std'])
        t *= probability_density(cd2_chem_shift, t_cd2['avr'], t_cd2['std'])

    total = gp + t

    if total in (0.0, 2.0):
        return 0.0, 0.0, 0.0

    return gp / total, t / total, 0.0


def predict_rotamer_state_of_valine(cg1_chem_shift, cg2_chem_shift):
    """ Return prediction of rotermeric state of Valine using assigned CG1 and CG2 chemical shifts.
        @return: probability of gauche+, trans, gauche-
        Reference:
          Dependence of Amino Acid Side Chain 13C Shifts on Dihedral Angle: Application to Conformational Analysis.
          Robert E. London, Brett D. Wingad, and Geoffrey A. Mueller.
          Journal of the American Chemical Society 2008 130 (33), 11097-11105
          DOI: 10.1021/ja802729t
    """

    gm_cg1 = {'avr': 22.05, 'std': 1.36}
    gm_cg2 = {'avr': 20.1, 'std': 1.55}

    gp_cg1 = {'avr': 20.87, 'std': 1.36}
    gp_cg2 = {'avr': 21.23, 'std': 1.55}

    t_cg1 = {'avr': 21.74, 'std': 1.36}
    t_cg2 = {'avr': 21.97, 'std': 1.55}

    gm = 1.0
    gp = 1.0
    t = 1.0

    if cg1_chem_shift is not None:
        gm *= probability_density(cg1_chem_shift, gm_cg1['avr'], gm_cg1['std'])
        gp *= probability_density(cg1_chem_shift, gp_cg1['avr'], gp_cg1['std'])
        t *= probability_density(cg1_chem_shift, t_cg1['avr'], t_cg1['std'])

    if cg2_chem_shift is not None:
        gm *= probability_density(cg2_chem_shift, gm_cg2['avr'], gm_cg2['std'])
        gp *= probability_density(cg2_chem_shift, gp_cg2['avr'], gp_cg2['std'])
        t *= probability_density(cg2_chem_shift, t_cg2['avr'], t_cg2['std'])

    total = gm + gp + t

    if total in (0.0, 3.0):
        return 0.0, 0.0, 0.0

    return gp / total, t / total, gm / total


def predict_rotamer_state_of_isoleucine(cd1_chem_shift):
    """ Return prediction of rotermeric state of Isoleucine using assigned CD1 chemical shift.
        @return: probability of gauche+, trans, gauche-
        Reference:
          Determination of Isoleucine Side-Chain Conformations in Ground and Excited States of Proteins from Chemical Shifts.
          D. Flemming Hansen, Philipp Neudecker, and Lewis E. Kay.
          Journal of the American Chemical Society 2010 132 (22), 7589-7591
          DOI: 10.1021/ja102090z
    """

    if cd1_chem_shift is None:
        return 0.0, 0.0, 0.0

    if cd1_chem_shift < 9.3:
        return 0.0, 0.0, 1.0

    if cd1_chem_shift > 14.8:
        return 1.0 * (4.0 / 85.0), 1.0 * (81.0 / 85.0), 0.0

    pgm = (14.8 - cd1_chem_shift) / 5.5

    return (1.0 - pgm) * (4.0 / 85.0), (1.0 - pgm) * (81.0 / 85.0), pgm


def concat_nmr_restraint_names(content_subtype):
    """ Return concatenated NMR restraint names.
    """

    if content_subtype is None:
        return ''

    f = []

    for k, v in content_subtype.items():
        if v == 0:
            continue
        try:
            f.append(getRestraintName(k))
        except KeyError:
            pass

    return ', '.join(f)


def is_peak_list(line, has_header=True):
    """ Return whether a given input is derived from peak list in any native format.
    """

    if has_header and line.count('E') + line.count('e') >= 2:  # XEASY peak list
        s = line.split()
        return 'U' in s or 'T' in s

    if 'Assignment' in line and 'w1' in line and 'w2' in line:  # Sparky peak list
        return True

    if 'label' in line and 'dataset' in line and 'sw' in line and 'sf' in line:  # NMRView peak list
        return True

    if 'VARS' in line and 'X_PPM' in line and 'Y_PPM' in line:  # NMRPipe peak list
        return True

    return False


def get_peak_list_format(line, has_header=True):
    """ Return peak list format for a given input.
    """

    if has_header:  # and line.count('E') + line.count('e') >= 2:  # XEASY peak list
        s = line.split()
        if 'U' in s or 'T' in s:
            return 'XEASY'

    if 'Assignment' in line and 'w1' in line and 'w2' in line:  # Sparky peak list
        return 'Sparky'

    if 'label' in line and 'dataset' in line and 'sw' in line and 'sf' in line:  # NMRView peak list
        return 'NMRView'

    if 'VARS' in line and 'X_PPM' in line and 'Y_PPM' in line:
        return 'NMRPipe'

    return None


def get_number_of_dimensions_of_peak_list(file_format, line):
    """ Return number of dimensions of peak list of given format and input.
    """

    if file_format == 'XEASY':
        if 'dimensions' in line:
            col = line.split()
            if col[-1].isdigit():
                return int(col[-1])

    if file_format == 'Sparky':
        if 'w1' in line:
            col = line.split()
            dim = [int(w[1:]) for w in col if w.startswith('w') and w[1:].isdigit()]
            if len(dim) > 0:
                return max(dim)

    if file_format == 'NMRView':
        col = line.split()
        return len(col)

    if file_format == 'NMRPipe':
        if 'VARS' in line:
            col = line.split()
            if 'A_PPM' in col:
                return 4
            if 'Z_PPM' in col:
                return 3
            if 'Y_PPM' in col:
                return 2

    return None


class NmrDpUtility:
    """ Wrapper class for data processing for NMR data.
    """

    def __init__(self, verbose=False, log=sys.stderr):
        self.__verbose = verbose
        self.__lfh = log

        self.__debug = False
        self.__mr_debug = False

        # current workflow operation
        self.__op = None

        # whether to enable rescue routine
        self.__rescue_mode = True
        # whether to enable remediation routines
        self.__remediation_mode = False
        # whether NMR combined deposition or not (NMR conventional deposition)
        self.__combined_mode = True
        # whether native NMR combined deposition
        self.__native_combined = False
        # whether to allow sequence mismatch during annotation
        self.__annotation_mode = False
        # whether to use datablock name of public release
        self.__release_mode = False
        # whether to allow to raise internal error
        self.__internal_mode = False
        # whether to combine spectral peak list in any plain text format into single NMR-STAR file (must be trued off after Phase 2, DAOTHER-7407)
        self.__merge_any_pk_as_is = False

        # whether to allow empty coordinate file path
        self.__bmrb_only = False
        # whether not to block deposition because of anomalous cs
        self.__nonblk_anomalous_cs = False
        # whether not to block deposition because bad n-term amino group
        self.__nonblk_bad_nterm = False
        # whether to udpate polymer sequence
        self.__update_poly_seq = False
        # whether to resolve conflict
        self.__resolve_conflict = False
        # whether to detect missing mandatory tags as errors
        self.__check_mandatory_tag = False
        # whether to detect consistency of author sequence (nmr-star specific)
        self.__check_auth_seq = False
        # whether to skip missing_mandatory_content error for validation server (DAOTHER-8658)
        self.__validation_server = False
        # whether to translate conventional pseudo atom nomenclature in combined NMR-STAR file
        self.__transl_pseudo_name = False
        # whether to enable tolerant sequence alignment for residue variants
        self.__tolerant_seq_align = False

        # whether to fix format issue (enabled if NMR conventional deposition or release mode)
        self.__fix_format_issue = False
        # whether to exclude missing mandatory data (enabled if NMR conventional deposition)
        self.__excl_missing_data = False
        # whether to complement missing data (add missing pseudo atoms in NMR restraints, DAOTHER-7681, issue #1)
        self.__cmpl_missing_data = False
        # whether to trust pdbx_nmr_ensemble to get total number of models
        self.__trust_pdbx_nmr_ens = True

        # whether sf_framecode has to be fixed
        self.__has_legacy_sf_issue = False

        # default entry_id
        self.__entry_id__ = 'UNNAMED'
        # current entry_id, to be replaced
        self.__entry_id = 'EXTRACT_FROM_COORD'
        # bmrb id (internal use only)
        self.__bmrb_id = None

        # whether to retain original content if possible
        self.__retain_original = True
        # whether to leave internal commentary note in processed NMR-STAR file
        self.__leave_intl_note = True
        # whether to use reduced atom notation
        self.__reduced_atom_notation = True

        # whether entity category exists (nmr-star specific)
        self.__has_star_entity = False

        # whether a CS loop is in the primary NMR-STAR file (used only during NMR restraint remediation)
        self.__has_star_chem_shift = True

        # whether allow missing distance restraints (NMR unified deposition, DAOTHER-8088 1.b, 8108)
        self.__allow_missing_dist_restraint = True
        # whether allow missing distance restraints (NMR legacy deposition, DAOTHER-8088 1.b, 8108)
        self.__allow_missing_legacy_dist_restraint = True
        # whether legacy distance restraint has been uploaded
        self.__legacy_dist_restraint_uploaded = False

        # whether stereo-array isotope labeling method has been applied for the study
        self.__sail_flag = False

        # whether pdbx_database_status.recvd_nmr_constraints is 'Y'
        self.__recvd_nmr_constraints = False
        # whether pdbx_database_status.recvd_nmr_data is 'Y'
        self.__recvd_nmr_data = False

        # source, destination, and log file paths
        self.__srcPath = None
        self.__srcName = None
        self.__dstPath = None
        self.__logPath = None
        self.__dstPath__ = None

        self.__cifPath = None

        # temporary file path to be removed (release mode)
        self.__tmpPath = None

        # current working directory
        self.__dirPath = None

        # directory for cache files
        self.__cacheDirPath = None

        # hash code of the coordinate file
        self.__cifHashCode = None

        # whether coordinate file is already examined
        self.__cifChecked = False

        # cache path of coordinate assembly check
        self.__asmChkCachePath = None

        # auxiliary input resource
        self.__inputParamDict = {}

        # copy of  __inputParamDict to restart remediation
        self.__inputParamDictCopy = None

        # auxiliary output resource
        self.__outputParamDict = {}

        # list of known workflow operations
        self.__workFlowOps = ('nmr-nef-consistency-check',
                              'nmr-str-consistency-check',
                              'nmr-nef2str-deposit',
                              'nmr-nef2cif-deposit',
                              'nmr-str2str-deposit',
                              'nmr-str2cif-deposit',
                              'nmr-str2nef-release',
                              'nmr-cs-nef-consistency-check',
                              'nmr-cs-str-consistency-check',
                              'nmr-cs-mr-merge',
                              'nmr-str2cif-annotate'
                              )

        # validation tasks for NMR data only
        __nmrCheckTasks = [self.__detectContentSubType,
                           self.__extractPublicMrFileIntoLegacyMr,
                           self.__detectContentSubTypeOfLegacyMr,
                           self.__detectContentSubTypeOfLegacyPk,
                           self.__extractPolymerSequence,
                           self.__extractPolymerSequenceInLoop,
                           # self.__testSequenceConsistency,
                           self.__extractCommonPolymerSequence,
                           self.__extractNonStandardResidue,
                           self.__appendPolymerSequenceAlignment,
                           self.__testSequenceConsistency,
                           self.__validateAtomNomenclature,
                           self.__appendElemAndIsoNumOfNefCsLoop,
                           self.__validateAtomTypeOfCsLoop,
                           self.__validateAmbigCodeOfCsLoop,
                           self.__detectConflictDataInLoop,
                           self.__appendIndexTag,
                           self.__testIndexConsistency,
                           self.__appendWeightInLoop,
                           self.__appendDihedAngleType,
                           self.__testDataConsistencyInLoop,
                           # self.__detectConflictDataInLoop,
                           self.__testDataConsistencyInAuxLoop,
                           self.__testNmrCovalentBond,
                           self.__appendSfTagItem,
                           self.__testSfTagConsistency,
                           # self.__validateCsValue,
                           self.__testCsPseudoAtomNameConsistencyInMrLoop,
                           self.__testCsValueConsistencyInPkLoop,
                           self.__testCsValueConsistencyInPkAltLoop,
                           # self.__testRdcVector
                           ]

        # validation tasks for coordinate file only
        __cifCheckTasks = [self.__validateCoordInputSource,
                           self.__detectCoordContentSubType,
                           self.__extractCoordPolymerSequence,
                           self.__extractCoordPolymerSequenceInLoop,
                           self.__extractCoordAtomSite,
                           self.__extractCoordCommonPolymerSequence,
                           self.__extractCoordNonStandardResidue,
                           self.__appendCoordPolymerSequenceAlignment,
                           self.__testTautomerOfHistidinePerModel
                           ]

        # cross validation tasks
        __crossCheckTasks = [self.__assignCoordPolymerSequence,
                             self.__testCoordAtomIdConsistency,
                             self.__testCoordCovalentBond,
                             self.__testResidueVariant,
                             self.__validateCsValue,
                             self.__testRdcVector,
                             self.__extractCoordDisulfideBond,
                             self.__extractCoordOtherBond,
                             self.__validateStrMr,
                             self.__validateLegacyMr,
                             self.__validateSaxsMr,
                             self.__validateStrPk,
                             self.__updateConstraintStats,
                             self.__detectSimpleDistanceRestraint,
                             self.__calculateStatsOfExptlData
                             ]

        # nmr-*-consistency-check tasks
        __checkTasks = [self.__initializeDpReport,
                        self.__validateInputSource
                        ]
        __checkTasks.extend(__nmrCheckTasks)
        __checkTasks.extend(__cifCheckTasks)
        __checkTasks.extend(__crossCheckTasks)

        # nmr-*-deposit tasks
        __depositTasks = [self.__retrieveDpReport,
                          self.__validateInputSource,
                          # __updatePolymerSequence() depends on __extractCoordPolymerSequence()
                          self.__parseCoordinate,
                          self.__detectCoordContentSubType,
                          self.__extractCoordPolymerSequence,
                          self.__extractCoordAtomSite,
                          # resolve conflict
                          self.__resolveConflictsInLoop,
                          self.__resolveConflictsInAuxLoop,
                          # resolve minor issues
                          self.__validateAtomNomenclature,
                          self.__appendIndexTag,
                          self.__appendWeightInLoop,
                          self.__appendDihedAngleType,
                          self.__appendSfTagItem,
                          self.__deleteSkippedSf,
                          self.__deleteSkippedLoop,
                          self.__deleteUnparsedEntryLoop,
                          self.__updatePolymerSequence,
                          self.__updateAuthSequence,
                          self.__updateDihedralAngleType,
                          self.__fixDisorderedIndex,
                          self.__removeNonSenseZeroValue,
                          self.__fixNonSenseNegativeValue,
                          self.__fixEnumMismatch,
                          self.__fixEnumMismatchIgnorable,
                          self.__resetCapitalStringInLoop,
                          self.__resetBoolValueInLoop,
                          self.__resetBoolValueInAuxLoop,
                          self.__appendParentSfTag,
                          self.__addUnnamedEntryId,
                          self.__removeUnusedPdbInsCode,
                          self.__depositNmrData,
                          # re-setup for next
                          self.__initializeDpReportForNext,
                          self.__validateInputSourceForNext
                          ]

        __depositTasks.extend(__nmrCheckTasks)
        __depositTasks.extend(__cifCheckTasks)
        __depositTasks.extend(__crossCheckTasks)

        # additional nmr-nef2str/nef2cif tasks
        __nef2strTasks = [self.__translateNef2Str,
                          self.__dumpDpReport,
                          self.__initResourceForNef2Str
                          ]

        __nef2strTasks.extend(__checkTasks)
        __nef2strTasks.append(self.__dumpDpReport)
        __nef2strTasks.extend(__depositTasks)

        # additional nmr-str2nef tasks
        __str2nefTasks = [self.__translateStr2Nef,
                          self.__dumpDpReport,
                          self.__initResourceForStr2Nef
                          ]

        __str2nefTasks.extend(__checkTasks)
        __str2nefTasks.append(self.__dumpDpReport)
        __str2nefTasks.extend(__depositTasks)

        __mergeCsAndMrTasks = __checkTasks
        __mergeCsAndMrTasks.append(self.__updatePolymerSequence)
        __mergeCsAndMrTasks.append(self.__mergeLegacyCsAndMr)
        __mergeCsAndMrTasks.append(self.__detectSimpleDistanceRestraint)
        __mergeCsAndMrTasks.append(self.__calculateStatsOfExptlData)

        __annotateTasks = [self.__initializeDpReport,
                           self.__validateInputSource,
                           self.__detectContentSubType,
                           self.__extractPolymerSequence,
                           self.__extractPolymerSequenceInLoop,
                           self.__extractCommonPolymerSequence,
                           self.__extractNonStandardResidue,
                           self.__appendPolymerSequenceAlignment]
        __annotateTasks.extend(__cifCheckTasks)
        __annotateTasks.extend(__crossCheckTasks)
        __annotateTasks.append(self.__updatePolymerSequence)
        __annotateTasks.append(self.__depositNmrData)
        __annotateTasks.extend(__depositTasks)
        __annotateTasks.append(self.__depositNmrData)

        # dictionary of processing tasks of each workflow operation
        self.__procTasksDict = {'consistency-check': __checkTasks,
                                'deposit': __depositTasks,
                                'nmr-nef2str-deposit': __nef2strTasks,
                                'nmr-nef2cif-deposit': __nef2strTasks,
                                'nmr-str2nef-release': __str2nefTasks,
                                'nmr-cs-nef-consistency-check': [self.__depositLegacyNmrData],
                                'nmr-cs-str-consistency-check': [self.__depositLegacyNmrData],
                                'nmr-cs-mr-merge': __mergeCsAndMrTasks,
                                'nmr-str2cif-annotate': __annotateTasks
                                }

        # data processing report
        self.report = None
        self.report_prev = None

        # CCD accessing utility
        self.__ccU = ChemCompUtil(self.__verbose, self.__lfh)

        # BMRB chemical shift statistics
        self.__csStat = BMRBChemShiftStat(self.__verbose, self.__lfh, self.__ccU)

        # CifToNmrStar
        self.__c2S = CifToNmrStar(self.__verbose)

        # NEFTranslator
        self.__nefT = NEFTranslator(self.__verbose, self.__lfh, self.__ccU, self.__csStat, self.__c2S)
        self.__nefT.allow_missing_dist_restraint(self.__allow_missing_legacy_dist_restraint)

        # PyNMRSTAR data
        self.__file_path_list_len = self.__cs_file_path_list_len = 1

        self.__star_data_type = []
        self.__star_data = []
        self.__sf_name_corr = []

        self.__original_error_message = []
        self.__divide_mr_error_message = []
        self.__peel_mr_error_message = []

        self.__sf_category_list = []
        self.__lp_category_list = []

        self.__alt_chain = False
        self.__valid_seq = False

        self.__cur_original_ar_file_name = None

        self.__remediation_loop_count = 0

        self.__sll_pred_holder = {}

        self.__list_id_counter = None
        self.__mr_sf_dict_holder = None
        self.__pk_sf_holder = None

        self.__nmr_ext_poly_seq = None

        self.__cca_dat = None

        # combined nmr cif file path (used only for BMRB internal annotation)
        self.__srcNmrCifPath = None
        # saveframe category list of combined nmr cif file (used only for BMRB internal annotation)
        self.__nmr_cif_sf_category_list = None

        # NMR content types
        self.nmr_content_subtypes = ('entry_info', 'poly_seq', 'entity', 'chem_shift', 'chem_shift_ref',
                                     'dist_restraint', 'dihed_restraint', 'rdc_restraint',
                                     'spectral_peak', 'spectral_peak_alt',
                                     'noepk_restraint', 'jcoup_restraint', 'rdc_raw_data',
                                     'csa_restraint', 'ddc_restraint',
                                     'hvycs_restraint', 'procs_restraint',
                                     'csp_restraint', 'auto_relax_restraint',
                                     'heteronucl_noe_data', 'heteronucl_t1_data',
                                     'heteronucl_t2_data', 'heteronucl_t1r_data',
                                     'order_param_data',
                                     'ccr_d_csa_restraint', 'ccr_dd_restraint',
                                     'fchiral_restraint', 'saxs_restraint', 'other_restraint')

        self.mr_content_subtypes = ['dist_restraint', 'dihed_restraint', 'rdc_restraint',
                                    'noepk_restraint', 'jcoup_restraint', 'rdc_raw_data',
                                    'csa_restraint', 'ddc_restraint',
                                    'hvycs_restraint', 'procs_restraint',
                                    'csp_restraint', 'auto_relax_restraint',
                                    'heteronucl_noe_data', 'heteronucl_t1_data',
                                    'heteronucl_t2_data', 'heteronucl_t1r_data',
                                    'order_param_data',
                                    'ccr_d_csa_restraint', 'ccr_dd_restraint',
                                    'fchiral_restraint', 'saxs_restraint', 'other_restraint']

        self.nmr_rep_content_subtypes = ['chem_shift', 'spectral_peak']
        self.nmr_rep_content_subtypes.extend(self.mr_content_subtypes)

        self.pk_content_subtypes = ('spectral_peak', 'spectral_peak_alt')

        self.cif_content_subtypes = ('poly_seq', 'non_poly', 'branched', 'coordinate')

        # readable file type
        self.readable_file_type = {'nef': 'NEF (NMR Exchange Format)',
                                   'nmr-star': 'NMR-STAR',
                                   'pdbx': 'PDBx/mmCIF',
                                   'unknown': 'unknown'
                                   }

        # content type
        self.content_type = {'nef': 'nmr-data-nef',
                             'nmr-star': 'nmr-data-str',
                             'pdbx': 'model'
                             }

        # content type used for public release
        self.release_type = {'nef': 'nmr-data',
                             'nmr-star': 'nmr-data',
                             'pdbx': None
                             }

        # saveframe categories
        self.sf_categories = {'nef': {'entry_info': 'nef_nmr_meta_data',
                                      'poly_seq': 'nef_molecular_system',
                                      'entity': None,
                                      'chem_shift': 'nef_chemical_shift_list',
                                      'chem_shift_ref': None,
                                      'dist_restraint': 'nef_distance_restraint_list',
                                      'dihed_restraint': 'nef_dihedral_restraint_list',
                                      'rdc_restraint': 'nef_rdc_restraint_list',
                                      'spectral_peak': 'nef_nmr_spectrum',
                                      'spectral_peak_alt': None,
                                      'noepk_restraint': None,
                                      'jcoup_restraint': None,
                                      'rdc_raw_data': None,
                                      'csa_restraint': None,
                                      'ddc_restraint': None,
                                      'hvycs_restraint': None,
                                      'procs_restraint': None,
                                      'csp_restraint': None,
                                      'auto_relax_restraint': None,
                                      'heteronucl_noe_data': None,
                                      'heteronucl_t1_data': None,
                                      'heteronucl_t2_data': None,
                                      'heteronucl_t1r_data': None,
                                      'order_param_data': None,
                                      'ccr_d_csa_restraint': None,
                                      'ccr_dd_restraint': None,
                                      'fchiral_restraint': None,
                                      'saxs_restraint': None,
                                      'other_restraint': None
                                      },
                              'nmr-star': {'entry_info': 'entry_information',
                                           'poly_seq': 'assembly',
                                           'entity': 'entity',
                                           'chem_shift': 'assigned_chemical_shifts',
                                           'chem_shift_ref': 'chem_shift_reference',
                                           'dist_restraint': 'general_distance_constraints',
                                           'dihed_restraint': 'torsion_angle_constraints',
                                           'rdc_restraint': 'RDC_constraints',
                                           'spectral_peak': 'spectral_peak_list',
                                           'spectral_peak_alt': 'spectral_peak_list',
                                           'noepk_restraint': 'homonucl_NOEs',
                                           'jcoup_restraint': 'J_three_bond_constraints',
                                           'rdc_raw_data': 'RDCs',
                                           'csa_restraint': 'chem_shift_anisotropy',
                                           'ddc_restraint': 'dipolar_couplings',
                                           'hvycs_restraint': 'CA_CB_chem_shift_constraints',
                                           'procs_restraint': 'H_chem_shift_constraints',
                                           'csp_restraint': 'chem_shift_perturbation',
                                           'auto_relax_restraint': 'auto_relaxation',
                                           'heteronucl_noe_data': 'heteronucl_NOEs',
                                           'heteronucl_t1_data': 'heteronucl_T1_relaxation',
                                           'heteronucl_t2_data': 'heteronucl_T2_relaxation',
                                           'heteronucl_t1r_data': 'heteronucl_T1rho_relaxation',
                                           'order_param_data': 'order_parameters',
                                           'ccr_d_csa_restraint': 'dipole_CSA_cross_correlations',
                                           'ccr_dd_restraint': 'dipole_dipole_cross_correlations',
                                           'fchiral_restraint': 'floating_chiral_stereo_assign',
                                           'saxs_restraint': 'saxs_constraints',
                                           'other_restraint': 'other_data_types'
                                           }
                              }

        # loop categories
        self.lp_categories = {'nef': {'entry_info': '_nef_program_script',
                                      'poly_seq': '_nef_sequence',
                                      'entity': None,
                                      'chem_shift': '_nef_chemical_shift',
                                      'chem_shift_ref': None,
                                      'dist_restraint': '_nef_distance_restraint',
                                      'dihed_restraint': '_nef_dihedral_restraint',
                                      'rdc_restraint': '_nef_rdc_restraint',
                                      'spectral_peak': '_nef_peak',
                                      'spectral_peak_alt': None,
                                      'noepk_restraint': None,
                                      'jcoup_restraint': None,
                                      'rdc_raw_data': None,
                                      'csa_restraint': None,
                                      'ddc_restraint': None,
                                      'hvycs_restraint': None,
                                      'procs_restraint': None,
                                      'csp_restraint': None,
                                      'auto_relax_restraint': None,
                                      'heteronucl_noe_data': None,
                                      'heteronucl_t1_data': None,
                                      'heteronucl_t2_data': None,
                                      'heteronucl_t1r_data': None,
                                      'order_param_data': None,
                                      'ccr_d_csa_restraint': None,
                                      'ccr_dd_restraint': None,
                                      'fchiral_restraint': None,
                                      'saxs_restraint': None,
                                      'other_restraint': None
                                      },
                              'nmr-star': {'entry_info': '_Software_applied_methods',
                                           'poly_seq': '_Chem_comp_assembly',
                                           'entity': '_Entity_comp_index',
                                           'chem_shift': '_Atom_chem_shift',
                                           'chem_shift_ref': '_Chem_shift_ref',
                                           'dist_restraint': '_Gen_dist_constraint',
                                           'dihed_restraint': '_Torsion_angle_constraint',
                                           'rdc_restraint': '_RDC_constraint',
                                           'spectral_peak': '_Peak_row_format',
                                           'spectral_peak_alt': '_Peak',
                                           'noepk_restraint': '_Homonucl_NOE',
                                           'jcoup_restraint': '_J_three_bond_constraint',
                                           'rdc_raw_data': '_RDC',
                                           'csa_restraint': '_CS_anisotropy',
                                           'ddc_restraint': '_Dipolar_coupling',
                                           'hvycs_restraint': '_CA_CB_constraint',
                                           'procs_restraint': '_H_chem_shift_constraint',
                                           'csp_restraint': '_Chem_shift_perturbation',
                                           'auto_relax_restraint': '_Auto_relaxation',
                                           'heteronucl_noe_data': '_Heteronucl_NOE',
                                           'heteronucl_t1_data': '_T1',
                                           'heteronucl_t2_data': '_T2',
                                           'heteronucl_t1r_data': '_T1rho',
                                           'order_param_data': '_Order_param',
                                           'ccr_d_csa_restraint': '_Cross_correlation_D_CSA',
                                           'ccr_dd_restraint': '_Cross_correlation_DD',
                                           'fchiral_restraint': '_Floating_chirality',
                                           'saxs_restraint': '_SAXS_constraint',
                                           'other_restraint': '_Other_data'
                                           },
                              'pdbx': {'poly_seq': 'pdbx_poly_seq_scheme',
                                       'non_poly': 'pdbx_nonpoly_scheme',
                                       'branched': 'pdbx_branch_scheme',
                                       'coordinate': 'atom_site',
                                       'poly_seq_alias': 'ndb_poly_seq_scheme',
                                       'non_poly_alias': 'ndb_nonpoly_scheme'
                                       }
                              }

        # cutoff value for detection of aromatic atoms
        self.cutoff_aromatic = 5.0
        # cutoff value for detection of paramagnetic/ferromagnetic atoms
        self.cutoff_paramagnetic = 10.0

        # criterion for aromatic ring in the vicinity
        self.vicinity_aromatic = 4.0
        # criterion for paramagnetic/ferromagnetic atom in the vicinity
        self.vicinity_paramagnetic = 8.0

        # criterion for detection of not superimposed models
        self.rmsd_not_superimposed = 2.0

        # criterion for detection of exactly overlaid models
        self.rmsd_overlaid_exactly = 0.01

        # criterion for covalent bond length
        self.cutoff_bond_length = 3.5

        # magic angle in degrees
        self.magic_angle = 54.7356

        # criterion for inconsistent restraint condition scaled by the conflicted restraint condition
        self.inconsist_over_conflicted = 0.75
        # criterion on R factor for conflicted distance restraint
        self.r_conflicted_dist_restraint = 0.4
        # criterion on R factor for inconsistent distance restraint
        self.r_inconsistent_dist_restraint = self.r_conflicted_dist_restraint * self.inconsist_over_conflicted

        # criterion on chemical shift for anomalous value scaled by its sigma
        self.cs_anomalous_error_scaled_by_sigma = 8.0
        # criterion on chemical shift for unusual value scaled by its sigma
        self.cs_unusual_error_scaled_by_sigma = 5.0
        # criterion on chemical shift difference error scaled by its sigma
        self.cs_diff_error_scaled_by_sigma = 10.0

        # hardware limit of NMR prove design in Hz (DAOTHER-7389, issue #1)
        self.hard_probe_limit = 250000

        # maximum number of lines as spacer for recognition of MR files
        self.mr_max_spacer_lines = 20

        # loop index tags
        self.index_tags = {'nef': {'entry_info': None,
                                   'poly_seq': 'index',
                                   'entity': None,
                                   'chem_shift': None,
                                   'chem_shift_ref': None,
                                   'dist_restraint': 'index',
                                   'dihed_restraint': 'index',
                                   'rdc_restraint': 'index',
                                   'spectral_peak': 'index',
                                   'spectral_peak_alt': None,
                                   'noepk_restraint': None,
                                   'jcoup_restraint': None,
                                   'rdc_raw_data': None,
                                   'csa_restraint': None,
                                   'ddc_restraint': None,
                                   'hvycs_restraint': None,
                                   'procs_restraint': None,
                                   'csp_restraint': None,
                                   'auto_relax_restraint': None,
                                   'heteronucl_noe_data': None,
                                   'heteronucl_t1_data': None,
                                   'heteronucl_t2_data': None,
                                   'heteronucl_t1r_data': None,
                                   'order_param_data': None,
                                   'ccr_d_csa_restraint': None,
                                   'ccr_dd_restraint': None,
                                   'fchiral_restraint': None,
                                   'saxs_restraint': None,
                                   'other_restraint': None
                                   },
                           'nmr-star': {'entry_info': None,
                                        'poly_seq': None,
                                        'entity': None,
                                        'chem_shift': None,
                                        'chem_shift_ref': None,
                                        'dist_restraint': 'Index_ID',
                                        'dihed_restraint': 'Index_ID',
                                        'rdc_restraint': 'Index_ID',
                                        'spectral_peak': 'Index_ID',
                                        'spectral_peak_alt': None,
                                        'noepk_restraint': None,
                                        'jcoup_restraint': None,
                                        'rdc_raw_data': None,
                                        'csa_restraint': None,
                                        'ddc_restraint': None,
                                        'hvycs_restraint': None,
                                        'procs_restraint': None,
                                        'csp_restraint': None,
                                        'auto_relax_restraint': None,
                                        'heteronucl_noe_data': None,
                                        'heteronucl_t1_data': None,
                                        'heteronucl_t2_data': None,
                                        'heteronucl_t1r_data': None,
                                        'order_param_data': None,
                                        'ccr_d_csa_restraint': None,
                                        'ccr_dd_restraint': None,
                                        'fchiral_restraint': None,
                                        'saxs_restraint': None,
                                        'other_restraint': None
                                        },
                           'pdbx': {'poly_seq': None,
                                    'non_poly': None,
                                    'branched': None,
                                    'coordinate': 'id'
                                    }
                           }

        # weight tags
        self.weight_tags = {'nef': {'entry_info': None,
                                    'poly_seq': None,
                                    'entity': None,
                                    'chem_shift': None,
                                    'chem_shift_ref': None,
                                    'dist_restraint': 'weight',
                                    'dihed_restraint': 'weight',
                                    'rdc_restraint': 'weight',
                                    'spectral_peak': None,
                                    'spectral_peak_alt': None,
                                    'noepk_restraint': None,
                                    'jcoup_restraint': None,
                                    'rdc_raw_data': None,
                                    'csa_restraint': None,
                                    'ddc_restraint': None,
                                    'hvycs_restraint': None,
                                    'procs_restraint': None,
                                    'csp_restraint': None,
                                    'auto_relax_restraint': None,
                                    'heteronucl_noe_data': None,
                                    'heteronucl_t1_data': None,
                                    'heteronucl_t2_data': None,
                                    'heteronucl_t1r_data': None,
                                    'order_param_data': None,
                                    'ccr_d_csa_restraint': None,
                                    'ccr_dd_restraint': None,
                                    'fchiral_restraint': None,
                                    'saxs_restraint': None,
                                    'other_restraint': None
                                    },
                            'nmr-star': {'entry_info': None,
                                         'poly_seq': None,
                                         'entity': None,
                                         'chem_shift': None,
                                         'chem_shift_ref': None,
                                         'dist_restraint': 'Weight',
                                         'dihed_restraint': 'Weight',
                                         'rdc_restraint': 'Weight',
                                         'spectral_peak': None,
                                         'spectral_peak_alt': None,
                                         'noepk_restraint': None,
                                         'jcoup_restraint': None,
                                         'rdc_raw_data': None,
                                         'csa_restraint': None,
                                         'ddc_restraint': None,
                                         'hvycs_restraint': None,
                                         'procs_restraint': None,
                                         'csp_restraint': None,
                                         'auto_relax_restraint': None,
                                         'heteronucl_noe_data': None,
                                         'heteronucl_t1_data': None,
                                         'heteronucl_t2_data': None,
                                         'heteronucl_t1r_data': None,
                                         'order_param_data': None,
                                         'ccr_d_csa_restraint': None,
                                         'ccr_dd_restraint': None,
                                         'fchiral_restraint': None,
                                         'saxs_restraint': 'Weight_val',
                                         'other_restraint': None
                                         },
                            'pdbx': {'poly_seq': None,
                                     'non_poly': None,
                                     'branched': None,
                                     'coordinate': None
                                     }
                            }

        # dihedral angle type
        self.angle_types = {'nef': 'name',
                            'nmr-star': 'Torsion_angle_name'
                            }

        # loop id tag to check consistency
        self.consist_id_tags = {'nef': {'dist_restraint': 'restraint_id',
                                        'dihed_restraint': 'restraint_id',
                                        'rdc_restraint': 'restraint_id',
                                        'spectral_peak': 'peak_id',
                                        'spectral_peak_alt': None,
                                        'noepk_restraint': None,
                                        'jcoup_restraint': None,
                                        'rdc_raw_data': None,
                                        'csa_restraint': None,
                                        'ddc_restraint': None,
                                        'hvycs_restraint': None,
                                        'procs_restraint': None,
                                        'csp_restraint': None,
                                        'auto_relax_restraint': None,
                                        'heteronucl_noe_data': None,
                                        'heteronucl_t1_data': None,
                                        'heteronucl_t2_data': None,
                                        'heteronucl_t1r_data': None,
                                        'order_param_data': None,
                                        'ccr_d_csa_restraint': None,
                                        'ccr_dd_restraint': None,
                                        'fchiral_restraint': None,
                                        'saxs_restraint': None,
                                        'other_restraint': None
                                        },
                                'nmr-star': {'dist_restraint': 'ID',
                                             'dihed_restraint': 'ID',
                                             'rdc_restraint': 'ID',
                                             'spectral_peak': 'ID',
                                             'spectral_peak_alt': 'ID',
                                             'noepk_restraint': 'ID',
                                             'jcoup_restraint': 'ID',
                                             'rdc_raw_data': 'ID',
                                             'csa_restraint': 'ID',
                                             'ddc_restraint': 'ID',
                                             'hvycs_restraint': 'ID',
                                             'procs_restraint': 'ID',
                                             'csp_restraint': 'ID',
                                             'auto_relax_restraint': 'ID',
                                             'heteronucl_noe_data': 'ID',
                                             'heteronucl_t1_data': 'ID',
                                             'heteronucl_t2_data': 'ID',
                                             'heteronucl_t1r_data': 'ID',
                                             'order_param_data': 'ID',
                                             'ccr_d_csa_restraint': 'ID',
                                             'ccr_dd_restraint': 'ID',
                                             'fchiral_restraint': 'ID',
                                             'saxs_restraint': 'ID',
                                             'other_restraint': 'ID'
                                             }
                                }

        # key items of loop
        self.key_items = {'nef': {'poly_seq': [{'name': 'chain_code', 'type': 'str', 'default': 'A'},
                                               {'name': 'sequence_code', 'type': 'int',
                                                'remove-bad-pattern': True},
                                               {'name': 'residue_name', 'type': 'str', 'uppercase': True,
                                                'remove-bad-pattern': True}
                                               ],
                                  'entity': None,
                                  'chem_shift': [{'name': 'chain_code', 'type': 'str', 'default': 'A'},
                                                 {'name': 'sequence_code', 'type': 'int',
                                                  'remove-bad-pattern': True},
                                                 {'name': 'residue_name', 'type': 'str',
                                                  'uppercase': True,
                                                  'remove-bad-pattern': True},
                                                 {'name': 'atom_name', 'type': 'str',
                                                  'remove-bad-pattern': True}
                                                 ],
                                  'chem_shift_ref': None,
                                  'dist_restraint': [{'name': 'restraint_id', 'type': 'positive-int'},
                                                     {'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                     {'name': 'sequence_code_1', 'type': 'int',
                                                      'remove-bad-pattern': True},
                                                     {'name': 'residue_name_1', 'type': 'str', 'uppercase': True,
                                                      'remove-bad-pattern': True},
                                                     {'name': 'atom_name_1', 'type': 'str',
                                                      'remove-bad-pattern': True},
                                                     {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                     {'name': 'sequence_code_2', 'type': 'int',
                                                      'remove-bad-pattern': True},
                                                     {'name': 'residue_name_2', 'type': 'str', 'uppercase': True,
                                                      'remove-bad-pattern': True},
                                                     {'name': 'atom_name_2', 'type': 'str',
                                                      'remove-bad-pattern': True}
                                                     ],
                                  'dihed_restraint': [{'name': 'restraint_id', 'type': 'positive-int'},
                                                      {'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                      {'name': 'sequence_code_1', 'type': 'int',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'residue_name_1', 'type': 'str', 'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'atom_name_1', 'type': 'str',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                      {'name': 'sequence_code_2', 'type': 'int',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'residue_name_2', 'type': 'str', 'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'atom_name_2', 'type': 'str',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'chain_code_3', 'type': 'str', 'default': 'A'},
                                                      {'name': 'sequence_code_3', 'type': 'int',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'residue_name_3', 'type': 'str', 'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'atom_name_3', 'type': 'str',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'chain_code_4', 'type': 'str', 'default': 'A'},
                                                      {'name': 'sequence_code_4', 'type': 'int',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'residue_name_4', 'type': 'str', 'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'atom_name_4', 'type': 'str',
                                                       'remove-bad-pattern': True}
                                                      ],
                                  'rdc_restraint': [{'name': 'restraint_id', 'type': 'positive-int'},
                                                    {'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                    {'name': 'sequence_code_1', 'type': 'int',
                                                     'remove-bad-pattern': True},
                                                    {'name': 'residue_name_1', 'type': 'str', 'uppercase': True,
                                                     'remove-bad-pattern': True},
                                                    {'name': 'atom_name_1', 'type': 'str',
                                                     'remove-bad-pattern': True},
                                                    {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                    {'name': 'sequence_code_2', 'type': 'int',
                                                     'remove-bad-pattern': True},
                                                    {'name': 'residue_name_2', 'type': 'str', 'uppercase': True,
                                                     'remove-bad-pattern': True},
                                                    {'name': 'atom_name_2', 'type': 'str',
                                                     'remove-bad-pattern': True}
                                                    ],
                                  'spectral_peak': None,
                                  'spectral_peak_alt': None,
                                  'noepk_restraint': None,
                                  'jcoup_restraint': None,
                                  'rdc_raw_data': None,
                                  'csa_restraint': None,
                                  'ddc_restraint': None,
                                  'hvycs_restraint': None,
                                  'procs_restraint': None,
                                  'csp_restraint': None,
                                  'auto_relax_restraint': None,
                                  'heteronucl_noe_data': None,
                                  'heteronucl_t1_data': None,
                                  'heteronucl_t2_data': None,
                                  'heteronucl_t1r_data': None,
                                  'order_param_data': None,
                                  'ccr_d_csa_restraint': None,
                                  'ccr_dd_restraint': None,
                                  'fchiral_restraint': None,
                                  'saxs_restraint': None,
                                  'other_restraint': None
                                  },
                          'nmr-star': {'poly_seq': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID'},
                                                    {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                    {'name': 'Comp_ID', 'type': 'str', 'uppercase': True}
                                                    ],
                                       'entity': None,
                                       'chem_shift': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID'},
                                                      {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'Comp_ID', 'type': 'str',
                                                       'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'Atom_ID', 'type': 'str',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'Occupancy', 'type': 'positive-float', 'default': '.'}
                                                      ],
                                       'chem_shift_ref': [{'name': 'Atom_type', 'type': 'enum', 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number', 'type': 'enum-int', 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                           'enforce-enum': True},
                                                          {'name': 'Mol_common_name', 'type': 'str'}],
                                       'dist_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                          {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                          {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                          {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                          {'name': 'Atom_ID_1', 'type': 'str'},
                                                          {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                          {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                          {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                          {'name': 'Atom_ID_2', 'type': 'str'}
                                                          ],
                                       'dihed_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                           {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                           {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_1', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                           {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                           {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_2', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                           {'name': 'Comp_index_ID_3', 'type': 'int', 'default-from': 'Seq_ID_3'},
                                                           {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_3', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                           {'name': 'Comp_index_ID_4', 'type': 'int', 'default-from': 'Seq_ID_4'},
                                                           {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_4', 'type': 'str'}
                                                           ],
                                       'rdc_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                         {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                         {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                         {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_1', 'type': 'str'},
                                                         {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                         {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                         {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_2', 'type': 'str'}
                                                         ],
                                       'spectral_peak': None,
                                       'spectral_peak_alt': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                             {'name': 'Spectral_peak_list_ID', 'type': 'positive-int', 'default': '1', 'default-from': 'self'}
                                                             ],
                                       'noepk_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                           {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                           {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_1', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                           {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                           {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_2', 'type': 'str'}
                                                           ],
                                       'jcoup_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                           {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                           {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_1', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                           {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                           {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_2', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                           {'name': 'Comp_index_ID_3', 'type': 'int', 'default-from': 'Seq_ID_3'},
                                                           {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_3', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                           {'name': 'Comp_index_ID_4', 'type': 'int', 'default-from': 'Seq_ID_4'},
                                                           {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_4', 'type': 'str'}
                                                           ],
                                       'rdc_raw_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                        {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                        {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                        {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                        {'name': 'Atom_ID_1', 'type': 'str'},
                                                        {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                        {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                        {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                        {'name': 'Atom_ID_2', 'type': 'str'}
                                                        ],
                                       'csa_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                         {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                         {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                         {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID', 'type': 'str'}
                                                         ],
                                       'ddc_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                         {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                         {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                         {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_1', 'type': 'str'},
                                                         {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                         {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                         {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_2', 'type': 'str'}
                                                         ],
                                       'hvycs_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                           {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                           {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_1', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                           {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                           {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_2', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                           {'name': 'Comp_index_ID_3', 'type': 'int', 'default-from': 'Seq_ID_3'},
                                                           {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_3', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                           {'name': 'Comp_index_ID_4', 'type': 'int', 'default-from': 'Seq_ID_4'},
                                                           {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_4', 'type': 'str'},
                                                           {'name': 'Entity_assembly_ID_5', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_5'},
                                                           {'name': 'Comp_index_ID_5', 'type': 'int', 'default-from': 'Seq_ID_5'},
                                                           {'name': 'Comp_ID_5', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_5', 'type': 'str'}
                                                           ],
                                       'procs_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID'},
                                                           {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                           {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID', 'type': 'str'}
                                                           ],
                                       'csp_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                         {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                         {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                         {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID', 'type': 'str'}
                                                         ],
                                       'auto_relax_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                                {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                                {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                                {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                {'name': 'Atom_ID', 'type': 'str'}
                                                                ],
                                       'heteronucl_noe_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                               {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                               {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                               {'name': 'Atom_ID_1', 'type': 'str'},
                                                               {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                               {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                               {'name': 'Atom_ID_2', 'type': 'str'},
                                                               ],
                                       'heteronucl_t1_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                              {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                              {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                              {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                              {'name': 'Atom_ID', 'type': 'str'}
                                                              ],
                                       'heteronucl_t2_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                              {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                              {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                              {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                              {'name': 'Atom_ID', 'type': 'str'}
                                                              ],
                                       'heteronucl_t1r_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                               {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                               {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                               {'name': 'Atom_ID', 'type': 'str'}
                                                               ],
                                       'order_param_data': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                            {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                            {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                            {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                            {'name': 'Atom_ID', 'type': 'str'}
                                                            ],
                                       'ccr_d_csa_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                               {'name': 'Dipole_entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'Dipole_comp_index_ID_1', 'type': 'int', 'default-from': 'Dipole_seq_ID_1'},
                                                               {'name': 'Dipole_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                               {'name': 'Dipole_atom_ID_1', 'type': 'str'},
                                                               {'name': 'Dipole_entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'Dipole_comp_index_ID_2', 'type': 'int', 'default-from': 'Dipole_seq_ID_2'},
                                                               {'name': 'Dipole_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                               {'name': 'Dipole_atom_ID_2', 'type': 'str'},
                                                               {'name': 'CSA_entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'CSA_comp_index_ID_1', 'type': 'int', 'default-from': 'CSA_seq_ID_1'},
                                                               {'name': 'CSA_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                               {'name': 'CSA_atom_ID_1', 'type': 'str'},
                                                               {'name': 'CSA_entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                               {'name': 'CSA_comp_index_ID_2', 'type': 'int', 'default-from': 'CSA_seq_ID_2'},
                                                               {'name': 'CSA_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                               {'name': 'CSA_atom_ID_2', 'type': 'str'}
                                                               ],
                                       'ccr_dd_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                            {'name': 'Dipole_1_entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                            {'name': 'Dipole_1_comp_index_ID_1', 'type': 'int', 'default-from': 'Dipole_1_seq_ID_1'},
                                                            {'name': 'Dipole_1_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                            {'name': 'Dipole_1_atom_ID_1', 'type': 'str'},
                                                            {'name': 'Dipole_1_entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                            {'name': 'Dipole_1_comp_index_ID_2', 'type': 'int', 'default-from': 'Dipole_1_seq_ID_2'},
                                                            {'name': 'Dipole_1_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                            {'name': 'Dipole_1_atom_ID_2', 'type': 'str'},
                                                            {'name': 'Dipole_2_entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1'},
                                                            {'name': 'Dipole_2_comp_index_ID_1', 'type': 'int', 'default-from': 'Dipole_2_seq_ID_1'},
                                                            {'name': 'Dipole_2_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                            {'name': 'Dipole_2_atom_ID_1', 'type': 'str'},
                                                            {'name': 'Dipole_2_entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1'},
                                                            {'name': 'Dipole_2_comp_index_ID_2', 'type': 'int', 'default-from': 'Dipole_2_seq_ID_2'},
                                                            {'name': 'Dipole_2_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                            {'name': 'Dipole_2_atom_ID_2', 'type': 'str'}
                                                            ],
                                       'fchiral_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                             {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                             {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                             {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                             {'name': 'Atom_ID_1', 'type': 'str'},
                                                             {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                             {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                             {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                             {'name': 'Atom_ID_2', 'type': 'str'}
                                                             ],
                                       'saxs_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                          {'name': 'Q_value', 'type': 'positive-float'}
                                                          ],
                                       'other_restraint': [{'name': 'ID', 'type': 'positive-int', 'auto-increment': True},
                                                           {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1'},
                                                           {'name': 'Comp_index_ID', 'type': 'int', 'default-from': 'Seq_ID'},
                                                           {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID', 'type': 'str'}
                                                           ]
                                       },
                          'pdbx': {'poly_seq': [{'name': 'asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                {'name': 'seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                {'name': 'mon_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                {'name': 'pdb_strand_id', 'type': 'str', 'alt_name': 'auth_chain_id'},
                                                {'name': 'pdb_seq_num', 'type': 'int', 'alt_name': 'auth_seq_id'}
                                                ],
                                   'poly_seq_alias': [{'name': 'id', 'type': 'str', 'alt_name': 'chain_id'},
                                                      {'name': 'seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                      {'name': 'mon_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                      {'name': 'pdb_id', 'type': 'str', 'alt_name': 'auth_chain_id'},
                                                      {'name': 'pdb_num', 'type': 'int', 'alt_name': 'auth_seq_id'}
                                                      ],
                                   'non_poly': [{'name': 'asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                {'name': 'pdb_seq_num', 'type': 'int', 'alt_name': 'seq_id'},
                                                {'name': 'mon_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                {'name': 'pdb_strand_id', 'type': 'str', 'alt_name': 'auth_chain_id'}
                                                ],
                                   'non_poly_alias': [{'name': 'asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                      {'name': 'pdb_num', 'type': 'int', 'alt_name': 'seq_id'},
                                                      {'name': 'mon_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                      {'name': 'pdb_id', 'type': 'str', 'alt_name': 'auth_chain_id'}
                                                      ],
                                   'branched': [{'name': 'asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                {'name': 'pdb_seq_num', 'type': 'int', 'alt_name': 'seq_id'},
                                                {'name': 'mon_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                {'name': 'pdb_asym_id', 'type': 'str', 'alt_name': 'auth_chain_id'}
                                                ],
                                   'coordinate': [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                  {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                  {'name': 'auth_comp_id', 'type': 'str', 'alt_name': 'comp_id'},
                                                  {'name': 'pdbx_PDB_model_num', 'type': 'int', 'alt_name': 'model_id'}
                                                  ],
                                   'coordinate_alias': [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                        {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                        {'name': 'auth_comp_id', 'type': 'str', 'alt_name': 'comp_id'},
                                                        {'name': 'ndb_model', 'type': 'int', 'alt_name': 'model_id'}
                                                        ],
                                   'coordinate_ins': [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                      {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                      {'name': 'auth_comp_id', 'type': 'str', 'alt_name': 'comp_id'},
                                                      {'name': 'pdbx_PDB_ins_code', 'type': 'str', 'alt_name': 'ins_code', 'default': '?'},
                                                      {'name': 'label_seq_id', 'type': 'str', 'default': '.'},
                                                      {'name': 'pdbx_PDB_model_num', 'type': 'int', 'alt_name': 'model_id'}
                                                      ],
                                   'coordinate_ins_alias': [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                            {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                            {'name': 'auth_comp_id', 'type': 'str', 'alt_name': 'comp_id'},
                                                            {'name': 'ndb_ins_code', 'type': 'str', 'alt_name': 'ins_code', 'default': '?'},
                                                            {'name': 'label_seq_id', 'type': 'str', 'default': '.'},
                                                            {'name': 'ndb_model', 'type': 'int', 'alt_name': 'model_id'}
                                                            ]
                                   }
                          }

        # key items of loop to check consistency
        self.consist_key_items = {'nef': {'dist_restraint': [{'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                             {'name': 'sequence_code_1', 'type': 'int'},
                                                             {'name': 'residue_name_1', 'type': 'str', 'uppercase': True},
                                                             {'name': 'atom_name_1', 'type': 'str'},
                                                             {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                             {'name': 'sequence_code_2', 'type': 'int'},
                                                             {'name': 'residue_name_2', 'type': 'str', 'uppercase': True},
                                                             {'name': 'atom_name_2', 'type': 'str'}
                                                             ],
                                          'dihed_restraint': [{'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                              {'name': 'sequence_code_1', 'type': 'int'},
                                                              {'name': 'residue_name_1', 'type': 'str', 'uppercase': True},
                                                              {'name': 'atom_name_1', 'type': 'str'},
                                                              {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                              {'name': 'sequence_code_2', 'type': 'int'},
                                                              {'name': 'residue_name_2', 'type': 'str', 'uppercase': True},
                                                              {'name': 'atom_name_2', 'type': 'str'},
                                                              {'name': 'chain_code_3', 'type': 'str', 'default': 'A'},
                                                              {'name': 'sequence_code_3', 'type': 'int'},
                                                              {'name': 'residue_name_3', 'type': 'str', 'uppercase': True},
                                                              {'name': 'atom_name_3', 'type': 'str'},
                                                              {'name': 'chain_code_4', 'type': 'str', 'default': 'A'},
                                                              {'name': 'sequence_code_4', 'type': 'int'},
                                                              {'name': 'residue_name_4', 'type': 'str', 'uppercase': True},
                                                              {'name': 'atom_name_4', 'type': 'str'}
                                                              ],
                                          'rdc_restraint': [{'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                            {'name': 'sequence_code_1', 'type': 'int'},
                                                            {'name': 'residue_name_1', 'type': 'str', 'uppercase': True},
                                                            {'name': 'atom_name_1', 'type': 'str'},
                                                            {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                            {'name': 'sequence_code_2', 'type': 'int'},
                                                            {'name': 'residue_name_2', 'type': 'str', 'uppercase': True},
                                                            {'name': 'atom_name_2', 'type': 'str'}
                                                            ],
                                          'spectral_peak': None,
                                          'spectral_peak_alt': None,
                                          'noepk_restraint': None,
                                          'jcoup_restraint': None,
                                          'rdc_raw_data': None,
                                          'csa_restraint': None,
                                          'ddc_restraint': None,
                                          'hvycs_restraint': None,
                                          'procs_restraint': None,
                                          'csp_restraint': None,
                                          'auto_relax_restraint': None,
                                          'heteronucl_noe_data': None,
                                          'heteronucl_t1_data': None,
                                          'heteronucl_t2_data': None,
                                          'heteronucl_t1r_data': None,
                                          'order_param_data': None,
                                          'ccr_d_csa_restraint': None,
                                          'ccr_dd_restraint': None,
                                          'fchiral_restraint': None,
                                          'saxs_restraint': None,
                                          'other_restraint': None
                                          },
                                  'nmr-star': {'dist_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                   'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                                  {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                                  {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                  {'name': 'Atom_ID_1', 'type': 'str'},
                                                                  {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                   'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                                  {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                                  {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                  {'name': 'Atom_ID_2', 'type': 'str'}
                                                                  ],
                                               'dihed_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                                   {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                                   {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_1', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                                   {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                                   {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_2', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                                   {'name': 'Comp_index_ID_3', 'type': 'int', 'default-from': 'Seq_ID_3'},
                                                                   {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_3', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                                   {'name': 'Comp_index_ID_4', 'type': 'int', 'default-from': 'Seq_ID_4'},
                                                                   {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_4', 'type': 'str'}
                                                                   ],
                                               'rdc_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                  'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                                 {'name': 'Comp_index_ID_1', 'type': 'int', 'default-from': 'Seq_ID_1'},
                                                                 {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID_1', 'type': 'str'},
                                                                 {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                  'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                                 {'name': 'Comp_index_ID_2', 'type': 'int', 'default-from': 'Seq_ID_2'},
                                                                 {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID_2', 'type': 'str'}
                                                                 ],
                                               'spectral_peak': None,
                                               'spectral_peak_alt': None,
                                               'noepk_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                    'default': '1'},
                                                                   {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                    'default-from': 'Seq_ID_1'},
                                                                   {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_1', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                    'default': '1'},
                                                                   {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                    'default-from': 'Seq_ID_2'},
                                                                   {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_2', 'type': 'str'}
                                                                   ],
                                               'jcoup_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                                   {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                    'default-from': 'Seq_ID_1'},
                                                                   {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_1', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                                   {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                    'default-from': 'Seq_ID_2'},
                                                                   {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_2', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                                   {'name': 'Comp_index_ID_3', 'type': 'int',
                                                                    'default-from': 'Seq_ID_3'},
                                                                   {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_3', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                                   {'name': 'Comp_index_ID_4', 'type': 'int',
                                                                    'default-from': 'Seq_ID_4'},
                                                                   {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_4', 'type': 'str'}
                                                                   ],
                                               'rdc_raw_data': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                 'default': '1'},
                                                                {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                 'default-from': 'Seq_ID_1'},
                                                                {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                {'name': 'Atom_ID_1', 'type': 'str'},
                                                                {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                 'default': '1'},
                                                                {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                 'default-from': 'Seq_ID_2'},
                                                                {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                {'name': 'Atom_ID_2', 'type': 'str'}
                                                                ],
                                               'csa_restraint': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                  'default': '1'},
                                                                 {'name': 'Comp_index_ID', 'type': 'int',
                                                                  'default-from': 'Seq_ID'},
                                                                 {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID', 'type': 'str'}
                                                                 ],
                                               'ddc_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                  'default': '1'},
                                                                 {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                  'default-from': 'Seq_ID_1'},
                                                                 {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID_1', 'type': 'str'},
                                                                 {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                  'default': '1'},
                                                                 {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                  'default-from': 'Seq_ID_2'},
                                                                 {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID_2', 'type': 'str'}
                                                                 ],
                                               'hvycs_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                                   {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                    'default-from': 'Seq_ID_1'},
                                                                   {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_1', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                                   {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                    'default-from': 'Seq_ID_2'},
                                                                   {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_2', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_3', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_3'},
                                                                   {'name': 'Comp_index_ID_3', 'type': 'int',
                                                                    'default-from': 'Seq_ID_3'},
                                                                   {'name': 'Comp_ID_3', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_3', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_4', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_4'},
                                                                   {'name': 'Comp_index_ID_4', 'type': 'int',
                                                                    'default-from': 'Seq_ID_4'},
                                                                   {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_4', 'type': 'str'},
                                                                   {'name': 'Entity_assembly_ID_5', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID_5'},
                                                                   {'name': 'Comp_index_ID_5', 'type': 'int',
                                                                    'default-from': 'Seq_ID_5'},
                                                                   {'name': 'Comp_ID_5', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID_5', 'type': 'str'}
                                                                   ],
                                               'procs_restraint': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                    'default': '1', 'default-from': 'Auth_asym_ID'},
                                                                   {'name': 'Comp_index_ID', 'type': 'int',
                                                                    'default-from': 'Seq_ID'},
                                                                   {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID', 'type': 'str'}
                                                                   ],
                                               'csp_restraint': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                  'default': '1'},
                                                                 {'name': 'Comp_index_ID', 'type': 'int',
                                                                  'default-from': 'Seq_ID'},
                                                                 {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                 {'name': 'Atom_ID', 'type': 'str'}
                                                                 ],
                                               'auto_relax_restraint': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                         'default': '1'},
                                                                        {'name': 'Comp_index_ID', 'type': 'int',
                                                                         'default-from': 'Seq_ID'},
                                                                        {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                        {'name': 'Atom_ID', 'type': 'str'}
                                                                        ],
                                               'heteronucl_noe_data': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                        'default-from': 'Seq_ID_1'},
                                                                       {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'Atom_ID_1', 'type': 'str'},
                                                                       {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                        'default-from': 'Seq_ID_2'},
                                                                       {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'Atom_ID_2', 'type': 'str'}
                                                                       ],
                                               'heteronucl_t1_data': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                       'default': '1'},
                                                                      {'name': 'Comp_index_ID', 'type': 'int',
                                                                       'default-from': 'Seq_ID'},
                                                                      {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                      {'name': 'Atom_ID', 'type': 'str'},
                                                                      ],
                                               'heteronucl_t2_data': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                       'default': '1'},
                                                                      {'name': 'Comp_index_ID', 'type': 'int',
                                                                       'default-from': 'Seq_ID'},
                                                                      {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                      {'name': 'Atom_ID', 'type': 'str'},
                                                                      ],
                                               'heteronucl_t1r_data': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'Comp_index_ID', 'type': 'int',
                                                                        'default-from': 'Seq_ID'},
                                                                       {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'Atom_ID', 'type': 'str'},
                                                                       ],
                                               'order_param_data': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                     'default': '1'},
                                                                    {'name': 'Comp_index_ID', 'type': 'int',
                                                                     'default-from': 'Seq_ID'},
                                                                    {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                    {'name': 'Atom_ID', 'type': 'str'},
                                                                    ],
                                               'ccr_d_csa_restraint': [{'name': 'Dipole_entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'Dipole_comp_index_ID_1', 'type': 'int',
                                                                        'default-from': 'Dipole_seq_ID_1'},
                                                                       {'name': 'Dipole_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'Dipole_atom_ID_1', 'type': 'str'},
                                                                       {'name': 'Dipole_entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'Dipole_comp_index_ID_2', 'type': 'int',
                                                                        'default-from': 'Dipole_seq_ID_2'},
                                                                       {'name': 'Dipole_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'Dipole_atom_ID_2', 'type': 'str'},
                                                                       {'name': 'CSA_entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'CSA_comp_index_ID_1', 'type': 'int',
                                                                        'default-from': 'CSA_seq_ID_1'},
                                                                       {'name': 'CSA_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'CSA_atom_ID_1', 'type': 'str'},
                                                                       {'name': 'CSA_entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                        'default': '1'},
                                                                       {'name': 'CSA_comp_index_ID_2', 'type': 'int',
                                                                        'default-from': 'CSA_seq_ID_2'},
                                                                       {'name': 'CSA_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                       {'name': 'CSA_atom_ID_2', 'type': 'str'}
                                                                       ],
                                               'ccr_dd_restraint': [{'name': 'Dipole_1_entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                     'default': '1'},
                                                                    {'name': 'Dipole_1_comp_index_ID_1', 'type': 'int',
                                                                     'default-from': 'Dipole_1_seq_ID_1'},
                                                                    {'name': 'Dipole_1_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                    {'name': 'Dipole_1_atom_ID_1', 'type': 'str'},
                                                                    {'name': 'Dipole_1_entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                     'default': '1'},
                                                                    {'name': 'Dipole_1_comp_index_ID_2', 'type': 'int',
                                                                     'default-from': 'Dipole_1_seq_ID_2'},
                                                                    {'name': 'Dipole_1_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                    {'name': 'Dipole_1_atom_ID_2', 'type': 'str'},
                                                                    {'name': 'Dipole_2_entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                     'default': '1'},
                                                                    {'name': 'Dipole_2_comp_index_ID_1', 'type': 'int',
                                                                     'default-from': 'Dipole_2_seq_ID_1'},
                                                                    {'name': 'Dipole_2_comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                    {'name': 'Dipole_2_atom_ID_1', 'type': 'str'},
                                                                    {'name': 'Dipole_2_entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                     'default': '1'},
                                                                    {'name': 'Dipole_2_comp_index_ID_2', 'type': 'int',
                                                                     'default-from': 'Dipole_2_seq_ID_2'},
                                                                    {'name': 'Dipole_2_comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                    {'name': 'Dipole_2_atom_ID_2', 'type': 'str'}
                                                                    ],
                                               'fchiral_restraint': [{'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str',
                                                                      'default': '1'},
                                                                     {'name': 'Comp_index_ID_1', 'type': 'int',
                                                                      'default-from': 'Seq_ID_1'},
                                                                     {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                                     {'name': 'Atom_ID_1', 'type': 'str'},
                                                                     {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str',
                                                                      'default': '1'},
                                                                     {'name': 'Comp_index_ID_2', 'type': 'int',
                                                                      'default-from': 'Seq_ID_2'},
                                                                     {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                                     {'name': 'Atom_ID_2', 'type': 'str'}
                                                                     ],
                                               'saxs_restraint': [{'name': 'Q_value', 'type': 'positive-float'}],
                                               'other_restraint': [{'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str',
                                                                    'default': '1'},
                                                                   {'name': 'Comp_index_ID', 'type': 'int',
                                                                    'default-from': 'Seq_ID'},
                                                                   {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                   {'name': 'Atom_ID', 'type': 'str'}
                                                                   ]
                                               }
                                  }

        # key items for spectral peak
        self.pk_key_items = {'nef': [{'name': 'position_%s', 'type': 'float'},
                                     {'name': 'peak_id', 'type': 'positive-int'}
                                     ],
                             'nmr-star': [{'name': 'Position_%s', 'type': 'float'},
                                          {'name': 'ID', 'type': 'positive-int'}
                                          ]
                             }

        # data items of loop
        self.data_items = {'nef': {'poly_seq': [{'name': 'linking', 'type': 'enum', 'mandatory': False,
                                                 'enum': ('start', 'end', 'middle', 'cyclic', 'break', 'single', 'dummy'),
                                                 'enforce-enum': True},
                                                {'name': 'residue_variant', 'type': 'str', 'mandatory': False},
                                                {'name': 'cis_peptide', 'type': 'bool', 'mandatory': False}
                                                ],
                                   'entity': None,
                                   'chem_shift': [{'name': 'value', 'type': 'range-float', 'mandatory': True,
                                                   'remove-bad-patter': True,
                                                   'range': CS_RESTRAINT_RANGE},
                                                  {'name': 'value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                   'clear-bad-pattern': True,
                                                   'range': CS_UNCERTAINTY_RANGE},
                                                  {'name': 'element', 'type': 'enum', 'mandatory': True, 'default-from': 'atom_name',
                                                   'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                   'enforce-enum': True},
                                                  {'name': 'isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'atom_name',
                                                   'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                   'enforce-enum': True}
                                                  ],
                                   'chem_shift_ref': None,
                                   'dist_restraint': [{'name': 'index', 'type': 'index-int', 'mandatory': True},
                                                      # {'name': 'restraint_id', 'type': 'positive-int', 'mandatory': True,
                                                      # 'enforce-non-zero': True},
                                                      {'name': 'restraint_combination_id', 'type': 'positive-int', 'mandatory': False,
                                                       'enforce-non-zero': True},
                                                      {'name': 'weight', 'type': 'range-float', 'mandatory': True,
                                                       'range': WEIGHT_RANGE},
                                                      # 'enforce-non-zero': True},
                                                      {'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_RESTRAINT_RANGE,
                                                       'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                 'coexist-with': None,
                                                                 'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                 'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                      {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_UNCERTAINTY_RANGE},
                                                      {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_RESTRAINT_RANGE,
                                                       'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                 'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                 'smaller-than': None,
                                                                 'larger-than': ['lower_limit', 'upper_limit', 'upper_linear_limit']}},
                                                      {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_RESTRAINT_RANGE,
                                                       'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                 'coexist-with': None,  # ['upper_limit'],
                                                                 'smaller-than': ['lower_linear_limit'],
                                                                 'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                      {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_RESTRAINT_RANGE,
                                                       'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                 'coexist-with': None,  # ['lower_limit'],
                                                                 'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                 'larger-than': ['upper_linear_limit']}},
                                                      {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'clear-bad-pattern': True,
                                                       'range': DIST_RESTRAINT_RANGE,
                                                       'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                 'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                 'smaller-than': ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                 'larger-than': None}}
                                                      ],
                                   'dihed_restraint': [{'name': 'index', 'type': 'index-int', 'mandatory': True},
                                                       # {'name': 'restraint_id', 'type': 'positive-int', 'mandatory': True,
                                                       # 'enforce-non-zero': True},
                                                       {'name': 'restraint_combination_id', 'type': 'positive-int', 'mandatory': False,
                                                        'enforce-non-zero': True},
                                                       {'name': 'weight', 'type': 'range-float', 'mandatory': True,
                                                        'range': WEIGHT_RANGE},
                                                       # 'enforce-non-zero': True},
                                                       {'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_RESTRAINT_RANGE,
                                                        'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                  'coexist-with': None,
                                                                  'smaller-than': None,  # (DAOTHER-8442) ['lower_linear_limit', 'lower_limit'],
                                                                  'larger-than': None,  # (DAOTHER-8442) ['upper_limit', 'upper_linear_limit'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_UNCERTAINTY_RANGE},
                                                       {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_RESTRAINT_RANGE,
                                                        'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                  'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                  'smaller-than': None,
                                                                  'larger-than': ['lower_limit'],
                                                                  # (DAOTHER-8442) ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_RESTRAINT_RANGE,
                                                        'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                  'coexist-with': None,  # ['upper_limit'],
                                                                  'smaller-than': ['lower_linear_limit'],
                                                                  'larger-than': None,  # (DAOTHER-8442) ['upper_limit', 'upper_linear_limit'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_RESTRAINT_RANGE,
                                                        'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                  'coexist-with': None,  # ['lower_limit'],
                                                                  'smaller-than': None,  # (DAOTHER-8442) ['lower_linear_limit', 'lower_limit'],
                                                                  'larger-than': ['upper_linear_limit'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'clear-bad-pattern': True,
                                                        'range': ANGLE_RESTRAINT_RANGE,
                                                        'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                  'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                  'smaller-than': ['upper_limit'],
                                                                  # (DAOTHER-8442) ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                  'larger-than': None,
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'name', 'type': 'str', 'mandatory': False}
                                                       ],
                                   'rdc_restraint': [{'name': 'index', 'type': 'index-int', 'mandatory': True},
                                                     # {'name': 'restraint_id', 'type': 'positive-int', 'mandatory': True,
                                                     # 'enforce-non-zero': True},
                                                     {'name': 'restraint_combination_id', 'type': 'positive-int', 'mandatory': False,
                                                      'enforce-non-zero': True},
                                                     {'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_RESTRAINT_RANGE,
                                                      'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                'coexist-with': None,
                                                                'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                     {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_UNCERTAINTY_RANGE},
                                                     {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_RESTRAINT_RANGE,
                                                      'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                'smaller-than': None,
                                                                'larger-than': ['lower_limit', 'upper_limit', 'upper_linear_limit']}},
                                                     {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_RESTRAINT_RANGE,
                                                      'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                'coexist-with': None,  # ['upper_limit'],
                                                                'smaller-than': ['lower_linear_limit'],
                                                                'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                     {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_RESTRAINT_RANGE,
                                                      'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                'coexist-with': None,  # ['lower_limit'],
                                                                'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                'larger-than': ['upper_linear_limit']}},
                                                     {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'range': RDC_RESTRAINT_RANGE,
                                                      'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                'smaller-than': ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                'larger-than': None}},
                                                     {'name': 'scale', 'type': 'range-float', 'mandatory': False,
                                                      'range': SCALE_RANGE,
                                                      'enforce-non-zero': True},
                                                     {'name': 'distance_dependent', 'type': 'bool', 'mandatory': False}
                                                     ],
                                   'spectral_peak': [{'name': 'index', 'type': 'index-int', 'mandatory': True},
                                                     {'name': 'volume', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'group': {'member-with': ['height'],
                                                                'coexist-with': None}},
                                                     {'name': 'volume_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True,
                                                      'clear-bad-pattern': True},
                                                     {'name': 'height', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                      'clear-bad-pattern': True,
                                                      'group': {'member-with': ['volume'],
                                                                'coexist-with': None}},
                                                     {'name': 'height_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True,
                                                      'clear-bad-pattern': True}
                                                     ],
                                   'spectral_peak_alt': None,
                                   'noepk_restraint': None,
                                   'jcoup_restraint': None,
                                   'rdc_raw_data': None,
                                   'csa_restraint': None,
                                   'ddc_restraint': None,
                                   'hvycs_restraint': None,
                                   'procs_restraint': None,
                                   'csp_restraint': None,
                                   'auto_relax_restraint': None,
                                   'heteronucl_noe_data': None,
                                   'heteronucl_t1_data': None,
                                   'heteronucl_t2_data': None,
                                   'heteronucl_t1r_data': None,
                                   'order_param_data': None,
                                   'ccr_d_csa_restraint': None,
                                   'ccr_dd_restraint': None,
                                   'fchiral_restraint': None,
                                   'saxs_restraint': None,
                                   'other_restraint': None
                                   },
                           'nmr-star': {'poly_seq': [{'name': 'Entity_ID', 'type': 'positive-int', 'mandatory': False},
                                                     {'name': 'Seq_ID', 'type': 'int', 'mandatory': False},
                                                     {'name': 'Auth_asym_ID', 'type': 'str', 'mandatory': False},
                                                     {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                     {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                     {'name': 'Auth_variant_ID', 'type': 'str', 'mandatory': False},
                                                     {'name': 'Sequence_linking', 'type': 'enum', 'mandatory': False,
                                                      'enum': ('start', 'end', 'middle', 'cyclic', 'break', 'single', 'dummy'),
                                                      'enforce-enum': True},
                                                     {'name': 'Cis_residue', 'type': 'bool', 'mandatory': False},
                                                     {'name': 'NEF_index', 'type': 'index-int', 'mandatory': False},
                                                     {'name': 'Assembly_ID', 'type': 'pointer-index', 'mandatory': False, 'default': '1', 'default-from': 'parent'}
                                                     ],
                                        'entity': None,
                                        'chem_shift': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                        'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                        'enforce-enum': True},
                                                       {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                        'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                        'enforce-enum': True},
                                                       {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                        'remove-bad-patter': True,
                                                        'range': CS_RESTRAINT_RANGE},
                                                       {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                        'clear-bad-pattern': True,
                                                        'range': CS_UNCERTAINTY_RANGE},
                                                       {'name': 'Ambiguity_code', 'type': 'enum-int', 'mandatory': False,
                                                        'enum': ALLOWED_AMBIGUITY_CODES,
                                                        'enforce-enum': True},
                                                       {'name': 'Ambiguity_set_ID', 'type': 'positive-int', 'mandatory': False,
                                                        'enforce-non-zero': True},
                                                       {'name': 'Seq_ID', 'type': 'int', 'mandatory': False},
                                                       {'name': 'Auth_asym_ID', 'type': 'str', 'mandatory': False},
                                                       {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                       {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                       {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                       {'name': 'Assigned_chem_shift_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                       ],
                                        'chem_shift_ref': [{'name': 'Atom_group', 'type': 'enum', 'mandatory': True,
                                                            'enum': ('methyl carbon', 'methyl carbons', 'methyl protons', 'methylene protons',
                                                                     'nitrogen', 'phosphorus', 'protons')},
                                                           {'name': 'Chem_shift_val', 'type': 'float', 'mandatory': True},
                                                           {'name': 'Chem_shift_units', 'type': 'enum', 'mandatory': True,
                                                            'enum': ('ppm', 'Hz'),
                                                            'enforce-enum': True},
                                                           {'name': 'Correction_val', 'type': 'float', 'mandatory': False},
                                                           {'name': 'External_ref_axis', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('parallel', 'perpendicular')},
                                                           {'name': 'External_ref_loc', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('insert at center of a separate sample tube',
                                                                     'insert at center of experimental sample tube',
                                                                     'insert at outer edge of a separate sample tube',
                                                                     'insert at outer edge of experimental sample tube',
                                                                     'other',
                                                                     'separate tube (no insert) not similar to the experimental sample tube',
                                                                     'separate tube (no insert) similar to the experimental sample tube')},
                                                           {'name': 'External_ref_sample_geometry', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('cylindrical', 'other', 'spherical')},
                                                           {'name': 'Indirect_shift_ratio', 'type': 'range-float', 'mandatory': False,
                                                            'range': {'min_exclusive': 0.0, 'max_inclusive': 1.0}},
                                                           {'name': 'Rank', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Ref_correction_type', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Ref_method', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('external', 'internal', 'na')},
                                                           {'name': 'Ref_type', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('direct', 'indirect')},
                                                           {'name': 'Solvent', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Chem_shift_reference_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                           ],
                                        'dist_restraint': [{'name': 'Index_ID', 'type': 'index-int', 'mandatory': False},
                                                           {'name': 'Combination_ID', 'type': 'positive-int', 'mandatory': False,
                                                            'enforce-non-zero': True},
                                                           {'name': 'Member_ID', 'type': 'positive-int', 'mandatory': False,
                                                            'enforce-non-zero': True},
                                                           {'name': 'Member_logic_code', 'type': 'enum', 'mandatory': False,
                                                            'enum': ('OR', 'AND'),
                                                            'enforce-enum': True},
                                                           {'name': 'Target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_RESTRAINT_RANGE,
                                                            'group': {'member-with': ['Lower_linear_limit',
                                                                                      'Upper_linear_limit',
                                                                                      'Distance_lower_bound_val',
                                                                                      'Distance_upper_bound_val'],
                                                                      'coexist-with': None,
                                                                      'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val'],
                                                                      'larger-than': ['Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                           {'name': 'Target_val_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_UNCERTAINTY_RANGE},
                                                           {'name': 'Lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_RESTRAINT_RANGE,
                                                            'group': {'member-with': ['Target_val',
                                                                                      'Upper_linear_limit',
                                                                                      'Distance_lower_bound_val',
                                                                                      'Distance_upper_bound_val'],
                                                                      'coexist-with': None,  # ['Upper_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                      'smaller-than': None,
                                                                      'larger-than': ['Distance_lower_bound_val', 'Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                           {'name': 'Distance_lower_bound_val', 'type': 'range-float', 'mandatory': False,
                                                            'group-mandatory': True, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_RESTRAINT_RANGE,
                                                            'group': {'member-with': ['Target_val', 'Lower_linear_limit', 'Upper_linear_limit', 'Distance_upper_bound_val'],
                                                                      'coexist-with': None,  # ['Distance_upper_bound_val'],
                                                                      'smaller-than': ['Lower_linear_limit'],
                                                                      'larger-than': ['Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                           {'name': 'Distance_upper_bound_val', 'type': 'range-float', 'mandatory': False,
                                                            'group-mandatory': True, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_RESTRAINT_RANGE,
                                                            'group': {'member-with': ['Target_val', 'Lower_linear_limit', 'Upper_linear_limit', 'Distance_lower_bound_val'],
                                                                      'coexist-with': None,  # ['Distance_lower_bound_val'],
                                                                      'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val'],
                                                                      'larger-than': ['Upper_linear_limit']}},
                                                           {'name': 'Upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                            'clear-bad-pattern': True,
                                                            'range': DIST_RESTRAINT_RANGE,
                                                            'group': {'member-with': ['Target_val',
                                                                                      'Lower_linear_limit',
                                                                                      'Distance_lower_bound_val',
                                                                                      'Distance_upper_bound_val'],
                                                                      'coexist-with': None,  # ['Lower_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                      'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                      'larger-than': None}},
                                                           {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                            'range': WEIGHT_RANGE},
                                                           # 'enforce-non-zero': True},
                                                           {'name': 'Distance_val', 'type': 'range-float', 'mandatory': False,
                                                                    'range': DIST_RESTRAINT_RANGE},
                                                           {'name': 'Seq_ID_1', 'type': 'int', 'mandatory': False},
                                                           {'name': 'Seq_ID_2', 'type': 'int', 'mandatory': False},
                                                           {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                           {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                           {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                           {'name': 'Gen_dist_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                            'default': '1', 'default-from': 'parent'}
                                                           ],
                                        'dihed_restraint': [{'name': 'Index_ID', 'type': 'index-int', 'mandatory': False},
                                                            {'name': 'Combination_ID', 'type': 'positive-int', 'mandatory': False,
                                                             'enforce-non-zero': True},
                                                            {'name': 'Torsion_angle_name', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Angle_target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Angle_lower_linear_limit',
                                                                                       'Angle_upper_linear_limit',
                                                                                       'Angle_lower_bound_val',
                                                                                       'Angle_upper_bound_val'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': None,  # (DAOTHER-8442) ['Angle_lower_linear_limit', 'Angle_lower_bound_val'],
                                                                       'larger-than': None,  # (DAOTHER-8442) ['Angle_upper_bound_val', 'Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_target_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_UNCERTAINTY_RANGE},
                                                            {'name': 'Angle_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Angle_target_val', 'Angle_upper_linear_limit',
                                                                                       'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                       'coexist-with': None,  # ['Angle_upper_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                       'smaller-than': None,
                                                                       'larger-than': ['Angle_lower_bound_val'],
                                                                       # (DAOTHER-8442) ['Angle_lower_bound_val', 'Angle_upper_bound', 'Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_lower_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Angle_target_val', 'Angle_lower_linear_limit',
                                                                                       'Angle_upper_linear_limit', 'Angle_upper_bound_val'],
                                                                       'coexist-with': None,  # ['Angle_upper_bound_val'],
                                                                       'smaller-than': ['Angle_lower_linear_limit'],
                                                                       'larger-than': None,  # (DAOTHER-8442) ['Angle_upper_bound_val', 'Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_upper_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Angle_target_val', 'Angle_lower_linear_limit',
                                                                                       'Angle_upper_linear_limit', 'Angle_lower_bound_val'],
                                                                       'coexist-with': None,  # ['Angle_lower_bound_val'],
                                                                       'smaller-than': None,  # (DAOTHER-8442) ['Angle_lower_bound_val', 'Angle_upper_linear_limit'],
                                                                       'larger-than': ['Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': ANGLE_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Angle_target_val', 'Angle_lower_linear_limit',
                                                                                       'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                       'coexist-with': None,  # ['Angle_lower_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                       'smaller-than': ['Angle_upper_bound_val'],
                                                                       # (DAOTHER-8442) ['Angle_lower_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                       'larger-than': None,
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                             'range': WEIGHT_RANGE},
                                                            # 'enforce-non-zero': True},
                                                            {'name': 'Seq_ID_1', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Seq_ID_2', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Seq_ID_3', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Seq_ID_4', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_3', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_4', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Torsion_angle_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                             'default': '1', 'default-from': 'parent'}
                                                            ],
                                        'rdc_restraint': [{'name': 'Index_ID', 'type': 'index-int', 'mandatory': False},
                                                          {'name': 'Combination_ID', 'type': 'positive-int', 'mandatory': False,
                                                           'enforce-non-zero': True},
                                                          {'name': 'Target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['RDC_lower_linear_limit', 'RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'coexist-with': None,
                                                                     'smaller-than': ['RDC_lower_linear_limit', 'RDC_lower_bound'],
                                                                     'larger-than': ['RDC_upper_bound', 'RDC_upper_linear_limit']}},
                                                          {'name': 'Target_value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_UNCERTAINTY_RANGE},
                                                          {'name': 'RDC_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Target_value', 'RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'coexist-with': None,  # ['RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'smaller-than': None,
                                                                     'larger-than': ['RDC_lower_bound', 'RDC_upper_bound', 'RDC_upper_linear_limit']}},
                                                          {'name': 'RDC_lower_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Target_value', 'RDC_lower_linear_limit', 'RDC_upper_linear_limit', 'RDC_upper_bound'],
                                                                     'coexist-with': None,  # ['RDC_upper_bound'],
                                                                     'smaller-than': ['RDC_lower_linear_limit'],
                                                                     'larger-than': ['RDC_upper_bound', 'RDC_upper_linear_limit']}},
                                                          {'name': 'RDC_upper_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Target_value', 'RDC_lower_linear_limit', 'RDC_upper_linear_limit', 'RDC_lower_bound'],
                                                                     'coexist-with': None,  # ['RDC_lower_bound'],
                                                                     'smaller-than': ['RDC_lower_linear_limit', 'RDC_lower_bound'],
                                                                     'larger-than': ['RDC_upper_linear_limit']}},
                                                          {'name': 'RDC_upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Target_value', 'RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'coexist-with': None,  # ['RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'smaller-than': ['RDC_lower_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                     'larger-than': None}},
                                                          {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                           'range': WEIGHT_RANGE},
                                                          # 'enforce-non-zero': True},
                                                          {'name': 'RDC_val', 'type': 'range-float', 'mandatory': False,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_RESTRAINT_RANGE},
                                                          {'name': 'RDC_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': RDC_UNCERTAINTY_RANGE},
                                                          {'name': 'RDC_val_scale_factor', 'type': 'range-float', 'mandatory': False,
                                                           'range': SCALE_RANGE,
                                                           'enforce-non-zero': True},
                                                          {'name': 'RDC_distant_dependent', 'type': 'bool', 'mandatory': False},
                                                          {'name': 'Seq_ID_1', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Seq_ID_2', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'RDC_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                          ],
                                        'spectral_peak': [{'name': 'Index_ID', 'type': 'index-int', 'mandatory': False},
                                                          {'name': 'Volume', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'group': {'member-with': ['Height'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Volume_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True},
                                                          {'name': 'Height', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'group': {'member-with': ['Volume'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Height_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True},
                                                          {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                          ],
                                        'spectral_peak_alt': [{'name': 'Index_ID', 'type': 'index-int', 'mandatory': False},
                                                              {'name': 'Figure_of_merit', 'type': 'range-float', 'mandatory': False,
                                                               'range': WEIGHT_RANGE},
                                                              {'name': 'Restraint', 'type': 'enum', 'mandatory': False,
                                                               'enum': ('no', 'yes')}
                                                              ],
                                        'noepk_restraint': [{'name': 'Val', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'group': {'member-with': ['Val_min', 'Val_max'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': None,
                                                                       'larger-than': None}},
                                                            {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': {'min_inclusive': 0.0}},
                                                            {'name': 'Val_min', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'group': {'member-with': ['Val', 'Val_max'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': None,
                                                                       'larger-than': ['Val_max']}},
                                                            {'name': 'Val_max', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'group': {'member-with': ['Val', 'Val_min'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': ['Val_min'],
                                                                       'larger-than': None}},
                                                            {'name': 'Auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Homonucl_NOE_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                            ],
                                        'jcoup_restraint': [{'name': 'Coupling_constant_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': RDC_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Coupling_constant_lower_bound', 'Coupling_constant_upper_bound'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': None,
                                                                       'larger-than': None}},
                                                            {'name': 'Coupling_constant_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': {'min_inclusive': 0.0}},
                                                            {'name': 'Coupling_constant_lower_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': RDC_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Coupling_constant_upper_bound'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': None,
                                                                       'larger-than': ['Coupling_constant_upper_bound']}},
                                                            {'name': 'Coupling_constant_upper_bound', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': RDC_RESTRAINT_RANGE,
                                                             'group': {'member-with': ['Coupling_constant_lower_bound'],
                                                                       'coexist-with': None,
                                                                       'smaller-than': ['Coupling_constant_lower_bound'],
                                                                       'larger-than': None}},
                                                            {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_3', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_4', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'J_three_bond_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                             'default': '1', 'default-from': 'parent'}
                                                            ],
                                        'rdc_raw_data': [{'name': 'RDC_code', 'type': 'str', 'mandatory': True},
                                                         {'name': 'Atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                          'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                          'enforce-enum': True},
                                                         {'name': 'Atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                          'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                          'enforce-enum': True},
                                                         {'name': 'Ambiguity_code_1', 'type': 'enum-int', 'mandatory': False,
                                                          'enum': ALLOWED_AMBIGUITY_CODES,
                                                          'enforce-enum': True},
                                                         {'name': 'Atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                          'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                          'enforce-enum': True},
                                                         {'name': 'Atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                          'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                          'enforce-enum': True},
                                                         {'name': 'Ambiguity_code_2', 'type': 'enum-int', 'mandatory': False,
                                                          'enum': ALLOWED_AMBIGUITY_CODES,
                                                          'enforce-enum': True},
                                                         {'name': 'Val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                          'clear-bad-pattern': True,
                                                          'range': RDC_RESTRAINT_RANGE,
                                                          'group': {'member-with': ['Val_min', 'Val_max'],
                                                                    'coexist-with': None,
                                                                    'smaller-than': None,
                                                                    'larger-than': None}},
                                                         {'name': 'Val_min', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                          'clear-bad-pattern': True,
                                                          'range': RDC_RESTRAINT_RANGE,
                                                          'group': {'member-with': ['Val_max'],
                                                                    'coexist-with': None,
                                                                    'smaller-than': None,
                                                                    'larger-than': ['Val_max']}},
                                                         {'name': 'Val_max', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                          'clear-bad-pattern': True,
                                                          'range': RDC_RESTRAINT_RANGE,
                                                          'group': {'member-with': ['Val_min'],
                                                                    'coexist-with': None,
                                                                    'smaller-than': ['Val_min'],
                                                                    'larger-than': None}},
                                                         {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                          'clear-bad-pattern': True,
                                                          'range': {'min_inclusive': 0.0}},
                                                         {'name': 'Val_bond_length', 'type': 'range-float', 'mandatory': False,
                                                          'range': DIST_RESTRAINT_RANGE},
                                                         {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                         {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                         {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                         {'name': 'RDC_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'}
                                                         ],
                                        'csa_restraint': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                           'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                           'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                           'enforce-enum': True},
                                                          {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'range': CSA_RESTRAINT_RANGE},
                                                          {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': {'min_inclusive': 0.0}},
                                                          {'name': 'Principal_value_sigma_11_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': CSA_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_value_sigma_22_val', 'Principal_value_sigma_33_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_value_sigma_22_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': CSA_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_value_sigma_11_val', 'Principal_value_sigma_33_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_value_sigma_33_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': CSA_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_value_sigma_11_val', 'Principal_value_sigma_22_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_Euler_angle_alpha_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_beta_val', 'Principal_Euler_angle_gamma_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_Euler_angle_beta_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_gamma_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_Euler_angle_gamma_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_beta_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Bond_length', 'type': 'range-float', 'mandatory': False,
                                                           'range': DIST_RESTRAINT_RANGE},
                                                          {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Chem_shift_anisotropy_ID', 'type': 'pointer-index', 'mandatory': True,
                                                           'default': '1', 'default-from': 'parent'}
                                                          ],
                                        'ddc_restraint': [{'name': 'Dipolar_coupling_code', 'type': 'str', 'mandatory': True},
                                                          {'name': 'Atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                           'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                           'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                           'enforce-enum': True},
                                                          {'name': 'Ambiguity_code_1', 'type': 'enum-int', 'mandatory': False,
                                                           'enum': ALLOWED_AMBIGUITY_CODES,
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                           'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                           'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                           'enforce-enum': True},
                                                          {'name': 'Ambiguity_code_2', 'type': 'enum-int', 'mandatory': False,
                                                           'enum': ALLOWED_AMBIGUITY_CODES,
                                                           'enforce-enum': True},
                                                          {'name': 'Val', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'group': {'member-with': ['Val_min', 'Val_max'],
                                                                     'coexist-with': None,
                                                                     'smaller-than': None,
                                                                     'larger-than': None}},
                                                          {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': {'min_inclusive': 0.0}},
                                                          {'name': 'Val_min', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'group': {'member-with': ['Val_max'],
                                                                     'coexist-with': None,
                                                                     'smaller-than': None,
                                                                     'larger-than': ['Val_max']}},
                                                          {'name': 'Val_max', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'clear-bad-pattern': True,
                                                           'group': {'member-with': ['Val_min'],
                                                                     'coexist-with': None,
                                                                     'smaller-than': ['Val_min'],
                                                                     'larger-than': None}},
                                                          {'name': 'Principal_Euler_angle_alpha_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_beta_val', 'Principal_Euler_angle_gamma_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_Euler_angle_beta_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_gamma_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Principal_Euler_angle_gamma_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': False,
                                                           'range': ANGLE_RESTRAINT_RANGE,
                                                           'group': {'member-with': ['Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_beta_val'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Dipolar_coupling_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                           'default': '1', 'default-from': 'parent'}
                                                          ],
                                        'hvycs_restraint': [{'name': 'CA_chem_shift_val', 'type': 'range-float', 'mandatory': True,
                                                             'clear-bad-pattern': True,
                                                             'range': CS_RESTRAINT_RANGE},
                                                            {'name': 'CA_chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': CS_UNCERTAINTY_RANGE},
                                                            {'name': 'CB_chem_shift_val', 'type': 'range-float', 'mandatory': False,
                                                             'clear-bad-pattern': True,
                                                             'range': CS_RESTRAINT_RANGE},
                                                            {'name': 'CB_chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': CS_UNCERTAINTY_RANGE},
                                                            {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_3', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_3', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_4', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_4', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_asym_ID_5', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID_5', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID_5', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID_5', 'type': 'str', 'mandatory': False},
                                                            {'name': 'CA_CB_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                             'default': '1', 'default-from': 'parent'}
                                                            ],
                                        'procs_restraint': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                             'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                             'enforce-enum': True},
                                                            {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                             'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                             'enforce-enum': True},
                                                            {'name': 'Chem_shift_val', 'type': 'range-float', 'mandatory': True,
                                                             'remove-bad-patter': True,
                                                             'range': CS_RESTRAINT_RANGE},
                                                            {'name': 'Chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': CS_UNCERTAINTY_RANGE},
                                                            {'name': 'Auth_asym_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'H_chem_shift_constraint_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                             'default': '1', 'default-from': 'parent'}
                                                            ],
                                        'csp_restraint': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                           'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                           'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                           'enforce-enum': True},
                                                          {'name': 'Chem_shift_val', 'type': 'range-float', 'mandatory': False,
                                                           'clear-bad-pattern': True,
                                                           'range': CS_RESTRAINT_RANGE},
                                                          {'name': 'Chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': CS_UNCERTAINTY_RANGE},
                                                          {'name': 'Difference_chem_shift_val', 'type': 'range-float', 'mandatory': False,
                                                           'clear-bad-pattern': True,
                                                           'range': CS_RESTRAINT_RANGE},
                                                          {'name': 'Difference_chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'clear-bad-pattern': True,
                                                           'range': CS_UNCERTAINTY_RANGE},
                                                          {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Chem_shift_perturbation_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                           'default': '1', 'default-from': 'parent'}
                                                          ],
                                        'auto_relax_restraint': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                  'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                  'enforce-enum': True},
                                                                 {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                  'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                  'enforce-enum': True},
                                                                 {'name': 'Auto_relaxation_val', 'type': 'range-float', 'mandatory': True,
                                                                  'remove-bad-patter': True,
                                                                  'range': PRE_RESTRAINT_RANGE},
                                                                 {'name': 'Auto_relaxation_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                  'clear-bad-pattern': True,
                                                                  'range': PRE_RESTRAINT_RANGE},
                                                                 {'name': 'Rex_val', 'type': 'range-float', 'mandatory': False,
                                                                  'clear-bad-pattern': True,
                                                                  'range': PRE_RESTRAINT_RANGE},
                                                                 {'name': 'Rex_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                  'clear-bad-pattern': True,
                                                                  'range': PRE_RESTRAINT_RANGE},
                                                                 {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                                 {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                                 {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                                 {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                                 {'name': 'Auto_relaxation_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                  'default': '1', 'default-from': 'parent'}
                                                                 ],
                                        'heteronucl_noe_data': [{'name': 'Atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'Atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_1',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'Atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'Atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID_2',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                                 'remove-bad-patter': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                 'clear-bad-pattern': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'Auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Heteronucl_NOE_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                 'default': '1', 'default-from': 'parent'}
                                                                ],
                                        'heteronucl_t1_data': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                'enforce-enum': True},
                                                               {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                'enforce-enum': True},
                                                               {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                                'remove-bad-pattern': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                'clear-bad-pattern': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                               {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Heteronucl_T1_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                'default': '1', 'default-from': 'parent'}
                                                               ],
                                        'heteronucl_t2_data': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                'enforce-enum': True},
                                                               {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                'enforce-enum': True},
                                                               {'name': 'T2_val', 'type': 'range-float', 'mandatory': True,
                                                                'remove-bad-patter': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'T2_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                'clear-bad-pattern': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'Rex_val', 'type': 'range-float', 'mandatory': False,
                                                                'clear-bad-pattern': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'Rex_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                'clear-bad-pattern': True,
                                                                'range': PRE_RESTRAINT_RANGE},
                                                               {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                               {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                               {'name': 'Heteronucl_T2_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                'default': '1', 'default-from': 'parent'}
                                                               ],
                                        'heteronucl_t1r_data': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'T1rho_val', 'type': 'range-float', 'mandatory': True,
                                                                 'remove-bad-patter': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'T1rho_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                 'clear-bad-pattern': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'Rex_val', 'type': 'range-float', 'mandatory': False,
                                                                 'clear-bad-pattern': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'Rex_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                 'clear-bad-pattern': True,
                                                                 'range': PRE_RESTRAINT_RANGE},
                                                                {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Heteronucl_T1rho_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                 'default': '1', 'default-from': 'parent'}
                                                                ],
                                        'order_param_data': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                              'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                              'enforce-enum': True},
                                                             {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                              'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                              'enforce-enum': True},
                                                             {'name': 'Order_param_val', 'type': 'range-float', 'mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Order_param_val_fit_err', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Tau_e_val', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Tau_e_val_fit_err', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Tau_f_val', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Tau_f_val_fit_err', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Tau_s_val', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Tau_s_val_fit_err', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Rex_val', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PRE_RESTRAINT_RANGE},
                                                             {'name': 'Rex_val_fit_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                              'clear-bad-pattern': True,
                                                              'range': PRE_RESTRAINT_RANGE},
                                                             {'name': 'Model_free_sum_squared_errs', 'type': 'positive-float', 'mandatory': False,
                                                              'clear-bad-pattern': True},
                                                             {'name': 'Model_fit', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('Rex', 'S2', 'S2, te', 'S2, Rex', 'S2, te, Rex', 'S2f, S2, ts', 'S2f, S2s, ts',
                                                                       'S2f, tf, S2, ts', 'S2f, tf, S2s, ts', 'S2f, S2, ts, Rex', 'S2f, S2s, ts, Rex',
                                                                       'S2f, tf, S2, ts, Rex', 'S2f, tf, S2s, ts, Rex', 'na')},
                                                             {'name': 'Sf2_val', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Sf2_val_fit_err', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Ss2_val', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Ss2_val_fit_err', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'SH2_val', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'SH2_val_fit_err', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'SN2_val', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'SN2_val_fit_err', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': PROBABILITY_RANGE},
                                                             {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                             {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Order_parameter_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                              'default': '1', 'default-from': 'parent'}
                                                             ],
                                        'ccr_d_csa_restraint': [{'name': 'Dipole_atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_atom_ID_1',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'Dipole_atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_atom_ID_1',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'Dipole_atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_atom_ID_2',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'Dipole_atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_atom_ID_2',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'CSA_atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'CSA_atom_ID_1',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'CSA_atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'CSA_atom_ID_1',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'CSA_atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'CSA_atom_ID_2',
                                                                 'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                                 'enforce-enum': True},
                                                                {'name': 'CSA_atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'CSA_atom_ID_2',
                                                                 'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                                 'enforce-enum': True},
                                                                {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                                 'remove-bad-patter': True,
                                                                 'range': CCR_RESTRAINT_RANGE},
                                                                {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                 'clear-bad-pattern': True,
                                                                 'range': CCR_RESTRAINT_RANGE},
                                                                {'name': 'Dipole_auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Dipole_auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Dipole_auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Dipole_auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Dipole_auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Dipole_auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Dipole_auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Dipole_auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                                {'name': 'CSA_auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                                {'name': 'CSA_auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'CSA_auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Cross_correlation_D_CSA_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                 'default': '1', 'default-from': 'parent'}
                                                                ],
                                        'ccr_dd_restraint': [{'name': 'Dipole_1_atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_1_atom_ID_1',
                                                              'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_1_atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_1_atom_ID_1',
                                                              'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_1_atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_1_atom_ID_2',
                                                              'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_1_atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_1_atom_ID_2',
                                                              'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_2_atom_type_1', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_2_atom_ID_1',
                                                              'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_2_atom_isotope_number_1', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_2_atom_ID_1',
                                                              'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_2_atom_type_2', 'type': 'enum', 'mandatory': True, 'default-from': 'Dipole_2_atom_ID_2',
                                                              'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                              'enforce-enum': True},
                                                             {'name': 'Dipole_2_atom_isotope_number_2', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Dipole_2_atom_ID_2',
                                                              'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                              'enforce-enum': True},
                                                             {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                              'remove-bad-patter': True,
                                                              'range': CCR_RESTRAINT_RANGE},
                                                             {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                              'clear-bad-pattern': True,
                                                              'range': CCR_RESTRAINT_RANGE},
                                                             {'name': 'Dipole_1_auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_1_auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_entity_assembly_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_entity_assembly_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Dipole_2_auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Cross_correlation_DD_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                              'default': '1', 'default-from': 'parent'}
                                                             ],
                                        'fchiral_restraint': [{'name': 'Stereospecific_assignment_code', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                              {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                              {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                              {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False}
                                                              ],
                                        'saxs_restraint': [{'name': 'Intensity_val', 'type': 'float', 'mandatory': True,
                                                            'remove-bad-patter': True},
                                                           {'name': 'Intensity_val_err', 'type': 'float', 'mandatory': True,
                                                            'clear-bad-pattern': True},
                                                           {'name': 'Weight_val', 'type': 'range-float', 'mandatory': False,
                                                            'range': WEIGHT_RANGE}
                                                           ],
                                        'other_restraint': [{'name': 'Atom_type', 'type': 'enum', 'mandatory': True, 'default-from': 'Atom_ID',
                                                             'enum': set(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys()),
                                                             'enforce-enum': True},
                                                            {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                             'enum': set(ALLOWED_ISOTOPE_NUMBERS),
                                                             'enforce-enum': True},
                                                            {'name': 'Val', 'type': 'float', 'mandatory': True,
                                                             'remove-bad-patter': True},
                                                            {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'clear-bad-pattern': True,
                                                             'range': {'min_inclusive': 0.0}},
                                                            {'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                            {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Other_data_type_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                             'default': '1', 'default-from': 'parent'}
                                                            ],
                                        }
                           }

        # data items of loop to check consistency
        self.consist_data_items = {'nef': {'dist_restraint': [{'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_RESTRAINT_RANGE,
                                                               'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                         'coexist-with': None,
                                                                         'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                         'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                              {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_UNCERTAINTY_RANGE},
                                                              {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_RESTRAINT_RANGE,
                                                               'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                         'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                         'smaller-than': None,
                                                                         'larger-than': ['lower_limit', 'upper_limit', 'upper_linear_limit']}},
                                                              {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_RESTRAINT_RANGE,
                                                               'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                         'coexist-with': None,  # ['upper_limit'],
                                                                         'smaller-than': ['lower_linear_limit'],
                                                                         'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                              {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_RESTRAINT_RANGE,
                                                               'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                         'coexist-with': None,  # ['lower_limit'],
                                                                         'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                         'larger-than': ['upper_linear_limit']}},
                                                              {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'clear-bad-pattern': True,
                                                               'range': DIST_RESTRAINT_RANGE,
                                                               'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                         'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                         'smaller-than': ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                         'larger-than': None}}
                                                              ],
                                           'dihed_restraint': [{'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'clear-bad-pattern': True,
                                                               'range': ANGLE_RESTRAINT_RANGE,
                                                                'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                          'coexist-with': None,
                                                                          'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                          'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                               {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False,
                                                                'clear-bad-pattern': True,
                                                                'range': ANGLE_UNCERTAINTY_RANGE},
                                                               {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'clear-bad-pattern': True,
                                                                'range': ANGLE_RESTRAINT_RANGE,
                                                                'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                          'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                          'smaller-than': None,
                                                                          'larger-than': ['lower_limit', 'upper_limit', 'upper_linear_limit']}},
                                                               {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'clear-bad-pattern': True,
                                                                'range': ANGLE_RESTRAINT_RANGE,
                                                                'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                          'coexist-with': None,  # ['upper_limit'],
                                                                          'smaller-than': ['lower_linear_limit'],
                                                                          'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                               {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'clear-bad-pattern': True,
                                                                'range': ANGLE_RESTRAINT_RANGE,
                                                                'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                          'coexist-with': None,  # ['lower_limit'],
                                                                          'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                          'larger-than': ['upper_linear_limit']}},
                                                               {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'clear-bad-pattern': True,
                                                                'range': ANGLE_RESTRAINT_RANGE,
                                                                'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                          'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                          'smaller-than': ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                          'larger-than': None}}
                                                               ],
                                           'rdc_restraint': [{'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_RESTRAINT_RANGE,
                                                              'group': {'member-with': ['lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                        'coexist-with': None,
                                                                        'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                        'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                             {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_UNCERTAINTY_RANGE},
                                                             {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_RESTRAINT_RANGE,
                                                              'group': {'member-with': ['target_value', 'lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                        'coexist-with': None,  # ['lower_limit', 'upper_limit', 'upper_linear_limit'],
                                                                        'smaller-than': None,
                                                                        'larger-than': ['lower_limit', 'upper_limit', 'upper_linear_limit']}},
                                                             {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_RESTRAINT_RANGE,
                                                              'group': {'member-with': ['target_value', 'lower_linear_limit', 'upper_limit', 'upper_linear_limit'],
                                                                        'coexist-with': None,  # ['upper_limit'],
                                                                        'smaller-than': ['lower_linear_limit'],
                                                                        'larger-than': ['upper_limit', 'upper_linear_limit']}},
                                                             {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_RESTRAINT_RANGE,
                                                              'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_linear_limit'],
                                                                        'coexist-with': None,  # ['lower_limit'],
                                                                        'smaller-than': ['lower_linear_limit', 'lower_limit'],
                                                                        'larger-than': ['upper_linear_limit']}},
                                                             {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'clear-bad-pattern': True,
                                                              'range': RDC_RESTRAINT_RANGE,
                                                              'group': {'member-with': ['target_value', 'lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                        'coexist-with': None,  # ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                        'smaller-than': ['lower_linear_limit', 'lower_limit', 'upper_limit'],
                                                                        'larger-than': None}}
                                                             ],
                                           'spectral_peak': None,
                                           'spectral_peak_alt': None,
                                           'noepk_restraint': None,
                                           'jcoup_restraint': None,
                                           'rdc_raw_data': None,
                                           'csa_restraint': None,
                                           'ddc_restraint': None,
                                           'hvycs_restraint': None,
                                           'procs_restraint': None,
                                           'csp_restraint': None,
                                           'auto_relax_restraint': None,
                                           'heteronucl_noe_data': None,
                                           'heteronucl_t1_data': None,
                                           'heteronucl_t2_data': None,
                                           'heteronucl_t1r_data': None,
                                           'order_param_data': None,
                                           'ccr_d_csa_restraint': None,
                                           'ccr_dd_restraint': None,
                                           'fchiral_restraint': None,
                                           'saxs_restraint': None,
                                           'other_restraint': None
                                           },
                                   'nmr-star': {'dist_restraint': [{'name': 'Target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_RESTRAINT_RANGE,
                                                                    'group': {'member-with': ['Lower_linear_limit',
                                                                                              'Upper_linear_limit',
                                                                                              'Distance_lower_bound_val',
                                                                                              'Distance_upper_bound_val'],
                                                                              'coexist-with': None,
                                                                              'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val'],
                                                                              'larger-than': ['Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                                   {'name': 'Target_val_uncertainty', 'type': 'range-float', 'mandatory': False,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_UNCERTAINTY_RANGE},
                                                                   {'name': 'Lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_RESTRAINT_RANGE,
                                                                    'group': {'member-with': ['Target_val',
                                                                                              'Upper_linear_limit',
                                                                                              'Distance_lower_bound_val',
                                                                                              'Distance_upper_bound_val'],
                                                                              'coexist-with': None,
                                                                              # ['Upper_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                              'smaller-than': None,
                                                                              'larger-than': ['Distance_lower_bound_val', 'Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                                   {'name': 'Upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_RESTRAINT_RANGE,
                                                                    'group': {'member-with': ['Target_val',
                                                                                              'Lower_linear_limit',
                                                                                              'Distance_lower_bound_val',
                                                                                              'Distance_upper_bound_val'],
                                                                              'coexist-with': None,
                                                                              # ['Lower_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                              'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val', 'Distance_upper_bound_val'],
                                                                              'larger-than': None}},
                                                                   {'name': 'Distance_lower_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_RESTRAINT_RANGE,
                                                                    'group': {'member-with': ['Target_val',
                                                                                              'Lower_linear_limit',
                                                                                              'Upper_linear_limit',
                                                                                              'Distance_upper_bound_val'],
                                                                              'coexist-with': None,  # ['Distance_upper_bound_val'],
                                                                              'smaller-than': ['Lower_linear_limit'],
                                                                              'larger-than': ['Distance_upper_bound_val', 'Upper_linear_limit']}},
                                                                   {'name': 'Distance_upper_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'clear-bad-pattern': True,
                                                                    'range': DIST_RESTRAINT_RANGE,
                                                                    'group': {'member-with': ['Target_val',
                                                                                              'Lower_linear_limit',
                                                                                              'Upper_linear_limit',
                                                                                              'Distance_lower_bound_val'],
                                                                              'coexist-with': None,  # ['Distance_lower_bound_val'],
                                                                              'smaller-than': ['Lower_linear_limit', 'Distance_lower_bound_val'],
                                                                              'larger-than': ['Upper_linear_limit']}},
                                                                   {'name': 'Distance_val', 'type': 'range-float', 'mandatory': False,
                                                                    'range': DIST_RESTRAINT_RANGE}
                                                                   ],
                                                'dihed_restraint': [{'name': 'Angle_lower_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_RESTRAINT_RANGE,
                                                                     'group': {'member-with': ['Angle_target_val',
                                                                                               'Angle_lower_linear_limit',
                                                                                               'Angle_upper_linear_limit',
                                                                                               'Angle_upper_bound_val'],
                                                                               'coexist-with': None,  # ['Angle_upper_bound_val'],
                                                                               'smaller-than': ['Angle_lower_linear_limit'],
                                                                               'larger-than': ['Angle_upper_bound_val', 'Angle_upper_linear_limit']}},
                                                                    {'name': 'Angle_upper_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_RESTRAINT_RANGE,
                                                                     'group': {'member-with': ['Angle_target_val',
                                                                                               'Angle_lower_linear_limit',
                                                                                               'Angle_upper_linear_limit',
                                                                                               'Angle_lower_bound_val'],
                                                                               'coexist-with': None,  # ['Angle_lower_bound_val'],
                                                                               'smaller-than': ['Angle_lower_linear_limit', 'Angle_lower_bound_val'],
                                                                               'larger-than': ['Angle_upper_linear_limit']}},
                                                                    {'name': 'Angle_target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_RESTRAINT_RANGE,
                                                                     'group': {'member-with': ['Angle_lower_linear_limit',
                                                                                               'Angle_upper_linear_limit',
                                                                                               'Angle_lower_bound_val',
                                                                                               'Angle_upper_bound_val'],
                                                                               'coexist-with': None,
                                                                               'smaller-than': ['Angle_lower_linear_limit', 'Angle_lower_bound_val'],
                                                                               'larger-than': ['Angle_upper_bound_val', 'Angle_upper_linear_limit']}},
                                                                    {'name': 'Angle_target_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_UNCERTAINTY_RANGE},
                                                                    {'name': 'Angle_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_RESTRAINT_RANGE,
                                                                     'group': {'member-with': ['Angle_target_val',
                                                                                               'Angle_upper_linear_limit',
                                                                                               'Angle_lower_bound_val',
                                                                                               'Angle_upper_bound_val'],
                                                                               'coexist-with': None,
                                                                               # ['Angle_upper_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                               'smaller-than': None,
                                                                               'larger-than': ['Angle_lower_bound_val', 'Angle_upper_bound_val', 'Angle_upper_linear_limit']}},
                                                                    {'name': 'Angle_upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'clear-bad-pattern': True,
                                                                     'range': ANGLE_RESTRAINT_RANGE,
                                                                     'group': {'member-with': ['Angle_target_val',
                                                                                               'Angle_lower_linear_limit',
                                                                                               'Angle_lower_bound_val',
                                                                                               'Angle_upper_bound_val'],
                                                                               'coexist-with': None,
                                                                               # ['Angle_lower_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                               'smaller-than': ['Angle_lower_linear_limit', 'Angle_lower_bound_val', 'Angle_upper_bound_val'],
                                                                               'larger-than': None}}
                                                                    ],
                                                'rdc_restraint': [{'name': 'Target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE,
                                                                   'group': {'member-with': ['RDC_lower_linear_limit',
                                                                                             'RDC_upper_linear_limit',
                                                                                             'RDC_lower_bound',
                                                                                             'RDC_upper_bound'],
                                                                             'coexist-with': None,
                                                                             'smaller-than': ['RDC_lower_linear_limit', 'RDC_lower_bound'],
                                                                             'larger-than': ['RDC_upper_bound', 'RDC_upper_linear_limit']}},
                                                                  {'name': 'Target_value_uncertainty', 'type': 'range-float', 'mandatory': False,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_UNCERTAINTY_RANGE},
                                                                  {'name': 'RDC_lower_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE,
                                                                   'group': {'member-with': ['Target_value',
                                                                                             'RDC_lower_linear_limit',
                                                                                             'RDC_upper_linear_limit',
                                                                                             'RDC_upper_bound'],
                                                                             'coexist-with': None,  # ['RDC_upper_bound'],
                                                                             'smaller-than': ['RDC_lower_linear_limit'],
                                                                             'larger-than': ['RDC_upper_boud', 'RDC_upper_linear_limit']}},
                                                                  {'name': 'RDC_upper_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE,
                                                                   'group': {'member-with': ['Target_value',
                                                                                             'RDC_lower_linear_limit',
                                                                                             'RDC_upper_linear_limit',
                                                                                             'RDC_lower_bound'],
                                                                             'coexist-with': None,  # ['RDC_lower_bound'],
                                                                             'smaller-than': ['RDC_lower_linear_limit', 'RDC_lower_bound'],
                                                                             'larger-than': ['RDC_upper_linear_limit']}},
                                                                  {'name': 'RDC_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE,
                                                                   'group': {'member-with': ['Target_value', 'RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                             'coexist-with': None,  # ['RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                             'smaller-than': None,
                                                                             'larger-than': ['RDC_lower_bound', 'RDC_upper_bound', 'RDC_upper_linear_limit']}},
                                                                  {'name': 'RDC_upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE,
                                                                   'group': {'member-with': ['Target_value', 'RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                             'coexist-with': None,  # ['RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound'],
                                                                             'smaller-than': None,
                                                                             'larger-than': ['RDC_upper_linear_limit', 'RDC_lower_bound', 'RDC_upper_bound']}},
                                                                  {'name': 'RDC_val', 'type': 'range-float', 'mandatory': False,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_RESTRAINT_RANGE},
                                                                  {'name': 'RDC_val_err', 'type': 'range-float', 'mandatory': False,
                                                                   'clear-bad-pattern': True,
                                                                   'range': RDC_UNCERTAINTY_RANGE}
                                                                  ],
                                                'spectral_peak': None,
                                                'spectral_peak_alt': None,
                                                'noepk_restraint': None,
                                                'jcoup_restraint': None,
                                                'rdc_raw_data': None,
                                                'csa_restraint': None,
                                                'ddc_restraint': None,
                                                'hvycs_restraint': None,
                                                'procs_restraint': None,
                                                'csp_restraint': None,
                                                'auto_relax_restraint': None,
                                                'heteronucl_noe_data': None,
                                                'heteronucl_t1_data': None,
                                                'heteronucl_t2_data': None,
                                                'heteronucl_t1r_data': None,
                                                'order_param_data': None,
                                                'ccr_d_csa_restraint': None,
                                                'ccr_dd_restraint': None,
                                                'fchiral_restraint': None,
                                                'saxs_restraint': None,
                                                'other_restraint': None
                                                }
                                   }

        # common potential descriptor items
        self.potential_items = {'nef': {'dist_restraint': {'target_value': 'target_value',
                                                           'lower_limit': 'lower_limit',
                                                           'upper_limit': 'upper_limit',
                                                           'lower_linear_limit': 'lower_linear_limit',
                                                           'upper_linear_limit': 'upper_linear_limit'},
                                        'dihed_restraint': {'target_value': 'target_value',
                                                            'lower_limit': 'lower_limit',
                                                            'upper_limit': 'upper_limit',
                                                            'lower_linear_limit': 'lower_linear_limit',
                                                            'upper_linear_limit': 'upper_linear_limit'},
                                        'rdc_restraint': {'target_value': 'target_value',
                                                          'lower_limit': 'lower_limit',
                                                          'upper_limit': 'upper_limit',
                                                          'lower_linear_limit': 'lower_linear_limit',
                                                          'upper_linear_limit': 'upper_linear_limit'}
                                        },
                                'nmr-star': {'dist_restraint': {'target_value': 'Target_val',
                                                                'target_value_alt': 'Distance_val',
                                                                'lower_limit': 'Distance_lower_bound_val',
                                                                'upper_limit': 'Distance_upper_bound_val',
                                                                'lower_linear_limit': 'Lower_linear_limit',
                                                                'upper_linear_limit': 'Upper_linear_limit'},
                                             'dihed_restraint': {'target_value': 'Angle_target_val',
                                                                 'lower_limit': 'Angle_lower_bound_val',
                                                                 'upper_limit': 'Angle_upper_bound_val',
                                                                 'lower_linear_limit': 'Angle_lower_linear_limit',
                                                                 'upper_linear_limit': 'Angle_upper_linear_limit'},
                                             'rdc_restraint': {'target_value': 'Target_value',
                                                               'target_value_alt': 'RDC_val',
                                                               'lower_limit': 'RDC_lower_bound',
                                                               'upper_limit': 'RDC_upper_bound',
                                                               'lower_linear_limit': 'RDC_lower_linear_limit',
                                                               'upper_linear_limit': 'RDC_upper_linear_limit'}
                                             }
                                }

        # loop data items for spectral peak
        self.pk_data_items = {'nef': [{'name': 'position_uncertainty_%s', 'type': 'range-float', 'mandatory': False,
                                       'clear-bad-pattern': True,
                                       'range': CS_UNCERTAINTY_RANGE},
                                      {'name': 'chain_code_%s', 'type': 'str', 'mandatory': False,
                                       'relax-key-if-exist': True},
                                      {'name': 'sequence_code_%s', 'type': 'int', 'mandatory': False,
                                       'relax-key-if-exist': True,
                                       'clear-bad-pattern': True},
                                      {'name': 'residue_name_%s', 'type': 'str', 'mandatory': False,
                                       'relax-key-if-exist': True,
                                       'uppercase': True,
                                       'clear-bad-pattern': True},
                                      {'name': 'atom_name_%s', 'type': 'str', 'mandatory': False,
                                       'relax-key-if-exist': True,
                                       'clear-bad-pattern': True}],
                              'nmr-star': [{'name': 'Position_uncertainty_%s', 'type': 'range-float', 'mandatory': False,
                                            'clear-bad-pattern': True,
                                            'range': CS_UNCERTAINTY_RANGE},
                                           {'name': 'Entity_assembly_ID_%s', 'type': 'positive-int-as-str', 'mandatory': False,
                                            'default': '1', 'default-from': 'Auth_asym_ID_%s',
                                            'enforce-non-zero': True,
                                            'relax-key-if-exist': True},
                                           {'name': 'Comp_index_ID_%s', 'type': 'int', 'mandatory': False,
                                            'default-from': 'Seq_ID_%s',
                                            'relax-key-if-exist': True,
                                            'clear-bad-pattern': True},
                                           {'name': 'Comp_ID_%s', 'type': 'str', 'mandatory': False,
                                            'relax-key-if-exist': True,
                                            'uppercase': True,
                                            'clear-bad-pattern': True},
                                           {'name': 'Atom_ID_%s', 'type': 'str', 'mandatory': False,
                                            'relax-key-if-exist': True,
                                            'clear-bad-pattern': True},
                                           {'name': 'Seq_ID_%s', 'type': 'int', 'mandatory': False},
                                           {'name': 'Auth_asym_ID_%s', 'type': 'str', 'mandatory': False},
                                           {'name': 'Auth_seq_ID_%s', 'type': 'int', 'mandatory': False},
                                           {'name': 'Auth_comp_ID_%s', 'type': 'str', 'mandatory': False},
                                           {'name': 'Auth_atom_ID_%s', 'type': 'str', 'mandatory': False}]
                              }

        # number of dimension of spectral peak
        self.num_dim_items = {'nef': 'num_dimensions', 'nmr-star': 'Number_of_spectral_dimensions'}

        # allowed loop tags
        self.allowed_tags = {'nef': {'entry_info': ['program_name', 'script_name', 'script'],
                                     'poly_seq': ['index', 'chain_code', 'sequence_code', 'residue_name', 'linking', 'residue_variant', 'cis_peptide'],
                                     'entity': None,
                                     'chem_shift': ['chain_code', 'sequence_code', 'residue_name', 'atom_name', 'value', 'value_uncertainty', 'element', 'isotope_number'],
                                     'chem_shift_ref': None,
                                     'dist_restraint': ['index', 'restraint_id', 'restraint_combination_id', 'chain_code_1',
                                                        'sequence_code_1', 'residue_name_1', 'atom_name_1', 'chain_code_2',
                                                        'sequence_code_2', 'residue_name_2', 'atom_name_2', 'weight', 'target_value',
                                                        'target_value_uncertainty', 'lower_linear_limit', 'lower_limit',
                                                        'upper_limit', 'upper_linear_limit'],
                                     'dihed_restraint': ['index', 'restraint_id', 'restraint_combination_id',
                                                         'chain_code_1', 'sequence_code_1', 'residue_name_1', 'atom_name_1',
                                                         'chain_code_2', 'sequence_code_2', 'residue_name_2', 'atom_name_2',
                                                         'chain_code_3', 'sequence_code_3', 'residue_name_3', 'atom_name_3',
                                                         'chain_code_4', 'sequence_code_4', 'residue_name_4', 'atom_name_4',
                                                         'weight', 'target_value', 'target_value_uncertainty',
                                                         'lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit',
                                                         'name'],
                                     'rdc_restraint': ['index', 'restraint_id', 'restraint_combination_id',
                                                       'chain_code_1', 'sequence_code_1', 'residue_name_1', 'atom_name_1',
                                                       'chain_code_2', 'sequence_code_2', 'residue_name_2', 'atom_name_2',
                                                       'weight', 'target_value', 'target_value_uncertainty',
                                                       'lower_linear_limit', 'lower_limit', 'upper_limit', 'upper_linear_limit',
                                                       'scale', 'distance_dependent'],
                                     'spectral_peak': ['index', 'peak_id', 'volume', 'volume_uncertainty', 'height', 'height_uncertainty',
                                                       'position_1', 'position_uncertainty_1', 'position_2', 'position_uncertainty_2',
                                                       'position_3', 'position_uncertainty_3', 'position_4', 'position_uncertainty_4',
                                                       'position_5', 'position_uncertainty_5', 'position_6', 'position_uncertainty_6',
                                                       'position_7', 'position_uncertainty_7', 'position_8', 'position_uncertainty_8',
                                                       'position_9', 'position_uncertainty_9', 'position_10', 'position_uncertainty_10',
                                                       'position_11', 'position_uncertainty_11', 'position_12', 'position_uncertainty_12',
                                                       'position_13', 'position_uncertainty_13', 'position_14', 'position_uncertainty_14',
                                                       'position_15', 'position_uncertainty_15',
                                                       'chain_code_1', 'sequence_code_1', 'residue_name_1', 'atom_name_1',
                                                       'chain_code_2', 'sequence_code_2', 'residue_name_2', 'atom_name_2',
                                                       'chain_code_3', 'sequence_code_3', 'residue_name_3', 'atom_name_3',
                                                       'chain_code_4', 'sequence_code_4', 'residue_name_4', 'atom_name_4',
                                                       'chain_code_5', 'sequence_code_5', 'residue_name_5', 'atom_name_5',
                                                       'chain_code_6', 'sequence_code_6', 'residue_name_6', 'atom_name_6',
                                                       'chain_code_7', 'sequence_code_7', 'residue_name_7', 'atom_name_7',
                                                       'chain_code_8', 'sequence_code_8', 'residue_name_8', 'atom_name_8',
                                                       'chain_code_9', 'sequence_code_9', 'residue_name_9', 'atom_name_9',
                                                       'chain_code_10', 'sequence_code_10', 'residue_name_10', 'atom_name_10',
                                                       'chain_code_11', 'sequence_code_11', 'residue_name_11', 'atom_name_11',
                                                       'chain_code_12', 'sequence_code_12', 'residue_name_12', 'atom_name_12',
                                                       'chain_code_13', 'sequence_code_13', 'residue_name_13', 'atom_name_13',
                                                       'chain_code_14', 'sequence_code_14', 'residue_name_14', 'atom_name_14',
                                                       'chain_code_15', 'sequence_code_15', 'residue_name_15', 'atom_name_15'],
                                     'spectral_peak_alt': None,
                                     'noepk_restraint': None,
                                     'jcoup_restraint': None,
                                     'rdc_raw_data': None,
                                     'csa_restraint': None,
                                     'ddc_restraint': None,
                                     'hvycs_restraint': None,
                                     'procs_restraint': None,
                                     'csp_restraint': None,
                                     'auto_relax_restraint': None,
                                     'heteronucl_noe_data': None,
                                     'heteronucl_t1_data': None,
                                     'heteronucl_t2_data': None,
                                     'heteronucl_t1r_data': None,
                                     'order_param_data': None,
                                     'ccr_d_csa_restraint': None,
                                     'ccr_dd_restraint': None,
                                     'fchiral_restraint': None,
                                     'saxs_restraint': None,
                                     'other_restraint': None
                                     },
                             'nmr-star': {'entry_info': ['Software_ID', 'Software_label', 'Methods_ID', 'Methods_label', 'Software_name',
                                                         'Script_name', 'Script', 'Software_specific_info', 'Sf_ID', 'Entry_ID', 'Software_applied_list_ID'],
                                          'poly_seq': ['Assembly_chem_comp_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Comp_ID', 'Seq_ID',
                                                       'Auth_entity_assembly_ID', 'Auth_asym_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_variant_ID',
                                                       'Sequence_linking', 'Cis_residue', 'NEF_index', 'Sf_ID', 'Entry_ID', 'Assembly_ID'],
                                          'entity': None,
                                          # DAOTHER-7545 'Entity_assembly_asym_ID' is not authorized data item acoording to NMR-STAR dictionary, but it is still used conventionally
                                          'chem_shift': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_assembly_asym_ID', 'Entity_ID', 'Comp_index_ID',
                                                         'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                         'Val', 'Val_err', 'Assign_fig_of_merit', 'Ambiguity_code', 'Ambiguity_set_ID', 'Occupancy', 'Resonance_ID',
                                                         'Auth_entity_assembly_ID', 'Auth_asym_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                         'PDB_record_ID', 'PDB_model_num', 'PDB_strand_ID', 'PDB_ins_code', 'PDB_residue_no', 'PDB_residue_name', 'PDB_atom_name',
                                                         'Original_PDB_strand_ID', 'Original_PDB_residue_no', 'Original_PDB_residue_name',
                                                         'Original_PDB_atom_name', 'Details', 'Sf_ID', 'Entry_ID', 'Assigned_chem_shift_list_ID'],
                                          'chem_shift_ref': ['Atom_type', 'Atom_isotope_number', 'Mol_common_name', 'Atom_group',
                                                             'Concentration_val', 'Concentration_units', 'Solvent', 'Rank',
                                                             'Chem_shift_units', 'Chem_shift_val', 'Ref_method', 'Ref_type', 'Indirect_shift_ratio',
                                                             'External_ref_loc', 'External_ref_sample_geometry', 'External_ref_axis', 'Indirect_shift_ratio_cit_ID',
                                                             'Indirect_shift_ratio_cit_label', 'Ref_correction_type', 'Correction_val', 'Correction_val_cit_ID',
                                                             'Correction_val_cit_label', 'Sf_ID', 'Entry_ID', 'Chem_shift_reference_ID'],
                                          'dist_restraint': ['Index_ID', 'ID', 'Combination_ID', 'Member_ID', 'Member_logic_code',
                                                             'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1', 'Seq_ID_1',
                                                             'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1', 'Resonance_ID_1',
                                                             'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2', 'Seq_ID_2',
                                                             'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2', 'Resonance_ID_2',
                                                             'Intensity_val', 'Intensity_lower_val_err', 'Intensity_upper_val_err', 'Distance_val',
                                                             'Target_val', 'Target_val_uncertainty', 'Lower_linear_limit', 'Upper_linear_limit',
                                                             'Distance_lower_bound_val', 'Distance_upper_bound_val', 'Contribution_fractional_val', 'Weight',
                                                             'Spectral_peak_ID', 'Spectral_peak_list_ID',
                                                             'PDB_record_ID_1', 'PDB_model_num_1', 'PDB_strand_ID_1', 'PDB_ins_code_1', 'PDB_residue_no_1',
                                                             'PDB_residue_name_1', 'PDB_atom_name_1', 'PDB_record_ID_2', 'PDB_model_num_2', 'PDB_strand_ID_2',
                                                             'PDB_ins_code_2', 'PDB_residue_no_2', 'PDB_residue_name_2', 'PDB_atom_name_2',
                                                             'Auth_entity_assembly_ID_1', 'Auth_asym_ID_1', 'Auth_chain_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1',
                                                             'Auth_atom_ID_1', 'Auth_alt_ID_1', 'Auth_atom_name_1',
                                                             'Auth_entity_assembly_ID_2', 'Auth_asym_ID_2', 'Auth_chain_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2',
                                                             'Auth_atom_ID_2', 'Auth_alt_ID_2', 'Auth_atom_name_2',
                                                             'Sf_ID', 'Entry_ID', 'Gen_dist_constraint_list_ID',
                                                             # The following Original_PDB_* data items are not legitimate, but keep them for backward compatibility
                                                             'Original_PDB_strand_ID_1', 'Original_PDB_residue_no_1', 'Original_PDB_residue_name_1',
                                                             'Original_PDB_strand_ID_2', 'Original_PDB_residue_no_2', 'Original_PDB_residue_name_2'],
                                          'dihed_restraint': ['Index_ID', 'ID', 'Combination_ID', 'Set_ID', 'Torsion_angle_name',
                                                              'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1', 'Seq_ID_1',
                                                              'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Resonance_ID_1',
                                                              'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2', 'Seq_ID_2',
                                                              'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Resonance_ID_2',
                                                              'Assembly_atom_ID_3', 'Entity_assembly_ID_3', 'Entity_ID_3', 'Comp_index_ID_3', 'Seq_ID_3',
                                                              'Comp_ID_3', 'Atom_ID_3', 'Atom_type_3', 'Resonance_ID_3',
                                                              'Assembly_atom_ID_4', 'Entity_assembly_ID_4', 'Entity_ID_4', 'Comp_index_ID_4', 'Seq_ID_4',
                                                              'Comp_ID_4', 'Atom_ID_4', 'Atom_type_4', 'Resonance_ID_4',
                                                              'Angle_lower_bound_val', 'Angle_upper_bound_val', 'Angle_target_val', 'Angle_target_val_err',
                                                              'Angle_lower_linear_limit', 'Angle_upper_linear_limit', 'Weight', 'Source_experiment_ID', 'Figure_of_merit',
                                                              'PDB_record_ID_1', 'PDB_model_num_1', 'PDB_strand_ID_1', 'PDB_ins_code_1', 'PDB_residue_no_1',
                                                              'PDB_residue_name_1', 'PDB_atom_name_1',
                                                              'PDB_record_ID_2', 'PDB_model_num_2', 'PDB_strand_ID_2', 'PDB_ins_code_2', 'PDB_residue_no_2',
                                                              'PDB_residue_name_2', 'PDB_atom_name_2',
                                                              'PDB_record_ID_3', 'PDB_model_num_3', 'PDB_strand_ID_3', 'PDB_ins_code_3', 'PDB_residue_no_3',
                                                              'PDB_residue_name_3', 'PDB_atom_name_3',
                                                              'PDB_record_ID_4', 'PDB_model_num_4', 'PDB_strand_ID_4', 'PDB_ins_code_4', 'PDB_residue_no_4',
                                                              'PDB_residue_name_4', 'PDB_atom_name_4',
                                                              'Auth_entity_assembly_ID_1', 'Auth_asym_ID_1', 'Auth_chain_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1',
                                                              'Auth_atom_ID_1', 'Auth_alt_ID_1', 'Auth_atom_name_1',
                                                              'Auth_entity_assembly_ID_2', 'Auth_asym_ID_2', 'Auth_chain_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2',
                                                              'Auth_atom_ID_2', 'Auth_alt_ID_2', 'Auth_atom_name_2',
                                                              'Auth_entity_assembly_ID_3', 'Auth_asym_ID_3', 'Auth_chain_ID_3', 'Auth_seq_ID_3', 'Auth_comp_ID_3',
                                                              'Auth_atom_ID_3', 'Auth_alt_ID_3', 'Auth_atom_name_3',
                                                              'Auth_entity_assembly_ID_4', 'Auth_asym_ID_4', 'Auth_chain_ID_4', 'Auth_seq_ID_4', 'Auth_comp_ID_4',
                                                              'Auth_atom_ID_4', 'Auth_alt_ID_4', 'Auth_atom_name_4',
                                                              'Sf_ID', 'Entry_ID', 'Torsion_angle_constraint_list_ID'],
                                          'rdc_restraint': ['Index_ID', 'ID', 'Combination_ID',
                                                            'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1', 'Seq_ID_1',
                                                            'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1', 'Resonance_ID_1',
                                                            'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2', 'Seq_ID_2',
                                                            'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2', 'Resonance_ID_2',
                                                            'Weight', 'RDC_val', 'RDC_val_err', 'Target_value', 'Target_value_uncertainty',
                                                            'RDC_lower_bound', 'RDC_upper_bound', 'RDC_lower_linear_limit', 'RDC_upper_linear_limit',
                                                            'RDC_val_scale_factor', 'RDC_bond_length', 'RDC_distant_dependent', 'Source_experiment_ID',
                                                            'PDB_record_ID_1', 'PDB_model_num_1', 'PDB_strand_ID_1', 'PDB_ins_code_1', 'PDB_residue_no_1',
                                                            'PDB_residue_name_1', 'PDB_atom_name_1',
                                                            'PDB_record_ID_2', 'PDB_model_num_2', 'PDB_strand_ID_2', 'PDB_ins_code_2', 'PDB_residue_no_2',
                                                            'PDB_residue_name_2', 'PDB_atom_name_2',
                                                            'Auth_entity_assembly_ID_1', 'Auth_asym_ID_1', 'Auth_chain_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1',
                                                            'Auth_atom_ID_1', 'Auth_alt_ID_1', 'Auth_atom_name_1',
                                                            'Auth_entity_assembly_ID_2', 'Auth_asym_ID_2', 'Auth_chain_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2',
                                                            'Auth_atom_ID_2', 'Auth_alt_ID_2', 'Auth_atom_name_2',
                                                            'Sf_ID', 'Entry_ID', 'RDC_constraint_list_ID'],
                                          'spectral_peak': ['Index_ID', 'ID', 'Volume', 'Volume_uncertainty', 'Height', 'Height_uncertainty', 'Figure_of_merit', 'Restraint',
                                                            'Position_1', 'Position_uncertainty_1', 'Line_width_1', 'Line_width_uncertainty_1',
                                                            'Position_2', 'Position_uncertainty_2', 'Line_width_2', 'Line_width_uncertainty_2',
                                                            'Position_3', 'Position_uncertainty_3', 'Line_width_3', 'Line_width_uncertainty_3',
                                                            'Position_4', 'Position_uncertainty_4', 'Line_width_4', 'Line_width_uncertainty_4',
                                                            'Position_5', 'Position_uncertainty_5', 'Line_width_5', 'Line_width_uncertainty_5',
                                                            'Position_6', 'Position_uncertainty_6', 'Line_width_6', 'Line_width_uncertainty_6',
                                                            'Position_7', 'Position_uncertainty_7', 'Line_width_7', 'Line_width_uncertainty_7',
                                                            'Position_8', 'Position_uncertainty_8', 'Line_width_8', 'Line_width_uncertainty_8',
                                                            'Position_9', 'Position_uncertainty_9', 'Line_width_9', 'Line_width_uncertainty_9',
                                                            'Position_10', 'Position_uncertainty_10', 'Line_width_10', 'Line_width_uncertainty_10',
                                                            'Position_11', 'Position_uncertainty_11', 'Line_width_11', 'Line_width_uncertainty_11',
                                                            'Position_12', 'Position_uncertainty_12', 'Line_width_12', 'Line_width_uncertainty_12',
                                                            'Position_13', 'Position_uncertainty_13', 'Line_width_13', 'Line_width_uncertainty_13',
                                                            'Position_14', 'Position_uncertainty_14', 'Line_width_14', 'Line_width_uncertainty_14',
                                                            'Position_15', 'Position_uncertainty_15', 'Line_width_15', 'Line_width_uncertainty_15',
                                                            'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1', 'Seq_ID_1', 'Comp_ID_1',
                                                            'Atom_ID_1', 'Ambiguity_code_1', 'Ambiguity_set_ID_1',
                                                            'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2', 'Seq_ID_2', 'Comp_ID_2',
                                                            'Atom_ID_2', 'Ambiguity_code_2', 'Ambiguity_set_ID_2',
                                                            'Entity_assembly_ID_3', 'Entity_ID_3', 'Comp_index_ID_3', 'Seq_ID_3', 'Comp_ID_3',
                                                            'Atom_ID_3', 'Ambiguity_code_3', 'Ambiguity_set_ID_3',
                                                            'Entity_assembly_ID_4', 'Entity_ID_4', 'Comp_index_ID_4', 'Seq_ID_4', 'Comp_ID_4',
                                                            'Atom_ID_4', 'Ambiguity_code_4', 'Ambiguity_set_ID_4',
                                                            'Entity_assembly_ID_5', 'Entity_ID_5', 'Comp_index_ID_5', 'Seq_ID_5', 'Comp_ID_5',
                                                            'Atom_ID_5', 'Ambiguity_code_5', 'Ambiguity_set_ID_5',
                                                            'Entity_assembly_ID_6', 'Entity_ID_6', 'Comp_index_ID_6', 'Seq_ID_6', 'Comp_ID_6',
                                                            'Atom_ID_6', 'Ambiguity_code_6', 'Ambiguity_set_ID_6',
                                                            'Entity_assembly_ID_7', 'Entity_ID_7', 'Comp_index_ID_7', 'Seq_ID_7', 'Comp_ID_7',
                                                            'Atom_ID_7', 'Ambiguity_code_7', 'Ambiguity_set_ID_7',
                                                            'Entity_assembly_ID_8', 'Entity_ID_8', 'Comp_index_ID_8', 'Seq_ID_8', 'Comp_ID_8',
                                                            'Atom_ID_8', 'Ambiguity_code_8', 'Ambiguity_set_ID_8',
                                                            'Entity_assembly_ID_9', 'Entity_ID_9', 'Comp_index_ID_9', 'Seq_ID_9', 'Comp_ID_9',
                                                            'Atom_ID_9', 'Ambiguity_code_9', 'Ambiguity_set_ID_9',
                                                            'Entity_assembly_ID_10', 'Entity_ID_10', 'Comp_index_ID_10', 'Seq_ID_10', 'Comp_ID_10',
                                                            'Atom_ID_10', 'Ambiguity_code_10', 'Ambiguity_set_ID_10',
                                                            'Entity_assembly_ID_11', 'Entity_ID_11', 'Comp_index_ID_11', 'Seq_ID_11', 'Comp_ID_11',
                                                            'Atom_ID_11', 'Ambiguity_code_11', 'Ambiguity_set_ID_11',
                                                            'Entity_assembly_ID_12', 'Entity_ID_12', 'Comp_index_ID_12', 'Seq_ID_12', 'Comp_ID_12',
                                                            'Atom_ID_12', 'Ambiguity_code_12', 'Ambiguity_set_ID_12',
                                                            'Entity_assembly_ID_13', 'Entity_ID_13', 'Comp_index_ID_13', 'Seq_ID_13', 'Comp_ID_13',
                                                            'Atom_ID_13', 'Ambiguity_code_13', 'Ambiguity_set_ID_13',
                                                            'Entity_assembly_ID_14', 'Entity_ID_14', 'Comp_index_ID_14', 'Seq_ID_14', 'Comp_ID_14',
                                                            'Atom_ID_14', 'Ambiguity_code_14', 'Ambiguity_set_ID_14',
                                                            'Entity_assembly_ID_15', 'Entity_ID_15', 'Comp_index_ID_15', 'Seq_ID_15', 'Comp_ID_15',
                                                            'Atom_ID_15', 'Ambiguity_code_15', 'Ambiguity_set_ID_15',
                                                            'Auth_entity_assembly_ID_1', 'Auth_entity_ID_1', 'Auth_asym_ID_1', 'Auth_seq_ID_1',
                                                            'Auth_comp_ID_1', 'Auth_atom_ID_1', 'Auth_ambiguity_code_1', 'Auth_ambiguity_set_ID_1',
                                                            'Auth_entity_assembly_ID_2', 'Auth_entity_ID_2', 'Auth_asym_ID_2', 'Auth_seq_ID_2',
                                                            'Auth_comp_ID_2', 'Auth_atom_ID_2', 'Auth_ambiguity_code_2', 'Auth_ambiguity_set_ID_2',
                                                            'Auth_entity_assembly_ID_3', 'Auth_entity_ID_3', 'Auth_asym_ID_3', 'Auth_seq_ID_3',
                                                            'Auth_comp_ID_3', 'Auth_atom_ID_3', 'Auth_ambiguity_code_3', 'Auth_ambiguity_set_ID_3',
                                                            'Auth_entity_assembly_ID_4', 'Auth_entity_ID_4', 'Auth_asym_ID_4', 'Auth_seq_ID_4',
                                                            'Auth_comp_ID_4', 'Auth_atom_ID_4', 'Auth_ambiguity_code_4', 'Auth_ambiguity_set_ID_4',
                                                            'Auth_entity_assembly_ID_5', 'Auth_entity_ID_5', 'Auth_asym_ID_5', 'Auth_seq_ID_5',
                                                            'Auth_comp_ID_5', 'Auth_atom_ID_5', 'Auth_ambiguity_code_5', 'Auth_ambiguity_set_ID_5',
                                                            'Auth_entity_assembly_ID_6', 'Auth_entity_ID_6', 'Auth_asym_ID_6', 'Auth_seq_ID_6',
                                                            'Auth_comp_ID_6', 'Auth_atom_ID_6', 'Auth_ambiguity_code_6', 'Auth_ambiguity_set_ID_6',
                                                            'Auth_entity_assembly_ID_7', 'Auth_entity_ID_7', 'Auth_asym_ID_7', 'Auth_seq_ID_7',
                                                            'Auth_comp_ID_7', 'Auth_atom_ID_7', 'Auth_ambiguity_code_7', 'Auth_ambiguity_set_ID_7',
                                                            'Auth_entity_assembly_ID_8', 'Auth_entity_ID_8', 'Auth_asym_ID_8', 'Auth_seq_ID_8',
                                                            'Auth_comp_ID_8', 'Auth_atom_ID_8', 'Auth_ambiguity_code_8', 'Auth_ambiguity_set_ID_8',
                                                            'Auth_entity_assembly_ID_9', 'Auth_entity_ID_9', 'Auth_asym_ID_9', 'Auth_seq_ID_9',
                                                            'Auth_comp_ID_9', 'Auth_atom_ID_9', 'Auth_ambiguity_code_9', 'Auth_ambiguity_set_ID_9',
                                                            'Auth_entity_assembly_ID_10', 'Auth_entity_ID_10', 'Auth_asym_ID_10', 'Auth_seq_ID_10',
                                                            'Auth_comp_ID_10', 'Auth_atom_ID_10', 'Auth_ambiguity_code_10', 'Auth_ambiguity_set_ID_10',
                                                            'Auth_entity_assembly_ID_11', 'Auth_entity_ID_11', 'Auth_asym_ID_11', 'Auth_seq_ID_11',
                                                            'Auth_comp_ID_11', 'Auth_atom_ID_11', 'Auth_ambiguity_code_11', 'Auth_ambiguity_set_ID_11',
                                                            'Auth_entity_assembly_ID_12', 'Auth_entity_ID_12', 'Auth_asym_ID_12', 'Auth_seq_ID_12',
                                                            'Auth_comp_ID_12', 'Auth_atom_ID_12', 'Auth_ambiguity_code_12', 'Auth_ambiguity_set_ID_12',
                                                            'Auth_entity_assembly_ID_13', 'Auth_entity_ID_13', 'Auth_asym_ID_13', 'Auth_seq_ID_13',
                                                            'Auth_comp_ID_13', 'Auth_atom_ID_13', 'Auth_ambiguity_code_13', 'Auth_ambiguity_set_ID_13',
                                                            'Auth_entity_assembly_ID_14', 'Auth_entity_ID_14', 'Auth_asym_ID_14', 'Auth_seq_ID_14',
                                                            'Auth_comp_ID_14', 'Auth_atom_ID_14', 'Auth_ambiguity_code_14', 'Auth_ambiguity_set_ID_14',
                                                            'Auth_entity_assembly_ID_15', 'Auth_entity_ID_15', 'Auth_asym_ID_15', 'Auth_seq_ID_15',
                                                            'Auth_comp_ID_15', 'Auth_atom_ID_15', 'Auth_ambiguity_code_15', 'Auth_ambiguity_set_ID_15',
                                                            'Details', 'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                          'spectral_peak_alt': ['Index_ID', 'ID', 'Figure_of_merit', 'Restraint', 'Details', 'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                          'noepk_restraint': ['ID', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1', 'Seq_ID_1',
                                                              'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1',
                                                              'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2', 'Seq_ID_2',
                                                              'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2',
                                                              'Val', 'Val_min', 'Val_max', 'Val_err', 'Resonance_ID_1', 'Resonance_ID_2',
                                                              'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                              'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                              'Sf_ID', 'Entry_ID', 'Homonucl_NOE_list_ID'],
                                          'jcoup_restraint': ['ID', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1',
                                                              'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Resonance_ID_1',
                                                              'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2',
                                                              'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Resonance_ID_2',
                                                              'Assembly_atom_ID_3', 'Entity_assembly_ID_3', 'Entity_ID_3', 'Comp_index_ID_3',
                                                              'Seq_ID_3', 'Comp_ID_3', 'Atom_ID_3', 'Atom_type_3', 'Resonance_ID_3',
                                                              'Assembly_atom_ID_4', 'Entity_assembly_ID_4', 'Entity_ID_4', 'Comp_index_ID_4',
                                                              'Seq_ID_4', 'Comp_ID_4', 'Atom_ID_4', 'Atom_type_4', 'Resonance_ID_4',
                                                              'Coupling_constant_val', 'Coupling_constant_lower_bound', 'Coupling_constant_upper_bound',
                                                              'Coupling_constant_err', 'Source_experiment_ID',
                                                              'Auth_asym_ID_1', 'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                              'Auth_asym_ID_2', 'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                              'Auth_asym_ID_3', 'Auth_entity_assembly_ID_3', 'Auth_seq_ID_3', 'Auth_comp_ID_3', 'Auth_atom_ID_3',
                                                              'Auth_asym_ID_4', 'Auth_entity_assembly_ID_4', 'Auth_seq_ID_4', 'Auth_comp_ID_4', 'Auth_atom_ID_4',
                                                              'Sf_ID', 'Entry_ID', 'J_three_bond_constraint_list_ID'],
                                          'rdc_raw_data': ['ID', 'RDC_code', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1',
                                                           'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1', 'Ambiguity_code_1',
                                                           'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2',
                                                           'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2', 'Ambiguity_code_2',
                                                           'Val', 'Val_min', 'Val_max', 'Val_err', 'Val_bond_length',
                                                           'Resonance_ID_1', 'Resonance_ID_2',
                                                           'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                           'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                           'Sf_ID', 'Entry_ID', 'RDC_list_ID'],
                                          'csa_restraint': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                            'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                            'Val', 'Val_err', 'Principal_value_sigma_11_val', 'Principal_value_sigma_22_val', 'Principal_value_sigma_33_val',
                                                            'Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_beta_val', 'Principal_Euler_angle_gamma_val',
                                                            'Bond_length', 'Resonance_ID', 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                            'Sf_ID', 'Entry_ID', 'Chem_shift_anisotropy_ID'],
                                          'ddc_restraint': ['ID', 'Dipolar_coupling_code', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1',
                                                            'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1', 'Ambiguity_code_1',
                                                            'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2',
                                                            'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2', 'Ambiguity_code_2',
                                                            'Val', 'Val_min', 'Val_max', 'Val_err',
                                                            'Principal_Euler_angle_alpha_val', 'Principal_Euler_angle_beta_val', 'Principal_Euler_angle_gamma_val',
                                                            'Resonance_ID_1', 'Resonance_ID_2',
                                                            'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                            'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                            'Sf_ID', 'Entry_ID', 'Dipolar_coupling_list_ID'],
                                          'hvycs_restraint': ['ID', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1',
                                                              'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Resonance_ID_1',
                                                              'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2',
                                                              'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Resonance_ID_2',
                                                              'Assembly_atom_ID_3', 'Entity_assembly_ID_3', 'Entity_ID_3', 'Comp_index_ID_3',
                                                              'Seq_ID_3', 'Comp_ID_3', 'Atom_ID_3', 'Atom_type_3', 'Resonance_ID_3',
                                                              'Assembly_atom_ID_4', 'Entity_assembly_ID_4', 'Entity_ID_4', 'Comp_index_ID_4',
                                                              'Seq_ID_4', 'Comp_ID_4', 'Atom_ID_4', 'Atom_type_4', 'Resonance_ID_4',
                                                              'Assembly_atom_ID_5', 'Entity_assembly_ID_5', 'Entity_ID_5', 'Comp_index_ID_5',
                                                              'Seq_ID_5', 'Comp_ID_5', 'Atom_ID_5', 'Atom_type_5', 'Resonance_ID_5',
                                                              'CA_chem_shift_val', 'CA_chem_shift_val_err', 'CB_chem_shift_val', 'CB_chem_shift_val_err', 'Source_experiment_ID',
                                                              'Auth_asym_ID_1', 'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                              'Auth_asym_ID_2', 'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                              'Auth_asym_ID_3', 'Auth_entity_assembly_ID_3', 'Auth_seq_ID_3', 'Auth_comp_ID_3', 'Auth_atom_ID_3',
                                                              'Auth_asym_ID_4', 'Auth_entity_assembly_ID_4', 'Auth_seq_ID_4', 'Auth_comp_ID_4', 'Auth_atom_ID_4',
                                                              'Auth_asym_ID_5', 'Auth_entity_assembly_ID_5', 'Auth_seq_ID_5', 'Auth_comp_ID_5', 'Auth_atom_ID_5',
                                                              'Sf_ID', 'Entry_ID', 'CA_CB_constraint_list_ID'],
                                          'procs_restraint': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                              'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                              'Resonance_ID', 'Chem_shift_val', 'Chem_shift_val_err', 'Source_experiment_ID',
                                                              'Auth_asym_ID', 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                              'Sf_ID', 'Entry_ID', 'H_chem_shift_constraint_list_ID'],
                                          'csp_restraint': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                            'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                            'Chem_shift_val', 'Chem_shift_val_err', 'Difference_chem_shift_val', 'Difference_chem_shift_val_err',
                                                            'Resonance_ID', 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                            'Sf_ID', 'Entry_ID', 'Chem_shift_perturbation_list_ID'],
                                          'auto_relax_restraint': ['ID', 'Assembly_ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                                   'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                                   'Auto_relaxation_val', 'Auto_relaxation_val_err', 'Rex_val', 'Rex_val_err',
                                                                   'Resonance_ID', 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                                   'Sf_ID', 'Entry_ID', 'Auto_relaxation_list_ID'],
                                          'heteronucl_noe_data': ['ID', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1', 'Comp_index_ID_1',
                                                                  'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Atom_isotope_number_1',
                                                                  'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2', 'Comp_index_ID_2',
                                                                  'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Atom_isotope_number_2',
                                                                  'Val', 'Val_err', 'Resonance_ID_1', 'Resonance_ID_2',
                                                                  'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                                  'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                                  'Sf_ID', 'Entry_ID', 'Heteronucl_NOE_list_ID'],
                                          'heteronucl_t1_data': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                                 'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                                 'Val', 'Val_err', 'Resonance_ID',
                                                                 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                                 'Sf_ID', 'Entry_ID', 'Heteronucl_T1_list_ID'],
                                          'heteronucl_t2_data': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                                 'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                                 'T2_val', 'T2_val_err', 'Rex_val', 'Rex_err', 'Resonance_ID',
                                                                 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                                 'Sf_ID', 'Entry_ID', 'Heteronucl_T2_list_ID'],
                                          'heteronucl_t1r_data': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                                  'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                                  'T1rho_val', 'T1rho_val_err', 'Rex_val', 'Rex_val_err', 'Resonance_ID',
                                                                  'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                                  'Sf_ID', 'Entry_ID', 'Heteronucl_T1rho_list_ID'],
                                          'order_param_data': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                               'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                               'Order_param_val', 'Order_param_val_fit_err', 'Tau_e_val', 'Tau_e_val_fit_err',
                                                               'Tau_f_val', 'Tau_f_val_fit_err', 'Tau_s_val', 'Tau_s_val_fit_err',
                                                               'Rex_val', 'Rex_val_fit_err', 'Model_free_sum_squared_errs', 'Model_fit',
                                                               'Sf2_val', 'Sf2_val_fit_err', 'Ss2_val', 'Ss2_val_fit_err',
                                                               'SH2_val', 'SH2_val_fit_err', 'SN2_val', 'SN2_val_fit_err',
                                                               'Resonance_ID', 'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                               'Sf_ID', 'Entry_ID', 'Order_parameter_list_ID'],
                                          'ccr_d_csa_restraint': ['ID', 'Dipole_assembly_atom_ID_1', 'Dipole_entity_assembly_ID_1', 'Dipole_entity_ID_1', 'Dipole_comp_index_ID_1',
                                                                  'Dipole_seq_ID_1', 'Dipole_comp_ID_1', 'Dipole_atom_ID_1', 'Dipole_atom_type_1', 'Dipole_atom_isotope_number_1',
                                                                  'Dipole_assembly_atom_ID_2', 'Dipole_entity_assembly_ID_2', 'Dipole_entity_ID_2', 'Dipole_comp_index_ID_2',
                                                                  'Dipole_seq_ID_2', 'Dipole_comp_ID_2', 'Dipole_atom_ID_2', 'Dipole_atom_type_2', 'Dipole_atom_isotope_number_2',
                                                                  'CSA_assembly_atom_ID_1', 'CSA_entity_assembly_ID_1', 'CSA_entity_ID_1', 'CSA_comp_index_ID_1',
                                                                  'CSA_seq_ID_1', 'CSA_comp_ID_1', 'CSA_atom_ID_1', 'CSA_atom_type_1', 'CSA_atom_isotope_number_1',
                                                                  'CSA_assembly_atom_ID_2', 'CSA_entity_assembly_ID_2', 'CSA_entity_ID_2', 'CSA_comp_index_ID_2',
                                                                  'CSA_seq_ID_2', 'CSA_comp_ID_2', 'CSA_atom_ID_2', 'CSA_atom_type_2', 'CSA_atom_isotope_number_2',
                                                                  'Val', 'Val_err', 'Dipole_resonance_ID_1', 'Dipole_resonance_ID_2', 'CSA_resonance_ID_1', 'CSA_resonance_ID_2',
                                                                  'Dipole_auth_entity_assembly_ID_1', 'Dipole_auth_seq_ID_1', 'Dipole_auth_comp_ID_1', 'Dipole_auth_atom_ID_1',
                                                                  'Dipole_auth_entity_assembly_ID_2', 'Dipole_auth_seq_ID_2', 'Dipole_auth_comp_ID_2', 'Dipole_auth_atom_ID_2',
                                                                  'CSA_auth_entity_assembly_ID_1', 'CSA_auth_seq_ID_1', 'CSA_auth_comp_ID_1', 'CSA_auth_atom_ID_1',
                                                                  'CSA_auth_entity_assembly_ID_2', 'CSA_auth_seq_ID_2', 'CSA_auth_comp_ID_2', 'CSA_auth_atom_ID_2',
                                                                  'Sf_ID', 'Entry_ID', 'Cross_correlation_D_CSA_list_ID'],
                                          'ccr_dd_restraint': ['ID', 'Dipole_1_assembly_atom_ID_1', 'Dipole_1_entity_assembly_ID_1',
                                                               'Dipole_1_entity_ID_1', 'Dipole_1_comp_index_ID_1',
                                                               'Dipole_1_seq_ID_1', 'Dipole_1_comp_ID_1', 'Dipole_1_atom_ID_1',
                                                               'Dipole_1_atom_type_1', 'Dipole_1_atom_isotope_number_1',
                                                               'Dipole_1_assembly_atom_ID_2', 'Dipole_1_entity_assembly_ID_2',
                                                               'Dipole_1_entity_ID_2', 'Dipole_1_comp_index_ID_2',
                                                               'Dipole_1_seq_ID_2', 'Dipole_1_comp_ID_2', 'Dipole_1_atom_ID_2',
                                                               'Dipole_1_atom_type_2', 'Dipole_1_atom_isotope_number_2',
                                                               'Dipole_2_assembly_atom_ID_1', 'Dipole_2_entity_assembly_ID_1',
                                                               'Dipole_2_entity_ID_1', 'Dipole_2_comp_index_ID_1',
                                                               'Dipole_2_seq_ID_1', 'Dipole_2_comp_ID_1', 'Dipole_2_atom_ID_1',
                                                               'Dipole_2_atom_type_1', 'Dipole_2_atom_isotope_number_1',
                                                               'Dipole_2_assembly_atom_ID_2', 'Dipole_2_entity_assembly_ID_2',
                                                               'Dipole_2_entity_ID_2', 'Dipole_2_chem_comp_index_ID_2',
                                                               'Dipole_2_comp_index_ID_2',  # 'Dipole_2_comp_index_ID_2' is inferred from NMR-STAR Dictionary
                                                               'Dipole_2_seq_ID_2', 'Dipole_2_comp_ID_2', 'Dipole_2_atom_ID_2',
                                                               'Dipole_2_atom_type_2', 'Dipole_2_atom_isotope_number_2',
                                                               'Val', 'Val_err',
                                                               'Dipole_1_Resonance_ID_1', 'Dipole_1_Resonance_ID_2', 'Dipole_2_Resonance_ID_1', 'Dipole_2_Resonance_ID_2',
                                                               'Dipole_1_auth_entity_assembly_ID_1', 'Dipole_1_auth_seq_ID_1', 'Dipole_1_auth_comp_ID_1', 'Dipole_1_auth_atom_ID_1',
                                                               'Dipole_1_auth_entity_assembly_ID_2', 'Dipole_1_auth_seq_ID_2', 'Dipole_1_auth_comp_ID_2', 'Dipole_1_auth_atom_ID_2',
                                                               'Dipole_2_auth_entity_assembly_ID_1', 'Dipole_2_auth_seq_ID_1', 'Dipole_2_auth_comp_ID_1', 'Dipole_2_auth_atom_ID_1',
                                                               'Dipole_2_auth_entity_assembly_ID_2', 'Dipole_2_auth_seq_ID_2', 'Dipole_2_auth_comp_ID_2', 'Dipole_2_auth_atom_ID_2',
                                                               'Sf_ID', 'Entry_ID', 'Cross_correlation_DD_list_ID'],
                                          'fchiral_restraint': ['ID', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1', 'Entity_ID_1',
                                                                'Comp_index_ID_1', 'Seq_ID_1', 'Comp_ID_1', 'Atom_ID_1', 'Atom_type_1', 'Resonance_ID_1',
                                                                'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_ID_2',
                                                                'Comp_index_ID_2', 'Seq_ID_2', 'Comp_ID_2', 'Atom_ID_2', 'Atom_type_2', 'Resonance_ID_2',
                                                                'Stereospecific_assignment_code',
                                                                'Auth_asym_ID_1', 'Auth_entity_assembly_ID_1', 'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                                'Auth_asym_ID_2', 'Auth_entity_assembly_ID_2', 'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2',
                                                                'Sf_ID', 'Entry_ID', 'Floating_chirality_assign_ID'],
                                          'saxs_restraint': ['ID', 'Q_value', 'Intensity_val', 'Intensity_val_err', 'Weight_val',
                                                             'Sf_ID', 'Entry_ID', 'SAXS_constraint_list_ID'],
                                          'other_restraint': ['ID', 'Assembly_atom_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID',
                                                              'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                                                              'Val', 'Val_err', 'Resonance_ID',
                                                              'Auth_entity_assembly_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                                                              'Sf_ID', 'Entry_ID', 'Other_data_type_list_ID']
                                          }
                             }

        # disallowed loop tags of spectral peak
        self.spectral_peak_disallowed_tags = {'nef': ['position_%s', 'position_uncertainty_%s', 'chain_code_%s', 'sequence_code_%s', 'residue_name_%s', 'atom_name_%s'],
                                              'nmr-star': ['Position_%s', 'Position_uncertainty_%s', 'Line_width_%s',
                                                           'Line_width_uncertainty_%s', 'Entity_assembly_ID_%s', 'Entity_ID_%s',
                                                           'Comp_index_ID_%s', 'Seq_ID_%s', 'Comp_ID_%s', 'Atom_ID_%s',
                                                           'Ambiguity_code_%s', 'Ambiguity_set_ID_%s', 'Auth_entity_assembly_ID_%s',
                                                           'Auth_entity_ID_%s', 'Auth_asym_ID_%s', 'Auth_seq_ID_%s', 'Auth_comp_ID_%s',
                                                           'Auth_atom_ID_%s', 'Auth_ambiguity_code_%s', 'Auth_ambiguity_set_ID_%s']
                                              }

        # error template for missing mandatory loop tag
        self.__err_template_for_missing_mandatory_lp_tag = "The mandatory loop tag %r is missing. Please verify the value and re-upload the %s file."

        # saveframe tag prefixes (saveframe holder categories)
        self.sf_tag_prefixes = {'nef': {'entry_info': '_nef_nmr_meta_data',
                                        'poly_seq': '_nef_molecular_system',
                                        'entity': None,
                                        'chem_shift': '_nef_chemical_shift_list',
                                        'chem_shift_ref': None,
                                        'dist_restraint': '_nef_distance_restraint_list',
                                        'dihed_restraint': '_nef_dihedral_restraint_list',
                                        'rdc_restraint': '_nef_rdc_restraint_list',
                                        'spectral_peak': '_nef_nmr_spectrum',
                                        'spectral_peak_alt': None,
                                        'noepk_restraint': None,
                                        'jcoup_restraint': None,
                                        'rdc_raw_data': None,
                                        'csa_restraint': None,
                                        'ddc_restraint': None,
                                        'hvycs_restraint': None,
                                        'procs_restraint': None,
                                        'csp_restraint': None,
                                        'auto_relax_restraint': None,
                                        'heteronucl_noe_data': None,
                                        'heteronucl_t1_data': None,
                                        'heteronucl_t2_data': None,
                                        'heteronucl_t1r_data': None,
                                        'order_param_data': None,
                                        'ccr_d_csa_restraint': None,
                                        'ccr_dd_restraint': None,
                                        'fchiral_restraint': None,
                                        'saxs_restraint': None,
                                        'other_restraint': None
                                        },
                                'nmr-star': {'entry_info': '_Entry',
                                             'poly_seq': '_Assembly',
                                             'entity': '_Entity',
                                             'chem_shift': '_Assigned_chem_shift_list',
                                             'chem_shift_ref': '_Chem_shift_reference',
                                             'dist_restraint': '_Gen_dist_constraint_list',
                                             'dihed_restraint': '_Torsion_angle_constraint_list',
                                             'rdc_restraint': '_RDC_constraint_list',
                                             'spectral_peak': '_Spectral_peak_list',
                                             'spectral_peak_alt': '_Spectral_peak_list',
                                             'noepk_restraint': '_Homonucl_NOE_list',
                                             'jcoup_restraint': '_J_three_bond_constraint_list',
                                             'rdc_raw_data': '_RDC_list',
                                             'csa_restraint': '_Chem_shift_anisotropy',
                                             'ddc_restraint': '_Dipolar_coupling_list',
                                             'hvycs_restraint': '_CA_CB_constraint_list',
                                             'procs_restraint': '_H_chem_shift_constraint_list',
                                             'csp_restraint': '_Chem_shift_perturbation_list',
                                             'auto_relax_restraint': '_Auto_relaxation_list',
                                             'heteronucl_noe_data': '_Heteronucl_NOE_list',
                                             'heteronucl_t1_data': '_Heteronucl_T1_list',
                                             'heteronucl_t2_data': '_Heteronucl_T2_list',
                                             'heteronucl_t1r_data': '_Heteronucl_T1rho_list',
                                             'order_param_data': '_Order_parameter_list',
                                             'ccr_d_csa_restraint': '_Cross_correlation_D_CSA_list',
                                             'ccr_dd_restraint': '_Cross_correlation_DD_list',
                                             'fchiral_restraint': '_Floating_chirality_assign',
                                             'saxs_restraint': '_SAXS_constraint_list',
                                             'other_restraint': '_Other_data_type_list'
                                             }
                                }

        def sf_key(content_subtype):
            return self.__c2S.category_order.index(self.sf_tag_prefixes['nmr-star'][content_subtype])

        self.mr_content_subtypes.sort(key=sf_key)
        self.nmr_rep_content_subtypes.sort(key=sf_key)

        altPotentialType = {'?': 'undefined'}

        # saveframe tag items
        self.sf_tag_items = {'nef': {'entry_info': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                    {'name': 'sf_framecode', 'type': 'str', 'mandatory': True},
                                                    {'name': 'format_name', 'type': 'str', 'mandatory': True},
                                                    {'name': 'format_version', 'type': 'str', 'mandatory': True},
                                                    {'name': 'program_name', 'type': 'str', 'mandatory': True},
                                                    {'name': 'program_version', 'type': 'str', 'mandatory': True},
                                                    {'name': 'creation_date', 'type': 'str', 'mandatory': True},
                                                    {'name': 'uuid', 'type': 'str', 'mandatory': True},
                                                    {'name': 'coordinate_file_name', 'type': 'str', 'mandatory': False}
                                                    ],
                                     'entity': None,
                                     'poly_seq': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                  {'name': 'sf_framecode', 'type': 'str', 'mandatory': True}
                                                  ],
                                     'chem_shift': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                    {'name': 'sf_framecode', 'type': 'str', 'mandatory': True}
                                                    ],
                                     'chem_shift_ref': None,
                                     'dist_restraint': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                        {'name': 'sf_framecode', 'type': 'str', 'mandatory': True},
                                                        {'name': 'potential_type', 'type': 'enum', 'mandatory': False,
                                                         'enum': ('log-harmonic', 'parabolic', 'square-well-parabolic',
                                                                  'square-well-parabolic-linear', 'upper-bound-parabolic',
                                                                  'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                  'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                         'enum-alt': altPotentialType},
                                                        {'name': 'restraint_origin', 'type': 'enum', 'mandatory': False,
                                                         'enum': ('noe', 'noe_build_up', 'noe_not_seen', 'roe',
                                                                  'roe_build_up', 'hbond', 'disulfide_bond', 'pre',
                                                                  'symmetry', 'mutation', 'shift_perturbation',
                                                                  'undefined', 'unknown'),
                                                         'enum-alt': altDistanceConstraintType['nef']}
                                                        ],
                                     'dihed_restraint': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                         {'name': 'sf_framecode', 'type': 'str', 'mandatory': True},
                                                         {'name': 'potential_type', 'type': 'enum', 'mandatory': False,
                                                          'enum': ('parabolic', 'square-well-parabolic', 'square-well-parabolic-linear',
                                                                   'upper-bound-parabolic', 'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                   'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                          'enum-alt': altPotentialType},
                                                         {'name': 'restraint_origin', 'type': 'enum', 'mandatory': False,
                                                          'enum': ('jcoupling', 'chemical_shift', 'undefined', 'unknown'),
                                                          'enum-alt': altDihedralAngleConstraintType['nef']}
                                                         ],
                                     'rdc_restraint': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                       {'name': 'sf_framecode', 'type': 'str', 'mandatory': True},
                                                       {'name': 'potential_type', 'type': 'enum', 'mandatory': False,
                                                        'enum': ('parabolic', 'square-well-parabolic', 'square-well-parabolic-linear',
                                                                 'upper-bound-parabolic', 'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                 'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                        'enum-alt': altPotentialType},
                                                       {'name': 'restraint_origin', 'type': 'enum', 'mandatory': False,
                                                        'enum': ('measured', 'undefined', 'unknown'),
                                                        'enum-alt': altRdcConstraintType['nef']},
                                                       {'name': 'tensor_magnitude', 'type': 'float', 'mandatory': False},
                                                       {'name': 'tensor_rhombicity', 'type': 'positive-float', 'mandatory': False},
                                                       {'name': 'tensor_chain_code', 'type': 'str', 'mandatory': False},
                                                       {'name': 'tensor_sequence_code', 'type': 'str', 'mandatory': False},
                                                       {'name': 'tensor_residue_name', 'type': 'str', 'mandatory': False}
                                                       ],
                                     'spectral_peak': [{'name': 'sf_category', 'type': 'str', 'mandatory': True},
                                                       {'name': 'sf_framecode', 'type': 'str', 'mandatory': True},
                                                       {'name': 'num_dimensions', 'type': 'enum-int', 'mandatory': True,
                                                        'enum': set(range(1, MAX_DIM_NUM_OF_SPECTRA)),
                                                        'enforce-enum': True},
                                                       {'name': 'chemical_shift_list', 'type': 'str', 'mandatory': False},
                                                       {'name': 'experiment_classification', 'type': 'str', 'mandatory': False},
                                                       {'name': 'experiment_type', 'type': 'str', 'mandatory': False}
                                                       ],
                                     'spectral_peak_alt': None,
                                     'noepk_restraint': None,
                                     'jcoup_restraint': None,
                                     'rdc_raw_data': None,
                                     'csa_restraint': None,
                                     'ddc_restraint': None,
                                     'hvycs_restraint': None,
                                     'procs_restraint': None,
                                     'csp_restraint': None,
                                     'auto_relax_restraint': None,
                                     'heteronucl_noe_data': None,
                                     'heteronucl_t1_data': None,
                                     'heteronucl_t2_data': None,
                                     'heteronucl_t1r_data': None,
                                     'order_param_data': None,
                                     'ccr_d_csa_restraint': None,
                                     'ccr_dd_restraint': None,
                                     'fchiral_restraint': None,
                                     'saxs_restraint': None,
                                     'other_restraint': None
                                     },
                             'nmr-star': {'entry_info': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                         {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                         {'name': 'NMR_STAR_version', 'type': 'str', 'mandatory': True},
                                                         {'name': 'Source_data_format', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Source_data_format_version', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Generated_software_name', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Generated_software_version', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Generated_date', 'type': 'str', 'mandatory': False},
                                                         {'name': 'UUID', 'type': 'str', 'mandatory': False},
                                                         {'name': 'Related_coordinate_file_name', 'type': 'str', 'mandatory': False}
                                                         ],
                                          'entity': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                     {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                     {'name': 'ID', 'type': 'positive-int', 'mandatory': True},
                                                     {'name': 'Name', 'type': 'str', 'mandatory': True},
                                                     {'name': 'Polymer_common_type', 'type': 'enum', 'mandatory': False,
                                                      'enum': ('protein', 'DNA', 'RNA', 'DNA/RNA hybrid', 'polysaccharide')},
                                                     {'name': 'Polymer_type', 'type': 'enum', 'mandatory': False,
                                                      'enum': ('cyclic-pseudo-peptide', 'polypeptide(L)', 'polydeoxyribonucleotide', 'polyribonucleotide',
                                                               'polydeoxyribonucleotide/polyribonucleotide hybrid',
                                                               'polypeptide(D)', 'polysaccharide(D)', 'polysaccharide(L)', 'other')}
                                                     ],
                                          'poly_seq': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                       {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True}
                                                       ],
                                          'chem_shift': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                         {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True}
                                                         ],
                                          'chem_shift_ref': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                             {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                             {'name': 'ID', 'type': 'positive-int', 'mandatory': True},
                                                             {'name': 'Details', 'type': 'str', 'mandatory': False},
                                                             {'name': 'Proton_shifts_flag', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('no', 'yes', 'yes with IUPAC referencing')},
                                                             {'name': 'Carbon_shifts_flag', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('no', 'yes', 'yes with IUPAC referencing')},
                                                             {'name': 'Nitrogen_shifts_flag', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('no', 'yes', 'yes with IUPAC referencing')},
                                                             {'name': 'Phosphorus_shifts_flag', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('no', 'yes', 'yes with IUPAC referencing')},
                                                             {'name': 'Other_shifts_flag', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('no', 'yes', 'yes with IUPAC referencing')},
                                                             ],
                                          'dist_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                             {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                             {'name': 'Constraint_type', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('NOE', 'NOE build-up', 'NOE not seen', 'ROE', 'ROE build-up',
                                                                       'hydrogen bond', 'disulfide bond', 'paramagnetic relaxation',
                                                                       'symmetry', 'general distance', 'mutation', 'chemical shift perturbation',
                                                                       'undefined', 'unknown'),
                                                              'enum-alt': altDistanceConstraintType['nmr-star']},
                                                             {'name': 'Potential_type', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('log-harmonic', 'parabolic', 'square-well-parabolic',
                                                                       'square-well-parabolic-linear', 'upper-bound-parabolic',
                                                                       'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                       'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                              'enum-alt': altPotentialType}
                                                             ],
                                          'dihed_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Constraint_type', 'type': 'enum', 'mandatory': False,
                                                               'enum': ('J-couplings', 'backbone chemical shifts', 'undefined', 'unknown'),
                                                               'enum-alt': altDihedralAngleConstraintType['nmr-star']},
                                                              {'name': 'Potential_type', 'type': 'enum', 'mandatory': False,
                                                               'enum': ('parabolic', 'square-well-parabolic', 'square-well-parabolic-linear',
                                                                        'upper-bound-parabolic', 'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                        'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                               'enum-alt': altPotentialType}
                                                              ],
                                          'rdc_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Constraint_type', 'type': 'enum', 'mandatory': False,
                                                             'enum': ('RDC', 'undefined', 'unknown'),
                                                             'enum-alt': altRdcConstraintType['nmr-star']},
                                                            {'name': 'Potential_type', 'type': 'enum', 'mandatory': False,
                                                             'enum': ('parabolic', 'square-well-parabolic', 'square-well-parabolic-linear',
                                                                      'upper-bound-parabolic', 'lower-bound-parabolic', 'upper-bound-parabolic-linear',
                                                                      'lower-bound-parabolic-linear', 'undefined', 'unknown'),
                                                             'enum-alt': altPotentialType},
                                                            {'name': 'Tensor_magnitude', 'type': 'float', 'mandatory': False},
                                                            {'name': 'Tensor_rhombicity', 'type': 'positive-float', 'mandatory': False},
                                                            {'name': 'Tensor_auth_asym_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Tensor_auth_seq_ID', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Tensor_auth_comp_ID', 'type': 'str', 'mandatory': False}
                                                            ],
                                          'spectral_peak': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Experiment_class', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Experiment_type', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Number_of_spectral_dimensions', 'type': 'enum-int', 'mandatory': True,
                                                             'enum': set(range(1, MAX_DIM_NUM_OF_SPECTRA)),
                                                             'enforce-enum': True},
                                                            {'name': 'Chemical_shift_list', 'type': 'str', 'mandatory': True}
                                                            ],
                                          'spectral_peak_alt': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                {'name': 'Experiment_class', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Experiment_type', 'type': 'str', 'mandatory': False},
                                                                {'name': 'Number_of_spectral_dimensions', 'type': 'enum-int', 'mandatory': True,
                                                                 'enum': set(range(1, MAX_DIM_NUM_OF_SPECTRA)),
                                                                 'enforce-enum': True}
                                                                ],
                                          'noepk_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Homonuclear_NOE_val_type', 'type': 'enum', 'mandatory': True,
                                                               'enum': ('peak volume', 'peak height', 'contour count', 'na')}
                                                              ],
                                          'jcoup_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True}
                                                              ],
                                          'rdc_raw_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                           {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                           {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                            'enforce-non-zero': True}
                                                           ],
                                          'csa_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                            'enforce-non-zero': True},
                                                            {'name': 'Val_units', 'type': 'enum', 'mandatory': False,
                                                             'enum': ('ppm', 'ppb')}
                                                            ],
                                          'ddc_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                            'enforce-non-zero': True},
                                                            {'name': 'Scaling_factor', 'type': 'positive-float', 'mandatory': False},
                                                            {'name': 'Fitting_procedure', 'type': 'str', 'mandatory': False}
                                                            ],
                                          'hvycs_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Units', 'type': 'str', 'mandatory': False}
                                                              ],
                                          'procs_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Units', 'type': 'str', 'mandatory': False}
                                                              ],
                                          'csp_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                            {'name': 'Type', 'type': 'enum', 'mandatory': False,
                                                             'enum': ('macromolecular binding', 'ligand binding', 'ligand fragment binding', 'paramagnetic ligand binding')},
                                                            {'name': 'Details', 'type': 'str', 'mandatory': False}
                                                            ],
                                          'auto_relax_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                   {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                   {'name': 'Temp_calibration_method', 'type': 'enum', 'mandatory': False,
                                                                    'enum': ('methanol', 'monoethylene glycol', 'no calibration applied')},
                                                                   {'name': 'Temp_control_method', 'type': 'enum', 'mandatory': False,
                                                                    'enum': ('single scan interleaving', 'temperature compensation block',
                                                                             'single scan interleaving and temperature compensation block',
                                                                             'no temperature control applied')},
                                                                   {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                    'enforce-non-zero': True},
                                                                   {'name': 'Exact_field_strength', 'type': 'positive-float', 'mandatory': False,
                                                                    'enforce-non-zero': True},
                                                                   {'name': 'Common_relaxation_type_name', 'type': 'enum', 'mandatory': False,
                                                                    'enum': ('R1', 'R2', 'R1rho', 'ZQ relaxation', 'longitudinal spin order',
                                                                             'single quantum antiphase', 'DQ relaxation')},
                                                                   {'name': 'Relaxation_coherence_type', 'type': 'enum', 'mandatory': True,
                                                                    'enum': ('Iz', 'Sz', '(I+)+(I-)', '(S+)+(S-)', 'I+', 'I-', 'S+', 'S-',
                                                                             '(I+S-)+(I-S+)', 'I-S+', 'I+S-', 'IzSz', '((I+)+(I-))Sz', 'Iz((S+)+(S-))',
                                                                             'I+Sz', 'I-Sz', 'IzS+', 'IzS-', '(I+S+)+(I-S-)', 'I+S+', 'I-S-')},
                                                                   {'name': 'Relaxation_val_units', 'type': 'enum', 'mandatory': True,
                                                                    'enum': ('s-1', 'ms-1', 'us-1', 'ns-1', 'ps-1')},
                                                                   {'name': 'Rex_units', 'type': 'enum', 'mandatory': False,
                                                                    'enum': ('s-1', 'ms-1', 'us-1')},
                                                                   {'name': 'Rex_field_strength', 'type': 'positive-float', 'mandatory': False,
                                                                    'enforce-non-zero': True}
                                                                   ],
                                          'heteronucl_noe_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Heteronuclear_NOE_val_type', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('peak height', 'peak integral', 'contour count', 'relative intensities', 'na')}
                                                                  ],
                                          'heteronucl_t1_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                 {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                 {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                  'enforce-non-zero': True},
                                                                 {'name': 'T1_coherence_type', 'type': 'enum', 'mandatory': True,
                                                                  'enum': ('Iz', 'Sz', 'na')},
                                                                 {'name': 'T1_val_units', 'type': 'enum', 'mandatory': True,
                                                                  'enum': ('s', 's-1', 'ms', 'ms-1')},
                                                                 ],
                                          'heteronucl_t2_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                 {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                 {'name': 'Temp_calibration_method', 'type': 'enum', 'mandatory': False,
                                                                  'enum': ('methanol', 'monoethylene glycol', 'no calibration applied')},
                                                                 {'name': 'Temp_control_method', 'type': 'enum', 'mandatory': False,
                                                                  'enum': ('single scan interleaving', 'temperature compensation block',
                                                                           'single scan interleaving and temperature compensation block',
                                                                           'no temperature control applied')},
                                                                 {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                  'enforce-non-zero': True},
                                                                 {'name': 'T2_coherence_type', 'type': 'enum', 'mandatory': True,
                                                                  'enum': ('I(+,-)', 'S(+,-)', 'na')},
                                                                 {'name': 'T2_val_units', 'type': 'enum', 'mandatory': True,
                                                                  'enum': ('s', 's-1', 'ms', 'ms-1')},
                                                                 {'name': 'Rex_units', 'type': 'enum', 'mandatory': False,
                                                                  'enum': ('s-1', 'ms-1', 'us-1')}
                                                                 ],
                                          'heteronucl_t1r_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Temp_calibration_method', 'type': 'enum', 'mandatory': False,
                                                                   'enum': ('methanol', 'monoethylene glycol', 'no calibration applied')},
                                                                  {'name': 'Temp_control_method', 'type': 'enum', 'mandatory': False,
                                                                   'enum': ('single scan interleaving', 'temperature compensation block',
                                                                            'single scan interleaving and temperature compensation block',
                                                                            'no temperature control applied')},
                                                                  {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'T1rho_coherence_type', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('I(+,-)', 'S(+,-)', 'na')},
                                                                  {'name': 'T1rho_val_units', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('s', 's-1', 'ms', 'ms-1')},
                                                                  {'name': 'Rex_units', 'type': 'enum', 'mandatory': False,
                                                                   'enum': ('s-1', 'ms-1', 'us-1')}
                                                                  ],
                                          'order_param_data': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                               {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                               {'name': 'Tau_e_val_units', 'type': 'enum', 'mandatory': False,
                                                                'enum': ('s', 'ms', 'us', 'ns', 'ps')},
                                                               {'name': 'Tau_f_val_units', 'type': 'enum', 'mandatory': False,
                                                                'enum': ('s', 'ms', 'us', 'ns', 'ps')},
                                                               {'name': 'Tau_s_val_units', 'type': 'enum', 'mandatory': False,
                                                                'enum': ('s', 'ms', 'us', 'ns', 'ps')},
                                                               {'name': 'Rex_val_units', 'type': 'enum', 'mandatory': False,
                                                                'enum': ('s-1', 'ms-1', 'us-1')},
                                                               {'name': 'Rex_field_strength', 'type': 'positive-float', 'mandatory': False,
                                                                'enforce-non-zero': True}
                                                               ],
                                          'ccr_d_csa_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Val_units', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('s-1', 'ms-1', 'us-1')}
                                                                  ],
                                          'ccr_dd_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                               {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                               {'name': 'Spectrometer_frequency_1H', 'type': 'positive-float', 'mandatory': False,
                                                                'enforce-non-zero': True},
                                                               {'name': 'Val_units', 'type': 'enum', 'mandatory': True,
                                                                'enum': ('s-1', 'ms-1', 'us-1')}
                                                               ],
                                          'fchiral_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                                {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                                {'name': 'Stereo_count', 'type': 'int', 'mandatory': False},
                                                                {'name': 'Stereo_assigned_count', 'type': 'int', 'mandatory': True}
                                                                ],
                                          'saxs_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                             {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True}
                                                             ],
                                          'other_restraint': [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
                                                              {'name': 'Definition', 'type': 'str', 'mandatory': True}
                                                              ]
                                          }
                             }

        # required saveframe tag items by NmrDpUtility
        self._sf_tag_items = {'nef': {'entry_info': None,
                                      'poly_seq': None,
                                      'entity': None,
                                      'chem_shift': None,
                                      'chem_shift_ref': None,
                                      'dist_restraint': ['restraint_origin', 'potential_type'],
                                      'dihed_restraint': ['restraint_origin', 'potential_type'],
                                      'rdc_restraint': ['restraint_origin', 'potential_type'],
                                      'spectral_peak': ['experiment_type'],
                                      'spectral_peak_alt': None,
                                      'noepk_restraint': None,
                                      'jcoup_restraint': None,
                                      'rdc_raw_data': None,
                                      'csa_restraint': None,
                                      'ddc_restraint': None,
                                      'hvycs_restraint': None,
                                      'procs_restraint': None,
                                      'csp_restraint': None,
                                      'auto_relax_restraint': None,
                                      'heteronucl_noe_data': None,
                                      'heteronucl_t1_data': None,
                                      'heteronucl_t2_data': None,
                                      'heteronucl_t1r_data': None,
                                      'order_param_data': None,
                                      'ccr_d_csa_restraint': None,
                                      'ccr_dd_restraint': None,
                                      'fchiral_restraint': None,
                                      'saxs_restraint': None,
                                      'other_restraint': None
                                      },
                              'nmr-star': {'entry_info': None,
                                           'poly_seq': None,
                                           'entity': None,
                                           'chem_shift': None,
                                           'chem_shift_ref': None,
                                           'dist_restraint': ['Constraint_type', 'Potential_type'],
                                           'dihed_restraint': ['Constraint_type', 'Potential_type'],
                                           'rdc_restraint': ['Constraint_type', 'Potential_type'],
                                           'spectral_peak': ['Experiment_type'],
                                           'spectral_peak_alt': ['Experiment_type'],
                                           'noepk_restraint': None,
                                           'jcoup_restraint': None,
                                           'rdc_raw_data': None,
                                           'csa_restraint': None,
                                           'ddc_restraint': None,
                                           'hvycs_restraint': None,
                                           'procs_restraint': None,
                                           'csp_restraint': None,
                                           'auto_relax_restraint': None,
                                           'heteronucl_noe_data': None,
                                           'heteronucl_t1_data': None,
                                           'heteronucl_t2_data': None,
                                           'heteronucl_t1r_data': None,
                                           'order_param_data': None,
                                           'ccr_d_csa_restraint': None,
                                           'ccr_dd_restraint': None,
                                           'fchiral_restraint': None,
                                           'saxs_restraint': None,
                                           'other_restraint': None
                                           }
                              }

        # allowed saveframe tags
        self.sf_allowed_tags = {'nef': {'entry_info': ['sf_category', 'sf_framecode', 'format_name', 'format_version',
                                                       'program_name', 'program_version', 'creation_date', 'uuid', 'coordinate_file_name'],
                                        'poly_seq': ['sf_category', 'sf_framecode'],
                                        'entity': None,
                                        'chem_shift': ['sf_category', 'sf_framecode'],
                                        'chem_shift_ref': None,
                                        'dist_restraint': ['sf_category', 'sf_framecode', 'potential_type', 'restraint_origin'],
                                        'dihed_restraint': ['sf_category', 'sf_framecode', 'potential_type', 'restraint_origin'],
                                        'rdc_restraint': ['sf_category', 'sf_framecode', 'potential_type', 'restraint_origin',
                                                          'tensor_magnitude', 'tensor_rhombicity', 'tensor_chain_code',
                                                          'tensor_sequence_code', 'tensor_residue_name'],
                                        'spectral_peak': ['sf_category', 'sf_framecode', 'num_dimensions', 'chemical_shift_list',
                                                          'experiment_classification', 'experiment_type'],
                                        'spectral_peak_alt': None,
                                        'noepk_restraint': None,
                                        'jcoup_restraint': None,
                                        'rdc_raw_data': None,
                                        'csa_restraint': None,
                                        'ddc_restraint': None,
                                        'hvycs_restraint': None,
                                        'procs_restraint': None,
                                        'csp_restraint': None,
                                        'auto_relax_restraint': None,
                                        'heteronucl_noe_data': None,
                                        'heteronucl_t1_data': None,
                                        'heteronucl_t2_data': None,
                                        'heteronucl_t1r_data': None,
                                        'order_param_data': None,
                                        'ccr_d_csa_restraint': None,
                                        'ccr_dd_restraint': None,
                                        'fchiral_restraint': None,
                                        'saxs_restraint': None,
                                        'other_restraint': None
                                        },
                                'nmr-star': {'entry_info': ['Sf_category', 'Sf_framecode', 'Sf_ID', 'ID', 'Title', 'Type',
                                                            'Version_type', 'Submission_date', 'Accession_date', 'Last_release_date', 'Original_release_date',
                                                            'Origination', 'Format_name', 'NMR_STAR_version', 'Original_NMR_STAR_version',
                                                            'Experimental_method', 'Experimental_method_subtype',
                                                            'Source_data_format', 'Source_data_format_version',
                                                            'Generated_software_name', 'Generated_software_version', 'Generated_software_ID',
                                                            'Generated_software_label', 'Generated_date',
                                                            'DOI', 'UUID', 'Related_coordinate_file_name', 'Dep_release_code_coordinates',
                                                            'Dep_release_code_nmr_constraints', 'Dep_release_code_chemical_shifts',
                                                            'Dep_release_code_nmr_exptl', 'Dep_release_code_sequence',
                                                            'CASP_target', 'Details', 'Special_processing_instructions',
                                                            'Update_BMRB_accession_code', 'Replace_BMRB_accession_code',
                                                            'Update_PDB_accession_code', 'Replace_PDB_accession_code',
                                                            'PDB_coordinate_file_version', 'BMRB_update_details',
                                                            'PDB_update_details', 'Release_request',
                                                            'Release_date_request', 'Release_date_justification',
                                                            'Status_code', 'Recvd_deposit_form', 'Date_deposition_form',
                                                            'Recvd_coordinates', 'Date_coordinates', 'Recvd_nmr_constraints',
                                                            'Date_nmr_constraints', 'Recvd_chemical_shifts', 'Date_chemical_shifts',
                                                            'Recvd_manuscript', 'Date_manuscript', 'Recvd_author_approval', 'Date_author_approval',
                                                            'Recvd_initial_deposition_date', 'PDB_date_submitted', 'Author_release_status_code',
                                                            'Date_of_PDB_release', 'Date_hold_coordinates', 'Date_hold_nmr_constraints', 'Date_hold_chemical_shifts',
                                                            'PDB_deposit_site', 'PDB_process_site', 'BMRB_deposit_site', 'BMRB_process_site',
                                                            'BMRB_annotator', 'BMRB_internal_directory_name', 'RCSB_annotator', 'Author_approval_type',
                                                            'Assigned_BMRB_ID', 'Assigned_BMRB_deposition_code', 'Assigned_PDB_ID',
                                                            'Assigned_PDB_deposition_code', 'Assigned_restart_ID',
                                                            'NMR_STAR_dict_location'],
                                             'poly_seq': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'BMRB_code',
                                                          'Number_of_components', 'Organic_ligands', 'Metal_ions', 'Non_standard_bonds',
                                                          'Ambiguous_conformational_states', 'Ambiguous_chem_comp_sites', 'Molecules_in_chemical_exchange',
                                                          'Paramagnetic', 'Thiol_state', 'Molecular_mass', 'Enzyme_commission_number',
                                                          'Details', 'DB_query_date', 'DB_query_revised_last_date'],
                                             'entity': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'BMRB_code', 'Name',
                                                        'Type', 'Polymer_common_type', 'Polymer_type', 'Polymer_type_details', 'Polymer_strand_ID',
                                                        'Polymer_seq_one_letter_code_can', 'Polymer_seq_one_letter_code', 'Target_identifier',
                                                        'Polymer_author_defined_seq', 'Polymer_author_seq_details',
                                                        'Ambiguous_conformational_states', 'Ambiguous_chem_comp_sites',
                                                        'Nstd_monomer', 'Nstd_chirality', 'Nstd_linkage', 'Nonpolymer_comp_ID', 'Nonpolymer_comp_label',
                                                        'Number_of_monomers', 'Number_of_nonpolymer_components', 'Paramagnetic', 'Thiol_state', 'Src_method',
                                                        'Parent_entity_ID', 'Fragment', 'Mutation', 'EC_number', 'Calc_isoelectric_point',
                                                        'Formula_weight', 'Formula_weight_exptl', 'Formula_weight_exptl_meth',
                                                        'Details', 'DB_query_date', 'DB_query_revised_last_date'],
                                             'chem_shift': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                            'Sample_condition_list_ID', 'Sample_condition_list_label', 'Chem_shift_reference_ID',
                                                            'Chem_shift_reference_label',
                                                            'Chem_shift_1H_err', 'Chem_shift_13C_err', 'Chem_shift_15N_err', 'Chem_shift_31P_err',
                                                            'Chem_shift_2H_err', 'Chem_shift_19F_err',
                                                            'Error_derivation_method', 'Details', 'Text_data_format', 'Text_data'],
                                             'chem_shift_ref': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Proton_shifts_flag',
                                                                'Carbon_shifts_flag', 'Nitrogen_shifts_flag', 'Phosphorus_shifts_flag', 'Other_shifts_flag', 'Details'],
                                             'dist_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                'Constraint_type', 'Constraint_file_ID', 'Potential_type', 'Block_ID', 'Details', 'Text_data_format', 'Text_data'],
                                             'dihed_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID',
                                                                 'Name', 'Data_file_name', 'Constraint_file_ID', 'Potential_type', 'Constraint_type',
                                                                 'Block_ID', 'Details', 'Text_data_format', 'Text_data'],
                                             'rdc_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID',
                                                               'Name', 'Data_file_name', 'Constraint_file_ID', 'Block_ID', 'Potential_type', 'Constraint_type',
                                                               'Tensor_entity_assembly_ID', 'Tensor_comp_index_ID', 'Tensor_seq_ID', 'Tensor_comp_ID',
                                                               'Tensor_auth_entity_assembly_ID', 'Tensor_auth_asym_ID', 'Tensor_auth_seq_ID', 'Tensor_auth_comp_ID',
                                                               'Dipolar_constraint_calib_method', 'Tensor_magnitude', 'Tensor_rhombicity',
                                                               'Mol_align_tensor_axial_sym_mol', 'Mol_align_tensor_rhombic_mol', 'General_order_param_int_motions',
                                                               'Bond_length_usage_flag', 'Assumed_H_N_bond_length', 'Assumed_H_C_bond_length',
                                                               'Assumed_C_N_bond_length', 'Data_file_format', 'Details', 'Text_data_format', 'Text_data'],
                                             'spectral_peak': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                               'Sample_ID', 'Sample_label', 'Sample_condition_list_ID', 'Sample_condition_list_label',
                                                               'Experiment_ID', 'Experiment_name', 'Experiment_class', 'Experiment_type',
                                                               'Number_of_spectral_dimensions', 'Chemical_shift_list', 'Assigned_chem_shift_list_ID',
                                                               'Assigned_chem_shift_list_label', 'Details', 'Text_data_format', 'Text_data',
                                                               'Chem_shift_reference_ID', 'Chem_shift_reference_label'],
                                             'spectral_peak_alt': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                   'Sample_ID', 'Sample_label', 'Sample_condition_list_ID', 'Sample_condition_list_label',
                                                                   'Experiment_ID', 'Experiment_name', 'Experiment_class', 'Experiment_type',
                                                                   'Number_of_spectral_dimensions', 'Chemical_shift_list', 'Assigned_chem_shift_list_ID',
                                                                   'Assigned_chem_shift_list_label', 'Details', 'Text_data_format', 'Text_data',
                                                                   'Chem_shift_reference_ID', 'Chem_shift_reference_label'],
                                             'noepk_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                 'Sample_condition_list_ID', 'Sample_condition_list_label', 'Homonuclear_NOE_val_type',
                                                                 'NOE_ref_val', 'NOE_ref_description', 'Details', 'Text_data_format', 'Text_data'],
                                             'jcoup_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                 'Data_file_format', 'Constraint_file_ID', 'Block_ID', 'Text_data_format', 'Text_data', 'Details'],
                                             'rdc_raw_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                              'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                              'Bond_length_usage_flag', 'Dipolar_constraint_calib_method',
                                                              'Mol_align_tensor_axial_sym_mol', 'Mol_align_tensor_rhombic_mol', 'General_order_param_int_motions',
                                                              'Assumed_H_N_bond_length', 'Assumed_H_C_bond_length', 'Assumed_C_N_bond_length',
                                                              'Details', 'Text_data_format', 'Text_data'],
                                             'csa_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                               'Sample_condition_list_ID', 'Sample_condition_list_label',
                                                               'Spectrometer_frequency_1H', 'Val_units', 'Details', 'Text_data_format', 'Text_data'],
                                             'ddc_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                               'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                               'Scaling_factor', 'Fitting_procedure', 'Details', 'Text_data_format', 'Text_data'],
                                             'hvycs_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name', 'Data_file_format',
                                                                 'Constraint_file_ID', 'Block_ID', 'Units', 'Details', 'Text_data_format', 'Text_data'],
                                             'procs_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Units', 'Data_file_name', 'Data_file_format',
                                                                 'Constraint_file_ID', 'Block_ID', 'Details', 'Text_data_format', 'Text_data'],
                                             'csp_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Type', 'Data_file_name',
                                                               'Sample_condition_list_ID', 'Sample_condition_list_label', 'Chem_shift_ref_set_ID', 'Chem_shift_ref_set_label',
                                                               'Details', 'Text_data_format', 'Text_data'],
                                             'auto_relax_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                      'Sample_condition_list_ID', 'Sample_condition_list_label',
                                                                      'Temp_calibration_method', 'Temp_control_method', 'Spectrometer_frequency_1H', 'Exact_field_strength',
                                                                      'Common_relaxation_type_name', 'Relaxation_coherence_type', 'Relaxation_val_units',
                                                                      'Rex_units', 'Rex_field_strength', 'Details', 'Text_data_format', 'Text_data'],
                                             'heteronucl_noe_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                     'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                                     'Heteronuclear_NOE_val_type', 'NOE_ref_val', 'NOE_ref_description',
                                                                     'Details', 'Text_data_format', 'Text_data'],
                                             'heteronucl_t1_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                    'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                                    'T1_coherence_type', 'T1_val_units',
                                                                    'Details', 'Text_data_format', 'Text_data'],
                                             'heteronucl_t2_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                    'Sample_condition_list_ID', 'Sample_condition_list_label', 'Temp_calibration_method', 'Temp_control_method',
                                                                    'Spectrometer_frequency_1H', 'T2_coherence_type', 'T2_val_units', 'Rex_units',
                                                                    'Details', 'Text_data_format', 'Text_data'],
                                             'heteronucl_t1r_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                     'Sample_condition_list_ID', 'Sample_condition_list_label', 'Temp_calibration_method', 'Temp_control_method',
                                                                     'Spectrometer_frequency_1H', 'T1rho_coherence_type', 'T1rho_val_units', 'Rex_units',
                                                                     'Details', 'Text_data_format', 'Text_data'],
                                             'order_param_data': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                  'Sample_condition_list_ID', 'Sample_condition_list_label',
                                                                  'Tau_e_val_units', 'Tau_f_val_units', 'Tau_s_val_units',
                                                                  'Rex_field_strength', 'Rex_val_units', 'Details', 'Text_data_format', 'Text_data'],
                                             'ccr_d_csa_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                     'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                                     'Val_units', 'Details', 'Text_data_format', 'Text_data'],
                                             'ccr_dd_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                  'Sample_condition_list_ID', 'Sample_condition_list_label', 'Spectrometer_frequency_1H',
                                                                  'Val_units', 'Details', 'Text_data_format', 'Text_data'],
                                             'fchiral_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                   'Details', 'Stereo_count', 'Stereo_assigned_count'],
                                             'saxs_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Data_file_name',
                                                                'Sample_condition_list_ID', 'Sample_condition_list_label', 'Details', 'Text_data_format', 'Text_data'],
                                             'other_restraint': ['Sf_category', 'Sf_framecode', 'Entry_ID', 'Sf_ID', 'ID', 'Name', 'Definition', 'Data_file_name',
                                                                 'Sample_condition_list_ID', 'Sample_condition_list_label', 'Details', 'Text_data_format', 'Text_data']
                                             }
                                }

        # warning template for missing mandatory saveframe tag
        self.__warn_template_for_missing_mandatory_sf_tag = "The mandatory saveframe tag %r is missing. Please verify the value and re-upload the %s file."

        # auxiliary loop categories
        self.aux_lp_categories = {'nef': {'entry_info': None,
                                          'poly_seq': ['_nef_covalent_links', '_nef_sequence'],
                                          'entity': None,
                                          'chem_shift': None,
                                          'chem_shift_ref': None,
                                          'dist_restraint': None,
                                          'dihed_restraint': None,
                                          'rdc_restraint': None,
                                          'spectral_peak': ['_nef_spectrum_dimension', '_nef_spectrum_dimension_transfer'],
                                          'spectral_peak_alt': None,
                                          'noepk_restraint': None,
                                          'jcoup_restraint': None,
                                          'rdc_raw_data': None,
                                          'csa_restraint': None,
                                          'ddc_restraint': None,
                                          'hvycs_restraint': None,
                                          'procs_restraint': None,
                                          'csp_restraint': None,
                                          'auto_relax_restraint': None,
                                          'heteronucl_noe_data': None,
                                          'heteronucl_t1_data': None,
                                          'heteronucl_t2_data': None,
                                          'heteronucl_t1r_data': None,
                                          'order_param_data': None,
                                          'ccr_d_csa_restraint': None,
                                          'ccr_dd_restraint': None,
                                          'fchiral_restraint': None,
                                          'saxs_restraint': None,
                                          'other_restraint': None
                                          },
                                  'nmr-star': {'entry_info': None,
                                               'poly_seq': ['_Bond', '_Entity_deleted_atom', '_Entity_assembly'],
                                               'entity': ['_Entity_poly_seq'],
                                               'chem_shift': ['_Ambiguous_atom_chem_shift'],
                                               'chem_shift_ref': None,
                                               'dist_restraint': None,
                                               'dihed_restraint': None,
                                               'rdc_restraint': None,
                                               'spectral_peak': ['_Spectral_dim', '_Spectral_dim_transfer'],
                                               'spectral_peak_alt': ['_Spectral_dim', '_Spectral_dim_transfer', '_Peak_general_char', '_Peak_char', '_Assigned_peak_chem_shift'],
                                               'noepk_restraint': None,
                                               'jcoup_restraint': None,
                                               'rdc_raw_data': None,
                                               'csa_restraint': None,
                                               'ddc_restraint': None,
                                               'hvycs_restraint': None,
                                               'procs_restraint': None,
                                               'csp_restraint': None,
                                               'auto_relax_restraint': None,
                                               'heteronucl_noe_data': None,
                                               'heteronucl_t1_data': None,
                                               'heteronucl_t2_data': None,
                                               'heteronucl_t1r_data': None,
                                               'order_param_data': None,
                                               'ccr_d_csa_restraint': None,
                                               'ccr_dd_restraint': None,
                                               'fchiral_restraint': None,
                                               'saxs_restraint': None,
                                               'other_restraint': None
                                               }
                                  }

        # linked loop categories
        self.linked_lp_categories = {'nef': {'entry_info': ['_nef_related_entries', '_nef_program_script', '_nef_run_history', '_audit'],
                                             'poly_seq': ['_nef_covalent_links', '_nef_sequence'],
                                             'entity': [],
                                             'chem_shift': ['_nef_chemical_shift'],
                                             'chem_shift_ref': [],
                                             'dist_restraint': ['_nef_distance_restraint'],
                                             'dihed_restraint': ['_nef_dihedral_restraint'],
                                             'rdc_restraint': ['_nef_rdc_restraint'],
                                             'spectral_peak': ['_nef_spectrum_dimension', '_nef_spectrum_dimension_transfer', '_nef_peak'],
                                             'spectral_peak_alt': [],
                                             'noepk_restraint': [],
                                             'jcoup_restraint': [],
                                             'rdc_raw_data': [],
                                             'csa_restraint': [],
                                             'ddc_restraint': [],
                                             'hvycs_restraint': [],
                                             'procs_restraint': [],
                                             'csp_restraint': [],
                                             'auto_relax_restraint': [],
                                             'heteronucl_noe_data': [],
                                             'heteronucl_t1_data': [],
                                             'heteronucl_t2_data': [],
                                             'heteronucl_t1r_data': [],
                                             'order_param_data': [],
                                             'ccr_d_csa_restraint': [],
                                             'ccr_dd_restraint': [],
                                             'fchiral_restraint': [],
                                             'saxs_restraint': [],
                                             'other_restraint': []
                                             },
                                     'nmr-star': {'entry_info': ['_Study_list', '_Entry_experimental_methods', '_Entry_author',
                                                                 '_SG_project', '_Entry_src', '_Struct_keywords', '_Data_set',
                                                                 '_Datum', '_Release', '_Related_entries', '_Matched_entries',
                                                                 '_Auxiliary_files', '_Citation',
                                                                 '_Assembly', '_Assembly_annotation_list', '_Assembly_subsystem',
                                                                 '_Entity', '_Entity_natural_src_list', '_Entity_natural_src',
                                                                 '_Entity_experimental_src_list', '_Chem_comp', '_Chem_comp_atom',
                                                                 '_Sample', '_Sample_condition_list', '_Entity_purity_list', '_Software', '_Method',
                                                                 '_Mass_spec', '_Mass_spectrometer_list', '_Mass_spec_ref_compd_set',
                                                                 '_Chromatographic_system', '_Chromatographic_column',
                                                                 '_Fluorescence_instrument', '_EMR_instrument', '_Xray_instrument',
                                                                 '_NMR_spectrometer', '_NMR_spectrometer_list', '_NMR_spectrometer_probe',
                                                                 '_Experiment_list', '_NMR_spec_expt', '_NMR_spectral_processing',
                                                                 '_MS_expt', '_MS_expt_param', '_MS_expt_software',
                                                                 '_Computer', '_Chem_shift_reference', '_Assigned_chem_shift_list',
                                                                 '_Chem_shifts_calc_type', '_Theoretical_chem_shift_list', '_Theoretical_chem_shift',
                                                                 '_Coupling_constant_list', '_Theoretical_coupling_constant_list', '_Spectral_peak_list',
                                                                 '_Resonance_linker_list', '_Resonance_assignment',
                                                                 '_Chem_shift_isotope_effect_list', '_Chem_shift_perturbation_list', '_Chem_shift_anisotropy',
                                                                 '_RDC_list', '_RDC_experiment', '_RDC_software', '_RDC',
                                                                 '_Dipolar_coupling_list', '_Dipolar_coupling_experiment', '_Dipolar_coupling_software',
                                                                 '_Dipolar_coupling', '_Spectral_density_list', '_Spectral_density_experiment',
                                                                 '_Spectral_density_software', '_Spectral_density', '_Other_data_type_list',
                                                                 '_Other_data_experiment', '_Other_data_software', '_Other_data',
                                                                 '_Chemical_rate_list', '_Chemical_rate_experiment', '_Chemical_rate_software', '_Chemical_rate',
                                                                 '_H_exch_rate_list', '_H_exch_rate_experiment', '_H_exch_rate_software', '_H_exch_rate',
                                                                 '_H_exch_protection_factor_list', '_H_exch_protection_fact_experiment',
                                                                 '_H_exch_protection_fact_software', '_H_exch_protection_factor',
                                                                 '_Homonucl_NOE_list', '_Homonucl_NOE_experiment', '_Homonucl_NOE_software',
                                                                 '_Homonucl_NOE', '_Heteronucl_NOE_list', '_Heteronucl_NOE_experiment',
                                                                 '_Heteronucl_NOE_software', '_Heteronucl_NOE', '_Theoretical_heteronucl_NOE_list',
                                                                 '_Theoretical_heteronucl_NOE_experiment', '_Theoretical_heteronucl_NOE_software',
                                                                 '_Theoretical_heteronucl_NOE',
                                                                 '_Heteronucl_T1_list', '_Heteronucl_T1_experiment', '_Heteronucl_T1_software', '_T1',
                                                                 '_Theoretical_heteronucl_T1_list', '_Theoretical_heteronucl_T1_experiment',
                                                                 '_Theoretical_heteronucl_T1_software', '_Theoretical_T1',
                                                                 '_Heteronucl_T1rho_list', '_Heteronucl_T1rho_experiment', '_Heteronucl_T1rho_software',
                                                                 '_T1rho',
                                                                 '_Heteronucl_T2_list', '_Heteronucl_T2_experiment', '_Heteronucl_T2_software', '_T2',
                                                                 '_Theoretical_heteronucl_T2_list', '_Theoretical_heteronucl_T2_experiment',
                                                                 '_Theoretical_heteronucl_T2_software', '_Theoretical_T2',
                                                                 '_Auto_relaxation_list', '_Auto_relaxation_experiment', '_Auto_relaxation_software',
                                                                 '_Auto_relaxation', '_Theoretical_auto_relaxation_list', '_Theoretical_auto_relaxation_experiment',
                                                                 '_Theoretical_auto_relaxation_software', '_Theoretical_auto_relaxation',
                                                                 '_Dipole_dipole_relax_list', '_Dipole_dipole_relax_experiment', '_Dipole_dipole_relax_software',
                                                                 '_Dipole_dipole_relax',
                                                                 '_Cross_correlation_DD_list', '_Cross_correlation_DD_experiment', '_Cross_correlation_DD_software',
                                                                 '_Cross_correlation_DD', '_Theoretical_cross_correlation_DD_list', '_Theoretical_cross_correlation_DD_experiment',
                                                                 '_Theoretical_cross_correlation_DD_software', '_Theoretical_cross_correlation_DD',
                                                                 '_Cross_correlation_D_CSA_list', '_Cross_correlation_D_CSA_experiment', '_Cross_correlation_D_CSA_software',
                                                                 '_Cross_correlation_D_CSA', '_Order_parameter_list', '_Order_parameter_experiment',
                                                                 '_Order_parameter_software', '_Order_param',
                                                                 '_PH_titration_list', '_PH_titration_experiment', '_PH_titration_software', '_PH_titr_result',
                                                                 '_PH_param_list', '_PH_param', '_D_H_fractionation_factor_list', '_D_H_fract_factor_experiment',
                                                                 '_D_H_fract_factor_software', '_D_H_fractionation_factor',
                                                                 '_Binding_value_list', '_Binding_experiment', '_Binding_software', '_Binding_result',
                                                                 '_Binding_partners', '_Binding_param_list', '_Binding_param',
                                                                 '_Deduced_secd_struct_list', '_Deduced_secd_struct_experiment', '_Deduced_secd_struct_software',
                                                                 '_Deduced_secd_struct_exptl', '_Deduced_secd_struct_feature', '_Deduced_H_bond_list',
                                                                 '_Deduced_H_bond_experiment', '_Deduced_H_bond_software', '_Deduced_H_bond',
                                                                 '_Conformer_stat_list', '_Conformer_stat_list_ens', '_Conformer_stat_list_rep', '_Conf_stats_software',
                                                                 '_Conformer_family_coord_set', '_Conformer_family_refinement', '_Conformer_family_software',
                                                                 '_Energetic_penalty_function', '_Conformer_family_coord_set_expt', '_Conf_family_coord_set_constr_list',
                                                                 '_Struct_image', '_Local_structure_quality', '_Model_type', '_Atom_site', '_Atom_sites_footnote',
                                                                 '_Representative_conformer', '_Rep_conf_refinement', '_Rep_conf_software', '_Terminal_residue',
                                                                 '_Rep_conf', '_Rep_coordinate_details',
                                                                 '_Constraint_stat_list', '_Constraint_stat_list_ens', '_Constraint_stat_list_rep',
                                                                 '_Constraint_stats_constr_list', '_Constraint_file', '_Force_constant_list', '_Force_constant_software',
                                                                 '_Force_constant', '_Angular_order_parameter_list', '_Angular_order_param',
                                                                 '_Tertiary_struct_element_list', '_Tertiary_struct_element_sel', '_Tertiary_struct',
                                                                 '_Structure_annotation', '_Struct_anno_software', '_Struct_classification', '_Struct_anno_char',
                                                                 '_Secondary_struct_list', '_Secondary_struct_sel', '_Secondary_struct', '_Bond_annotation_list',
                                                                 '_Bond_annotation', '_Bond_observed_conformer',
                                                                 '_Structure_interaction_list', '_Structure_interaction', '_Observed_conformer',
                                                                 '_Other_struct_feature_list', '_Other_struct_feature', '_Tensor_list',
                                                                 '_Interatomic_distance_list', '_Interatomic_dist',
                                                                 '_Gen_dist_constraint_list', '_Gen_dist_constraint_expt', '_Gen_dist_constraint_software',
                                                                 '_Gen_dist_constraint_software_param', '_Gen_dist_constraint', '_Gen_dist_constraint_comment_org',
                                                                 '_Gen_dist_constraint_parse_err', '_Gen_dist_constraint_parse_file', '_Gen_dist_constraint_conv_err',
                                                                 '_Distance_constraint_list', '_Distance_constraint_expt', '_Distance_constraint_software',
                                                                 '_Dist_constr_software_setting', '_Dist_constraint_tree', '_Dist_constraint',
                                                                 '_Dist_constraint_value', '_Dist_constraint_comment_org', '_Dist_constraint_parse_err',
                                                                 '_Dist_constraint_parse_file', '_Dist_constraint_conv_err',
                                                                 '_Floating_chirality_assign', '_Floating_chirality_software', '_Floating_chirality',
                                                                 '_Torsion_angle_constraint_list', '_Torsion_angle_constraints_expt', '_Torsion_angle_constraint_software',
                                                                 '_Karplus_equation', '_Torsion_angle_constraint', '_TA_constraint_comment_org', '_TA_constraint_parse_err',
                                                                 '_TA_constraint_parse_file', '_TA_constraint_conv_err',
                                                                 '_RDC_constraint_list', '_RDC_constraint_expt', '_RDC_constraint_software', '_RDC_constraint',
                                                                 '_RDC_constraint_comment_org', '_RDC_constraint_parse_err', '_RDC_constraint_parse_file',
                                                                 '_RDC_constraint_conv_err',
                                                                 '_J_three_bond_constraint_list', '_J_three_bond_constraint_expt', '_J_three_bond_constraint_software',
                                                                 '_J_three_bond_constraint', '_CA_CB_constraint_list', '_CA_CB_constraint_expt',
                                                                 '_CA_CB_constraint_software', '_CA_CB_constraint',
                                                                 '_H_chem_shift_constraint_list', '_H_chem_shift_constraint_expt', '_H_chem_shift_constraint_software',
                                                                 '_H_chem_shift_constraint', '_Peak_constraint_link_list', '_Peak_constraint_link',
                                                                 '_SAXS_constraint_list', '_SAXS_constraint_expt', '_SAXS_constraint_software', '_SAXS_constraint',
                                                                 '_Other_constraint_list', '_Other_constraint_expt', '_Other_constraint_software', '_Org_constr_file_comment',
                                                                 '_MZ_ratio_data_list', '_MZ_ratio_experiment', '_MZ_ratio_software', '_MZ_ratio_spectrum_param',
                                                                 '_MZ_precursor_ion', '_MZ_precursor_ion_annotation', '_MZ_product_ion', '_MZ_product_ion_annotation',
                                                                 '_MS_chromatogram_list', '_MS_chromatogram_experiment', '_MS_chromatogram_software', '_MS_chromatogram_param',
                                                                 '_MS_chromatogram_ion', '_MS_chrom_ion_annotation',
                                                                 '_Software_specific_info_list', '_Software_specific_info', '_Software_applied_list', '_Software_applied_methods',
                                                                 '_Software_applied_history', '_History',
                                                                 '_Audit'],
                                                  'poly_seq': ['_Assembly_type', '_Entity_assembly', '_Bond', '_Entity_deleted_atom',
                                                               '_Struct_asym', '_Assembly_db_link', '_Assembly_common_name',
                                                               '_Assembly_systematic_name', '_Assembly_interaction', '_Chem_comp_assembly',
                                                               '_PDBX_poly_seq_scheme', '_PDBX_nonpoly_scheme', '_Atom_type', '_Atom',
                                                               '_Assembly_bio_function', '_Angle', '_Torsion_angle',
                                                               '_Assembly_segment', '_Assembly_segment_description', '_Assembly_keyword',
                                                               '_Assembly_citation', '_Author_annotation', '_Sample_component',
                                                               '_Chemical_rate', '_Auto_relaxation', '_Theoretical_auto_relaxation',
                                                               '_Binding_result', '_Binding_partners', '_Struct_anno_char'],
                                                  'entity': ['_Entity_db_link', '_Entity_biological_function', '_Entity_common_name', '_Entity_systematic_name', '_Entity_keyword',
                                                             '_Entity_comp_index', '_Entity_poly_seq', '_Entity_chimera_segment', '_Entity_comp_index_alt',
                                                             '_Entity_atom_list', '_Entity_chem_comp_deleted_atom', '_Entity_bond', '_Entity_citation'],
                                                  'chem_shift': ['_Chem_shift_experiment', '_Systematic_chem_shift_offset',
                                                                 '_Chem_shift_software', '_Atom_chem_shift', '_Ambiguous_atom_chem_shift',
                                                                 '_Spectral_peak_list', '_Assigned_peak_chem_shift', '_Assigned_spectral_transition'],
                                                  'chem_shift_ref': ['_Chem_shift_ref', '_Assigned_chem_shift_list', '_Chem_shifts_calc_type'],
                                                  'dist_restraint': ['_Gen_dist_constraint_expt', '_Gen_dist_constraint_software',
                                                                     '_Gen_dist_constraint_software_param', '_Gen_dist_constraint',
                                                                     '_Gen_dist_constraint_comment_org', '_Gen_dist_constraint_parse_err',
                                                                     '_Gen_dist_constraint_parse_file', '_Gen_dist_constraint_conv_err'],
                                                  'dihed_restraint': ['_Torsion_angle_constraints_expt', '_Torsion_angle_constraint_software',
                                                                      '_Karplus_equation', '_Torsion_angle_constraint', '_TA_constraint_comment_org',
                                                                      '_TA_constraint_parse_err', '_TA_constraint_parse_file', '_TA_constraint_conv_err'],
                                                  'rdc_restraint': ['_RDC_constraint_expt', '_RDC_constraint_software', '_RDC_constraint',
                                                                    '_RDC_constraint_comment_org', '_RDC_constraint_parse_err',
                                                                    '_RDC_constraint_parse_file', '_RDC_constraint_conv_err'],
                                                  'spectral_peak': ['_Spectral_dim', '_Spectral_dim_transfer', '_Spectral_peak_software',
                                                                    '_Peak', '_Peak_general_char', '_Peak_char', '_Assigned_peak_chem_shift',
                                                                    '_Peak_row_format', '_Spectral_transition', '_Spectral_transition_general_char',
                                                                    '_Spectral_transition_char', '_Assigned_spectral_transition', '_Gen_dist_constraint',
                                                                    '_Dist_constraint_value'],
                                                  'spectral_peak_alt': ['_Spectral_dim', '_Spectral_dim_transfer', '_Spectral_peak_software',
                                                                        '_Peak', '_Peak_general_char', '_Peak_char', '_Assigned_peak_chem_shift',
                                                                        '_Peak_row_format', '_Spectral_transition', '_Spectral_transition_general_char',
                                                                        '_Spectral_transition_char', '_Assigned_spectral_transition',
                                                                        '_Gen_dist_constraint', '_Dist_constraint_value'],
                                                  'noepk_restraint': ['Homonucl_NOE_experiment', 'Homonucl_NOE_software', 'Homonucl_NOE'],
                                                  'jcoup_restraint': ['J_three_bond_constraint_expt', 'J_three_bond_constraint_software', 'J_three_bond_constraint'],
                                                  'rdc_raw_data': ['RDC_experiment', 'RDC_software', 'RDC'],
                                                  'csa_restraint': ['CS_anisotroty_experiment', 'CS_anisotroty_software', 'CS_anisotroty'],
                                                  'ddc_restraint': ['Dipolar_coupling_experiment', 'Dipolar_coupling_software', 'Dipolar_coupling'],
                                                  'hvycs_restraint': ['CA_CB_constraint_expt', 'CA_CB_constraint_software', 'CA_CB_constraint'],
                                                  'procs_restraint': ['H_chem_shift_constraint_expt', 'H_chem_shift_constraint_software', 'H_chem_shift_constraint'],
                                                  'csp_restraint': ['Chem_shift_perturbation_experiment', 'Chem_shift_perturbation_software',
                                                                    'Chem_shift_perturbation'],
                                                  'auto_relax_restraint': ['Auto_relaxation_experiment', 'Auto_relaxation_software', 'Auto_relaxation'],
                                                  'heteronucl_noe_data': ['Heteronucl_NOE_experiment', 'Heteronucl_NOE_software', 'Heteronucl_NOE'],
                                                  'heteronucl_t1_data': ['Heteronucl_T1_experiment', 'Heteronucl_T1_software', 'T1'],
                                                  'heteronucl_t2_data': ['Heteronucl_T2_experiment', 'Heteronucl_T2_software', 'T2'],
                                                  'heteronucl_t1r_data': ['Heteronucl_T1rho_experiment', 'Heteronucl_T1rho_software', 'T1rho'],
                                                  'order_param_data': ['Order_parameter_experiment', 'Order_parameter_software', 'Order_param'],
                                                  'ccr_d_csa_restraint': ['Cross_correlation_D_CSA_experiment', 'Cross_correlation_D_CSA_software',
                                                                          'Cross_correlation_D_CSA'],
                                                  'ccr_dd_restraint': ['Cross_correlation_DD_experiment', 'Cross_correlation_DD_software', 'Cross_correlation_DD'],
                                                  'fchiral_restraint': ['Floating_chirality_software', 'Floating_chirality'],
                                                  'saxs_restraint': ['SAXS_constraint_expt', 'SAXS_constraint_software', 'SAXS_constraint'],
                                                  'other_restraint': ['Other_data_experiment', 'Other_data_software', 'Other_data']
                                                  }
                                     }

        # auxiliary loop key items
        self.aux_key_items = {'nef': {'entry_info': None,
                                      'poly_seq': {
                                          '_nef_covalent_links': [{'name': 'chain_code_1', 'type': 'str', 'default': 'A'},
                                                                  {'name': 'sequence_code_1', 'type': 'int'},
                                                                  {'name': 'residue_name_1', 'type': 'str', 'uppercase': True},
                                                                  {'name': 'atom_name_1', 'type': 'str'},
                                                                  {'name': 'chain_code_2', 'type': 'str', 'default': 'A'},
                                                                  {'name': 'sequence_code_2', 'type': 'int'},
                                                                  {'name': 'residue_name_2', 'type': 'str', 'uppercase': True},
                                                                  {'name': 'atom_name_2', 'type': 'str'}
                                                                  ],
                                          '_nef_sequence': [{'name': 'chain_code', 'type': 'str', 'default': 'A'},
                                                            {'name': 'sequence_code', 'type': 'int'},
                                                            {'name': 'residue_name', 'type': 'str', 'uppercase': True}
                                                            ]
                                      },
                                      'entity': None,
                                      'chem_shift': None,
                                      'chem_shift_ref': None,
                                      'dist_restraint': None,
                                      'dihed_restraint': None,
                                      'rdc_restraint': None,
                                      'spectral_peak': {
                                          '_nef_spectrum_dimension': [{'name': 'dimension_id', 'type': 'index-int'}
                                                                      ],
                                          '_nef_spectrum_dimension_transfer': [{'name': 'dimension_1', 'type': 'positive-int'},
                                                                               {'name': 'dimension_2', 'type': 'positive-int'},
                                                                               ]
                                      },
                                      'spectral_peak_alt': None,
                                      'noepk_restraint': None,
                                      'jcoup_restraint': None,
                                      'rdc_raw_data': None,
                                      'csa_restraint': None,
                                      'ddc_restraint': None,
                                      'hvycs_restraint': None,
                                      'procs_restraint': None,
                                      'csp_restraint': None,
                                      'auto_relax_restraint': None,
                                      'heteronucl_noe_data': None,
                                      'heteronucl_t1_data': None,
                                      'heteronucl_t2_data': None,
                                      'heteronucl_t1r_data': None,
                                      'order_param_data': None,
                                      'ccr_d_csa_restraint': None,
                                      'ccr_dd_restraint': None,
                                      'fchiral_restraint': None,
                                      'saxs_restraint': None,
                                      'other_restraint': None
                                      },
                              'nmr-star': {'entry_info': None,
                                           'poly_seq': {
                                               '_Bond': [{'name': 'ID', 'type': 'index-int', 'mandatory': True, 'default-from': 'self'},
                                                         {'name': 'Type', 'type': 'enum', 'mandatory': True, 'default': 'covalent',
                                                          'enum': ('amide', 'covalent', 'directed', 'disulfide', 'ester', 'ether', 'hydrogen',
                                                                   'metal coordination', 'peptide', 'thioether', 'oxime', 'thioester',
                                                                   'phosphoester', 'phosphodiester', 'diselenide', 'na')},
                                                         {'name': 'Value_order', 'type': 'enum', 'mandatory': True, 'default': 'sing',
                                                          'enum': ('sing', 'doub', 'trip', 'quad', 'arom', 'poly', 'delo', 'pi', 'directed')},
                                                         {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_1'},
                                                         {'name': 'Comp_index_ID_1', 'type': 'int'},
                                                         {'name': 'Comp_ID_1', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_1', 'type': 'str'},
                                                         {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'Auth_asym_ID_2'},
                                                         {'name': 'Comp_index_ID_2', 'type': 'int'},
                                                         {'name': 'Comp_ID_2', 'type': 'str', 'uppercase': True},
                                                         {'name': 'Atom_ID_2', 'type': 'str'}
                                                         ],
                                               '_Entity_deleted_atom': [{'name': 'ID', 'type': 'index-int', 'mandatory': True, 'default-from': 'self'},
                                                                        {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'default': '1', 'default-from': 'self'},
                                                                        {'name': 'Comp_index_ID', 'type': 'int'},
                                                                        {'name': 'Comp_ID', 'type': 'str', 'uppercase': True},
                                                                        {'name': 'Atom_ID', 'type': 'str'}
                                                                        ],
                                               '_Entity_assembly': [{'name': 'ID', 'type': 'index-int', 'mandatory': True, 'default-from': 'self'},
                                                                    {'name': 'Entity_assembly_name', 'type': 'str', 'mandatory': True},
                                                                    {'name': 'Entity_ID', 'type': 'positive-int'},
                                                                    {'name': 'Entity_label', 'type': 'str'}
                                                                    ]
                                           },
                                           'entity': None,
                                           'chem_shift': {
                                               '_Ambiguous_atom_chem_shift': [{'name': 'Ambiguous_shift_set_ID', 'type': 'positive-int', 'mandatory': True,
                                                                               'default-from': 'self'},
                                                                              {'name': 'Atom_chem_shift_ID', 'type': 'positive-int', 'mandatory': True}
                                                                              ]
                                           },
                                           'chem_shift_ref': None,
                                           'dist_restraint': None,
                                           'dihed_restraint': None,
                                           'rdc_restraint': None,
                                           'spectral_peak': {
                                               '_Spectral_dim': [{'name': 'ID', 'type': 'index-int'}
                                                                 ],
                                               '_Spectral_dim_transfer': [{'name': 'Spectral_dim_ID_1', 'type': 'positive-int'},
                                                                          {'name': 'Spectral_dim_ID_2', 'type': 'positive-int'},
                                                                          ]
                                           },
                                           'spectral_peak_alt': {
                                               '_Spectral_dim': [{'name': 'ID', 'type': 'index-int'}
                                                                 ],
                                               '_Spectral_dim_transfer': [{'name': 'Spectral_dim_ID_1', 'type': 'positive-int'},
                                                                          {'name': 'Spectral_dim_ID_2', 'type': 'positive-int'},
                                                                          ],
                                               '_Peak_general_char': [],
                                               '_Peak_char': [],
                                               '_Assigned_peak_chem_shift': []
                                           },
                                           'noepk_restraint': None,
                                           'jcoup_restraint': None,
                                           'rdc_raw_data': None,
                                           'csa_restraint': None,
                                           'ddc_restraint': None,
                                           'hvycs_restraint': None,
                                           'procs_restraint': None,
                                           'csp_restraint': None,
                                           'auto_relax_restraint': None,
                                           'heteronucl_noe_data': None,
                                           'heteronucl_t1_data': None,
                                           'heteronucl_t2_data': None,
                                           'heteronucl_t1r_data': None,
                                           'order_param_data': None,
                                           'ccr_d_csa_restraint': None,
                                           'ccr_dd_restraint': None,
                                           'fchiral_restraint': None,
                                           'saxs_restraint': None,
                                           'other_restraint': None
                                           }
                              }

        # auxiliary loop data items
        self.aux_data_items = {'nef': {'entry_info': None,
                                       'poly_seq': {
                                           '_nef_covalent_links': [],
                                           '_nef_sequence': [{'name': 'linking', 'type': 'enum', 'mandatory': False,
                                                              'enum': ('start', 'end', 'middle', 'cyclic', 'break', 'single', 'dummy'),
                                                              'enforce-enum': True},
                                                             {'name': 'residue_variant', 'type': 'str', 'mandatory': False},
                                                             {'name': 'cis_peptide', 'type': 'bool', 'mandatory': False}
                                                             ]
                                       },
                                       'entity': None,
                                       'chem_shift': None,
                                       'chem_shift_ref': None,
                                       'dist_restraint': None,
                                       'dihed_restraint': None,
                                       'rdc_restraint': None,
                                       'spectral_peak': {
                                           '_nef_spectrum_dimension': [{'name': 'axis_unit', 'type': 'enum', 'mandatory': True,
                                                                        'enum': ('ppm', 'Hz'),
                                                                        'enforce-enum': True},
                                                                       {'name': 'axis_code', 'type': 'str', 'mandatory': True},
                                                                       {'name': 'spectrometer_frequency', 'type': 'positive-float', 'mandatory': False,
                                                                        'enforce-non-zero': True},
                                                                       {'name': 'spectral_width', 'type': 'positive-float', 'mandatory': False,
                                                                        'enforce-non-zero': True},
                                                                       {'name': 'value_first_point', 'type': 'float', 'mandatory': False},
                                                                       {'name': 'folding', 'type': 'enum', 'mandatory': False,
                                                                        'enum': ('circular', 'mirror', 'none')},
                                                                       {'name': 'absolute_peak_positions', 'type': 'bool', 'mandatory': False},
                                                                       {'name': 'is_acquisition', 'type': 'bool', 'mandatory': False},
                                                                       ],
                                           '_nef_spectrum_dimension_transfer': [{'name': 'transfer_type', 'type': 'enum', 'mandatory': True,
                                                                                 'enum': ('onebond', 'jcoupling', 'jmultibond', 'relayed', 'relayed-alternate', 'through-space'),
                                                                                 'enforce-enum': True},
                                                                                {'name': 'is_indirect', 'type': 'bool', 'mandatory': False}
                                                                                ]
                                       },
                                       'spectral_peak_alt': None,
                                       'noepk_restraint': None,
                                       'jcoup_restraint': None,
                                       'rdc_raw_data': None,
                                       'csa_restraint': None,
                                       'ddc_restraint': None,
                                       'hvycs_restraint': None,
                                       'procs_restraint': None,
                                       'csp_restraint': None,
                                       'auto_relax_restraint': None,
                                       'heteronucl_noe_data': None,
                                       'heteronucl_t1_data': None,
                                       'heteronucl_t2_data': None,
                                       'heteronucl_t1r_data': None,
                                       'order_param_data': None,
                                       'ccr_d_csa_restraint': None,
                                       'ccr_dd_restraint': None,
                                       'fchiral_restraint': None,
                                       'saxs_restraint': None,
                                       'other_restraint': None
                                       },
                               'nmr-star': {'entry_info': None,
                                            'poly_seq': {
                                                '_Bond': [{'name': 'Auth_asym_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                                          {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                                          {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False}
                                                          ],
                                                '_Entity_deleted_atom': [{'name': 'Auth_entity_assembly_ID', 'type': 'str', 'mandatory': False},
                                                                         {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                                         {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                                         {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False}
                                                                         ],
                                                '_Entity_assembly': [{'name': 'Asym_ID', 'type': 'str', 'mandatory': False},
                                                                     {'name': 'PDB_chain_ID', 'type': 'str', 'mandatory': False},
                                                                     {'name': 'Experimental_data_reported', 'type': 'enum', 'mandatory': False,
                                                                      'enum': ('no', 'yes')},
                                                                     {'name': 'Physical_state', 'type': 'enum', 'mandatory': False,
                                                                      'enum': ('native', 'denatured', 'molten globule', 'unfolded',
                                                                               'intrinsically disordered', 'partially disordered', 'na')}
                                                                     ]
                                            },
                                            'entity': None,
                                            'chem_shift': {
                                                '_Ambiguous_atom_chem_shift': [{'name': 'Assigned_chem_shift_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                                'default': '1', 'default-from': 'parent'}
                                                                               ]
                                            },
                                            'chem_shift_ref': None,
                                            'dist_restraint': None,
                                            'dihed_restraint': None,
                                            'rdc_restraint': None,
                                            'spectral_peak': {
                                                '_Spectral_dim': [{'name': 'Axis_code', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Spectrometer_frequency', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Under_sampling_type', 'type': 'enum', 'mandatory': False,
                                                                   'enum': ('aliased', 'folded', 'not observed')},
                                                                  {'name': 'Sweep_width', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Sweep_width_units', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('ppm', 'Hz'),
                                                                   'enforce-enum': True},
                                                                  {'name': 'Value_first_point', 'type': 'float', 'mandatory': False},
                                                                  {'name': 'Absolute_peak_positions', 'type': 'bool', 'mandatory': False},
                                                                  {'name': 'Acquisition', 'type': 'bool', 'mandatory': False},
                                                                  {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                                  ],
                                                '_Spectral_dim_transfer': [{'name': 'Indirect', 'type': 'bool', 'mandatory': False},
                                                                           {'name': 'Type', 'type': 'enum', 'mandatory': True,
                                                                            'enum': ('onebond', 'jcoupling', 'jmultibond', 'relayed', 'relayed-alternate', 'through-space'),
                                                                            'enforce-enum': True},
                                                                           {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                                           ]
                                            },
                                            'spectral_peak_alt': {
                                                '_Spectral_dim': [{'name': 'Axis_code', 'type': 'str', 'mandatory': True},
                                                                  {'name': 'Spectrometer_frequency', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Under_sampling_type', 'type': 'enum', 'mandatory': False,
                                                                   'enum': ('aliased', 'folded', 'not observed')},
                                                                  {'name': 'Sweep_width', 'type': 'positive-float', 'mandatory': False,
                                                                   'enforce-non-zero': True},
                                                                  {'name': 'Sweep_width_units', 'type': 'enum', 'mandatory': True,
                                                                   'enum': ('ppm', 'Hz'),
                                                                   'enforce-enum': True},
                                                                  {'name': 'Value_first_point', 'type': 'float', 'mandatory': False},
                                                                  {'name': 'Absolute_peak_positions', 'type': 'bool', 'mandatory': False},
                                                                  {'name': 'Acquisition', 'type': 'bool', 'mandatory': False},
                                                                  {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                                  ],
                                                '_Spectral_dim_transfer': [{'name': 'Indirect', 'type': 'bool', 'mandatory': False},
                                                                           {'name': 'Type', 'type': 'enum', 'mandatory': True,
                                                                            'enum': ('onebond', 'jcoupling', 'jmultibond', 'relayed', 'relayed-alternate', 'through-space'),
                                                                            'enforce-enum': True},
                                                                           {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                                           ],
                                                '_Peak_general_char': [{'name': 'Peak_ID', 'type': 'positive-int', 'mandatory': True},
                                                                       {'name': 'Intensity_val', 'type': 'float', 'mandatory': True},
                                                                       {'name': 'Intensity_val_err', 'type': 'positive-float', 'mandatory': False, 'void-zero': True},
                                                                       {'name': 'Measurement_method', 'type': 'enum', 'mandatory': False,
                                                                        'enum': ('absolute height', 'height', 'relative height', 'volume', 'number of contours', 'integration')},
                                                                       {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                                       ],
                                                '_Peak_char': [{'name': 'Peak_ID', 'type': 'positive-int', 'mandatory': True},
                                                               {'name': 'Spectral_dim_ID', 'type': 'enum-int', 'mandatory': True,
                                                                'enum': set(range(1, MAX_DIM_NUM_OF_SPECTRA)),
                                                                'enforce-enum': True},
                                                               {'name': 'Chem_shift_val', 'type': 'range-float', 'mandatory': True,
                                                                'range': CS_RESTRAINT_RANGE},
                                                               {'name': 'Chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                'range': CS_UNCERTAINTY_RANGE},
                                                               {'name': 'Line_width_val', 'type': 'positive-float', 'mandatory': False},
                                                               {'name': 'Line_width_val_err', 'type': 'positive-float', 'mandatory': False, 'void-zero': True},
                                                               {'name': 'Coupling_pattern', 'type': 'enum', 'mandatory': False,
                                                                'enum': ('d', 'dd', 'ddd', 'dm', 'dt', 'hxt', 'hpt', 'm', 'q', 'qd', 'qn', 's', 'sxt', 't', 'td', 'LR', '1JCH')},
                                                               {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True, 'default-from': 'parent'}
                                                               ],
                                                '_Assigned_peak_chem_shift': [{'name': 'Peak_ID', 'type': 'positive-int', 'mandatory': True},
                                                                              {'name': 'Spectral_dim_ID', 'type': 'enum-int', 'mandatory': True,
                                                                               'enum': set(range(1, MAX_DIM_NUM_OF_SPECTRA)),
                                                                               'enforce-enum': True},
                                                                              {'name': 'Set_ID', 'type': 'positive-int', 'mandatory': False},
                                                                              {'name': 'Magnetization_linkage_ID', 'type': 'positive-int', 'mandatory': False},
                                                                              {'name': 'Val', 'type': 'range-float', 'mandatory': False,
                                                                               'range': CS_RESTRAINT_RANGE},
                                                                              {'name': 'Contribution_fractional_val', 'type': 'range-float', 'mandatory': False,
                                                                               'range': WEIGHT_RANGE},
                                                                              {'name': 'Figure_of_merit', 'type': 'range-float', 'mandatory': False,
                                                                               'range': WEIGHT_RANGE},
                                                                              {'name': 'Assigned_chem_shift_list_ID', 'type': 'pointer-index', 'mandatory': False},
                                                                              {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str', 'mandatory': False},
                                                                              {'name': 'Comp_index_ID', 'type': 'int', 'mandatory': False},
                                                                              {'name': 'Comp_ID', 'type': 'str', 'mandatory': False, 'uppercase': True},
                                                                              {'name': 'Atom_ID', 'type': 'str', 'mandatory': False},
                                                                              {'name': 'Ambiguity_code', 'type': 'enum-int', 'mandatory': False,
                                                                               'enum': ALLOWED_AMBIGUITY_CODES},
                                                                              {'name': 'Ambiguity_set_ID', 'type': 'positive-int', 'mandatory': False},
                                                                              {'name': 'Auth_seq_ID', 'type': 'int', 'mandatory': False},
                                                                              {'name': 'Auth_comp_ID', 'type': 'str', 'mandatory': False},
                                                                              {'name': 'Auth_atom_ID', 'type': 'str', 'mandatory': False},
                                                                              {'name': 'Spectral_peak_list_ID', 'type': 'pointer-index', 'mandatory': True,
                                                                               'default-from': 'parent'}
                                                                              ]
                                            },
                                            'noepk_restraint': None,
                                            'jcoup_restraint': None,
                                            'rdc_raw_data': None,
                                            'csa_restraint': None,
                                            'ddc_restraint': None,
                                            'hvycs_restraint': None,
                                            'procs_restraint': None,
                                            'csp_restraint': None,
                                            'auto_relax_restraint': None,
                                            'heteronucl_noe_data': None,
                                            'heteronucl_t1_data': None,
                                            'heteronucl_t2_data': None,
                                            'heteronucl_t1r_data': None,
                                            'order_param_data': None,
                                            'ccr_d_csa_restraint': None,
                                            'ccr_dd_restraint': None,
                                            'fchiral_restraint': None,
                                            'saxs_restraint': None,
                                            'other_restraint': None
                                            }
                               }

        # allowed auxiliary loop tags
        self.aux_allowed_tags = {'nef': {'entry_info': None,
                                         'poly_seq': {
                                             '_nef_covalent_links': ['chain_code_1', 'sequence_code_1', 'residue_name_1', 'atom_name_1',
                                                                     'chain_code_2', 'sequence_code_2', 'residue_name_2', 'atom_name_2'],
                                             '_nef_sequence': ['index', 'chain_code', 'sequence_code', 'residue_name', 'linking', 'residue_variant', 'cis_peptide']
                                         },
                                         'entity': None,
                                         'chem_shift': None,
                                         'chem_shift_ref': None,
                                         'dist_restraint': None,
                                         'dihed_restraint': None,
                                         'rdc_restraint': None,
                                         'spectral_peak': {
                                             '_nef_spectrum_dimension': ['dimension_id', 'axis_unit', 'axis_code',
                                                                         'spectrometer_frequency', 'spectral_width',
                                                                         'value_first_point', 'folding',
                                                                         'absolute_peak_positions', 'is_acquisition'],
                                             '_nef_spectrum_dimension_transfer': ['dimension_1', 'dimension_2', 'transfer_type', 'is_indirect']
                                         },
                                         'spectral_peak_alt': None,
                                         'noepk_restraint': None,
                                         'jcoup_restraint': None,
                                         'rdc_raw_data': None,
                                         'csa_restraint': None,
                                         'ddc_restraint': None,
                                         'hvycs_restraint': None,
                                         'procs_restraint': None,
                                         'csp_restraint': None,
                                         'auto_relax_restraint': None,
                                         'heteronucl_noe_data': None,
                                         'heteronucl_t1_data': None,
                                         'heteronucl_t2_data': None,
                                         'heteronucl_t1r_data': None,
                                         'order_param_data': None,
                                         'ccr_d_csa_restraint': None,
                                         'ccr_dd_restraint': None,
                                         'fchiral_restraint': None,
                                         'saxs_restraint': None,
                                         'other_restraint': None
                                         },
                                 'nmr-star': {'entry_info': None,
                                              'poly_seq': {
                                                  '_Bond': ['ID', 'Type', 'Value_order', 'Assembly_atom_ID_1', 'Entity_assembly_ID_1',
                                                            'Entity_assembly_name_1', 'Entity_ID_1', 'Comp_ID_1', 'Comp_index_ID_1',
                                                            'Seq_ID_1', 'Atom_ID_1',
                                                            'Assembly_atom_ID_2', 'Entity_assembly_ID_2', 'Entity_assembly_name_2',
                                                            'Entity_ID_2', 'Comp_ID_2', 'Comp_index_ID_2', 'Seq_ID_2', 'Atom_ID_2',
                                                            'Auth_entity_assembly_ID_1', 'Auth_entity_assembly_name_1', 'Auth_asym_ID_1',
                                                            'Auth_seq_ID_1', 'Auth_comp_ID_1', 'Auth_atom_ID_1',
                                                            'Auth_entity_assembly_ID_2', 'Auth_entity_assembly_name_2', 'Auth_asym_ID_2',
                                                            'Auth_seq_ID_2', 'Auth_comp_ID_2', 'Auth_atom_ID_2', 'Sf_ID', 'Entry_ID', 'Assembly_ID'],
                                                  '_Entity_deleted_atom': ['ID', 'Entity_atom_list_ID', 'Entity_assembly_ID', 'Entity_ID',
                                                                           'Comp_ID', 'Comp_index_ID', 'Seq_ID', 'Atom_ID', 'Auth_entity_assembly_ID',
                                                                           'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID', 'Sf_ID', 'Entry_ID', 'Assembly_ID'],
                                                  '_Entity_assembly': ['ID', 'Entity_assembly_name', 'Entity_ID', 'Entity_label', 'Asym_ID', 'PDB_chain_ID',
                                                                       'Experimental_data_reported', 'Physical_state', 'Conformational_isomer', 'Chemical_exchange_state',
                                                                       'Magnetic_equivalence_group_code', 'Role', 'Details', 'Sf_ID', 'Entry_ID', 'Assembly_ID']
                                              },
                                              'entity': None,
                                              'chem_shift': {
                                                  '_Ambiguous_atom_chem_shift': ['Ambiguous_shift_set_ID', 'Atom_chem_shift_ID',
                                                                                 'Sf_ID', 'Entry_ID', 'Assigned_chem_shift_list_ID']
                                              },
                                              'chem_shift_ref': None,
                                              'dist_restraint': None,
                                              'dihed_restraint': None,
                                              'rdc_restraint': None,
                                              'spectral_peak': {
                                                  '_Spectral_dim': ['ID', 'Axis_code', 'Spectrometer_frequency', 'Atom_type',
                                                                    'Atom_isotope_number', 'Spectral_region', 'Magnetization_linkage_ID',
                                                                    'Under_sampling_type', 'Sweep_width', 'Sweep_width_units', 'Value_first_point',
                                                                    'Absolute_peak_positions', 'Acquisition', 'Center_frequency_offset',
                                                                    'Encoding_code', 'Encoded_reduced_dimension_ID', 'Sf_ID', 'Entry_ID',
                                                                    'Spectral_peak_list_ID'],
                                                  '_Spectral_dim_transfer': ['Spectral_dim_ID_1', 'Spectral_dim_ID_2', 'Indirect', 'Type',
                                                                             'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID']
                                              },
                                              'spectral_peak_alt': {
                                                  '_Spectral_dim': ['ID', 'Axis_code', 'Spectrometer_frequency', 'Atom_type', 'Atom_isotope_number',
                                                                    'Spectral_region', 'Magnetization_linkage_ID', 'Under_sampling_type', 'Sweep_width',
                                                                    'Sweep_width_units', 'Value_first_point', 'Absolute_peak_positions', 'Acquisition',
                                                                    'Center_frequency_offset', 'Encoding_code', 'Encoded_reduced_dimension_ID',
                                                                    'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                                  '_Spectral_dim_transfer': ['Spectral_dim_ID_1', 'Spectral_dim_ID_2', 'Indirect',
                                                                             'Type', 'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                                  '_Peak_general_char': ['Peak_ID', 'Intensity_val', 'Intensity_val_err', 'Measurement_method',
                                                                         'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                                  '_Peak_char': ['Peak_ID', 'Spectral_dim_ID', 'Chem_shift_val', 'Chem_shift_val_err', 'Line_width_val',
                                                                 'Line_width_val_err', 'Phase_val', 'Phase_val_err', 'Decay_rate_val', 'Decay_rate_val_err',
                                                                 'Coupling_pattern', 'Bounding_box_upper_val', 'Bounding_box_lower_val', 'Bounding_box_range_val',
                                                                 'Details', 'Derivation_method_ID', 'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID'],
                                                  '_Assigned_peak_chem_shift': ['Peak_ID', 'Spectral_dim_ID', 'Set_ID', 'Magnetization_linkage_ID', 'Assembly_atom_ID',
                                                                                'Val', 'Contribution_fractional_val', 'Figure_of_merit', 'Assigned_chem_shift_list_ID',
                                                                                'Atom_chem_shift_ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Comp_ID',
                                                                                'Atom_ID', 'Ambiguity_code', 'Ambiguity_set_ID', 'Auth_atom_peak_num', 'Auth_entity_ID',
                                                                                'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID', 'Auth_ambiguity_code',
                                                                                'Auth_ambiguity_set_ID', 'Auth_amb_atom_grp_ID', 'Resonance_ID', 'Details',
                                                                                'Sf_ID', 'Entry_ID', 'Spectral_peak_list_ID']
                                              },
                                              'noepk_restraint': None,
                                              'jcoup_restraint': None,
                                              'rdc_raw_data': None,
                                              'csa_restraint': None,
                                              'ddc_restraint': None,
                                              'hvycs_restraint': None,
                                              'procs_restraint': None,
                                              'csp_restraint': None,
                                              'auto_relax_restraint': None,
                                              'heteronucl_noe_data': None,
                                              'heteronucl_t1_data': None,
                                              'heteronucl_t2_data': None,
                                              'heteronucl_t1r_data': None,
                                              'order_param_data': None,
                                              'ccr_d_csa_restraint': None,
                                              'ccr_dd_restraint': None,
                                              'fchiral_restraint': None,
                                              'saxs_restraint': None,
                                              'other_restraint': None
                                              }
                                 }

        # item name in cs loop
        self.item_names_in_cs_loop = {'nef': {'chain_id': 'chain_code',
                                              'seq_id': 'sequence_code',
                                              'comp_id': 'residue_name',
                                              'atom_id': 'atom_name',
                                              'value': 'value',
                                              'error': 'value_uncertainty',
                                              'atom_type': 'element',
                                              'isotope_number': 'isotope_number'
                                              },
                                      'nmr-star': {'chain_id': 'Entity_assembly_ID',
                                                   'seq_id': 'Comp_index_ID',
                                                   'comp_id': 'Comp_ID',
                                                   'atom_id': 'Atom_ID',
                                                   'value': 'Val',
                                                   'error': 'Val_err',
                                                   'atom_type': 'Atom_type',
                                                   'isotope_number': 'Atom_isotope_number',
                                                   'alt_seq_id': 'Seq_ID'
                                                   }
                                      }

        # item name in spectral peak loop
        self.item_names_in_pk_loop = {'nef': {'chain_id': 'chain_code_%s',
                                              'seq_id': 'sequence_code_%s',
                                              'comp_id': 'residue_name_%s',
                                              'atom_id': 'atom_name_%s',
                                              'position': 'position_%s'
                                              },
                                      'nmr-star': {'chain_id': 'Entity_assembly_ID_%s',
                                                   'seq_id': 'Comp_index_ID_%s',
                                                   'comp_id': 'Comp_ID_%s',
                                                   'atom_id': 'Atom_ID_%s',
                                                   'position': 'Position_%s',
                                                   'alt_seq_id': 'Seq_ID_%s'
                                                   }
                                      }

        # item name in distance restraint loop
        self.item_names_in_ds_loop = {'nef': {'combination_id': 'restraint_combination_id',
                                              'chain_id_1': 'chain_code_1',
                                              'seq_id_1': 'sequence_code_1',
                                              'comp_id_1': 'residue_name_1',
                                              'atom_id_1': 'atom_name_1',
                                              'chain_id_2': 'chain_code_2',
                                              'seq_id_2': 'sequence_code_2',
                                              'comp_id_2': 'residue_name_2',
                                              'atom_id_2': 'atom_name_2',
                                              'target_value': 'target_value',
                                              'lower_linear_limit': 'lower_linear_limit',
                                              'upper_linear_limit': 'upper_linear_limit',
                                              'lower_limit': 'lower_limit',
                                              'upper_limit': 'upper_limit'
                                              },
                                      'nmr-star': {'combination_id': 'Combination_ID',
                                                   'chain_id_1': 'Entity_assembly_ID_1',
                                                   'seq_id_1': 'Comp_index_ID_1',
                                                   'comp_id_1': 'Comp_ID_1',
                                                   'atom_id_1': 'Atom_ID_1',
                                                   'chain_id_2': 'Entity_assembly_ID_2',
                                                   'seq_id_2': 'Comp_index_ID_2',
                                                   'comp_id_2': 'Comp_ID_2',
                                                   'atom_id_2': 'Atom_ID_2',
                                                   'target_value': 'Target_val',
                                                   'target_value_alt': 'Distance_val',
                                                   'lower_linear_limit': 'Lower_linear_limit',
                                                   'upper_linear_limit': 'Upper_linear_limit',
                                                   'lower_limit': 'Distance_lower_bound_val',
                                                   'upper_limit': 'Distance_upper_bound_val',
                                                   'alt_seq_id_1': 'Seq_ID_1',
                                                   'alt_seq_id_2': 'Seq_ID_2',
                                                   'member_id': 'Member_ID',
                                                   'member_logic_code': 'Member_logic_code',
                                                   }
                                      }

        # item name in dihedral angle restraint loop
        self.item_names_in_dh_loop = {'nef': {'combination_id': 'restraint_combination_id',
                                              'chain_id_1': 'chain_code_1',
                                              'seq_id_1': 'sequence_code_1',
                                              'comp_id_1': 'residue_name_1',
                                              'atom_id_1': 'atom_name_1',
                                              'chain_id_2': 'chain_code_2',
                                              'seq_id_2': 'sequence_code_2',
                                              'comp_id_2': 'residue_name_2',
                                              'atom_id_2': 'atom_name_2',
                                              'chain_id_3': 'chain_code_3',
                                              'seq_id_3': 'sequence_code_3',
                                              'comp_id_3': 'residue_name_3',
                                              'atom_id_3': 'atom_name_3',
                                              'chain_id_4': 'chain_code_4',
                                              'seq_id_4': 'sequence_code_4',
                                              'comp_id_4': 'residue_name_4',
                                              'atom_id_4': 'atom_name_4',
                                              'angle_type': 'name',
                                              'target_value': 'target_value',
                                              'lower_linear_limit': 'lower_linear_limit',
                                              'upper_linear_limit': 'upper_linear_limit',
                                              'lower_limit': 'lower_limit',
                                              'upper_limit': 'upper_limit'
                                              },
                                      'nmr-star': {'combination_id': 'Combination_ID',
                                                   'chain_id_1': 'Entity_assembly_ID_1',
                                                   'seq_id_1': 'Comp_index_ID_1',
                                                   'comp_id_1': 'Comp_ID_1',
                                                   'atom_id_1': 'Atom_ID_1',
                                                   'chain_id_2': 'Entity_assembly_ID_2',
                                                   'seq_id_2': 'Comp_index_ID_2',
                                                   'comp_id_2': 'Comp_ID_2',
                                                   'atom_id_2': 'Atom_ID_2',
                                                   'chain_id_3': 'Entity_assembly_ID_3',
                                                   'seq_id_3': 'Comp_index_ID_3',
                                                   'comp_id_3': 'Comp_ID_3',
                                                   'atom_id_3': 'Atom_ID_3',
                                                   'chain_id_4': 'Entity_assembly_ID_4',
                                                   'seq_id_4': 'Comp_index_ID_4',
                                                   'comp_id_4': 'Comp_ID_4',
                                                   'atom_id_4': 'Atom_ID_4',
                                                   'angle_type': 'Torsion_angle_name',
                                                   'alt_seq_id_1': 'Seq_ID_1',
                                                   'alt_seq_id_2': 'Seq_ID_2',
                                                   'alt_seq_id_3': 'Seq_ID_3',
                                                   'alt_seq_id_4': 'Seq_ID_4',
                                                   'target_value': 'Angle_target_val',
                                                   'lower_linear_limit': 'Angle_lower_linear_limit',
                                                   'upper_linear_limit': 'Angle_upper_linear_limit',
                                                   'lower_limit': 'Angle_lower_bound_val',
                                                   'upper_limit': 'Angle_upper_bound_val'
                                                   }
                                      }

        # item name in RDC restraint loop
        self.item_names_in_rdc_loop = {'nef': {'combination_id': 'restraint_combination_id',
                                               'chain_id_1': 'chain_code_1',
                                               'seq_id_1': 'sequence_code_1',
                                               'comp_id_1': 'residue_name_1',
                                               'atom_id_1': 'atom_name_1',
                                               'chain_id_2': 'chain_code_2',
                                               'seq_id_2': 'sequence_code_2',
                                               'comp_id_2': 'residue_name_2',
                                               'atom_id_2': 'atom_name_2',
                                               'target_value': 'target_value',
                                               'lower_linear_limit': 'lower_linear_limit',
                                               'upper_linear_limit': 'upper_linear_limit',
                                               'lower_limit': 'lower_limit',
                                               'upper_limit': 'upper_limit'
                                               },
                                       'nmr-star': {'combination_id': 'Combination_ID',
                                                    'chain_id_1': 'Entity_assembly_ID_1',
                                                    'seq_id_1': 'Comp_index_ID_1',
                                                    'comp_id_1': 'Comp_ID_1',
                                                    'atom_id_1': 'Atom_ID_1',
                                                    'chain_id_2': 'Entity_assembly_ID_2',
                                                    'seq_id_2': 'Comp_index_ID_2',
                                                    'comp_id_2': 'Comp_ID_2',
                                                    'atom_id_2': 'Atom_ID_2',
                                                    'alt_seq_id_1': 'Seq_ID_1',
                                                    'alt_seq_id_2': 'Seq_ID_2',
                                                    'target_value': 'Target_value',
                                                    'lower_linear_limit': 'RDC_lower_linear_limit',
                                                    'upper_linear_limit': 'RDC_upper_linear_limit',
                                                    'lower_limit': 'RDC_lower_bound',
                                                    'upper_limit': 'RDC_upper_bound'
                                                    }
                                       }

        # saveframe tag name for chemical shift list in spectral peak
        self.cs_list_sf_tag_name = {'nef': 'chemical_shift_list',
                                    'nmr-star': 'Chemical_shift_list'
                                    }

        # patterns for enum failure message
        self.chk_desc_pat = re.compile(r'^(.*) \'(.*)\' should be one of \((.*)\)\.(.*)$')
        self.chk_desc_pat_one = re.compile(r'^(.*) \'(.*)\' should be one of (.*)\.(.*)$')
        self.chk_desc_pat_mand = re.compile(r'^The mandatory type _.*\.(.*) \'(.*)\' is missing and the type must be one of \((.*)\)\.(.*)$')
        self.chk_desc_pat_mand_one = re.compile(r'^The mandatory type _.*\.(.*) \'(.*)\' is missing and the type must be one of (.*)\.(.*)$')

        # pattern for guessing original saveframe name DAOTHER-7389, issue #4
        self.chk_unresolved_sf_name_pat = re.compile(r'^(.*)_\d+$')

        # main contents of loops
        self.__lp_data = {'entry_info': [],
                          'poly_seq': [],
                          'entity': [],
                          'chem_shift': [],
                          'chem_shift_ref': [],
                          'dist_restraint': [],
                          'dihed_restraint': [],
                          'rdc_restraint': [],
                          'spectral_peak': [],
                          'spectral_peak_alt': [],
                          'noepk_restraint': [],
                          'jcoup_restraint': [],
                          'rdc_raw_data': [],
                          'csa_restraint': [],
                          'ddc_restraint': [],
                          'hvycs_restraint': [],
                          'procs_restraint': [],
                          'csp_restraint': [],
                          'auto_relax_restraint': [],
                          'heteronucl_noe_data': [],
                          'heteronucl_t1_data': [],
                          'heteronucl_t2_data': [],
                          'heteronucl_t1r_data': [],
                          'order_param_data': [],
                          'ccr_d_csa_restraint': [],
                          'ccr_dd_restraint': [],
                          'fchiral_restraint': [],
                          'saxs_restraint': [],
                          'other_restraint': []
                          }

        # auxiliary contents of loops
        self.__aux_data = {'entry_info': [],
                           'poly_seq': [],
                           'entity': [],
                           'chem_shift': [],
                           'chem_shift_ref': [],
                           'dist_restraint': [],
                           'dihed_restraint': [],
                           'rdc_restraint': [],
                           'spectral_peak': [],
                           'spectral_peak_alt': [],
                           'noepk_restraint': [],
                           'jcoup_restraint': [],
                           'rdc_raw_data': [],
                           'csa_restraint': [],
                           'ddc_restraint': [],
                           'hvycs_restraint': [],
                           'procs_restraint': [],
                           'csp_restraint': [],
                           'auto_relax_restraint': [],
                           'heteronucl_noe_data': [],
                           'heteronucl_t1_data': [],
                           'heteronucl_t2_data': [],
                           'heteronucl_t1r_data': [],
                           'order_param_data': [],
                           'ccr_d_csa_restraint': [],
                           'ccr_dd_restraint': [],
                           'fchiral_restraint': [],
                           'saxs_restraint': [],
                           'other_restraint': []
                           }

        # contents of savefram tags
        self.__sf_tag_data = {'entry_info': [],
                              'poly_seq': [],
                              'entity': [],
                              'chem_shift': [],
                              'chem_shift_ref': [],
                              'dist_restraint': [],
                              'dihed_restraint': [],
                              'rdc_restraint': [],
                              'spectral_peak': [],
                              'spectral_peak_alt': [],
                              'noepk_restraint': [],
                              'jcoup_restraint': [],
                              'rdc_raw_data': [],
                              'csa_restraint': [],
                              'ddc_restraint': [],
                              'hvycs_restraint': [],
                              'procs_restraint': [],
                              'csp_restraint': [],
                              'auto_relax_restraint': [],
                              'heteronucl_noe_data': [],
                              'heteronucl_t1_data': [],
                              'heteronucl_t2_data': [],
                              'heteronucl_t1r_data': [],
                              'order_param_data': [],
                              'ccr_d_csa_restraint': [],
                              'ccr_dd_restraint': [],
                              'fchiral_restraint': [],
                              'saxs_restraint': [],
                              'other_restraint': []
                              }

        self.authSeqMap = None

        # Pairwise align
        self.__pA = PairwiseAlign()
        self.__pA.setVerbose(self.__verbose)

        # experimental method
        self.__exptl_method = ''

        # whether solid-state NMR is applied to symmetric samples such as fibrils
        self.__symmetric = None
        # representative model id
        self.__representative_model_id = REPRESENTATIVE_MODEL_ID
        # representative_alt_id
        self.__representative_alt_id = REPRESENTATIVE_ALT_ID
        # total number of models
        self.__total_models = 0
        # list of effective model_id
        self.__eff_model_ids = None
        # item tag names of 'atom_site' category of the coordinates
        self.__coord_atom_site_tags = None
        # atom id list in model
        self.__coord_atom_site = None
        # residues not observed in the coordinates (DAOTHER-7665)
        self.__coord_unobs_res = None
        # conversion dictionary from auth_seq_id to label_seq_id of the coordinates
        self.__auth_to_label_seq = None
        # conversion dictionary from label_seq_id to auth_seq_id of the coordinates
        self.__label_to_auth_seq = None
        # tautomeric state in model
        self.__coord_tautomer = {}
        # rotamer state in model
        self.__coord_rotamer = {}
        # nearest aromatic ring in model
        self.__coord_near_ring = {}
        # nearest paramagnetic/ferromagnetic atom in model
        self.__coord_near_para_ferro = {}
        # bond length in model
        self.__coord_bond_length = {}

        # sub-directory name for cache file
        self.__sub_dir_name_for_cache = 'utils_nmr'

        # CIF reader
        self.__cR = CifReader(self.__verbose, self.__lfh,
                              use_cache=True,
                              sub_dir_name_for_cache=self.__sub_dir_name_for_cache)

        # ParserListerUtil.coordAssemblyChecker()
        self.__caC = None

        # set of entity_assembly_id having experimental data
        self.__ent_asym_id_with_exptl_data = set()
        # set of label_aysm_id having experimental data
        self.__label_asym_id_with_exptl_data = set()
        # set of auth_asym_id indicating occurence of chemical exchange (eNOE)
        self.__auth_asym_ids_with_chem_exch = {}
        # set of residue numbering scheme indicating occurrence of chemical exchange (eNOE)
        self.__auth_seq_ids_with_chem_exch = {}

        # extracted conformational annotation of coordinate file
        self.__nmr_struct_conf = {}
        # whether nmr chain is cyclic polymer or not
        self.__is_cyclic_polymer = {}
        # mapping of chain_id for remediation
        self.__chain_id_map_for_remediation = {}
        # mapping of chain_id and seq_id for remediation
        self.__seq_id_map_for_remediation = {}

        # used for debuging only, it should be empty for production
        self.__target_framecode = ''

        # suspended error items for lazy evaluation
        self.__suspended_errors_for_lazy_eval = []
        # suspended warning items for lazy evaluation
        self.__suspended_warnings_for_lazy_eval = []

        # atom name mapping of public MR file between the coordinates and submitted file
        self.__mr_atom_name_mapping = None

        # RCI
        self.__rci = RCI(False, self.__lfh)

    def setVerbose(self, verbose):
        """ Set verbose mode.
        """

        self.__verbose = verbose
        self.__debug = verbose

    def setMrDebugMode(self, debug):
        """ Set debug mode for MR splitter.
        """

        self.__mr_debug = debug

    def setSource(self, fPath, originalName=None):
        """ Set primary source file path.
        """

        if os.access(fPath, os.F_OK):
            self.__srcPath = os.path.abspath(fPath)
            if originalName is not None:
                self.__srcName = originalName
            else:
                self.__srcName = os.path.basename(self.__srcPath)

        else:
            raise IOError(f"+NmrDpUtility.setSource() ++ Error  - Could not access to file path {fPath}.")

    def setDestination(self, fPath):
        """ Set primary destination file path.
        """

        if fPath is not None:
            self.__dstPath = os.path.abspath(fPath)
            self.__dstPath__ = copy.copy(self.__dstPath)

    def setLog(self, fPath):
        """ Set a log file path for the primary input source.
        """

        if fPath is not None:
            self.__logPath = os.path.abspath(fPath)

    def addInput(self, name=None, value=None, type='file'):  # pylint: disable=redefined-builtin
        """ Add a named input and value to the dictionary of input parameters.
        """

        try:

            if type == 'param':
                self.__inputParamDict[name] = value
            elif type == 'file':
                self.__inputParamDict[name] = os.path.abspath(value)
            elif type == 'file_list':
                self.__inputParamDict[name] = [os.path.abspath(f) for f in value]
            elif type == 'file_dict_list':
                if any(f for f in value if 'original_file_name' in f):
                    self.__inputParamDict[name] = []
                    for f in value:
                        if 'original_file_name' in f:
                            self.__inputParamDict[name].append({'file_name': os.path.abspath(f['file_name']), 'file_type': f['file_type'],
                                                                'original_file_name': f['original_file_name']})
                        else:
                            self.__inputParamDict[name].append({'file_name': os.path.abspath(f['file_name']), 'file_type': f['file_type']})
                else:
                    self.__inputParamDict[name] = [{'file_name': os.path.abspath(f['file_name']), 'file_type': f['file_type']} for f in value]
            else:
                raise ValueError(f"+NmrDpUtility.addInput() ++ Error  - Unknown input type {type}.")

        except Exception as e:
            raise ValueError("+NmrDpUtility.addInput() ++ Error  - " + str(e))

    def addOutput(self, name=None, value=None, type='file'):  # pylint: disable=redefined-builtin
        """ Add a named input and value to the dictionary of output parameters.
        """

        try:

            if type == 'param':
                self.__outputParamDict[name] = value
            elif type == 'file':
                self.__outputParamDict[name] = os.path.abspath(value)
            elif type == 'file_list':
                self.__outputParamDict[name] = [os.path.abspath(f) for f in value]
            else:
                raise ValueError(f"+NmrDpUtility.addOutput() ++ Error  - Unknown output type {type}.")

            return True

        except Exception as e:
            raise ValueError("+NmrDpUtility.addOutput() ++ Error  - " + str(e))

    def op(self, op):
        """ Perform a series of tasks for a given workflow operation.
        """

        self.__rescue_mode = True

        self.__combined_mode = 'cs' not in op

        if self.__combined_mode:
            if self.__srcPath is None:
                raise ValueError(f"+NmrDpUtility.op() ++ Error  - No input provided for workflow operation {op}.")

            self.__cs_file_path_list_len = 0
            self.__file_path_list_len = 1

        else:
            cs_file_path_list = 'chem_shift_file_path_list'

            if cs_file_path_list not in self.__inputParamDict:
                raise ValueError(f"+NmrDpUtility.op() ++ Error  - No input provided for workflow operation {op}.")

            self.__cs_file_path_list_len = len(self.__inputParamDict[cs_file_path_list])
            self.__file_path_list_len = self.__cs_file_path_list_len

            mr_file_path_list = 'restraint_file_path_list'

            if mr_file_path_list in self.__inputParamDict:
                self.__file_path_list_len += len(self.__inputParamDict[mr_file_path_list])

        self.__cifPath = self.__cifHashCode = None
        self.__cifChecked = False

        # incomplete assignments are edited by biocurators for conventional assigned cemical shifts (DAOTHER-7662)
        for key in self.key_items['nmr-star']['chem_shift']:
            if 'remove-bad-pattern' in key:
                key['remove-bad-pattern'] = self.__combined_mode

        if has_key_value(self.__inputParamDict, 'remediation'):
            if isinstance(self.__inputParamDict['remediation'], bool):
                self.__remediation_mode = self.__inputParamDict['remediation']
            else:
                self.__remediation_mode = self.__inputParamDict['remediation'] in trueValue

        if op == 'nmr-cs-mr-merge':

            if has_key_value(self.__inputParamDict, 'internal'):
                if isinstance(self.__inputParamDict['internal'], bool):
                    self.__internal_mode = self.__inputParamDict['internal']
                else:
                    self.__internal_mode = self.__inputParamDict['internal'] in trueValue

            self.__remediation_mode = True
            self.__has_star_chem_shift = True

            if self.__inputParamDictCopy is None:
                self.__inputParamDictCopy = copy.deepcopy(self.__inputParamDict)

            for v in self.key_items['nmr-star'].values():
                if v is None:
                    continue
                for d in v:
                    if d['name'].startswith('Entity_assembly_ID'):
                        d['type'] = 'str'
                        d['default'] = 'A'
                        if 'default-from' in d:
                            del d['default-from']

            for v in self.consist_key_items['nmr-star'].values():
                if v is None:
                    continue
                for d in v:
                    if d['name'].startswith('Entity_assembly_ID'):
                        d['type'] = 'str'
                        d['default'] = 'A'
                        if 'default-from' in d:
                            del d['default-from']

            for d in self.pk_data_items['nmr-star']:
                if d['name'].startswith('Entity_assembly_ID'):
                    d['type'] = 'str'
                    d['default'] = 'A'
                    if 'default-from' in d:
                        del d['default-from']
                    if 'enforce-non-zero' in d:
                        del d['enforce-non-zero']

            for v in self.aux_key_items['nmr-star'].values():
                if v is None:
                    continue
                for v2 in v.values():
                    for d in v2:
                        if d['name'].startswith('Entity_assembly_ID'):
                            d['type'] = 'str'
                            d['default'] = 'A'
                            if 'default-from' in d:
                                del d['default-from']

            for v in self.aux_data_items['nmr-star'].values():
                if v is None:
                    continue
                for v2 in v.values():
                    for d in v2:
                        if d['name'].startswith('Entity_assembly_ID'):
                            d['type'] = 'str'
                            d['default'] = 'A'
                            if 'default-from' in d:
                                del d['default-from']

        elif self.__combined_mode and not self.__remediation_mode:
            self.__native_combined = True

        self.__remediation_loop_count = 0

        self.__sll_pred_holder = {}

        self.__nefT.set_remediation_mode(self.__remediation_mode)

        if not self.__allow_missing_legacy_dist_restraint and self.__remediation_mode:
            self.__nefT.allow_missing_dist_restraint(True)
            self.__allow_missing_dist_restraint = self.__allow_missing_legacy_dist_restraint = True

        self.__annotation_mode = 'annotate' in op
        self.__release_mode = 'release' in op

        if self.__verbose:
            self.__lfh.write(f"+NmrDpUtility.op() starting op {op}\n")

        if op not in self.__workFlowOps:
            raise KeyError(f"+NmrDpUtility.op() ++ Error  - Unknown workflow operation {op}.")

        if 'cif' in op and 'nmr_cif_file_path' not in self.__outputParamDict:
            raise KeyError("+NmrDpUtility.op() ++ Error  - Could not find 'nmr_cif_file_path' output parameter.")

        if has_key_value(self.__inputParamDict, 'bmrb_only'):
            if isinstance(self.__inputParamDict['bmrb_only'], bool):
                self.__bmrb_only = self.__inputParamDict['bmrb_only']
            else:
                self.__bmrb_only = self.__inputParamDict['bmrb_only'] in trueValue

        if self.__bmrb_only:
            self.cs_anomalous_error_scaled_by_sigma = 4.0
            self.cs_unusual_error_scaled_by_sigma = 3.5
            self.cs_diff_error_scaled_by_sigma = 5.0
            self.__nefT.set_bmrb_only_mode(True)

            if has_key_value(self.__inputParamDict, 'bmrb_id'):
                if isinstance(self.__inputParamDict['bmrb_id'], int):
                    self.__bmrb_id = str(self.__inputParamDict['bmrb_id'])
                elif isinstance(self.__inputParamDict['bmrb_id'], str):
                    self.__bmrb_id = self.__inputParamDict['bmrb_id']
                if self.__bmrb_id is not None:
                    if bmrb_id_pattern.match(self.__bmrb_id):
                        if self.__bmrb_id.startswith('bmr'):
                            self.__bmrb_id = self.__bmrb_id[3:]
                    else:
                        self.__bmrb_id = None

                if self.__bmrb_id is not None:
                    self.__entry_id = self.__bmrb_id.strip().replace(' ', '_')  # DAOTHER-9511: replace white space in a datablock name to underscore

        entity_name_item = next(item for item in self.sf_tag_items['nmr-star']['entity'] if item['name'] == 'Name')
        entity_name_item['mandatory'] = self.__bmrb_only

        if has_key_value(self.__inputParamDict, 'merge_any_pk_as_is'):
            if isinstance(self.__inputParamDict['merge_any_pk_as_is'], bool):
                self.__merge_any_pk_as_is = self.__inputParamDict['merge_any_pk_as_is']
            else:
                self.__merge_any_pk_as_is = self.__inputParamDict['merge_any_pk_as_is'] in trueValue

        if has_key_value(self.__inputParamDict, 'nonblk_anomalous_cs'):
            if isinstance(self.__inputParamDict['nonblk_anomalous_cs'], bool):
                self.__nonblk_anomalous_cs = self.__inputParamDict['nonblk_anomalous_cs']
            else:
                self.__nonblk_anomalous_cs = self.__inputParamDict['nonblk_anomalous_cs'] in trueValue

        if has_key_value(self.__inputParamDict, 'nonblk_bad_nterm'):
            if isinstance(self.__inputParamDict['nonblk_bad_nterm'], bool):
                self.__nonblk_bad_nterm = self.__inputParamDict['nonblk_bad_nterm']
            else:
                self.__nonblk_bad_nterm = self.__inputParamDict['nonblk_bad_nterm'] in trueValue

        if has_key_value(self.__inputParamDict, 'update_poly_seq'):
            if isinstance(self.__inputParamDict['update_poly_seq'], bool):
                self.__update_poly_seq = self.__inputParamDict['update_poly_seq']
            else:
                self.__update_poly_seq = self.__inputParamDict['update_poly_seq'] in trueValue

        if has_key_value(self.__inputParamDict, 'resolve_conflict'):
            if isinstance(self.__inputParamDict['resolve_conflict'], bool):
                self.__resolve_conflict = self.__inputParamDict['resolve_conflict']
            else:
                self.__resolve_conflict = self.__inputParamDict['resolve_conflict'] in trueValue

        if has_key_value(self.__inputParamDict, 'check_mandatory_tag'):
            if isinstance(self.__inputParamDict['check_mandatory_tag'], bool):
                self.__check_mandatory_tag = self.__inputParamDict['check_mandatory_tag']
            else:
                self.__check_mandatory_tag = self.__inputParamDict['check_mandatory_tag'] in trueValue

        if has_key_value(self.__inputParamDict, 'check_auth_seq'):
            if isinstance(self.__inputParamDict['check_auth_seq'], bool):
                self.__check_auth_seq = self.__inputParamDict['check_auth_seq']
            else:
                self.__check_auth_seq = self.__inputParamDict['check_auth_seq'] in trueValue

        if has_key_value(self.__inputParamDict, 'validation_server'):
            if isinstance(self.__inputParamDict['validation_server'], bool):
                self.__validation_server = self.__inputParamDict['validation_server']
            else:
                self.__validation_server = self.__inputParamDict['validation_server'] in trueValue

        if has_key_value(self.__inputParamDict, 'transl_pseudo_name'):
            if isinstance(self.__inputParamDict['transl_pseudo_name'], bool):
                self.__transl_pseudo_name = self.__inputParamDict['transl_pseudo_name']
            else:
                self.__transl_pseudo_name = self.__inputParamDict['transl_pseudo_name'] in trueValue
        elif op in ('nmr-str-consistency-check', 'nmr-str2str-deposit', 'nmr-str2cif-deposit', 'nmr-str2nef-release', 'nmr-str2cif-annotate'):
            self.__transl_pseudo_name = True

        if has_key_value(self.__inputParamDict, 'tolerant_seq_align'):
            if isinstance(self.__inputParamDict['tolerant_seq_align'], bool):
                self.__tolerant_seq_align = self.__inputParamDict['tolerant_seq_align']
            else:
                self.__tolerant_seq_align = self.__inputParamDict['tolerant_seq_align'] in trueValue

        if has_key_value(self.__inputParamDict, 'fix_format_issue'):
            if isinstance(self.__inputParamDict['fix_format_issue'], bool):
                self.__fix_format_issue = self.__inputParamDict['fix_format_issue']
            else:
                self.__fix_format_issue = self.__inputParamDict['fix_format_issue'] in trueValue
        elif not self.__combined_mode or self.__release_mode:
            self.__fix_format_issue = True

        if has_key_value(self.__inputParamDict, 'excl_missing_data'):
            if isinstance(self.__inputParamDict['excl_missing_data'], bool):
                self.__excl_missing_data = self.__inputParamDict['excl_missing_data']
            else:
                self.__excl_missing_data = self.__inputParamDict['excl_missing_data'] in trueValue
        elif not self.__combined_mode:
            self.__excl_missing_data = True

        if has_key_value(self.__inputParamDict, 'cmpl_missing_data'):
            if isinstance(self.__inputParamDict['cmpl_missing_data'], bool):
                self.__cmpl_missing_data = self.__inputParamDict['cmpl_missing_data']
            else:
                self.__cmpl_missing_data = self.__inputParamDict['cmpl_missing_data'] in trueValue
        elif not self.__combined_mode:
            self.__cmpl_missing_data = True

        if has_key_value(self.__inputParamDict, 'trust_pdbx_nmr_ens'):
            if isinstance(self.__inputParamDict['trust_pdbx_nmr_ens'], bool):
                self.__trust_pdbx_nmr_ens = self.__inputParamDict['trust_pdbx_nmr_ens']
            else:
                self.__trust_pdbx_nmr_ens = self.__inputParamDict['trust_pdbx_nmr_ens'] in trueValue
        elif self.__release_mode:
            self.__trust_pdbx_nmr_ens = True

        if has_key_value(self.__inputParamDict, 'rmsd_not_superimposed'):
            if isinstance(self.__inputParamDict['rmsd_not_superimposed'], float):
                self.rmsd_not_superimposed = self.__inputParamDict['rmsd_not_superimposed']

        if has_key_value(self.__inputParamDict, 'rmsd_overlaid_exactly'):
            if isinstance(self.__inputParamDict['rmsd_overlaid_exactly'], float):
                self.rmsd_overlaid_exactly = self.__inputParamDict['rmsd_overlaid_exactly']

        if has_key_value(self.__outputParamDict, 'entry_id'):
            self.__entry_id = self.__outputParamDict['entry_id'].strip().replace(' ', '_')  # DAOTHER-9511: replace white space in a datablock name to underscore

        if has_key_value(self.__outputParamDict, 'retain_original'):
            if isinstance(self.__outputParamDict['retain_original'], bool):
                self.__retain_original = self.__outputParamDict['retain_original']
            else:
                self.__retain_original = self.__outputParamDict['retain_original'] in trueValue

        if has_key_value(self.__outputParamDict, 'leave_intl_note'):
            if isinstance(self.__outputParamDict['leave_intl_note'], bool):
                self.__leave_intl_note = self.__outputParamDict['leave_intl_note']
            else:
                self.__leave_intl_note = self.__outputParamDict['leave_intl_note'] in trueValue

        if has_key_value(self.__outputParamDict, 'reduced_atom_notation'):
            if isinstance(self.__outputParamDict['reduced_atom_notation'], bool):
                self.__reduced_atom_notation = self.__outputParamDict['reduced_atom_notation']
            else:
                self.__reduced_atom_notation = self.__outputParamDict['reduced_atom_notation'] in trueValue

        self.__op = op

        if op.endswith('consistency-check'):

            for task in self.__procTasksDict['consistency-check']:

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.op() starting op {op} - task {task.__name__}\n")

                start_time = time.time()

                if not task():
                    pass

                if self.__debug:
                    end_time = time.time()
                    if end_time - start_time > 1.0:
                        self.__lfh.write(f"op: {op}, task: {task.__name__}, elapsed time: {end_time - start_time:.1f} sec\n")

        elif op.endswith('deposit') or op.endswith('release'):

            for task in self.__procTasksDict['deposit']:

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.op() starting op {op} - task {task.__name__}\n")

                start_time = time.time()

                if not task():
                    pass

                if self.__debug:
                    end_time = time.time()
                    if end_time - start_time > 1.0:
                        self.__lfh.write(f"op: {op}, task: {task.__name__}, elapsed time: {end_time - start_time:.1f} sec\n")

        # run workflow operation specific tasks
        if op in self.__procTasksDict:

            for task in self.__procTasksDict[op]:

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.op() starting op {op} - task {task.__name__}\n")

                start_time = time.time()

                if not task():
                    if task.__name__ in (self.__translateNef2Str.__name__, self.__translateStr2Nef.__name__):
                        break

                if self.__debug:
                    end_time = time.time()
                    if end_time - start_time > 1.0:
                        self.__lfh.write(f"op: {op}, task: {task.__name__}, elapsed time: {end_time - start_time:.1f} sec\n")

        self.__dumpDpReport()

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        if ((self.__op == 'nmr-cs-mr-merge'
             and self.report.error.getValueList('missing_mandatory_content',
                                                input_source_dic['file_name'],
                                                key='_Atom_chem_shift') is not None)
            or (self.__op in ('nmr-str2str-deposit', 'nmr-str2cif-deposit', 'nmr-str2cif-annotate') and self.__remediation_mode))\
           and self.report.isError() and self.__dstPath is not None:

            dir_path = os.path.dirname(self.__dstPath)

            rem_dir = os.path.join(dir_path, 'remediation')

            if os.path.isdir(rem_dir):

                for link_file in os.listdir(rem_dir):

                    link_path = os.path.join(rem_dir, link_file)

                    if os.path.islink(link_path):
                        os.remove(link_path)

                os.removedirs(rem_dir)

            pk_dir = os.path.join(dir_path, 'nmr_peak_lists')

            if os.path.isdir(pk_dir):

                for link_file in os.listdir(pk_dir):

                    link_path = os.path.join(pk_dir, link_file)

                    if os.path.islink(link_path):
                        os.remove(link_path)

                os.removedirs(pk_dir)

        return not self.report.isError()

    def __dumpDpReport(self):
        """ Dump current NMR data processing report.
        """

        if self.report_prev is not None:
            self.report.inheritFormatIssueErrors(self.report_prev)
            self.report.inheritCorrectedFormatIssueWarnings(self.report_prev)
            self.report.inheritCorrectedSaveframeNameWarnings(self.report_prev)

            if self.report_prev.error.get() is not None:
                self.report.setCorrectedError(self.report_prev)

            if self.report_prev.warning.get() is not None:
                self.report.setCorrectedWarning(self.report_prev)

        self.report.error.sortFormatIssueError()
        self.report.warning.sortChemicalShiftValidation()
        self.report.warning.sortBySigma('conflicted_data')
        self.report.warning.sortBySigma('inconsistent_data')

        self.report.clean()

        if self.__logPath is None:
            return False

        return self.report.writeFile(self.__logPath)

    def __initializeDpReport(self, srcPath=None):
        """ Initialize NMR data processing report.
        """

        srcName = None
        if srcPath is None:
            srcPath = self.__srcPath
            if self.__srcName is not None:
                srcName = self.__srcName

        self.report = NmrDpReport(self.__verbose, self.__lfh)

        input_source = None

        if self.__combined_mode:

            # set primary input source as NMR unified data
            input_source = self.report.input_sources[0]

            file_type = 'nef' if 'nef' in self.__op and 'str2nef' not in self.__op else 'nmr-star'
            content_type = self.content_type[file_type]

            input_source.setItemValue('file_name', os.path.basename(srcPath))
            input_source.setItemValue('file_type', file_type)
            input_source.setItemValue('content_type', content_type)
            if srcName is not None:
                input_source.setItemValue('original_file_name', srcName)

        else:

            cs_file_path_list = 'chem_shift_file_path_list'

            for csListId, cs in enumerate(self.__inputParamDict[cs_file_path_list]):

                if csListId > 0:
                    self.report.appendInputSource()

                input_source = self.report.input_sources[csListId]

                file_type = 'nmr-star'  # 'nef' in self.__op else 'nmr-star' # DAOTHER-5673

                if isinstance(cs, str):

                    if cs.endswith('.gz'):

                        _cs = os.path.splitext(cs)[0]

                        if not os.path.exists(_cs):

                            try:

                                uncompress_gzip_file(cs, _cs)

                            except Exception as e:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__initializeDpReport() ++ Error  - " + str(e))
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__initializeDpReport() ++ Error  - {str(e)}\n")

                                return False

                        cs = _cs

                    if not os.path.basename(cs).startswith('bmr') and\
                            (self.__op == 'nmr-cs-mr-merge'
                             or get_type_of_star_file(cs) == 'cif'
                             or self.__nefT.read_input_file(cs)[1] == 'Saveframe'):

                        input_source.setItemValue('original_file_name', os.path.basename(cs))

                        _cs = cs + '.cif2str'

                        # if not os.path.exists(_cs):

                        if not self.__c2S.convert(cs, _cs):
                            _cs = cs

                        cs = _cs

                    input_source.setItemValue('file_name', os.path.basename(cs))
                    input_source.setItemValue('file_type', file_type)
                    input_source.setItemValue('content_type', 'nmr-chemical-shifts')

                else:

                    if cs['file_name'].endswith('.gz'):

                        _cs = os.path.splitext(cs['file_name'])[0]

                        if not os.path.exists(_cs):

                            try:

                                uncompress_gzip_file(cs['file_name'], _cs)

                            except Exception as e:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__initializeDpReport() ++ Error  - " + str(e))
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__initializeDpReport() ++ Error  - {str(e)}\n")

                                return False

                        cs['file_name'] = _cs

                    if not os.path.basename(cs['file_name']).startswith('bmr') and\
                            (self.__op == 'nmr-cs-mr-merge'
                             or get_type_of_star_file(cs['file_name']) == 'cif'
                             or self.__nefT.read_input_file(cs['file_name'])[1] == 'Saveframe'):

                        if 'original_file_name' not in cs:
                            input_source.setItemValue('original_file_name', os.path.basename(cs['file_name']))

                        _cs = cs['file_name'] + '.cif2str'

                        # if not os.path.exists(_cs):

                        if not self.__c2S.convert(cs['file_name'], _cs):
                            _cs = cs['file_name']

                        cs['file_name'] = _cs

                    input_source.setItemValue('file_name', os.path.basename(cs['file_name']))
                    input_source.setItemValue('file_type', file_type)
                    input_source.setItemValue('content_type', 'nmr-chemical-shifts')
                    if 'original_file_name' in cs:
                        input_source.setItemValue('original_file_name', cs['original_file_name'])

            mr_file_path_list = 'restraint_file_path_list'

            if mr_file_path_list in self.__inputParamDict:

                for mr in self.__inputParamDict[mr_file_path_list]:

                    self.report.appendInputSource()

                    input_source = self.report.input_sources[-1]

                    file_type = 'nmr-star'  # 'nef' if 'nef' in self.__op else 'nmr-star' # DAOTHER-5673

                    if isinstance(mr, str):

                        if get_type_of_star_file(mr) == 'cif'\
                           or self.__nefT.read_input_file(mr)[1] == 'Saveframe':

                            input_source.setItemValue('original_file_name', os.path.basename(mr))

                            _mr = mr + '.cif2str'

                            # if not os.path.exists(_mr):

                            if not self.__c2S.convert(mr, _mr):
                                _mr = mr

                            mr = _mr

                        input_source.setItemValue('file_name', os.path.basename(mr))
                        input_source.setItemValue('file_type', file_type)
                        input_source.setItemValue('content_type', 'nmr-restraints')

                    else:

                        if get_type_of_star_file(mr['file_name']) == 'cif'\
                           or self.__nefT.read_input_file(mr['file_name'])[1] == 'Saveframe':

                            if 'original_file_name' not in mr:
                                input_source.setItemValue('original_file_name', os.path.basename(mr['file_name']))

                            _mr = mr['file_name'] + '.cif2str'

                            # if not os.path.exists(_mr):

                            if not self.__c2S.convert(mr['file_name'], _mr):
                                _mr = mr['file_name']

                            mr['file_name'] = _mr

                        input_source.setItemValue('file_name', os.path.basename(mr['file_name']))
                        input_source.setItemValue('file_type', file_type)
                        input_source.setItemValue('content_type', 'nmr-restraints')
                        if 'original_file_name' in mr:
                            input_source.setItemValue('original_file_name', mr['original_file_name'])

            ar_file_path_list = 'atypical_restraint_file_path_list'

            if ar_file_path_list in self.__inputParamDict:

                for ar in self.__inputParamDict[ar_file_path_list]:

                    self.report.appendInputSource()

                    input_source = self.report.input_sources[-1]

                    arPath = ar['file_name']

                    if arPath.endswith('.gz'):

                        _arPath = os.path.splitext(arPath)[0]

                        if not os.path.exists(_arPath):

                            try:

                                uncompress_gzip_file(arPath, _arPath)

                            except Exception as e:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__initializeDpReport() ++ Error  - " + str(e))
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__initializeDpReport() ++ Error  - {str(e)}\n")

                                return False

                        arPath = _arPath

                    input_source.setItemValue('file_name', os.path.basename(arPath))
                    input_source.setItemValue('file_type', ar['file_type'])
                    input_source.setItemValue('content_type', 'nmr-restraints')
                    if 'original_file_name' in ar:
                        input_source.setItemValue('original_file_name', ar['original_file_name'])

            if self.__bmrb_only and self.__internal_mode and 'nmr_cif_file_path' in self.__inputParamDict:

                nmr_cif = self.__inputParamDict['nmr_cif_file_path']

                _nmr_cif = nmr_cif + '.cif2str'

                if self.__c2S.convert(nmr_cif, _nmr_cif):
                    self.__srcNmrCifPath = _nmr_cif
                    self.__native_combined = True  # DAOTHER-8855

        self.__star_data_type = []
        self.__star_data = []
        self.__sf_name_corr = []

        self.__original_error_message = []

        self.__testDiamagnetism()

        return input_source is not None

    def __testDiamagnetism(self):
        """ Test diamagnetism of molecular assembly.
        """

        if not self.__parseCoordFilePath():
            return

        try:

            chem_comp = self.__cR.getDictList('chem_comp')

            nstd_comp_ids = [item['id'] for item in chem_comp if item['mon_nstd_flag'] != 'y']

            if len(nstd_comp_ids) == 0:
                return

            for comp_id in nstd_comp_ids:

                if self.__ccU.updateChemCompDict(comp_id):  # matches with comp_id in CCD

                    ref_elems = set(a[self.__ccU.ccaTypeSymbol] for a in self.__ccU.lastAtomList if a[self.__ccU.ccaLeavingAtomFlag] != 'Y')

                    for elem in ref_elems:
                        if elem in PARAMAGNETIC_ELEMENTS or elem in FERROMAGNETIC_ELEMENTS:
                            self.report.setDiamagnetic(False)
                            break

        except Exception:
            pass

    def __validateInputSource(self, srcPath=None):
        """ Validate NMR data as primary input source.
        """

        if srcPath is None:
            srcPath = self.__srcPath

        is_done = True

        if self.__combined_mode:

            self.__dirPath = os.path.dirname(srcPath)

            if os.path.exists(srcPath):
                codec = detect_bom(srcPath, 'utf-8')

                _srcPath = None

                if codec != 'utf-8':
                    _srcPath = srcPath + '~'
                    convert_codec(srcPath, _srcPath, codec, 'utf-8')
                    srcPath = _srcPath

                if is_rtf_file(srcPath):
                    _srcPath = srcPath + '.rtf2txt'
                    convert_rtf_to_ascii(srcPath, _srcPath)
                    srcPath = _srcPath

            is_valid, message = self.__nefT.validate_file(srcPath, 'A')  # 'A' for NMR unified data

            if not is_valid:
                _srcPath = srcPath + '.cif2str'

                if self.__c2S.convert(srcPath, _srcPath):
                    is_valid, message = self.__nefT.validate_file(_srcPath, 'A')  # 'A' for NMR unified data
                    self.__srcPath = srcPath = _srcPath

            self.__original_error_message.append(message)

            _file_type = message['file_type']  # nef/nmr-star/unknown

            input_source = self.report.input_sources[0]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if is_valid:

                if _file_type != file_type:

                    err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                          f"but recognized as {self.readable_file_type[_file_type]} file. Please re-upload the file."

                    if len(message['error']) > 0:
                        for err_message in message['error']:
                            if 'No such file or directory' not in err_message:
                                err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Error  - {err}\n")

                    is_done = False

                else:

                    # NEFTranslator.validate_file() generates this object internally, but not re-used.
                    is_done, star_data_type, star_data = self.__nefT.read_input_file(srcPath)

                    if len(self.__star_data_type) > 0:
                        del self.__star_data_type[-1]
                        del self.__star_data[-1]

                    self.__star_data_type.append(star_data_type)
                    self.__star_data.append(star_data)

                    self.__rescueFormerNef(0)
                    self.__rescueImmatureStr(0)

            elif not self.__fixFormatIssueOfInputSource(0, file_name, file_type, srcPath, 'A', message):

                if any(err_message for err_message in message['error'] if 'The mandatory loop' in err_message):

                    # NEFTranslator.validate_file() generates this object internally, but not re-used.
                    _, star_data_type, star_data = self.__nefT.read_input_file(srcPath)

                    if len(self.__star_data_type) > 0:
                        del self.__star_data_type[-1]
                        del self.__star_data[-1]

                    self.__star_data_type.append(star_data_type)
                    self.__star_data.append(star_data)

                    self.__rescueFormerNef(0)
                    self.__rescueImmatureStr(0)

                is_done = False

            if _srcPath is not None and not self.__annotation_mode:
                try:
                    os.remove(_srcPath)
                except OSError:
                    pass

        else:

            cs_file_path_list = 'chem_shift_file_path_list'

            for csListId, cs in enumerate(self.__inputParamDict[cs_file_path_list]):

                if isinstance(cs, str):
                    csPath = cs
                else:
                    csPath = cs['file_name']

                if csListId == 0:
                    self.__dirPath = os.path.dirname(csPath)

                if csPath.endswith('.gz'):

                    _csPath = os.path.splitext(csPath)[0]

                    if not os.path.exists(_csPath):

                        try:

                            uncompress_gzip_file(csPath, _csPath)

                        except Exception as e:

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateInputSource() ++ Error  - " + str(e))
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Error  - {str(e)}\n")

                            return False

                    csPath = _csPath

                if self.__op == 'nmr-cs-mr-merge' and not os.path.basename(csPath).startswith('bmr'):

                    _csPath = csPath + '.cif2str'

                    # if not os.path.exists(_csPath):

                    if not self.__c2S.convert(csPath, _csPath):
                        _csPath = csPath

                    csPath = _csPath

                codec = detect_bom(csPath, 'utf-8')

                _csPath = None

                if codec != 'utf-8':
                    _csPath = csPath + '~'
                    convert_codec(csPath, _csPath, codec, 'utf-8')
                    csPath = _csPath

                if is_rtf_file(csPath):
                    _csPath = csPath + '.rtf2txt'
                    convert_rtf_to_ascii(csPath, _csPath)
                    csPath = _csPath

                if self.__op == 'nmr-cs-mr-merge':

                    dir_path = os.path.dirname(csPath)

                    rem_dir = os.path.join(dir_path, 'remediation')

                    try:

                        if not os.path.isdir(rem_dir):
                            os.makedirs(rem_dir)

                        cs_file_name = os.path.basename(csPath)

                        if cs_file_name.endswith('.cif2str'):
                            cs_file_name = os.path.splitext(cs_file_name)[0]

                        if cs_file_name.endswith('.str'):
                            cs_file_name = os.path.splitext(cs_file_name)[0]

                        if cs_file_name.endswith('-corrected'):
                            cs_file_link = os.path.join(rem_dir, cs_file_name[:-10] + '.str')
                            cs_file_path = os.path.join(dir_path, cs_file_name + '.str')

                            if os.path.exists(cs_file_link):
                                os.remove(cs_file_link)

                            os.symlink(cs_file_path, cs_file_link)

                    except OSError:
                        pass

                allow_empty = self.__bmrb_only and self.__internal_mode\
                    and ('nmr_cif_file_path' in self.__inputParamDict
                         or (csListId == 0 and len(self.__inputParamDict[cs_file_path_list]) > 1))

                is_valid, message = self.__nefT.validate_file(csPath, 'S', allow_empty)  # 'S' for assigned chemical shifts

                self.__original_error_message.append(message)

                _file_type = message['file_type']  # nef/nmr-star/unknown

                input_source = self.report.input_sources[csListId]
                input_source_dic = input_source.get()

                file_name = input_source_dic['file_name']
                file_type = input_source_dic['file_type']

                if cs_file_path_list in self.__outputParamDict:
                    if csListId < len(self.__outputParamDict[cs_file_path_list]):
                        dstPath = self.__outputParamDict[cs_file_path_list][csListId]
                        if dstPath is not None and dstPath not in self.__inputParamDict[cs_file_path_list]:
                            shutil.copyfile(csPath, dstPath)

                if is_valid:

                    if _file_type != file_type:

                        err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                              f"but recognized as {self.readable_file_type[_file_type]} file."
                        # DAOTHER-5673
                        err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                        if len(message['error']) > 0:
                            for err_message in message['error']:
                                if 'No such file or directory' not in err_message:
                                    err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                        self.report.error.appendDescription('content_mismatch',
                                                            {'file_name': file_name, 'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Error  - {err}\n")

                        is_done = False

                    else:

                        # NEFTranslator.validate_file() generates this object internally, but not re-used.
                        _is_done, star_data_type, star_data = self.__nefT.read_input_file(csPath)

                        self.__has_legacy_sf_issue = False

                        if star_data_type == 'Saveframe':
                            self.__has_legacy_sf_issue = True
                            self.__fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message, allowEmpty=allow_empty)
                            _is_done, star_data_type, star_data = self.__nefT.read_input_file(csPath)

                        if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):

                            if len(self.__star_data_type) > csListId:
                                self.__star_data_type[csListId] = star_data_type
                                self.__star_data[csListId] = star_data
                            else:
                                self.__star_data_type.append(star_data_type)
                                self.__star_data.append(star_data)

                            self.__rescueFormerNef(csListId)
                            self.__rescueImmatureStr(csListId)

                        if star_data_type != 'Entry':
                            _star_data = self.__convertCsToEntry(star_data, csListId + 1)
                            if isinstance(_star_data, pynmrstar.Entry):
                                self.__star_data[-1] = _star_data
                                self.__star_data_type[-1] = 'Entry'
                        else:
                            self.__star_data[-1] = self.__convertCsToEntry(star_data)

                elif not self.__fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message, allowEmpty=allow_empty):
                    is_done = False

                if _csPath is not None:
                    try:
                        os.remove(_csPath)
                    except OSError:
                        pass

            mr_file_path_list = 'restraint_file_path_list'
            ar_file_path_list = 'atypical_restraint_file_path_list'

            self.__legacy_dist_restraint_uploaded = False

            if mr_file_path_list in self.__inputParamDict:

                for mr in self.__inputParamDict[mr_file_path_list]:

                    if isinstance(mr, str):
                        mrPath = mr
                    else:
                        mrPath = mr['file_name']

                    codec = detect_bom(mrPath, 'utf-8')

                    _mrPath = None

                    if codec != 'utf-8':
                        _mrPath = mrPath + '~'
                        convert_codec(mrPath, _mrPath, codec, 'utf-8')
                        mrPath = _mrPath

                    if is_rtf_file(mrPath):
                        _mrPath = mrPath + '.rtf2txt'
                        convert_rtf_to_ascii(mrPath, _mrPath)
                        mrPath = _mrPath

                    is_valid, message = self.__nefT.validate_file(mrPath, 'R')  # 'R' for restraints

                    if is_valid:
                        self.__legacy_dist_restraint_uploaded = True

                    if _mrPath is not None:
                        try:
                            os.remove(_mrPath)
                        except OSError:
                            pass

                has_atypical_restraint = False

                if ar_file_path_list in self.__inputParamDict:

                    for ar in self.__inputParamDict[ar_file_path_list]:

                        arPath = ar['file_name']

                        if os.path.exists(arPath):
                            has_atypical_restraint = True
                            break

                # DAOTHER-7545, issue #2, 'R' for restraints, 'O' for other conventional restraints
                file_subtype = 'O' if self.__legacy_dist_restraint_uploaded or has_atypical_restraint else 'R'

                file_path_list_len = self.__cs_file_path_list_len

                for mr in self.__inputParamDict[mr_file_path_list]:

                    if isinstance(mr, str):
                        mrPath = mr
                    else:
                        mrPath = mr['file_name']

                    codec = detect_bom(mrPath, 'utf-8')

                    _mrPath = None

                    if codec != 'utf-8':
                        _mrPath = mrPath + '~'
                        convert_codec(mrPath, _mrPath, codec, 'utf-8')
                        mrPath = _mrPath

                    if is_rtf_file(mrPath):
                        _mrPath = mrPath + '.rtf2txt'
                        convert_rtf_to_ascii(mrPath, _mrPath)
                        mrPath = _mrPath

                    is_valid, message = self.__nefT.validate_file(mrPath, file_subtype)

                    self.__original_error_message.append(message)

                    _file_type = message['file_type']  # nef/nmr-star/unknown

                    input_source = self.report.input_sources[file_path_list_len]
                    input_source_dic = input_source.get()

                    file_name = input_source_dic['file_name']
                    file_type = input_source_dic['file_type']

                    if mr_file_path_list in self.__outputParamDict:
                        if file_path_list_len - self.__cs_file_path_list_len < len(self.__outputParamDict[mr_file_path_list]):
                            dstPath = self.__outputParamDict[mr_file_path_list][file_path_list_len - self.__cs_file_path_list_len]
                            if dstPath is not None and dstPath not in self.__inputParamDict[mr_file_path_list]:
                                shutil.copyfile(mrPath, dstPath)

                    if is_valid:

                        if _file_type != file_type:

                            err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                                  f"but recognized as {self.readable_file_type[_file_type]} file."
                            # DAOTHER-5673
                            err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                            if len(message['error']) > 0:
                                for err_message in message['error']:
                                    if 'No such file or directory' not in err_message:
                                        err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                            self.report.error.appendDescription('content_mismatch',
                                                                {'file_name': file_name, 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Error  - {err}\n")

                            is_done = False

                        else:

                            # NEFTranslator.validate_file() generates this object internally, but not re-used.
                            _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                            self.__has_legacy_sf_issue = False

                            if star_data_type == 'Saveframe':
                                self.__has_legacy_sf_issue = True
                                self.__fixFormatIssueOfInputSource(file_path_list_len, file_name, file_type, mrPath, file_subtype, message)
                                _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                            self.__star_data_type.append(star_data_type)
                            self.__star_data.append(star_data)

                            if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):
                                if len(self.__star_data_type) > file_path_list_len:
                                    self.__star_data_type[file_path_list_len] = star_data_type
                                    self.__star_data[file_path_list_len] = star_data
                                else:
                                    self.__rescueFormerNef(file_path_list_len)
                                    self.__rescueImmatureStr(file_path_list_len)

                            if not _is_done:
                                is_done = False

                    elif not self.__fixFormatIssueOfInputSource(file_path_list_len, file_name, file_type, mrPath, file_subtype, message):
                        is_done = False

                    file_path_list_len += 1

                    if _mrPath is not None:
                        try:
                            os.remove(_mrPath)
                        except OSError:
                            pass

            if ar_file_path_list in self.__inputParamDict:

                for ar in self.__inputParamDict[ar_file_path_list]:

                    arPath = ar['file_name']

                    if arPath.endswith('.gz'):

                        _arPath = os.path.splitext(arPath)[0]

                        if not os.path.exists(_arPath):

                            try:

                                uncompress_gzip_file(arPath, _arPath)

                            except Exception as e:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateInputSource() ++ Error  - " + str(e))
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Error  - {str(e)}\n")

                                return False

                        arPath = _arPath

                    codec = detect_bom(arPath, 'utf-8')

                    if codec != 'utf-8':
                        arPath_ = arPath + '~'
                        convert_codec(arPath, arPath_, codec, 'utf-8')
                        arPath = arPath_

                    if is_rtf_file(arPath):
                        arPath_ = arPath + '.rtf2txt'
                        convert_rtf_to_ascii(arPath, arPath_)
                        arPath = arPath_

                    ar['file_name'] = arPath

            if self.__bmrb_only and self.__internal_mode and len(self.__inputParamDict[cs_file_path_list]) > 1:
                for csListId, cs in enumerate(self.__inputParamDict[cs_file_path_list]):
                    if csListId == 0:
                        dst_sf_category_list, _ = self.__nefT.get_inventory_list(self.__star_data[0])
                        if 'assigned_chemical_shifts' in dst_sf_category_list:
                            for sf in self.__star_data[0].get_saveframes_by_category('assigned_chemical_shifts'):
                                sf_framecode = get_first_sf_tag(sf, 'Sf_framecode')
                                self.__star_data[0].remove_saveframe(sf_framecode)
                        continue
                    if self.__star_data_type[csListId] == 'Entry' and self.__star_data[csListId] is not None:
                        src_sf_category_list, _ = self.__nefT.get_inventory_list(self.__star_data[csListId])

                        # copy cs data of the annotated cs file to the master template
                        if 'assigned_chemical_shifts' in src_sf_category_list:
                            for _sf in self.__star_data[csListId].get_saveframes_by_category('assigned_chemical_shifts'):
                                self.__star_data[0].add_saveframe(_sf)
                                self.__star_data[csListId].remove_saveframe(_sf)

            if self.__bmrb_only and self.__internal_mode and self.__srcNmrCifPath is not None:

                is_valid, message = self.__nefT.validate_file(self.__srcNmrCifPath, 'A')  # 'A' for NMR unified data

                _file_type = message['file_type']  # nef/nmr-star/unknown

                file_name = self.__srcNmrCifPath
                file_type = 'nmr-star'

                if is_valid:

                    if _file_type == file_type:

                        # NEFTranslator.validate_file() generates this object internally, but not re-used.
                        _is_done, _star_data_type, _star_data = self.__nefT.read_input_file(self.__srcNmrCifPath)

                        if _is_done and _star_data_type == 'Entry' and is_done and self.__star_data_type[0] == 'Entry':

                            self.__nmr_cif_sf_category_list, _ = self.__nefT.get_inventory_list(_star_data)
                            dst_sf_category_list, _ = self.__nefT.get_inventory_list(self.__star_data[0])

                            # give priority to cs data of the combined file over ones of the cs-annotate file
                            if 'assigned_chemical_shifts' in self.__nmr_cif_sf_category_list:
                                if 'assigned_chemical_shifts' in dst_sf_category_list:
                                    for sf in self.__star_data[0].get_saveframes_by_category('assigned_chemical_shifts'):
                                        sf_framecode = get_first_sf_tag(sf, 'Sf_framecode')
                                        self.__star_data[0].remove_saveframe(sf_framecode)
                                for _sf in _star_data.get_saveframes_by_category('assigned_chemical_shifts'):
                                    self.__star_data[0].add_saveframe(_sf)

                            # move restraints of the combined file to the primary file
                            for src_sf_category in self.__nmr_cif_sf_category_list:
                                if src_sf_category not in dst_sf_category_list and src_sf_category != 'constraint_statistics':
                                    for _sf in _star_data.get_saveframes_by_category(src_sf_category):
                                        for sf in self.__star_data[0].frame_list:
                                            if sf.name == _sf.name:
                                                self.__star_data[0].remove_saveframe(_sf.name)
                                                break
                                        self.__star_data[0].add_saveframe(_sf)

        return is_done

    def __fixFormatIssueOfInputSource(self, file_list_id, file_name, file_type, srcPath=None, fileSubType='S',
                                      message=None, tmpPaths=None, allowEmpty=False):
        """ Fix format issue of NMR data.
        """

        if not self.__fix_format_issue or srcPath is None or fileSubType not in ('A', 'S', 'R', 'O') or message is None:

            if message is not None:

                missing_loop = True

                err = f"{file_name!r} is not compliant with the {self.readable_file_type[file_type]} dictionary."

                if len(message['error']) > 0:

                    if any(err_message for err_message in message['error'] if 'The mandatory loop' in err_message):

                        err = ''
                        for err_message in message['error']:
                            if 'No such file or directory' not in err_message:
                                err += re.sub('not in list', 'unknown item.', err_message) + ' '
                        err = err[:-1]

                    else:
                        missing_loop = False

                        for err_message in self.__original_error_message[file_list_id]['error']:
                            if 'No such file or directory' not in err_message:
                                err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                if not self.__remediation_mode or not missing_loop or file_list_id > 0:

                    self.report.error.appendDescription('missing_mandatory_content' if missing_loop else 'format_issue',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    self.__lfh.write("+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Error  - "
                                     f"{file_name} {err}\n")

                else:

                    self.__has_star_chem_shift = False

                    self.__suspended_errors_for_lazy_eval.append({'missing_mandatory_content':
                                                                  {'file_name': file_name, 'description': err}})

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Error  - {err}\n")

            if not self.__has_legacy_sf_issue and fileSubType in ('S', 'R', 'O'):
                return False

        star_data_type = self.__nefT.read_input_file(srcPath)[1] if self.__has_legacy_sf_issue else None

        _srcPath = srcPath
        if tmpPaths is None:
            tmpPaths = []

        len_tmp_paths = len(tmpPaths)

        msg_template = "Saveframe improperly terminated at end of file."

        if any(msg for msg in message['error'] if msg_template in msg):
            warn = msg_template

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                    open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                for line in ifh:
                    ofh.write(line)

                ofh.write('save_\n')

                _srcPath = ofh.name
                tmpPaths.append(_srcPath)

        msg_template = "Loop improperly terminated at end of file."

        if any(msg for msg in message['error'] if msg_template in msg):
            warn = msg_template

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                    open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                for line in ifh:
                    ofh.write(line)

                ofh.write('save_\n')

                _srcPath = ofh.name
                tmpPaths.append(_srcPath)

#        if __pynmrstar_v3_1__:
#            msg_template = 'Invalid token found in loop contents. Expecting \'loop_\' but found:' # \'*\' Error detected on line *.'
#        else:
        if __pynmrstar_v3_2__:
            msg_template = "Invalid file. NMR-STAR files must start with 'data_' followed by the data name. Did you accidentally select the wrong file?"
        else:
            msg_template = "Invalid file. NMR-STAR files must start with 'data_'. Did you accidentally select the wrong file?"

        if any(msg for msg in message['error'] if msg_template in msg) or (self.__has_legacy_sf_issue and star_data_type == 'Saveframe'):
            warn = 'The datablock must hook saveframe(s).'

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Warning  - {warn}\n")

            with open(_srcPath, 'r', encoding='utf-8') as ifh:
                lines = ifh.read().splitlines()
                total = len(lines)

                j = total - 1

                while total - j < 10:
                    if save_pattern.match(lines[j]) or stop_pattern.match(lines[j]):
                        break
                    j -= 1

            j += 1
            i = 1

            with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                    open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                ofh.write('data_' + os.path.basename(srcPath) + '\n\n')
                for line in ifh:
                    if i <= j:
                        ofh.write(line)
                    i += 1

                _srcPath = ofh.name
                tmpPaths.append(_srcPath)

        msg_template = "Only 'save_NAME' is valid in the body of a NMR-STAR file. Found 'loop_'."

        if any(msg for msg in message['error'] if msg_template in msg):
            warn = 'A saveframe, instead of the datablock, must hook the loop.'

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Warning  - {warn}\n")

            pass_datablock = False

            with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                    open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                for line in ifh:
                    if pass_datablock:
                        ofh.write(line)
                    elif datablock_pattern.match(line):
                        pass_datablock = True
                    else:
                        ofh.write(line)

                _srcPath = ofh.name
                tmpPaths.append(_srcPath)

        msg_template = "Cannot use keywords as data values unless quoted or semi-colon delineated. "\
            "Perhaps this is a loop that wasn't properly terminated? Illegal value:"

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = 'Loops must properly terminated.'

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            _msg_template = r"Cannot use keywords as data values unless quoted or semi-colon delineated. "\
                r"Perhaps this is a loop that wasn't properly terminated\? Illegal value:"

            if __pynmrstar_v3__:
                msg_pattern = re.compile(r'^.*' + _msg_template + r".*on line (\d+).*$")
            else:
                msg_pattern = re.compile(r'^.*' + _msg_template + r".*, (\d+).*$")

            try:

                g = msg_pattern.search(msg).groups()

                line_num = int(g[0])

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if i == line_num:
                            ofh.write('stop_\n')
                        ofh.write(line)
                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

            except AttributeError:
                pass

        except StopIteration:
            pass

        msg_template = "Cannot have a tag value start with an underscore unless the entire value is quoted. "\
            "You may be missing a data value on the previous line. Illegal value:"

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = "Loops must start with the 'loop_' keyword."

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            if __pynmrstar_v3__:
                msg_pattern = re.compile(r'^.*' + msg_template + r".*on line (\d+).*$")
            else:
                msg_pattern = re.compile(r'^.*' + msg_template + r".*, (\d+).*$")

            try:

                g = msg_pattern.search(msg).groups()

                line_num = int(g[0])

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if i == line_num - 1:
                            ofh.write('loop_\n')
                        ofh.write(line)
                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

            except AttributeError:
                pass

        except StopIteration:
            pass

        msg_template = "Only 'save_NAME' is valid in the body of a NMR-STAR file. Found"

        try:

            is_cs_cif = False

            if self.__op == 'nmr-cs-str-consistency-check':

                is_cs_cif = True

                try:

                    with open(_srcPath, 'r', encoding='utf-8') as ifh:
                        for line in ifh:
                            if save_pattern.match(line) or stop_pattern.match(line):
                                is_cs_cif = False
                                break

                    if is_cs_cif:

                        loop_count = 0
                        has_sf_category = False
                        has_sf_framecode = False

                        with open(_srcPath, 'r', encoding='utf-8') as ifh:
                            for line in ifh:
                                if loop_pattern.match(line):
                                    loop_count += 1
                                elif sf_category_pattern.match(line):
                                    has_sf_category = True
                                elif sf_framecode_pattern.match(line):
                                    has_sf_framecode = True

                        if not has_sf_category and not has_sf_framecode:

                            in_loop = False

                            with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                                    open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                                for line in ifh:
                                    if datablock_pattern.match(line):
                                        g = datablock_pattern.search(line).groups()
                                        if loop_count < 2:
                                            ofh.write(f"save_{g[0]}\n")
                                    elif cif_stop_pattern.match(line):
                                        if in_loop:
                                            if loop_count < 2:
                                                ofh.write('stop_\nsave_\n')
                                            else:
                                                ofh.write('stop_\n')
                                        else:
                                            ofh.write(line)
                                        in_loop = False
                                    elif loop_pattern.match(line):
                                        in_loop = True
                                        ofh.write(line)
                                    else:
                                        if in_loop or loop_count < 2:
                                            ofh.write(line)

                                _srcPath = ofh.name
                                tmpPaths.append(_srcPath)

                        else:

                            if self.__c2S.convert(_srcPath, _srcPath + '~'):
                                _srcPath += '~'
                                tmpPaths.append(_srcPath)

                except AttributeError:
                    pass

            if not is_cs_cif:

                msg = next(msg for msg in message['error'] if msg_template in msg)
                warn = "Loops must start with the 'loop_' keyword."

                self.report.warning.appendDescription('corrected_format_issue',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

                msg_pattern = re.compile(r'^.*' + msg_template + r" '(.*)'.*$")

                try:

                    g = msg_pattern.search(msg).groups()

                    tag_name = g[0]

                    tag_name_pattern = re.compile(r'\s*' + tag_name + r'\s*')

                    with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                            open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                        for line in ifh:
                            if tag_name_pattern.match(line) is None:
                                ofh.write(line)
                            else:
                                ofh.write('loop_\n')

                        _srcPath = ofh.name
                        tmpPaths.append(_srcPath)

                except AttributeError:
                    pass

        except StopIteration:
            pass

        msg_template = "'save_' must be followed by saveframe name. You have a 'save_' tag which is illegal without a specified saveframe name."

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = "The saveframe must have a specified saveframe name."

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            if __pynmrstar_v3__:
                msg_pattern = re.compile(r'^.*' + msg_template + r".*on line (\d+).*$")
            else:
                msg_pattern = re.compile(r'^.*' + msg_template + r".*, (\d+).*$")

            try:

                g = msg_pattern.search(msg).groups()

                line_num = int(g[0])

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if i != line_num:
                            ofh.write(line)
                        else:
                            ofh.write(f"save_{os.path.basename(srcPath)}\n")
                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

            except AttributeError:
                pass

        except StopIteration:
            pass

        if __pynmrstar_v3__:
            msg_template = "The tag prefix was never set! Either the saveframe had no tags, "\
                "you tried to read a version 2.1 file, or there is something else wrong with your file. "\
                "Saveframe error occurred within:"
        else:
            msg_template = "The tag prefix was never set! Either the saveframe had no tags, "\
                "you tried to read a version 2.1 file without setting ALLOW_V2_ENTRIES to True, "\
                "or there is something else wrong with your file. Saveframe error occured:"

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = 'The saveframe must have NMR-STAR V3.2 tags. Saveframe error occured:'\
                + msg[len(msg_template):].replace('<pynmrstar.', '').replace("'>", "'")

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            msg_pattern = re.compile(r'^' + msg_template + r" '(.*)'$")

            targets = []

            for msg in message['error']:

                if msg_template not in msg:
                    continue

                try:

                    target = {}

                    g = msg_pattern.search(msg).groups()
                    sf_framecode = str(g[0])

                    target = {'sf_framecode': sf_framecode}

                    pass_sf_framecode = False
                    pass_sf_loop = False

                    sf_named_pattern = re.compile(r'\s*save_' + sf_framecode + r'\s*')

                    with open(_srcPath, 'r', encoding='utf-8') as ifh:
                        for line in ifh:
                            if pass_sf_framecode:
                                if pass_sf_loop:
                                    if category_pattern.match(line):
                                        target['lp_category'] = '_' + category_pattern.search(line).groups()[0]
                                        content_subtype = next((k for k, v in self.lp_categories[file_type].items() if v == target['lp_category']), None)
                                        if content_subtype is not None:
                                            target['sf_category'] = self.sf_categories[file_type][content_subtype]
                                            target['sf_tag_prefix'] = self.sf_tag_prefixes[file_type][content_subtype]
                                        break
                                elif loop_pattern.match(line):
                                    pass_sf_loop = True
                            elif sf_named_pattern.match(line):
                                pass_sf_framecode = True

                    targets.append(target)

                except AttributeError:
                    pass

            for target in targets:

                sf_framecode = target['sf_framecode']

                pass_sf_framecode = False
                pass_sf_loop = False

                sf_named_pattern = re.compile(r'\s*save_' + sf_framecode + r'\s*')

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if pass_sf_loop:
                            ofh.write(line)
                        elif pass_sf_framecode:
                            if loop_pattern.match(line):
                                pass_sf_loop = True
                                if 'sf_category' in target:
                                    ofh.write(target['sf_tag_prefix'] + '.' + ('sf_framecode' if file_type == 'nef' else 'Sf_framecode') + '   ' + sf_framecode + '\n')
                                    ofh.write(target['sf_tag_prefix'] + '.' + ('sf_category' if file_type == 'nef' else 'Sf_category') + '    ' + target['sf_category'] + '\n')
                                    ofh.write('#\n')
                                ofh.write(line)
                        elif sf_named_pattern.match(line):
                            pass_sf_framecode = True
                            ofh.write(line)
                        elif not pass_sf_framecode:
                            ofh.write(line)

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

        except StopIteration:
            pass

        msg_template = "You attempted to parse one loop but the source you provided had more than one loop. "\
            "Please either parse all loops as a saveframe or only parse one loop. Loops detected:"

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = 'Saveframe(s), instead of the datablock, must hook more than one loop. Loops detected:'\
                + msg[len(msg_template):].replace('<pynmrstar.', '').replace("'>", "'")

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            msg_pattern = re.compile(r'^' + msg_template + r" \[(.*)\]$")

            targets = []

            for msg in message['error']:

                if msg_template not in msg:
                    continue

                try:

                    g = msg_pattern.search(msg).groups()

                    for lp_obj in g[0].split(', '):

                        lp_category = str(pynmrstar_lp_obj_pattern.search(lp_obj).groups()[0])

                        if lp_category == 'None':
                            continue

                        target = {'lp_category': lp_category}

                        pass_loop = False

                        lp_loc = -1
                        i = 1

                        with open(_srcPath, 'r', encoding='utf-8') as ifh:
                            for line in ifh:
                                if pass_loop:
                                    if category_pattern.match(line):
                                        _lp_category = '_' + category_pattern.search(line).groups()[0]
                                        if lp_category == _lp_category:
                                            target['loop_location'] = lp_loc
                                            content_subtype = next((k for k, v in self.lp_categories[file_type].items() if v == target['lp_category']), None)
                                            if content_subtype is not None:
                                                target['sf_category'] = self.sf_categories[file_type][content_subtype]
                                                target['sf_tag_prefix'] = self.sf_tag_prefixes[file_type][content_subtype]
                                                target['sf_framecode'] = target['sf_category'] + '_1'
                                        pass_loop = False
                                elif loop_pattern.match(line):
                                    pass_loop = True
                                    lp_loc = i
                                elif stop_pattern.match(line):
                                    if 'loop_location' in target and 'stop_location' not in target:
                                        target['stop_location'] = i
                                        break

                                i += 1

                        targets.append(target)

                except AttributeError:
                    pass

            if len(targets) > 0:
                target_loop_locations = [target['loop_location'] for target in targets]
                target_stop_locations = [target['stop_location'] for target in targets]
                ignored_loop_locations = []
                for target in targets:
                    if 'sf_category' not in target:
                        ignored_loop_locations.extend(list(range(target['loop_location'], target['stop_location'] + 1)))

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    ofh.write('data_' + os.path.basename(srcPath) + '\n\n')
                    for line in ifh:
                        if i in target_loop_locations:
                            target = next(target for target in targets if target['loop_location'] == i)
                            if 'sf_category' in target:
                                ofh.write('save_' + target['sf_framecode'] + '\n')
                                ofh.write(target['sf_tag_prefix'] + '.' + ('sf_framecode' if file_type == 'nef' else 'Sf_framecode') + '   ' + target['sf_framecode'] + '\n')
                                ofh.write(target['sf_tag_prefix'] + '.' + ('sf_category' if file_type == 'nef' else 'Sf_category') + '    ' + target['sf_category'] + '\n')
                                ofh.write('#\n')
                        if i not in ignored_loop_locations:
                            ofh.write(line)
                        if i in target_stop_locations:
                            target = next(target for target in targets if target['stop_location'] == i)
                            if 'sf_category' in target:
                                ofh.write('save_\n')

                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

        except StopIteration:
            pass

        msg_template = "One saveframe cannot have tags with different categories (or tags that don't match the set category)!"

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = msg

            _msg_template = r"One saveframe cannot have tags with different categories \(or tags that don't match the set category\)!"

            msg_pattern = re.compile(r'^' + _msg_template + r" '(.*)' vs '(.*)'.$")

            targets = []

            for msg in message['error']:

                if msg_template not in msg:
                    continue

                try:

                    target = {}

                    g = msg_pattern.search(msg).groups()

                    try:
                        category_1 = str(g[0])
                        category_2 = str(g[1])
                    except IndexError:
                        continue

                    target = {'category_1': category_1, 'category_2': category_2}

                    pass_sf_framecode = False
                    pass_category_1 = False
                    pass_category_2 = False
                    pass_sf_loop = False

                    i = 1

                    with open(_srcPath, 'r', encoding='utf-8') as ifh:
                        for line in ifh:
                            if pass_sf_framecode:
                                if save_pattern.match(line):
                                    if 'category_1_begin' in target and 'category_2_begin' in target:
                                        targets.append(target)
                                        break
                                    pass_sf_framecode = False
                                    pass_category_1 = False
                                    pass_category_2 = False
                                    pass_sf_loop = False
                                elif loop_pattern.match(line):
                                    pass_sf_loop = True
                                elif not pass_sf_loop:
                                    if category_pattern.match(line):
                                        category = '_' + category_pattern.search(line).groups()[0]
                                        if category == category_1:
                                            if not pass_category_1:
                                                target['category_1_begin'] = i
                                                content_subtype = next((k for k, v in self.sf_tag_prefixes[file_type].items() if v == category), None)
                                                if content_subtype is not None:
                                                    target['content_subtype_1'] = content_subtype
                                                content_subtype = next((k for k, v in self.lp_categories[file_type].items() if v == category), None)
                                                if content_subtype is not None:
                                                    target['content_subtype_1'] = content_subtype
                                            pass_category_1 = True
                                            target['category_1_end'] = i
                                        elif category == category_2 and pass_category_1:
                                            if not pass_category_2:
                                                target['category_2_begin'] = i
                                                content_subtype = next((k for k, v in self.sf_tag_prefixes[file_type].items() if v == category), None)
                                                if content_subtype is not None:
                                                    target['category_type_2'] = 'saveframe'
                                                    target['content_subtype_2'] = content_subtype
                                                    target['sf_tag_prefix_2'] = self.sf_tag_prefixes[file_type][content_subtype]
                                                    target['sf_category_2'] = self.sf_categories[file_type][content_subtype]
                                                    target['sf_framecode_2'] = target['sf_category_2'] + '_1'
                                                content_subtype = next((k for k, v in self.lp_categories[file_type].items() if v == category), None)
                                                if content_subtype is not None:
                                                    target['category_type_2'] = 'loop'
                                                    target['content_subtype_2'] = content_subtype
                                                    target['sf_tag_prefix_2'] = self.sf_tag_prefixes[file_type][content_subtype]
                                                    target['sf_category_2'] = self.sf_categories[file_type][content_subtype]
                                                    target['sf_framecode_2'] = target['sf_category_2'] + '_1'
                                                if 'category_type_2' not in target:
                                                    content_subtype = target['content_subtype_1']
                                                    target['category_type_2'] = 'loop'
                                                    target['content_subtype_2'] = content_subtype
                                            pass_category_2 = True
                                            target['category_2_end'] = i
                                elif loop_pattern.match(line):
                                    pass_sf_loop = True
                            elif sf_anonymous_pattern.match(line):
                                pass_sf_framecode = True
                                pass_category_1 = False
                                pass_category_2 = False
                                pass_sf_loop = False

                            i += 1

                except AttributeError:
                    pass

            if len(targets) > 0:

                self.report.warning.appendDescription('corrected_format_issue',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

                target_category_begins = [target['category_2_begin'] for target in targets]
                target_category_ends = [target['category_2_end'] for target in targets]

                loop_category_locations = []
                for target in targets:
                    _range = list(range(target['category_2_begin'], target['category_2_end'] + 1))
                    if target['category_type_2'] == 'loop':
                        loop_category_locations.extend(_range)

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if i in target_category_begins:
                            target = next(target for target in targets if target['category_2_begin'] == i)
                            if target['content_subtype_1'] != target['content_subtype_2']:
                                ofh.write('save_\n')
                                if target['category_type_2'] == 'saveframe':
                                    ofh.write('save_' + target['sf_framecode_2'] + '\n')
                                else:
                                    ofh.write('save_' + target['sf_framecode_2'] + '\n')
                                    ofh.write(target['sf_tag_prefix_2'] + '.' + ('sf_framecode' if file_type == 'nef' else 'Sf_framecode')
                                              + '   ' + target['sf_framecode_2'] + '\n')
                                    ofh.write(target['sf_tag_prefix_2'] + '.' + ('sf_category' if file_type == 'nef' else 'Sf_category')
                                              + '    ' + target['category_2'] + '\n')
                                    ofh.write('loop_\n')
                                    lp_tags = lp_vals = ''
                            elif target['category_type_2'] == 'loop':
                                ofh.write('loop_\n')
                                lp_tags = lp_vals = ''
                        if i not in loop_category_locations:
                            ofh.write(line)
                        else:
                            g = tagvalue_pattern.search(line).groups()
                            try:
                                lp_tags += f"_{g[0]}.{g[1]}\n"
                                lp_vals += f" {g[2].strip(' ')} "
                            except IndexError:
                                continue
                        if i in target_category_ends:
                            target = next(target for target in targets if target['category_2_end'] == i)
                            if target['content_subtype_1'] != target['content_subtype_2']:
                                if target['category_type_2'] == 'saveframe':
                                    pass
                                else:
                                    ofh.write(lp_tags)
                                    ofh.write(lp_vals.rstrip(' ') + '\n')
                                    ofh.write('stop_\n')
                            elif target['category_type_2'] == 'loop':
                                ofh.write(lp_tags)
                                ofh.write(lp_vals.rstrip(' ') + '\n')
                                ofh.write('stop_\n')

                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

        except StopIteration:
            pass

        msg_template = 'The Sf_framecode tag cannot be different from the saveframe name.'

        try:

            msg = next(msg for msg in message['error'] if msg_template in msg)
            warn = "Sf_framecode tag value should match with the saveframe name."

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateInputSource() ++ Warning  - {warn}\n")

            if __pynmrstar_v3_3__:
                msg_pattern = re.compile(r'^.*' + msg_template + r" Error occurred in tag _\S+ with value (\S+) which conflicts with the saveframe name (\S+)\. "
                                         r"Error detected on line (\d+).*$")
            else:
                msg_pattern = re.compile(r'^.*' + msg_template + r" Error occurred in tag _\S+ with value (\S+) which conflicts with.* the saveframe name (\S+)\. "
                                         r"Error detected on line (\d+).*$")

            try:

                g = msg_pattern.search(msg).groups()

                sf_framecode = g[0]
                saveframe_name = g[1]
                line_num = int(g[2])

                i = 1

                with open(_srcPath, 'r', encoding='utf-8') as ifh, \
                        open(_srcPath + '~', 'w', encoding='utf-8') as ofh:
                    for line in ifh:
                        if i == line_num:
                            ofh.write(re.sub(sf_framecode + r'\s$', saveframe_name + r'\n', line))
                        else:
                            ofh.write(line)
                        i += 1

                    _srcPath = ofh.name
                    tmpPaths.append(_srcPath)

            except AttributeError:
                pass

        except StopIteration:
            pass

        if len(tmpPaths) > len_tmp_paths:

            is_valid, _message = self.__nefT.validate_file(_srcPath, fileSubType, allowEmpty)

            if not is_valid:

                retry = len(message['error']) != len(_message['error'])

                if not retry:

                    for msg, _msg in zip(message['error'], _message['error']):
                        if msg != _msg:
                            retry = True
                            break

                if retry and len_tmp_paths < 10:
                    return self.__fixFormatIssueOfInputSource(file_list_id, file_name, file_type, _srcPath, fileSubType,
                                                              _message, tmpPaths, allowEmpty)

        is_done = True

        is_valid, message = self.__nefT.validate_file(_srcPath, fileSubType, allowEmpty)

        _file_type = message['file_type']  # nef/nmr-star/unknown

        if not self.__combined_mode:

            if file_list_id < self.__cs_file_path_list_len:

                cs_file_path_list = 'chem_shift_file_path_list'

                if cs_file_path_list in self.__outputParamDict:
                    if file_list_id < len(self.__outputParamDict[cs_file_path_list]):
                        dstPath = self.__outputParamDict[cs_file_path_list][file_list_id]
                        if dstPath is not None and dstPath not in self.__inputParamDict[cs_file_path_list]:
                            shutil.copyfile(_srcPath, dstPath)

            else:

                mr_file_path_list = 'restraint_file_path_list'

                if mr_file_path_list in self.__outputParamDict:
                    if file_list_id - self.__cs_file_path_list_len < len(self.__outputParamDict[mr_file_path_list]):
                        dstPath = self.__outputParamDict[mr_file_path_list][file_list_id - self.__cs_file_path_list_len]
                        if dstPath is not None and dstPath not in self.__inputParamDict[mr_file_path_list]:
                            shutil.copyfile(_srcPath, dstPath)

        if is_valid:

            if _file_type != file_type:

                err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                    f"but recognized as {self.readable_file_type[_file_type]} file. Please re-upload the file."

                if len(message['error']) > 0:
                    for err_message in message['error']:
                        if 'No such file or directory' not in err_message:
                            err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Error  - {err}\n")

            else:

                # NEFTranslator.validate_file() generates this object internally, but not re-used.
                is_done, star_data_type, star_data = self.__nefT.read_input_file(_srcPath)

                rescued = self.__has_legacy_sf_issue and is_done and star_data_type == 'Entry'

                if len(self.__star_data_type) > file_list_id:
                    self.__star_data_type[file_list_id] = star_data_type
                    self.__star_data[file_list_id] = star_data
                else:
                    self.__star_data_type.append(star_data_type)
                    self.__star_data.append(star_data)

                self.__rescueFormerNef(file_list_id)
                self.__rescueImmatureStr(file_list_id)

                if rescued:
                    if onedep_upload_file_pattern.match(srcPath):
                        g = onedep_upload_file_pattern.search(srcPath).groups()
                        srcPath = g[0] + '-upload-convert_' + g[1] + '.V' + g[2]
                    else:
                        if onedep_file_pattern.match(srcPath):
                            g = onedep_file_pattern.search(srcPath).groups()
                            srcPath = g[0] + '.V' + str(int(g[1]) + 1)
                    if __pynmrstar_v3__:
                        self.__star_data[file_list_id].write_to_file(srcPath, show_comments=False, skip_empty_loops=True, skip_empty_tags=False)
                    else:
                        self.__star_data[file_list_id].write_to_file(srcPath)

        else:

            missing_loop = True

            err = f"{file_name!r} is not compliant with the {self.readable_file_type[file_type]} dictionary."

            if len(message['error']) > 0:

                if any(err_message for err_message in message['error'] if 'The mandatory loop' in err_message):

                    err = ''
                    for err_message in message['error']:
                        if 'No such file or directory' not in err_message:
                            err += re.sub('not in list', 'unknown item.', err_message) + ' '
                    err = err[:-1]

                else:
                    missing_loop = False

                    for err_message in self.__original_error_message[file_list_id]['error']:
                        if 'No such file or directory' not in err_message:
                            err += ' ' + re.sub('not in list', 'unknown item.', err_message)

            if not self.__remediation_mode or not missing_loop or file_list_id > 0:

                self.report.error.appendDescription('missing_mandatory_content' if missing_loop else 'format_issue',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                self.__lfh.write("+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Error  - "
                                 f"{file_name} {err}\n")

                is_done = False

            else:

                self.__has_star_chem_shift = False

                self.__suspended_errors_for_lazy_eval.append({'missing_mandatory_content':
                                                              {'file_name': file_name, 'description': err}})

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__fixFormatIssueOfInputSource() ++ Error  - {err}\n")

        try:

            if self.__release_mode and len(tmpPaths) > 0:
                self.__tmpPath = tmpPaths[-1]
                self.__srcPath = self.__tmpPath
                for tmpPath in tmpPaths[:-1]:
                    if os.path.exists(tmpPath):
                        os.remove(tmpPath)
            else:
                for tmpPath in tmpPaths:
                    if os.path.exists(tmpPath):
                        os.remove(tmpPath)

        except OSError:
            pass

        return is_done

    def __rescueFormerNef(self, file_list_id):
        """ Rescue former NEF version prior to 1.0.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if file_type != 'nef' or file_list_id >= len(self.__star_data) or self.__star_data[file_list_id] is None:
            return True

        if self.__combined_mode or self.__star_data_type[file_list_id] == 'Entry':

            for content_subtype in self.nmr_content_subtypes:

                sf_category = self.sf_categories[file_type][content_subtype]

                if sf_category is None:
                    continue

                for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if self.__getSaveframeByName(file_list_id, sf_framecode) is None:

                        itName = '_' + sf_category + '.sf_framecode'

                        if self.__resolve_conflict:
                            warn = f"{itName} {sf_framecode!r} should be matched with saveframe name {sf.name!r}. {itName} will be overwritten."

                            self.report.warning.appendDescription('missing_saveframe',
                                                                  {'file_name': file_name, 'sf_framecode': sf.name,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__rescueFormerNef() ++ Warning  - {warn}\n")

                            set_sf_tag(sf, 'sf_framecode', sf.name)

                        else:
                            err = f"{itName} {sf_framecode!r} must be matched with saveframe name {sf.name!r}."

                            self.report.error.appendDescription('format_issue',
                                                                {'file_name': file_name, 'sf_framecode': sf.name,
                                                                 'description': err})
                            self.report.setError()

                            self.__lfh.write("+NmrDpUtility.__rescueFormerNef() ++ Error  - "
                                             f"{file_name} {sf.name} {err}\n")

        if not self.__rescue_mode:
            return True

        if self.__combined_mode or self.__star_data_type[file_list_id] == 'Entry':

            content_subtype = 'entry_info'

            sf_category = self.sf_categories[file_type][content_subtype]

            for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                format_version = get_first_sf_tag(sf, 'format_version')

                if not format_version.startswith('0.'):
                    sf.format_version = NEF_VERSION

            for content_subtype in self.nmr_content_subtypes:

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if sf_category is None or lp_category is None:
                    continue

                for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__rescueFormerNef__(file_name, file_type, content_subtype, sf, sf_framecode, sf_category, lp_category)

        else:

            self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(self.__star_data[file_list_id])

            # initialize loop counter
            lp_counts = {t: 0 for t in self.nmr_content_subtypes}

            # increment loop counter of each content subtype
            for lp_category in self.__lp_category_list:
                if lp_category in self.lp_categories[file_type].values():
                    lp_counts[[k for k, v in self.lp_categories[file_type].items() if v == lp_category][0]] += 1

            content_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

            for content_subtype in self.nmr_content_subtypes:

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if sf_category is None or lp_category is None:
                    continue

                if self.__star_data_type[file_list_id] == 'Loop':

                    if content_subtype not in content_subtypes:
                        continue

                    sf = self.__star_data[file_list_id]
                    sf_framecode = ''

                    self.__rescueFormerNef__(file_name, file_type, content_subtype, sf, sf_framecode, sf_category, lp_category)

                else:  # if self.__star_data_type[file_list_id] == 'Saveframe':

                    if content_subtype not in content_subtypes:
                        continue

                    sf = self.__star_data[file_list_id]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__rescueFormerNef__(file_name, file_type, content_subtype, sf, sf_framecode, sf_category, lp_category)

        return True

    def __rescueFormerNef__(self, file_name, file_type, content_subtype, sf, sf_framecode, sf_category, lp_category):
        """ Rescue former NEF version prior to 1.0.
        """

        if isinstance(sf, pynmrstar.Loop):
            loop = sf
        else:
            if __pynmrstar_v3_2__:
                loop = sf.get_loop(lp_category)
            else:
                loop = sf.get_loop_by_category(lp_category)

        try:

            index_tag = self.index_tags[file_type][content_subtype]

            if index_tag is not None:

                try:
                    tag_pos = next(loop.tags.index(tag) for tag in loop.tags if tag == 'ordinal')
                    loop.tags[tag_pos] = 'index'
                except StopIteration:
                    pass

                try:
                    tag_pos = next(loop.tags.index(tag) for tag in loop.tags if tag == 'index_id')
                    loop.tags[tag_pos] = 'index'
                except StopIteration:
                    pass

            if content_subtype == 'poly_seq':

                try:
                    tag_pos = next(loop.tags.index(tag) for tag in loop.tags if tag == 'residue_type')
                    loop.tags[tag_pos] = 'residue_name'
                except StopIteration:
                    pass

                if 'index' not in loop.tags:

                    lp_tag = lp_category + '.index'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueFormerNef() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        for idx, row in enumerate(loop, start=1):
                            row.append(idx)

                        loop.add_tag(lp_category + '.index')

                    except ValueError:
                        pass

            elif content_subtype == 'chem_shift':

                if any(tag for tag in sf.tags if tag[0] == 'atom_chemical_shift_units'):
                    if __pynmrstar_v3_2__:
                        sf.remove_tag('atom_chemical_shift_units')
                    else:
                        sf.delete_tag('atom_chemical_shift_units')

                try:
                    tag_pos = next(loop.tags.index(tag) for tag in loop.tags if tag == 'residue_type')
                    loop.tags[tag_pos] = 'residue_name'
                except StopIteration:
                    pass

                if 'element' not in loop.tags:

                    lp_tag = lp_category + '.element'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueFormerNef() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        atom_name_col = loop.tags.index('atom_name')

                        for row in loop:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row.append(atom_type)

                        loop.add_tag(lp_category + '.element')

                    except ValueError:
                        pass

                elif not self.__combined_mode:

                    atom_type_col = loop.tags.index('element')
                    atom_name_col = loop.tags.index('atom_name')

                    for row in loop:
                        if row[atom_type_col] in emptyValue:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[atom_type_col] = atom_type

                if 'isotope_number' not in loop.tags:

                    lp_tag = lp_category + '.isotope_number'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueFormerNef() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        atom_name_col = loop.tags.index('atom_name')

                        for row in loop:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row.append(str(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type][0]))

                        loop.add_tag(lp_category + '.isotope_number')

                    except ValueError:
                        pass

                elif not self.__combined_mode:

                    iso_num_col = loop.tags.index('isotope_number')
                    atom_name_col = loop.tags.index('atom_name')

                    for row in loop:
                        if row[iso_num_col] in emptyValue:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[iso_num_col] = str(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type][0])

            elif content_subtype == 'dihed_restraint':

                if 'name' not in loop.tags:

                    lp_tag = lp_category + '.name'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueFormerNef() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        for row in loop:
                            row.append('.')

                        loop.add_tag(lp_category + '.name')

                    except ValueError:
                        pass

            elif content_subtype == 'rdc_restraint':

                try:

                    tag = next(tag for tag in sf.tags if tag[0] == 'tensor_residue_type')
                    sf.add_tag(sf_category + '.tensor_residue_name', tag[1])
                    if __pynmrstar_v3_2__:
                        sf.remove_tag('tensor_residue_type')
                    else:
                        sf.delete_tag('tensor_residue_type')

                except StopIteration:
                    pass
                except ValueError:
                    pass

            if content_subtype in ('dist_restraint', 'rdc_restraint'):
                max_dim = 3

            elif content_subtype == 'dihed_restraint':
                max_dim = 5

            elif content_subtype == 'spectral_peak':

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return

                max_dim = num_dim + 1

            else:
                return

            for j in range(1, max_dim):

                _residue_type = 'residue_type_' + str(j)

                try:
                    tag_pos = next(loop.tags.index(tag) for tag in loop.tags if tag == _residue_type)
                    loop.tags[tag_pos] = 'residue_name_' + str(j)
                except StopIteration:
                    pass

        except KeyError:
            pass

    def __rescueImmatureStr(self, file_list_id):
        """ Rescue immature NMR-STAR.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if file_type != 'nmr-star' or file_list_id >= len(self.__star_data) or self.__star_data[file_list_id] is None:
            return True

        if self.__combined_mode or self.__star_data_type[file_list_id] == 'Entry':

            for content_subtype in self.nmr_content_subtypes:

                if content_subtype == 'entry_info':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]

                if sf_category is None:
                    continue

                for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if self.__getSaveframeByName(file_list_id, sf_framecode) is None:

                        itName = '_' + sf_category + '.Sf_framecode'

                        if self.__resolve_conflict:
                            warn = f"{itName} {sf_framecode!r} should be matched with saveframe name {sf.name!r}. {itName} will be overwritten."

                            self.report.warning.appendDescription('missing_saveframe',
                                                                  {'file_name': file_name, 'sf_framecode': sf.name,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__rescueImmatureStr() ++ Warning  - {warn}\n")

                            tagNames = [t[0] for t in sf.tags]

                            if 'Sf_framecode' in tagNames:
                                set_sf_tag(sf, 'Sf_framecode', sf.name)
                            elif 'sf_framecode' in tagNames:
                                set_sf_tag(sf, 'sf_framecode', sf.name)

                        else:
                            err = f"{itName} {sf_framecode!r} must be matched with saveframe name {sf.name!r}."

                            self.report.error.appendDescription('format_issue',
                                                                {'file_name': file_name, 'sf_framecode': sf.name,
                                                                 'description': err})
                            self.report.setError()

                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ Error  - "
                                             f"{file_name} {sf.name} {err}\n")

        if not self.__rescue_mode:
            return True

        self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(self.__star_data[file_list_id])

        # initialize loop counter
        lp_counts = {t: 0 for t in self.nmr_content_subtypes}

        # increment loop counter of each content subtype
        for lp_category in self.__lp_category_list:
            if lp_category in self.lp_categories[file_type].values():
                lp_counts[[k for k, v in self.lp_categories[file_type].items() if v == lp_category][0]] += 1

        content_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

        for content_subtype in self.nmr_content_subtypes:

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if content_subtype.startswith('spectral_peak'):
                lp_category = self.aux_lp_categories[file_type][content_subtype][0]  # _Spectral_dim

            if sf_category is None or lp_category is None:
                continue

            if self.__star_data_type[file_list_id] == 'Loop':

                if content_subtype not in content_subtypes:
                    continue

                sf = self.__star_data[file_list_id]
                sf_framecode = ''

                self.__rescueImmatureStr__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

            elif self.__star_data_type[file_list_id] == 'Saveframe':

                if content_subtype not in content_subtypes:
                    continue

                sf = self.__star_data[file_list_id]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__rescueImmatureStr__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__rescueImmatureStr__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

        return True

    def __rescueImmatureStr__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category):
        """ Rescue immature NMR-STAR.
        """

        if isinstance(sf, pynmrstar.Loop):
            loop = sf
        else:
            if __pynmrstar_v3_2__:
                loop = sf.get_loop(lp_category)
            else:
                loop = sf.get_loop_by_category(lp_category)

        try:

            if content_subtype == 'chem_shift':

                if 'Atom_type' not in loop.tags:

                    lp_tag = lp_category + '.Atom_type'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        atom_name_col = loop.tags.index('Atom_ID')

                        for row in loop:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row.append(atom_type)

                        loop.add_tag(lp_category + '.Atom_type')

                    except ValueError:
                        pass

                elif not self.__combined_mode:

                    atom_type_col = loop.tags.index('Atom_type')
                    atom_name_col = loop.tags.index('Atom_ID')

                    for row in loop:
                        if row[atom_type_col] in emptyValue:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[atom_type_col] = atom_type

                if 'Atom_isotope_number' not in loop.tags:

                    lp_tag = lp_category + '.Atom_isotope_number'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        atom_name_col = loop.tags.index('Atom_ID')

                        for row in loop:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row.append(str(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type][0]))

                        loop.add_tag(lp_category + '.Atom_isotope_number')

                    except ValueError:
                        pass

                elif not self.__combined_mode:

                    iso_num_col = loop.tags.index('Atom_isotope_number')
                    atom_name_col = loop.tags.index('Atom_ID')

                    for row in loop:
                        if row[iso_num_col] in emptyValue:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[iso_num_col] = str(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type][0])

            elif content_subtype == 'dist_restraint':  # backward compatibility

                original_items = ['Original_PDB_strand_ID', 'Original_PDB_residue_no', 'Original_PDB_residue_name']

                for i in range(1, 3):
                    for original_item in original_items:
                        tag = original_item + '_' + str(i)
                        if tag in loop.tags:
                            if __pynmrstar_v3_2__:
                                loop.remove_tag(tag)
                            else:
                                loop.delete_tag(tag)

                    tag = 'Original_PDB_atom_name_' + str(i)
                    if tag in loop.tags:

                        _tag = 'Auth_atom_name_' + str(i)
                        if _tag not in loop.tags:
                            _dat = get_lp_tag(loop, [tag])

                            for idx, row in enumerate(loop):
                                row.append(_dat[idx])

                            loop.add_tag(_tag)

                        if __pynmrstar_v3_2__:
                            loop.remove_tag(tag)
                        else:
                            loop.delete_tag(tag)

            elif content_subtype == 'dihed_restraint':

                if 'Torsion_angle_name' not in loop.tags:

                    lp_tag = lp_category + '.Torsion_angle_name'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        for row in loop:
                            row.append('.')

                        loop.add_tag(lp_category + '.Torsion_angle_name')

                    except ValueError:
                        pass

            elif content_subtype.startswith('spectral_peak'):

                if 'Atom_type' not in loop.tags:

                    lp_tag = lp_category + '.Atom_type'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        axis_code_name_col = loop.tags.index('Axis_code')

                        for row in loop:
                            atom_type = re.sub(r'\d+', '', row[axis_code_name_col])
                            row.append(atom_type)

                        loop.add_tag(lp_category + '.Atom_type')

                    except ValueError:
                        pass

                if 'Atom_isotope_number' not in loop.tags:

                    lp_tag = lp_category + '.Atom_isotope_number'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        axis_code_name_col = loop.tags.index('Axis_code')

                        for row in loop:
                            atom_type = re.sub(r'\d+', '', row[axis_code_name_col])
                            row.append(str(ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type][0]))

                        loop.add_tag(lp_category + '.Atom_isotope_number')

                    except ValueError:
                        pass

                if 'Axis_code' not in loop.tags:

                    lp_tag = lp_category + '.Axis_code'
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):
                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write("+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    try:

                        atom_type_name_col = loop.tags.index('Atom_type')
                        iso_num_name_col = loop.tags.index('Atom_isotope_number')

                        for row in loop:
                            atom_type = row[atom_type_name_col]
                            iso_num = row[iso_num_name_col]
                            row.append(iso_num + atom_type)

                        loop.add_tag(lp_category + '.Axis_code')

                    except ValueError:
                        pass

        except KeyError:
            pass

    def __detectContentSubType(self):
        """ Detect content subtype of NMR data file in any STAR format.
        """

        if len(self.__star_data) != self.__file_path_list_len:
            return False

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]

            self.__detectContentSubType__(fileListId, input_source, self.__dirPath)

        return not self.report.isError()

    def __detectContentSubType__(self, file_list_id, input_source, dir_path=None):
        """ Detect content subtype of NMR data file in any STAR format.
        """

        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']
        content_type = input_source_dic['content_type']

        if input_source_dic['content_subtype'] is not None:
            return

        self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(self.__star_data[file_list_id])

        if self.__combined_mode and file_list_id == 0 and file_type == 'nmr-star'\
           and 'constraint_statistics' in self.__sf_category_list\
           and '_Constraint_file' in self.__lp_category_list:
            _sf = self.__star_data[file_list_id].get_saveframes_by_category('constraint_statistics')[0]
            data_file_name = get_first_sf_tag(_sf, 'Data_file_name')
            if mr_file_name_pattern.match(data_file_name) or proc_mr_file_name_pattern.match(data_file_name):
                entry_id = get_first_sf_tag(_sf, 'Entry_ID')
                if pdb_id_pattern.match(entry_id) or dep_id_pattern.match(entry_id):
                    self.__remediation_mode = True
                    self.__nefT.set_remediation_mode(True)

        is_valid, messages, corrections = self.__nefT.resolve_sf_names_for_cif(self.__star_data[file_list_id])  # DAOTHER-7389, issue #4
        self.__sf_name_corr.append(corrections)

        if not is_valid:

            for warn in messages:
                self.report.warning.appendDescription('corrected_saveframe_name',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

        tags_with_null_str = []

        for sf_category in self.__sf_category_list:  # DAOTHER-5896

            for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                for tag in sf.tags:
                    if isinstance(tag[1], str) and len(tag[1]) == 0:
                        tags_with_null_str.append('_' + sf_category + '.' + tag[0])
                        tag[1] = '.'

        if len(tags_with_null_str) > 0:

            warn = f"Empty strings for {tags_with_null_str} are not allowed as values. Use a '.' or a '?' if needed."

            self.report.warning.appendDescription('corrected_format_issue',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

        for sf_category in self.__sf_category_list:

            if file_type == 'nmr-star' and sf_category == 'entity':
                self.__has_star_entity = True

            if sf_category is not None and sf_category not in self.sf_categories[file_type].values():

                if not self.__bmrb_only:

                    if file_type == 'nef':
                        warn = f"Ignored third party software's saveframe {sf_category!r}."
                    else:

                        if sf_category == 'constraint_statistics':
                            continue

                        warn = f"Ignored saveframe category {sf_category!r}."

                    self.report.warning.appendDescription('skipped_saveframe_category',
                                                          {'file_name': file_name, 'sf_category': sf_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

        # initialize loop counter
        lp_counts = {t: 0 for t in self.nmr_content_subtypes}

        # increment loop counter of each content subtype
        for lp_category in self.__lp_category_list:
            if lp_category in self.lp_categories[file_type].values():
                lp_counts[[k for k, v in self.lp_categories[file_type].items() if v == lp_category][0]] += 1

        content_subtype = 'poly_seq'

        lp_category = self.lp_categories[file_type][content_subtype]

        if lp_counts[content_subtype] == 0:

            if not self.__has_star_entity and self.__combined_mode:

                if self.__resolve_conflict and self.__update_poly_seq:  # DAOTHER-6694
                    warn = f"A saveframe with a category {lp_category!r} is missing in the NMR data."

                    self.report.warning.appendDescription('missing_saveframe',
                                                          {'file_name': file_name, 'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

                elif not self.__remediation_mode:
                    err = f"A saveframe with a category {lp_category!r} is missing. Please re-upload the {file_type.upper()} file."

                    self.report.error.appendDescription('missing_mandatory_content',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

            elif lp_counts['chem_shift'] == 0 and lp_counts['dist_restraint'] > 0 and content_type != 'nmr-restraints':
                err = f"A saveframe with a category {lp_category!r} is missing. Please re-upload the {file_type.upper()} file."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

        elif lp_counts[content_subtype] > 1:

            err = f"Unexpectedly, multiple saveframes having {lp_category!r} category exist."

            self.report.error.appendDescription('format_issue',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

            self.__lfh.write("+NmrDpUtility.__detectContentSubType() ++ Error  - "
                             f"{file_name} {err}\n")

        if self.__remediation_mode and not self.__bmrb_only:

            if content_type == 'nmr-restraints':

                for content_subtype in ('entry_info', 'poly_seq', 'entity', 'chem_shift', 'chem_shift_ref'):

                    sf_category = self.sf_categories[file_type][content_subtype]

                    if sf_category is None or lp_counts[content_subtype] == 0:
                        continue

                    for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if content_subtype == 'chem_shift' and not self.__has_star_chem_shift:
                            if self.__star_data[0] is None:
                                self.__star_data[0] = pynmrstar.Entry.from_scratch(self.__entry_id)
                                self.__star_data_type[0] = 'Entry'

                            if sf not in self.__star_data[0].frame_list:
                                self.__star_data[0].add_saveframe(sf)

                                input_source_ = self.report.input_sources[0]
                                input_source_dic_ = input_source_.get()
                                content_subtypes_ = input_source_dic_['content_subtype']

                                if content_subtypes_ is None:
                                    content_subtypes_ = {content_subtype: 0}

                                content_subtypes_[content_subtype] += 1

                                input_source_.setItemValue('content_subtype', content_subtypes_)

                                for idx, msg in enumerate(self.__suspended_errors_for_lazy_eval):
                                    for k, v in msg.items():
                                        if k == 'missing_mandatory_content':
                                            del self.__suspended_errors_for_lazy_eval[idx]
                                            break

                            cs_file_path_list = 'chem_shift_file_path_list'

                            cs = self.__inputParamDict[cs_file_path_list][0]

                            if isinstance(cs, str):
                                cs_path = cs
                            else:
                                cs_path = cs['file_name']

                            if dir_path is None:
                                dir_path = os.path.dirname(cs_path)

                            cs_file_name = os.path.basename(cs_path)

                            if cs_file_name.endswith('.cif2str'):
                                cs_file_name = os.path.splitext(cs_file_name)[0]

                            if cs_file_name.endswith('.str'):
                                cs_file_name = os.path.splitext(cs_file_name)[0]

                            if cs_file_name.endswith('-corrected'):
                                cs_file_name = cs_file_name[:-10]

                            cs_base_name = cs_file_name
                            cs_file_name = cs_base_name + '-corrected.str'
                            cs_file_path = os.path.join(dir_path, cs_file_name)

                            if not os.path.exists(cs_file_path):
                                master_entry = self.__star_data[0]

                                if __pynmrstar_v3__:
                                    master_entry.write_to_file(cs_file_path, show_comments=False, skip_empty_loops=True, skip_empty_tags=False)
                                else:
                                    master_entry.write_to_file(cs_file_path)

                                compress_as_gzip_file(cs_file_path, cs_file_path + '.gz')

                            rem_dir = os.path.join(dir_path, 'remediation')

                            try:

                                if not os.path.isdir(rem_dir):
                                    os.makedirs(rem_dir)

                                cs_file_link = os.path.join(rem_dir, cs_base_name + '.str')

                                if os.path.exists(cs_file_link):
                                    os.remove(cs_file_link)

                                os.symlink(cs_file_path, cs_file_link)

                            except OSError:
                                pass

                        self.__star_data[file_list_id].remove_saveframe(sf_framecode)

                    lp_counts[content_subtype] = 0

            elif content_type == 'nmr-chemical-shifts' and bmrb_nmr_star_file_name_pattern.match(file_name):

                for content_subtype in self.nmr_content_subtypes:

                    if content_subtype == 'chem_shift':
                        continue

                    sf_category = self.sf_categories[file_type][content_subtype]

                    if sf_category is None or lp_counts[content_subtype] == 0:
                        continue

                    for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        self.__star_data[file_list_id].remove_saveframe(sf_framecode)

                    lp_counts[content_subtype] = 0

        content_subtype = 'chem_shift'

        if lp_counts[content_subtype] == 0 and self.__combined_mode:

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            err = f"The saveframe with a category {sf_category!r} is missing, "\
                f"Deposition of assigned chemical shifts is mandatory. Please re-upload the {file_type.upper()} file."

            self.report.error.appendDescription('missing_mandatory_content',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

            if self.__remediation_mode:
                dir_path = os.path.dirname(self.__dstPath)

                touch_file = os.path.join(dir_path, '.entry_without_cs')
                if not os.path.exists(touch_file):
                    with open(touch_file, 'w') as ofh:
                        ofh.write('')

        if lp_counts[content_subtype] > 0 and content_type == 'nmr-restraints' and not self.__bmrb_only:

            if self.__remediation_mode and lp_counts['dist_restraint'] + lp_counts['dihed_restraint'] + lp_counts['rdc_restraint'] > 0:

                warn = "NMR restraint file includes assigned chemical shifts. "\
                    "which will be ignored during remediation."

                self.report.warning.appendDescription('corrected_format_issue',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

            else:

                err = "NMR restraint file includes assigned chemical shifts. "\
                    f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

        content_subtype = 'dist_restraint'

        if lp_counts[content_subtype] == 0 and self.__combined_mode:

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if self.__allow_missing_dist_restraint:

                warn = f"The saveframe with a category {sf_category!r} is missing. "\
                       "The wwPDB NEF Working Group strongly recommends the submission of distance restraints "\
                       "used for the structure determination."

                if 'noepk_restraint' in lp_counts and lp_counts['noepk_restraint'] > 0:
                    warn += " '_Homonucl_NOE' category is only useful for describing assigned NOE peak height/volume. "\
                        "Please use the '_Gen_dist_constraint' category to describe general distance restraint."

                if 'other_data_types' in self.__sf_category_list:
                    sf_framecodes_wo_loop = []
                    for sf in self.__star_data[file_list_id].get_saveframes_by_category('other_data_types'):

                        try:
                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop('_Other_data')
                            else:
                                loop = sf.get_loop_by_category('_Other_data')
                        except KeyError:
                            sf_framecodes_wo_loop.append(get_first_sf_tag(sf, 'sf_framecode'))
                            continue

                        if loop is None:
                            sf_framecodes_wo_loop.append(get_first_sf_tag(sf, 'sf_framecode'))

                    if len(sf_framecodes_wo_loop) > 0:
                        _sf_framecodes_wo_loop = "', '".join(sf_framecodes_wo_loop)
                        warn += f" Uninterpreted NMR restraints are stored in {_sf_framecodes_wo_loop!r} "\
                            f"saveframe{'s' if len(sf_framecodes_wo_loop) > 1 else ''} as raw text format. "\
                            "Please consider incorporating those restraints into well-known formats that OneDep supports, if possible."

                self.report.warning.appendDescription('missing_content',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

            elif not self.__validation_server:

                err = f"The saveframe with a category {sf_category!r} is missing, "\
                    f"Deposition of distance restraints is mandatory. Please re-upload the {file_type.upper()} file."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

        if (lp_counts['dist_restraint'] > 0 or lp_counts['dihed_restraint'] or lp_counts['rdc_restraint'])\
           and content_type == 'nmr-chemical-shifts' and not self.__bmrb_only:

            err = "The assigned chemical shift file includes NMR restraints. "\
                f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

            self.report.error.appendDescription('content_mismatch',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

        has_spectral_peak = lp_counts['spectral_peak'] + lp_counts['spectral_peak_alt'] > 0

        if not has_spectral_peak and self.__remediation_mode:
            if 'spectral_peak_list' in self.__sf_category_list:
                has_spectral_peak = True

        if not has_spectral_peak and self.__combined_mode:

            warn = "The wwPDB NMR Validation Task Force strongly encourages the submission of spectral peak lists, "\
                "in particular those generated from NOESY spectra."

            self.report.warning.appendDescription('encouragement',
                                                  {'file_name': file_name, 'description': warn})
            self.report.setWarning()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Warning  - {warn}\n")

        if has_spectral_peak and content_type == 'nmr-chemical-shifts' and not self.__bmrb_only:

            err = "The assigned chemical shift file includes spectral peak lists. "\
                f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

            self.report.error.appendDescription('content_mismatch',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

            if self.__remediation_mode and dir_path is not None:
                touch_file = os.path.join(dir_path, '.entry_with_pk')
                if not os.path.exists(touch_file):
                    with open(touch_file, 'w') as ofh:
                        ofh.write('')

        if self.__combined_mode:

            mr_loops = 0

            for content_subtype in self.mr_content_subtypes:
                if content_subtype in lp_counts:
                    mr_loops += lp_counts[content_subtype]

            if mr_loops == 0 and not self.__validation_server:

                if 'other_data_types' not in self.__sf_category_list:

                    err = "Deposition of NMR restraints used for the structure determination is mandatory. "\
                        f"Please re-upload the {file_type.upper()} file."

                    self.report.error.appendDescription('missing_mandatory_content',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubType() ++ Error  - {err}\n")

        content_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

        if not self.__combined_mode and self.__remediation_mode and file_list_id == 0 and file_type == 'nmr-star':

            content_subtype = 'chem_shift_ref'

            # Delete extra saveframes for chemical shift reference

            if content_subtype in content_subtypes.keys():
                while content_subtypes[content_subtype] > content_subtypes['chem_shift']:
                    sf_category = self.sf_categories[file_type][content_subtype]
                    csr_sf = self.__star_data[file_list_id].get_saveframes_by_category(sf_category)[-1]
                    del self.__star_data[file_list_id][csr_sf]
                    content_subtypes[content_subtype] -= 1

        input_source.setItemValue('content_subtype', content_subtypes)

    def __detectContentSubTypeOfLegacyMr(self):
        """ Detect content subtype of legacy NMR restraint files.
        """

        if self.__combined_mode:
            return True

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        corrected = False

        hbond_da_atom_types = ('O', 'N', 'F')
        rdc_origins = ('OO', 'X', 'Y', 'Z')

        md5_list = []

        fileListId = self.__file_path_list_len

        for ar in self.__inputParamDict[ar_file_path_list]:
            file_path = ar['file_name']

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            fileListId += 1

            if file_type in ('nm-res-mr', 'nm-res-sax', 'nm-pea-any'):
                if file_type == 'nm-res-mr':
                    md5_list.append(None)
                else:
                    with open(file_path, 'r', encoding='utf-8', errors='ignore') as ifh:
                        md5_list.append(hashlib.md5(ifh.read().encode('utf-8')).hexdigest())
                continue

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])
                if file_name != original_file_name and original_file_name is not None:
                    file_name = f"{original_file_name} ({file_name})"

            self.__cur_original_ar_file_name = original_file_name

            with open(file_path, 'r', encoding='utf-8', errors='ignore') as ifh:
                md5_list.append(hashlib.md5(ifh.read().encode('utf-8')).hexdigest())

            is_aux_amb = file_type == 'nm-aux-amb'
            is_aux_gro = file_type == 'nm-aux-gro'

            _mr_format_name = getRestraintFormatName(file_type)
            mr_format_name = _mr_format_name.split()[0]
            a_mr_format_name = ('an ' if mr_format_name[0] in ('AINMX') else 'a ') + _mr_format_name

            atom_like_names =\
                self.__csStat.getAtomLikeNameSet(minimum_len=(2 if file_type in ('nm-res-ros', 'nm-res-bio', 'nm-res-dyn', 'nm-res-syb',
                                                                                 'nm-res-isd', 'nm-res-ari', 'nm-res-oth') or is_aux_amb or is_aux_gro else 1))
            cs_atom_like_names = list(filter(is_half_spin_nuclei, atom_like_names))  # DAOTHER-7491

            has_chem_shift = False
            has_dist_restraint = False
            has_dihed_restraint = False
            has_rdc_restraint = False
            has_plane_restraint = False
            has_hbond_restraint = False
            has_ssbond_restraint = False
            has_rdc_origins = False
            has_spectral_peak = False

            has_coordinate = False
            has_amb_coord = False
            has_amb_inpcrd = False
            has_ens_coord = False
            has_topology = False

            has_first_atom = False

            if file_type in ('nm-res-xpl', 'nm-res-cns'):

                with open(file_path, 'r', encoding='utf-8') as ifh:

                    atom_likes = 0
                    atom_unlikes = 0
                    cs_atom_likes = 0
                    resid_likes = 0
                    real_likes = 0
                    names = []
                    resids = []

                    rdc_atom_names = set()

                    cs_range_like = False
                    dist_range_like = False
                    dihed_range_like = False
                    rdc_range_like = False

                    for line in ifh:

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if pdb_first_atom_pattern.match(line):
                                if has_first_atom:
                                    has_ens_coord = True
                                has_first_atom = True

                        elif line.startswith('MODEL') or line.startswith('ENDMDL')\
                                or line.startswith('_atom_site.pdbx_PDB_model_num')\
                                or line.startswith('_atom_site.ndb_model'):
                            has_ens_coord = True

                        _line = ' '.join(line.split())

                        s = re.split('[ ()]', _line)

                        _t_lower = ""

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] in ('#', '!'):
                                break

                            t_lower = t.lower()

                            if t_lower.startswith('assi') or (real_likes == 3 and t_lower.startswith('weight')):

                                if cs_atom_likes == 1 and resid_likes == 1 and cs_range_like:
                                    has_chem_shift = True

                                elif (atom_likes == 2 or (atom_likes > 0 and resid_likes == 2)) and dist_range_like:
                                    has_dist_restraint = True

                                elif atom_likes == 4 and dihed_range_like:
                                    has_dihed_restraint = True

                                elif cs_atom_likes + atom_unlikes == 6 and rdc_range_like:
                                    has_rdc_restraint = True

                                elif atom_likes == 3 and not (cs_range_like or dist_range_like or dihed_range_like or rdc_range_like or has_hbond_restraint)\
                                        and names[0][0] in hbond_da_atom_types and names[1][0] in protonBeginCode and names[2][0] in hbond_da_atom_types:
                                    has_hbond_restraint = True

                                atom_likes = 0
                                atom_unlikes = 0
                                cs_atom_likes = 0
                                resid_likes = 0
                                real_likes = 0
                                names = []
                                resids = []
                                cs_range_like = False
                                dist_range_like = False
                                dihed_range_like = False
                                rdc_range_like = False

                            elif _t_lower == 'name':
                                name = t.upper()
                                if name in atom_like_names:
                                    if name not in names or len(names) > 1:
                                        atom_likes += 1
                                        names.append(name)
                                    if name in cs_atom_like_names:
                                        cs_atom_likes += 1
                                else:
                                    atom_unlikes += 1
                                    if not has_rdc_origins and name in rdc_origins:
                                        rdc_atom_names.add(name)
                                        if len(rdc_atom_names) == 4:
                                            has_rdc_origins = True

                            elif _t_lower == 'resid':
                                try:
                                    v = int(t)
                                    if v not in resids:
                                        resid_likes += 1
                                        resids.append(v)
                                except ValueError:
                                    pass

                            elif '.' in t:
                                try:
                                    v = float(t)
                                    if CS_RANGE_MIN <= v <= CS_RANGE_MAX:
                                        cs_range_like = True
                                    if DIST_RANGE_MIN <= v <= DIST_RANGE_MAX:
                                        dist_range_like = True
                                    if ANGLE_RANGE_MIN <= v <= ANGLE_RANGE_MAX:
                                        dihed_range_like = True
                                    if RDC_RANGE_MIN <= v <= RDC_RANGE_MAX:
                                        rdc_range_like = True
                                    real_likes += 1
                                except ValueError:
                                    pass

                            _t_lower = t_lower

                with open(file_path, 'r', encoding='utf-8') as ifh:

                    atom_likes = 0
                    names = []
                    has_rest = False
                    has_plan = False
                    has_grou = False
                    has_sele = False
                    has_resi = False

                    for line in ifh:

                        _line = ' '.join(line.split())

                        s = re.split('[ ()=]', _line)

                        _t_lower = ""

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] in ('#', '!'):
                                break

                            t_lower = t.lower()

                            if t_lower.startswith('rest'):
                                has_rest = True

                            elif t_lower.startswith('plan'):
                                has_plan = True

                            elif has_rest and has_plan:

                                if t_lower.startswith('grou'):
                                    has_grou = True

                                elif t_lower.startswith('sele'):
                                    has_sele = True

                                    atom_likes = 0
                                    names = []

                                elif _t_lower == 'name':
                                    name = t.upper()
                                    if name in atom_like_names:
                                        if name not in names or len(names) > 1:
                                            atom_likes += 1
                                            names.append(name)

                                elif t_lower.startswith('resi'):
                                    has_resi = True

                                elif has_grou and has_sele and has_resi and not has_plane_restraint and _t_lower.startswith('weig'):
                                    if atom_likes > 0:
                                        try:
                                            v = float(t)
                                            if WEIGHT_RANGE_MIN <= v <= WEIGHT_RANGE_MAX:
                                                has_plane_restraint = True
                                        except ValueError:
                                            pass

                                elif t_lower == 'end':
                                    has_grou = False
                                    has_sele = False
                                    has_resi = False

                            _t_lower = t_lower

            elif file_type == 'nm-res-amb':

                with open(file_path, 'r', encoding='utf-8') as ifh:

                    in_rst = False
                    in_iat = False
                    in_igr1 = False
                    in_igr2 = False

                    names = []
                    values = []

                    pos = 0

                    dist_range_like = False
                    dihed_range_like = False
                    rdc_range_like = False

                    for line in ifh:

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if pdb_first_atom_pattern.match(line):
                                if has_first_atom:
                                    has_ens_coord = True
                                has_first_atom = True

                        elif line.startswith('MODEL') or line.startswith('ENDMDL')\
                                or line.startswith('_atom_site.pdbx_PDB_model_num')\
                                or line.startswith('_atom_site.ndb_model'):
                            has_ens_coord = True

                        pos += 1

                        if pos == 1 and not line.isdigit():
                            has_amb_inpcrd = True

                        elif pos == 2 and has_amb_inpcrd:
                            try:
                                int(line.lstrip().split()[0])
                            except (ValueError, IndexError):
                                has_amb_inpcrd = False

                        elif pos == 3 and has_amb_inpcrd:
                            if line.count('.') != 6:
                                has_amb_inpcrd = False

                        if '&rst ' in line:
                            line = re.sub('&rst ', '&rst,', line)

                        elif '&end' in line:
                            line = re.sub('&end', ',&end', line)

                        elif '/' in line:
                            line = re.sub('/', ',&end', line)

                        _line = ' '.join(line.split())

                        if len(_line) == 0 or _line.startswith('#') or _line.startswith('!'):
                            continue

                        s = re.split(',', ws_pattern.sub('', _line).lower())

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] in ('#', '!'):
                                break

                            if t == '&rst':
                                in_rst = True

                            elif in_rst:

                                if t == '&end':

                                    atom_likes = 0
                                    atom_unlikes = 0

                                    for name in names:

                                        if isinstance(name, int):
                                            if name != -1:
                                                atom_likes += 1
                                            else:
                                                atom_unlikes += 1

                                        if isinstance(name, list):

                                            if any(n for n in name if n != -1):
                                                atom_likes += 1
                                            else:
                                                atom_unlikes += 1

                                    if len(values) == 4:
                                        v = (values[1] + values[2]) / 2.0

                                        if DIST_RANGE_MIN <= v <= DIST_RANGE_MAX:
                                            dist_range_like = True
                                        if ANGLE_RANGE_MIN <= v <= ANGLE_RANGE_MAX:
                                            dihed_range_like = True
                                        if RDC_RANGE_MIN <= v <= RDC_RANGE_MAX:
                                            rdc_range_like = True

                                        if atom_likes == 2 and dist_range_like:
                                            has_dist_restraint = True

                                        elif atom_likes == 4 and dihed_range_like:
                                            has_dihed_restraint = True

                                        elif atom_likes + atom_unlikes == 6 and rdc_range_like:
                                            has_rdc_restraint = True

                                    names = []
                                    values = []

                                    in_rst = False
                                    in_iat = False
                                    in_igr1 = False
                                    in_igr2 = False

                                elif t.startswith('iat='):
                                    in_iat = True
                                    try:
                                        iat = int(t[4:])
                                        names.append(iat)
                                    except ValueError:
                                        pass

                                    in_igr1 = False
                                    in_igr2 = False

                                elif '=' not in t and in_iat:
                                    try:
                                        iat = int(t)
                                        names.append(iat)
                                    except ValueError:
                                        pass

                                elif amber_r_pattern.match(t):
                                    len_values = len(values)
                                    g = amber_r_pattern.search(t).groups()
                                    try:
                                        r_idx = int(g[0]) - 1
                                        v = float(g[1])
                                        if len_values == r_idx:
                                            values.append(v)
                                        elif len_values > r_idx:
                                            values.insert(r_idx, v)
                                        else:
                                            while len(values) < r_idx:
                                                values.append(None)
                                            values.append(v)
                                    except ValueError:
                                        pass

                                    in_iat = False
                                    in_igr1 = False
                                    in_igr2 = False

                                elif t.startswith('igr1'):
                                    in_igr1 = True
                                    try:
                                        iat = int(t[5:])
                                        names.insert(0, [iat])
                                    except ValueError:
                                        pass

                                    in_iat = False
                                    in_igr2 = False

                                elif '=' not in t and in_igr1:
                                    try:
                                        iat = int(t)
                                        g = names[0]
                                        g.append(iat)
                                    except ValueError:
                                        pass

                                elif t.startswith('igr2'):
                                    in_igr2 = True
                                    try:
                                        iat = int(t[5:])
                                        names.insert(1, [iat])
                                    except ValueError:
                                        pass

                                    in_iat = False
                                    in_igr1 = False

                                elif '=' not in t and in_igr2:
                                    try:
                                        iat = int(t)
                                        g = names[1]
                                        g.append(iat)
                                    except ValueError:
                                        pass

                                elif '=' in t:
                                    in_iat = False
                                    in_igr1 = False
                                    in_igr2 = False

            elif file_type in ('nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-dyn', 'nm-res-syb',
                               'nm-res-isd', 'nm-res-ari', 'nm-res-oth') or is_aux_amb or is_aux_gro:

                if is_aux_amb:

                    has_atom_name = False
                    has_residue_label = False
                    has_residue_pointer = False
                    has_amb_atom_type = False

                    chk_atom_name_format = False
                    chk_residue_label_format = False
                    chk_residue_pointer_format = False
                    chk_amb_atom_type_format = False

                    in_atom_name = False
                    in_residue_label = False
                    in_residue_pointer = False
                    in_amb_atom_type = False

                    atom_names = 0
                    residue_labels = 0
                    residue_pointers = 0
                    amb_atom_types = 0

                elif is_aux_gro:

                    has_system = False
                    has_molecules = False
                    has_atoms = False

                    in_system = False
                    in_molecules = False
                    in_atoms = False

                    system_names = 0
                    molecule_names = 0
                    atom_names = 0

                atom_like_names_oth = self.__csStat.getAtomLikeNameSet(1)
                cs_atom_like_names_oth = list(filter(is_half_spin_nuclei, atom_like_names_oth))  # DAOTHER-7491

                one_letter_codes = monDict3.values()
                three_letter_codes = monDict3.keys()

                prohibited_col = set()

                with open(file_path, 'r', encoding='utf-8') as ifh:

                    pos = 0

                    for line in ifh:
                        pos += 1

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if pdb_first_atom_pattern.match(line):
                                if has_first_atom:
                                    has_ens_coord = True
                                has_first_atom = True
                            if is_aux_amb:  # and line.count('.') >= 3:
                                has_amb_coord = True

                        elif line.startswith('MODEL') or line.startswith('ENDMDL')\
                                or line.startswith('_atom_site.pdbx_PDB_model_num')\
                                or line.startswith('_atom_site.ndb_model'):
                            has_ens_coord = True

                        elif is_aux_amb:

                            if pos == 1 and not line.isdigit():
                                has_amb_inpcrd = True

                            elif pos == 2 and has_amb_inpcrd:
                                try:
                                    int(line.lstrip().split()[0])
                                except (ValueError, IndexError):
                                    has_amb_inpcrd = False

                            elif pos == 3 and has_amb_inpcrd:
                                if line.count('.') != 6:
                                    has_amb_inpcrd = False

                            if line.startswith('%FLAG'):
                                in_atom_name = in_residue_label = in_residue_pointer = False

                                if line.startswith('%FLAG ATOM_NAME'):
                                    has_atom_name = True
                                    chk_atom_name_format = True

                                elif line.startswith('%FLAG RESIDUE_LABEL'):
                                    has_residue_label = True
                                    chk_residue_label_format = True

                                elif line.startswith('%FLAG RESIDUE_POINTER'):
                                    has_residue_pointer = True
                                    chk_residue_pointer_format = True

                                elif line.startswith('%FLAG AMBER_ATOM_TYPE'):
                                    has_amb_atom_type = True
                                    chk_amb_atom_type_format = True

                            elif chk_atom_name_format:
                                chk_atom_name_format = amber_a_format_pattern.match(line)
                                if chk_atom_name_format:
                                    in_atom_name = True
                                    g = amber_a_format_pattern.search(line).groups()
                                    max_cols = int(g[0])
                                    max_char = int(g[1])
                                else:
                                    has_atom_name = False
                                chk_atom_name_format = False

                            elif chk_residue_label_format:
                                chk_residue_label_format = amber_a_format_pattern.match(line)
                                if chk_residue_label_format:
                                    in_residue_label = True
                                    g = amber_a_format_pattern.search(line).groups()
                                    max_cols = int(g[0])
                                    max_char = int(g[1])
                                else:
                                    has_residue_label = False
                                chk_residue_label_format = False

                            elif chk_residue_pointer_format:
                                chk_residue_pointer_format = amber_i_format_pattern.match(line)
                                if chk_residue_pointer_format:
                                    in_residue_pointer = True
                                    g = amber_i_format_pattern.search(line).groups()
                                    max_cols = int(g[0])
                                    max_char = int(g[1])
                                else:
                                    has_residue_pointer = False
                                chk_residue_pointer_format = False

                            elif chk_amb_atom_type_format:
                                chk_amb_atom_type_format = amber_a_format_pattern.match(line)
                                if chk_amb_atom_type_format:
                                    in_amb_atom_type = True
                                    g = amber_a_format_pattern.search(line).groups()
                                    max_cols = int(g[0])
                                    max_char = int(g[1])
                                else:
                                    has_amb_atom_type = False
                                chk_amb_atom_type_format = False

                            elif in_atom_name:
                                len_line = len(line)
                                begin = 0
                                end = max_char
                                col = 0
                                while col < max_cols and end < len_line:
                                    if len(line[begin:end].rstrip()) > 0:
                                        atom_names += 1
                                    begin = end
                                    end += max_char
                                    col += 1

                            elif in_residue_label:
                                len_line = len(line)
                                begin = 0
                                end = max_char
                                col = 0
                                while col < max_cols and end < len_line:
                                    if len(line[begin:end].rstrip()) > 0:
                                        residue_labels += 1
                                    begin = end
                                    end += max_char
                                    col += 1

                            elif in_residue_pointer:
                                len_line = len(line)
                                begin = 0
                                end = max_char
                                col = 0
                                while col < max_cols and end < len_line:
                                    try:
                                        _residue_pointer = line[begin:end].lstrip()
                                        if len(_residue_pointer) > 0:
                                            int(_residue_pointer)
                                            residue_pointers += 1
                                    except ValueError:
                                        pass
                                    begin = end
                                    end += max_char
                                    col += 1

                            elif in_amb_atom_type:
                                len_line = len(line)
                                begin = 0
                                end = max_char
                                col = 0
                                while col < max_cols and end < len_line:
                                    if len(line[begin:end].rstrip()) > 0:
                                        amb_atom_types += 1
                                    begin = end
                                    end += max_char
                                    col += 1

                        elif is_aux_gro:

                            if line.startswith('['):
                                in_system = in_molecules = in_atoms = False

                                if line.startswith('[ system ]'):
                                    has_system = in_system = True

                                elif line.startswith('[ molecules ]'):
                                    has_molecules = in_molecules = True

                                elif line.startswith('[ atoms ]'):
                                    has_atoms = in_atoms = True

                            elif in_system or in_molecules or in_atoms:
                                l_split = line.split()
                                _line = ' '.join(l_split)

                                if len(_line) == 0 or _line.startswith('#') or _line.startswith('!') or _line.startswith(';'):
                                    continue

                                if in_system:
                                    system_names += 1

                                elif in_molecules:
                                    if len(l_split) == 2:
                                        try:
                                            num = int(l_split[1])
                                            if num > 0 and l_split[0].isalnum():
                                                molecule_names += 1
                                        except ValueError:
                                            pass

                                else:  # [ atoms ]
                                    if len(l_split) > 6:
                                        try:
                                            atom_num = int(l_split[0])
                                            seq_id = int(l_split[2])
                                            comp_id = l_split[3]
                                            atom_id = l_split[4]
                                            if atom_num > 0 and seq_id > 0 and comp_id in three_letter_codes and atom_id in atom_like_names_oth:
                                                atom_names += 1
                                        except ValueError:
                                            pass

                        _line = ' '.join(line.split())

                        if len(_line) == 0 or _line.startswith('#') or _line.startswith('!') or _line.startswith(';'):
                            continue

                        s = re.split('[ ()]', _line)

                        atom_likes = 0
                        cs_atom_likes = 0
                        names = []
                        res_like = False
                        angle_like = False
                        cs_range_like = False
                        dist_range_like = False
                        dihed_range_like = False

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] in ('#', '!', ';'):
                                break

                            name = t.upper()

                            if name in atom_like_names:
                                if name not in names or len(names) > 1:
                                    atom_likes += 1
                                    names.append(name)
                                if names in cs_atom_like_names:
                                    cs_atom_likes += 1

                            elif name in one_letter_codes and name not in atom_like_names_oth:
                                prohibited_col.add(s.index(t))

                            elif '.' in t:
                                try:
                                    v = float(t)
                                    if CS_RANGE_MIN <= v <= CS_RANGE_MAX:
                                        cs_range_like = True
                                    if DIST_RANGE_MIN <= v <= DIST_RANGE_MAX:
                                        dist_range_like = True
                                    if ANGLE_RANGE_MIN <= v <= ANGLE_RANGE_MAX:
                                        dihed_range_like = True
                                except ValueError:
                                    pass

                            elif name in three_letter_codes:
                                res_like = True

                            elif name in KNOWN_ANGLE_NAMES:
                                angle_like = True

                        if cs_atom_likes == 1 and cs_range_like:
                            has_chem_shift = True

                        elif atom_likes == 2 and dist_range_like:
                            has_dist_restraint = True

                        elif (atom_likes == 4 or (res_like and angle_like)) and dihed_range_like:
                            has_dihed_restraint = True

                if file_type == 'nm-res-oth' and has_chem_shift and not has_dist_restraint and not has_dihed_restraint:

                    with open(file_path, 'r', encoding='utf-8') as ifh:

                        for line in ifh:

                            _line = ' '.join(line.split())

                            if len(_line) == 0 or _line.startswith('#') or _line.startswith('!'):
                                continue

                            s = re.split('[ ()]', _line)

                            atom_likes = 0
                            cs_atom_likes = 0
                            names = []
                            res_like = False
                            angle_like = False
                            cs_range_like = False
                            dist_range_like = False
                            dihed_range_like = False

                            for t in s:

                                if len(t) == 0:
                                    continue

                                if t[0] in ('#', '!'):
                                    break

                                if s.index(t) in prohibited_col:
                                    continue

                                name = t.upper()

                                if name in atom_like_names_oth:
                                    if name not in names or len(names) > 1:
                                        atom_likes += 1
                                        names.append(name)
                                    if name in cs_atom_like_names_oth:
                                        cs_atom_likes += 1

                                elif '.' in t:
                                    try:
                                        v = float(t)
                                        if CS_RANGE_MIN <= v <= CS_RANGE_MAX:
                                            cs_range_like = True
                                        if DIST_RANGE_MIN <= v <= DIST_RANGE_MAX:
                                            dist_range_like = True
                                        if ANGLE_RANGE_MIN <= v <= ANGLE_RANGE_MAX:
                                            dihed_range_like = True
                                    except ValueError:
                                        pass

                                elif name in three_letter_codes:
                                    res_like = True

                                elif name in KNOWN_ANGLE_NAMES:
                                    angle_like = True

                            if cs_atom_likes == 1 and cs_range_like:
                                has_chem_shift = True

                            elif atom_likes == 2 and dist_range_like:
                                has_dist_restraint = True

                            elif (atom_likes == 4 or (res_like and angle_like)) and dihed_range_like:
                                has_dihed_restraint = True

                if file_type == 'nm-res-oth':

                    with open(file_path, 'r', encoding='utf-8') as ifh:
                        has_header = False
                        for idx, line in enumerate(ifh):
                            if line.isspace() or comment_pattern.match(line):
                                if line.startswith('#INAME'):
                                    has_header = True
                                continue
                            if is_peak_list(line, has_header):
                                has_spectral_peak = True
                            if has_spectral_peak or idx >= self.mr_max_spacer_lines:
                                break

                    with open(file_path, 'r', encoding='utf-8') as ifh:
                        for pos, line in enumerate(ifh, start=1):
                            if pos == 1:
                                if 'Structures from CYANA' not in line:
                                    break
                            elif pos == 2:
                                if 'CYANA' not in line:
                                    break
                            elif pos == 3:
                                if line.count('Number') < 3:
                                    break
                            elif pos == 4:
                                if line.count('.') >= 3:
                                    has_coordinate = True
                                break

                    with open(file_path, 'r', encoding='utf-8') as ifh:
                        for pos, line in enumerate(ifh, start=1):
                            if pos == 1:
                                if line.isdigit():
                                    break
                            elif pos == 2:
                                try:
                                    int(line.lstrip().split()[0])
                                except (ValueError, IndexError):
                                    break
                            elif pos == 3:
                                if line.count('.') == 6:
                                    has_coordinate = True
                                break

                if is_aux_amb:

                    if has_atom_name and has_residue_label and has_residue_pointer and has_amb_atom_type and\
                       atom_names > 0 and residue_labels > 0 and residue_pointers > 0 and amb_atom_types > 0:
                        has_topology = True

                    if has_amb_coord and (not has_first_atom or has_ens_coord):
                        has_amb_coord = False

                elif is_aux_gro:

                    if has_system and has_molecules and has_atoms and\
                       system_names > 0 and molecule_names > 0 and atom_names > 0:
                        has_topology = True

            if file_type in ('nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-dyn', 'nm-res-syb',
                             'nm-res-isd', 'nm-res-ari', 'nm-res-oth') and not has_dist_restraint:  # DAOTHER-7491

                with open(file_path, 'r', encoding='utf-8') as ifh:

                    for line in ifh:

                        _line = ' '.join(line.split())

                        if len(_line) == 0 or _line.startswith('#') or _line.startswith('!'):
                            continue

                        s = re.split('[ ()]', _line)

                        if len(s) < 7:
                            continue

                        try:
                            int(s[0])
                            int(s[3])
                            v = float(s[6])
                            if v < DIST_RANGE_MIN or DIST_RANGE_MAX < v:
                                continue
                        except ValueError:
                            continue

                        if s[1].isalnum():
                            comp_id = s[1].upper()
                            atom_id = s[2].upper()

                            if comp_id in three_letter_codes:
                                if atom_id not in atom_like_names:
                                    continue

                            elif len(comp_id) > 3:
                                continue

                            elif not self.__ccU.updateChemCompDict(comp_id):
                                continue

                        if s[4].isalnum():
                            comp_id = s[4].upper()
                            atom_id = s[5].upper()

                            if comp_id in three_letter_codes:
                                if atom_id not in atom_like_names:
                                    continue

                            elif len(comp_id) > 3:
                                continue

                            elif not self.__ccU.updateChemCompDict(comp_id):
                                continue

                        has_dist_restraint = True

                        break

            content_subtype = None
            valid = True
            div_test = False

            try:

                if file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-aux-amb', 'nm-res-cya',
                                 'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-aux-gro', 'nm-res-dyn',
                                 'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari'):
                    sll_pred = False
                    if file_path in self.__sll_pred_holder and file_type in self.__sll_pred_holder[file_path]:
                        sll_pred = self.__sll_pred_holder[file_path][file_type]

                    reader = self.__getSimpleMrPtFileReader(file_type, self.__verbose, sll_pred=sll_pred)

                    listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

                    if listener is not None and file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-cya', 'nm-res-ros', 'nm-res-bio',
                                                              'nm-res-dyn', 'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari'):
                        reasons = listener.getReasonsForReparsing()

                        if reasons is not None:
                            reader = self.__getSimpleMrPtFileReader(file_type, self.__verbose, sll_pred=sll_pred, reasons=reasons)

                            listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

                    err = ''
                    err_lines = []

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                    has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None
                    content_subtype = listener.getContentSubtype() if listener is not None else None
                    if content_subtype is not None and len(content_subtype) == 0:
                        content_subtype = None
                    elif file_type in ('nm-aux-amb', 'nm-aux-gro'):
                        has_topology = True
                        content_subtype = {'topology': 1}

                    has_content = content_subtype is not None

                    if has_lexer_error and has_parser_error and has_content:
                        # parser error occurrs before occurrenece of lexer error that implies mixing of different MR formats in a file
                        if lexer_err_listener.getErrorLineNumber()[0] > parser_err_listener.getErrorLineNumber()[0]:
                            corrected |= self.__peelLegacyMrIfNecessary(file_path, file_type,
                                                                        parser_err_listener.getMessageList()[0],
                                                                        str(file_path), 0)
                            div_test = True

                    fixed_line_num = -1

                    if has_lexer_error:
                        messageList = lexer_err_listener.getMessageList()

                        for description in messageList:
                            err_lines.append(description['line_number'])
                            err += f"[Syntax error] line {description['line_number']}:{description['column_position']} {description['message']}\n"
                            if 'input' in description:
                                enc = detect_encoding(description['input'])
                                is_not_ascii = False
                                if enc is not None and enc != 'ascii':
                                    err += f"{description['input']}\n".encode().decode('ascii', 'backslashreplace')
                                    is_not_ascii = True
                                else:
                                    err += f"{description['input']}\n"
                                err += f"{description['marker']}\n"
                                if is_not_ascii:
                                    err += f"[Unexpected text encoding] Encoding used in the above line is {enc!r} and must be 'ascii'.\n"
                                elif not div_test and has_content and self.__remediation_mode:
                                    fixed = self.__divideLegacyMrIfNecessary(file_path, file_type, description, str(file_path), 0)
                                    corrected |= fixed
                                    if fixed:
                                        fixed_line_num = description['line_number']
                                    div_test = file_type != 'nm-res-amb'  # remediate missing comma issue in AMBER MR

                    if has_parser_error:
                        with open(file_path, 'r') as ifh:
                            total_line = len(ifh.readlines())

                        messageList = parser_err_listener.getMessageList()

                        for description in messageList:
                            # ignore noeol error for linear mr file formats
                            if description['line_number'] == total_line and file_type in ('nm-res-cya', 'nm-res-ros', 'nm-res-bio',
                                                                                          'nm-res-syb', 'nm-res-ari'):
                                continue
                            err_lines.append(description['line_number'])
                            err += f"[Syntax error] line {description['line_number']}:{description['column_position']} {description['message']}\n"
                            len_err = len(err)
                            if 0 < fixed_line_num <= description['line_number']:
                                div_test = True
                            if 'input' in description:
                                err += f"{description['input']}\n"
                                err += f"{description['marker']}\n"
                                if not div_test and has_content and self.__remediation_mode:
                                    corrected |= self.__divideLegacyMrIfNecessary(file_path, file_type, description, str(file_path), 0)
                                    div_test = True
                            elif not div_test and has_content and self.__remediation_mode:
                                corrected |= self.__divideLegacyMrIfNecessary(file_path, file_type, description, str(file_path), 0)
                                div_test = True

                            _err = self.__retrieveErroneousPreviousInput(description)
                            if _err is not None and not comment_pattern.match(_err) and not _err.isspace():
                                s = '. ' if _err.startswith('Do you') else ':\n'
                                err = err[:len_err] +\
                                    ("However, the error may be due to missing statement (e.g. 'noe', 'restraint dihedral', 'sanisotropy') "
                                     f"at the beginning of {_err.strip().split(' ')[0]!r} and note that the statement should be ended with 'end' tag "
                                     if _err.lower().strip().startswith('class')
                                     else "However, the error may be due to the previous input ") +\
                                    f"(line {description['line_number']-1}){s}{_err}" + err[len_err:]

                    if len(err) > 0:
                        valid = False

                        err = f"Could not interpret {file_name!r} as {a_mr_format_name} file:\n{err[0:-1]}"

                        ar['format_mismatch'], _err, _, _ = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(file_path, file_name, file_type, err_lines)

                        if ar['format_mismatch']:
                            err += '\n' + _err

                        self.report.error.appendDescription('format_issue',
                                                            {'file_name': file_name, 'description': err})
                        self.report.setError()

                        if not self.__remediation_mode or self.__remediation_loop_count > 0:
                            self.__lfh.write("+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - "
                                             f"{file_type} {file_name} {err}\n")

                        has_dist_restraint = has_dihed_restraint = has_rdc_restraint = False

                    elif listener is not None:

                        if listener.warningMessage is not None:

                            messages = [msg for msg in listener.warningMessage
                                        if 'warning' not in msg and 'Unsupported' not in msg
                                        and 'Redundant' not in msg
                                        and ((self.__remediation_mode and 'Range value error' not in msg) or not self.__remediation_mode)]

                            if len(messages) > 0:
                                valid = False

                                if len(messages) > 5:
                                    messages = messages[:5]
                                    msg = '\n'.join(messages)
                                    msg += '\nThose similar errors may continue...'
                                else:
                                    msg = '\n'.join(messages)
                                err = f"Could not interpret {file_name!r} due to the following data issue(s):\n{msg}"

                                self.report.error.appendDescription('format_issue',
                                                                    {'file_name': file_name, 'description': err})
                                self.report.setError()

                                if not self.__remediation_mode or self.__remediation_loop_count > 0:
                                    self.__lfh.write("+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - "
                                                     f"{file_type} {file_name} {err}\n")

                        if valid:

                            has_chem_shift = has_coordinate = False

                            if content_subtype is not None:
                                has_dist_restraint = 'dist_restraint' in content_subtype
                                has_dihed_restraint = 'dihed_restraint' in content_subtype
                                has_rdc_restraint = 'rdc_restraint' in content_subtype
                                has_plane_restraint = 'plane_restraint' in content_subtype
                                has_hbond_restraint = 'hbond_restraint' in content_subtype
                                has_ssbond_restraint = 'ssbond_restraint' in content_subtype

                                if file_type == 'nm-res-cya' and has_dist_restraint:
                                    ar['dist_type'] = listener.getTypeOfDistanceRestraints()
                                if file_type == 'nm-res-amb':
                                    ar['has_comments'] = listener.hasComments()

                                ar['is_valid'] = True

                elif file_type == 'nm-res-oth':
                    if not (self.__remediation_mode and file_path.endswith('-div_ext.mr')):
                        ar['format_mismatch'], _, _, _ = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(file_path, file_name, file_type, [])

            except ValueError as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {str(e)}\n")

            if has_coordinate and not has_dist_restraint and not has_dihed_restraint and not has_rdc_restraint\
                    and not has_plane_restraint and not has_hbond_restraint and not has_ssbond_restraint:

                if not is_aux_amb and not is_aux_gro:
                    err = f"The {mr_format_name} restraint file includes coordinates. "\
                        "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."
                else:
                    err = f"The {mr_format_name} topology file includes coordinates. "\
                        "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

                has_chem_shift = False

            elif has_chem_shift and not has_coordinate and not has_amb_inpcrd and not has_dist_restraint and not has_dihed_restraint\
                    and not has_rdc_restraint and not has_plane_restraint and not has_hbond_restraint and not has_ssbond_restraint:

                if has_rdc_origins:

                    hint = 'assign ( resid # and name OO ) ( resid # and name X ) ( resid # and name Y ) ( resid # and name Z ) "\
                        "( segid $ and resid # and name $ ) ( segid $ and resid # and name $ ) #.# #.#'

                    err = f"The NMR restraint file {file_name!r} may be a malformed XPLOR-NIH RDC restraint file. "\
                        f"Tips for XPLOR-NIH RDC restraints: {hint!r} pattern must be present in the file. "\
                        "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

                    has_chem_shift = False

                elif valid:

                    if not is_aux_amb and not is_aux_gro:
                        err = f"The {mr_format_name} restraint file includes assigned chemical shifts. "\
                            "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."
                    else:
                        err = f"The {mr_format_name} topology file includes assigned chemical shifts. "\
                            "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

            elif has_chem_shift:
                has_chem_shift = False

            if has_spectral_peak:

                err = f"The {mr_format_name} restraint file includes spectral peak list. "\
                    "Did you accidentally select the wrong format? Please re-upload the file as spectral peak list file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

            if content_subtype is None:
                content_subtype = {'chem_shift': 1 if has_chem_shift else 0,
                                   'dist_restraint': 1 if has_dist_restraint else 0,
                                   'dihed_restraint': 1 if has_dihed_restraint else 0,
                                   'rdc_restraint': 1 if has_rdc_restraint else 0,
                                   'plane_restraint': 1 if has_plane_restraint else 0,
                                   'hbond_restraint': 1 if has_hbond_restraint else 0,
                                   'ssbond_restraint': 1 if has_ssbond_restraint else 0,
                                   'coordinate': 1 if has_coordinate else 0,
                                   'topology': 1 if has_topology else 0}
            else:
                if 'dist_restraint' in content_subtype:
                    has_dist_restraint = True
                if 'dihed_restraint' in content_subtype:
                    has_dihed_restraint = True
                if 'rdc_restraint' in content_subtype:
                    has_rdc_restraint = True
                if 'plane_restraint' in content_subtype:
                    has_plane_restraint = True
                if 'hbond_restraint' in content_subtype:
                    has_hbond_restraint = True
                if 'ssbond_restraint' in content_subtype:
                    has_ssbond_restraint = True

            if not is_aux_amb and not is_aux_gro and not has_chem_shift and not has_dist_restraint and not has_dihed_restraint and not has_rdc_restraint\
               and not has_plane_restraint and not has_hbond_restraint and not has_ssbond_restraint and not valid:

                hint = ""
                if len(concat_nmr_restraint_names(content_subtype)) == 0:
                    if file_type in ('nm-res-xpl', 'nm-res-cns') and not has_rdc_origins:
                        hint = 'assign ( segid $ and resid # and name $ ) ( segid $ and resid # and name $ ) #.# #.# #.#'
                    elif file_type == 'nm-res-amb':
                        hint = '&rst iat=#[,#], r1=#.#, r2=#.#, r3=#.#, r4=#.#, [igr1=#[,#],] [igr2=#[,#],] &end'

                if len(hint) > 0:
                    hint = f" Tips for {mr_format_name} restraints: {hint!r} pattern must be present in the file."

                warn = f"Constraint type of the NMR restraint file ({mr_format_name}) could not be identified."\
                    + hint + " Did you accidentally select the wrong format?"

                self.report.warning.appendDescription('missing_content',
                                                      {'file_name': file_name, 'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Warning  - {warn}\n")

            elif is_aux_amb and not has_amb_coord and not has_topology:

                subtype_name = ""
                if has_chem_shift:
                    subtype_name += "Assigned chemical shifts, "
                if has_dist_restraint:
                    subtype_name += "Distance restraints, "
                if has_dihed_restraint:
                    subtype_name += "Dihedral angle restraints, "
                if has_rdc_restraint:
                    subtype_name += "RDC restraints, "
                if has_plane_restraint:
                    subtype_name += "Planarity restraints, "
                if has_hbond_restraint:
                    subtype_name += "Hydrogen bond restraints, "
                if has_ssbond_restraint:
                    subtype_name += "Disulfide bond restraints, "
                if has_amb_inpcrd:
                    subtype_name += "AMBER restart coordinates (aka. .crd or .rst file), "

                if len(subtype_name) > 0:
                    subtype_name = ". It looks like to have " + subtype_name[:-2] + " instead"

                hint = " Tips for AMBER topology: Proper contents starting with '%FLAG ATOM_NAME', '%FLAG RESIDUE_LABEL', "\
                    "'%FLAG RESIDUE_POINTER', and '%FLAG AMBER_ATOM_TYPE' lines must be present in the file."

                if has_coordinate:
                    hint = " Tips for AMBER coordinates: It should be directory generated by 'ambpdb' command and must not have MODEL/ENDMDL keywords "\
                        "to ensure that AMBER atomic IDs, referred as 'iat' in the AMBER restraint file, are preserved in the file."

                err = f"{file_name} is neither AMBER topology (.prmtop) nor coordinates (.inpcrd.pdb){subtype_name}."\
                    + hint + " Did you accidentally select the wrong format? Please re-upload the AMBER topology file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

            elif is_aux_gro and not has_topology:

                subtype_name = ""
                if has_chem_shift:
                    subtype_name += "Assigned chemical shifts, "
                if has_dist_restraint:
                    subtype_name += "Distance restraints, "
                if has_dihed_restraint:
                    subtype_name += "Dihedral angle restraints, "
                if has_rdc_restraint:
                    subtype_name += "RDC restraints, "
                if has_plane_restraint:
                    subtype_name += "Planarity restraints, "
                if has_hbond_restraint:
                    subtype_name += "Hydrogen bond restraints, "
                if has_ssbond_restraint:
                    subtype_name += "Disulfide bond restraints, "

                if len(subtype_name) > 0:
                    subtype_name = ". It looks like to have " + subtype_name[:-2] + " instead"

                hint = " Tips for GROMACS topology: Proper contents starting with '[ system ]', '[ molecules ]', "\
                    "and '[ atoms ]' lines must be present in the file."

                err = f"{file_name} is not GROMACS topology {subtype_name}."\
                    + hint + " Did you accidentally select the wrong format? Please re-upload the GROMACS topology file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

            self.__legacy_dist_restraint_uploaded |= has_dist_restraint

            input_source.setItemValue('content_subtype', content_subtype)

        if not self.__legacy_dist_restraint_uploaded:

            fileListId = self.__file_path_list_len

            for ar in self.__inputParamDict[ar_file_path_list]:

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_name = input_source_dic['file_name']
                file_type = input_source_dic['file_type']
                content_subtype = input_source_dic['content_subtype']

                fileListId += 1

                if file_type in ('nmr-star', 'nm-res-mr', 'nm-res-oth', 'nm-res-sax', 'nm-pea-any'):
                    continue

                if (content_subtype is not None and 'dist_restraint' in content_subtype) or file_type in ('nm-aux-amb', 'nm-aux-gro'):
                    continue

                if content_subtype is None:

                    if self.__allow_missing_legacy_dist_restraint:

                        err = f"NMR restraint file is not recognized properly {file_type}. "\
                            "Please fix the file so that it conformes to the format specifications."

                        self.__suspended_errors_for_lazy_eval.append({'content_mismatch':
                                                                      {'file_name': file_name, 'description': err}})

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

                    else:

                        err = "NMR restraint file is not recognized properly "\
                            "so that there is no mandatory distance restraints int the set of uploaded restraint files. "\
                            "Please re-upload the NMR restraint file."

                        self.__suspended_errors_for_lazy_eval.append({'content_mismatch':
                                                                      {'file_name': file_name, 'description': err}})

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

                elif 'chem_shift' not in content_subtype:

                    if not self.__remediation_mode:

                        if self.__allow_missing_legacy_dist_restraint:

                            warn = f"NMR restraint file includes {concat_nmr_restraint_names(content_subtype)}. "\
                                "However, distance restraints are missing in the set of uploaded restraint file(s). "\
                                "The wwPDB NMR Validation Task Force highly recommends the submission of distance restraints "\
                                "used for the structure determination."

                            self.__suspended_warnings_for_lazy_eval.append({'missing_content':
                                                                            {'file_name': file_name, 'description': warn}})

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Warning  - {warn}\n")

                        else:

                            err = f"NMR restraint file includes {concat_nmr_restraint_names(content_subtype)}. "\
                                "However, deposition of distance restraints is mandatory. "\
                                "Please re-upload the NMR restraint file."

                            self.__suspended_errors_for_lazy_eval.append({'content_mismatch':
                                                                          {'file_name': file_name, 'description': err}})

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

        md5_set = set(md5_list)

        if len(md5_set) != len(md5_list):

            ar_path_len = len(self.__inputParamDict[ar_file_path_list])

            for (i, j) in itertools.combinations(range(0, ar_path_len), 2):

                if md5_list[i] is None or md5_list[j] is None:
                    continue

                if md5_list[i] == md5_list[j]:

                    file_name_1 = os.path.basename(self.__inputParamDict[ar_file_path_list][i]['file_name'])
                    file_name_2 = os.path.basename(self.__inputParamDict[ar_file_path_list][j]['file_name'])

                    file_type_1 = self.__inputParamDict[ar_file_path_list][i]['file_type']
                    file_type_2 = self.__inputParamDict[ar_file_path_list][j]['file_type']

                    if file_type_1.startswith('nm-res') and file_type_2.startswith('nm-res'):
                        file_type = 'restraint'
                    elif file_type_1.startswith('nm-pea') and file_type_2.startswith('nm-pea'):
                        file_type = 'spectral peak list'
                    elif file_type_1.startswith('nm-res'):
                        file_type = 'restraint/spectral peak list'
                    else:
                        file_type = 'spectral peak list/restraint'

                    err = f"You have uploaded the same NMR {file_type} file twice. "\
                        f"Please replace/delete either {file_name_1} or {file_name_2}."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': f"{file_name_1} vs {file_name_2}", 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__detectContentSubTypeOfLegacyMr() ++ Error  - {err}\n")

                    if self.__remediation_mode:
                        file_path_2 = self.__inputParamDict[ar_file_path_list][j]['file_name']
                        shutil.copyfile(file_path_2, file_path_2 + '-ignored')

        # restart using format issue resolved input files

        if self.__remediation_mode and corrected:

            self.report = None
            self.report_prev = None

            self.__star_data_type = []
            self.__star_data = []
            self.__sf_name_corr = []

            self.__original_error_message = []

            self.__sf_category_list = []
            self.__lp_category_list = []

            self.__suspended_errors_for_lazy_eval = []
            self.__suspended_warnings_for_lazy_eval = []

            for content_subtype in self.__lp_data:
                self.__lp_data[content_subtype] = []

            for content_subtype in self.__aux_data:
                self.__aux_data[content_subtype] = []

            for content_subtype in self.__sf_tag_data:
                self.__sf_tag_data[content_subtype] = []

            self.__inputParamDict = copy.deepcopy(self.__inputParamDictCopy)

            self.__initializeDpReport()
            self.__validateInputSource()
            self.__detectContentSubType()
            self.__extractPublicMrFileIntoLegacyMr()
            self.__detectContentSubTypeOfLegacyMr()

            self.__remediation_loop_count += 1

            self.__sll_pred_holder = {}

            if self.__mr_debug:
                if self.__remediation_loop_count > 5:
                    self.__lfh.write(f'repetiation of remediation: {self.__inputParamDictCopy}\n')

        return not self.report.isError()

    def __detectContentSubTypeOfLegacyPk(self):
        """ Detect content subtype of legacy NMR spectral peak files.
        """

        if self.__combined_mode:
            return True

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        fileListId = self.__file_path_list_len

        for ar in self.__inputParamDict[ar_file_path_list]:
            file_path = ar['file_name']

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            fileListId += 1

            if file_type != 'nm-pea-any':
                continue

            content_subtype = input_source_dic['content_subtype']
            if content_subtype is None:
                input_source_dic['content_subtype'] = {'spectral_peak': 1}

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])
                if file_name != original_file_name and original_file_name is not None:
                    file_name = f"{original_file_name} ({file_name})"

            has_spectral_peak = False

            try:

                with open(file_path, 'r', encoding='utf-8') as ifh:
                    has_header = False
                    for idx, line in enumerate(ifh):
                        if line.isspace() or comment_pattern.match(line):
                            if line.startswith('#INAME'):
                                has_header = True
                            continue
                        if is_peak_list(line, has_header):
                            has_spectral_peak = True
                        if has_spectral_peak or idx >= self.mr_max_spacer_lines:
                            break

            except UnicodeDecodeError:  # catch exception due to binary format (DAOTHER-9425)

                err = f"The spectal peak list file {file_name!r} is not plain text file."

                self.report.error.appendDescription('format_issue',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

                continue

            if has_spectral_peak:
                continue

            has_mr_header = False
            has_pdb_format = False
            has_cif_format = False
            has_str_format = False

            try:

                header = True
                in_header = False
                pdb_record = False
                cs_str = False
                mr_str = False

                has_datablock = False
                has_anonymous_saveframe = False
                has_save = False
                has_loop = False
                has_stop = False

                first_str_line_num = -1
                last_str_line_num = -1

                i = 0

                with open(file_path, 'r') as ifh:
                    for line in ifh:
                        i += 1

                        # skip MR header
                        if header:
                            if line.startswith('*'):
                                continue
                            if startsWithPdbRecord(line):
                                continue
                            header = False

                        if line.startswith('*') and startsWithPdbRecord(line[1:]):
                            in_header = True
                            continue

                        if in_header and line.startswith('*END'):
                            in_header = False
                            continue

                        if mr_file_header_pattern.match(line):
                            has_mr_header = True

                        # skip legacy PDB
                        if startsWithPdbRecord(line):
                            has_pdb_format = pdb_record = True
                            continue
                        if pdb_record:
                            pdb_record = False
                            if line.startswith('END'):
                                continue

                        # check STAR
                        str_syntax = False
                        if datablock_pattern.match(line):
                            str_syntax = has_datablock = True
                        elif sf_anonymous_pattern.match(line):
                            str_syntax = has_anonymous_saveframe = True
                        elif save_pattern.match(line):
                            str_syntax = has_save = True
                        elif loop_pattern.match(line):
                            str_syntax = has_loop = True
                        elif stop_pattern.match(line):
                            str_syntax = has_stop = True

                        if str_syntax:
                            if first_str_line_num < 0:
                                first_str_line_num = i
                            last_str_line_num = i
                            if (has_anonymous_saveframe and has_save) or (has_loop and has_stop):
                                has_str_format = True
                            elif has_datablock and has_loop and not has_stop:
                                has_cif_format = True

                if last_str_line_num - first_str_line_num < 10:
                    has_str_format = has_cif_format = False

                if has_pdb_format:
                    err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as coordinate file. "\
                        "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

                    continue

                if has_mr_header:
                    err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as {getRestraintFormatName('nm-res-mr')}. "\
                        "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

                    continue

                message = None

                # split STAR and others
                if has_str_format:

                    file_subtype = 'O'

                    is_valid, message = self.__nefT.validate_file(file_path, file_subtype)

                    if not is_valid:
                        _is_valid, message = self.__nefT.validate_file(file_path, 'S')
                        if _is_valid:
                            cs_str = True
                    else:
                        mr_str = True

                elif has_cif_format:

                    _file_path = file_path + '.cif2str'

                    if not self.__c2S.convert(file_path, _file_path):
                        _file_path = file_path

                    file_subtype = 'O'

                    is_valid, message = self.__nefT.validate_file(_file_path, file_subtype)

                    if not is_valid:
                        _is_valid, message = self.__nefT.validate_file(_file_path, 'S')
                        if _is_valid:
                            cs_str = True
                    else:
                        mr_str = True

                if cs_str:
                    err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as "\
                        f"{self.readable_file_type[message['file_type']]} formatted assigned chemical shift file. "\
                        "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

                    continue

                if mr_str:
                    err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as "\
                        f"{self.readable_file_type[message['file_type']]} formatted restraint file. "\
                        "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                    self.report.error.appendDescription('content_mismatch',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

                    continue

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {str(e)}\n")

                return False

            _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(file_path, file_name, 'nm-pea-any', [], True)

            len_valid_types = len(valid_types)
            len_possible_types = len(possible_types)

            if len_valid_types == 0 and len_possible_types == 0:
                continue

            if len_possible_types == 0:

                err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as {getRestraintFormatNames(valid_types)} file. "\
                    "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

            elif len_valid_types == 0:

                err = f"The spectral peak list file {file_name!r} (any plain text format) can be {possible_types}. "\
                    "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

            else:

                err = f"The spectral peak list file {file_name!r} (any plain text format) is identified as {getRestraintFormatNames(valid_types)} file"\
                    f"and can be {getRestraintFormatNames(possible_types)} file as well. "\
                    "Did you accidentally select the wrong format? Please re-upload the spectral peak list file."

                self.report.error.appendDescription('content_mismatch',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyPk() ++ Error  - {err}\n")

        return not self.report.isError()

    def __retrieveOriginalFileExtensionOfCyanaMrFile(self):
        """ Retrieve original file extension of CYANA MR file.
        """

        if self.__cur_original_ar_file_name is None:
            return None

        if self.__cur_original_ar_file_name.endswith('.gz'):
            self.__cur_original_ar_file_name = os.path.splitext(self.__cur_original_ar_file_name)[0]

        if self.__cur_original_ar_file_name.endswith('.mr'):
            return None

        if self.__cur_original_ar_file_name.endswith('-corrected'):
            self.__cur_original_ar_file_name = self.__cur_original_ar_file_name.replace('-corrected', '')

        if self.__cur_original_ar_file_name.endswith('.txt'):
            self.__cur_original_ar_file_name = self.__cur_original_ar_file_name.replace('.txt', '')

        if self.__cur_original_ar_file_name.endswith('.tbl'):
            self.__cur_original_ar_file_name = self.__cur_original_ar_file_name.replace('.tbl', '')

        if self.__cur_original_ar_file_name.endswith('.dat'):
            self.__cur_original_ar_file_name = self.__cur_original_ar_file_name.replace('.dat', '')

        if self.__cur_original_ar_file_name.endswith('.10'):
            self.__cur_original_ar_file_name = self.__cur_original_ar_file_name.replace('.10', '')

        split_ext = os.path.splitext(self.__cur_original_ar_file_name)

        if len(split_ext) != 2 or len(split_ext[1]) == 0:

            file_ext = split_ext[0].lower()

            if len(file_ext) > 3:

                file_ext = file_ext[len(file_ext) - 3:]

                if file_ext not in CYANA_MR_FILE_EXTS:
                    return None

                return file_ext

            if len(file_ext) == 3:

                if file_ext not in CYANA_MR_FILE_EXTS:
                    return None

                return file_ext

            return None

        file_ext = split_ext[1][1:].lower()

        if len(file_ext) > 3:
            file_ext = file_ext[:3]

        if file_ext not in CYANA_MR_FILE_EXTS:
            return None

        return file_ext

    def __retrieveErroneousPreviousInput(self, err_desc):
        """ Retrieve erroneous previous input if possible.
        """

        try:
            _err_desc = next(_err_desc for _err_desc in self.__divide_mr_error_message
                             if _err_desc['file_path'] == err_desc['file_path']
                             and _err_desc['line_number'] == err_desc['line_number']
                             and _err_desc['message'] == err_desc['message'])
            return _err_desc.get('previous_input')
        except StopIteration:
            return None

    def __getCorrectedMrFilePath(self, src_path):
        """ Return corrected MR file path.
        """

        ar_file_path_list = 'atypical_restraint_file_path_list'

        dir_path = os.path.dirname(src_path)

        for div_file_name in os.listdir(dir_path):
            if os.path.isfile(os.path.join(dir_path, div_file_name))\
               and (div_file_name.endswith('-div_src.mr') or div_file_name.endswith('-div_dst.mr')):
                div_file_path = os.path.join(dir_path, div_file_name)
                if not any(ar for ar in self.__inputParamDict[ar_file_path_list] if ar['file_name'] == div_file_path):
                    os.remove(div_file_path)

        if os.path.exists(src_path):
            src_file_name = os.path.basename(src_path)
            cor_test = '-corrected' in src_file_name
            if cor_test:
                cor_src_path = src_path + '~'
            else:
                if src_path.endswith('.mr'):
                    cor_src_path = re.sub(r'\-trimmed$', '', os.path.splitext(src_path)[0]) + '-corrected.mr'
                else:
                    cor_src_path = re.sub(r'\-trimmed$', '', src_path) + '-corrected'

            return cor_src_path, cor_test

        return None, False

    def __getSimpleMrPtFileReader(self, file_type, verbose, sll_pred=True, reasons=None):
        """ Return simple MR/PT file reader for a given format.
        """

        if file_type == 'nm-res-xpl':
            reader = XplorMRReader(verbose, self.__lfh, None, None, None, None, None,
                                   self.__ccU, self.__csStat, self.__nefT,
                                   reasons)
            reader.setSllPredMode(sll_pred)
            return reader
        if file_type == 'nm-res-cns':
            reader = CnsMRReader(verbose, self.__lfh, None, None, None, None, None,
                                 self.__ccU, self.__csStat, self.__nefT,
                                 reasons)
            reader.setSllPredMode(sll_pred)
            return reader
        if file_type == 'nm-res-amb':
            return AmberMRReader(verbose, self.__lfh, None, None, None, None, None,
                                 self.__ccU, self.__csStat, self.__nefT)
        if file_type == 'nm-aux-amb':
            return AmberPTReader(verbose, self.__lfh, None, None, None, None, None,
                                 self.__ccU, self.__csStat, self.__nefT)
        if file_type == 'nm-res-cya':
            reader = CyanaMRReader(verbose, self.__lfh, None, None, None, None, None,
                                   self.__ccU, self.__csStat, self.__nefT,
                                   reasons,
                                   file_ext=self.__retrieveOriginalFileExtensionOfCyanaMrFile())
            reader.setRemediateMode(self.__remediation_mode)
            # do not use SLL prediction mode for CyanaMRReader
            # reader.setSllPredMode(sll_pred)
            return reader
        if file_type == 'nm-res-ros':
            reader = RosettaMRReader(verbose, self.__lfh, None, None, None, None, None,
                                     self.__ccU, self.__csStat, self.__nefT,
                                     reasons)
            reader.setRemediateMode(self.__remediation_mode)
            return reader
        if file_type == 'nm-res-bio':
            return BiosymMRReader(verbose, self.__lfh, None, None, None, None, None,
                                  self.__ccU, self.__csStat, self.__nefT,
                                  reasons)
        if file_type == 'nm-res-gro':
            return GromacsMRReader(verbose, self.__lfh, None, None, None, None, None,
                                   self.__ccU, self.__csStat, self.__nefT)
        if file_type == 'nm-aux-gro':
            return GromacsPTReader(verbose, self.__lfh, None, None, None, None, None,
                                   self.__ccU, self.__csStat, self.__nefT)
        if file_type == 'nm-res-dyn':
            return DynamoMRReader(verbose, self.__lfh, None, None, None, None, None,
                                  self.__ccU, self.__csStat, self.__nefT,
                                  reasons)
        if file_type == 'nm-res-syb':
            return SybylMRReader(verbose, self.__lfh, None, None, None, None, None,
                                 self.__ccU, self.__csStat, self.__nefT,
                                 reasons)
        if file_type == 'nm-res-isd':
            return IsdMRReader(verbose, self.__lfh, None, None, None, None, None,
                               self.__ccU, self.__csStat, self.__nefT,
                               reasons)
        if file_type == 'nm-res-cha':
            reader = CharmmMRReader(verbose, self.__lfh, None, None, None, None, None,
                                    self.__ccU, self.__csStat, self.__nefT,
                                    reasons)
            reader.setSllPredMode(sll_pred)
            return reader
        if file_type == 'nm-res-ari':
            reader = AriaMRReader(verbose, self.__lfh, None, None, None, None, None,
                                  self.__ccU, self.__csStat, self.__nefT,
                                  reasons)
            return reader

        return None

    def __divideLegacyMrIfNecessary(self, file_path, file_type, err_desc, src_path, offset):
        """ Divive legacy NMR restraint file if necessary.
        """

        src_basename = os.path.splitext(file_path)[0]
        div_src = 'div_dst' in src_basename
        div_src_file = src_basename + '-div_src.mr'
        div_ext_file = src_basename + '-div_ext.mr'
        div_try_file = src_basename + '-div_try.mr'
        div_dst_file = src_basename + '-div_dst.mr'

        if any(_err_desc for _err_desc in self.__divide_mr_error_message
               if err_desc['file_path'] == _err_desc['file_path']
               and err_desc['line_number'] == _err_desc['line_number']
               and err_desc['column_position'] == _err_desc['column_position']
               and err_desc['message'] == _err_desc['message']):
            if os.path.exists(div_src_file):
                os.remove(div_src_file)
            if os.path.exists(div_dst_file):
                os.remove(div_dst_file)
            if os.path.exists(div_ext_file):
                os.remove(div_ext_file)
            return False

        self.__divide_mr_error_message.append(err_desc)

        if self.__mr_debug:
            self.__lfh.write('DIV-MR\n')

        if file_type == 'nm-res-xpl':
            pass
        elif file_type == 'nm-res-cns':
            pass
        elif file_type in ('nm-res-amb', 'nm-aux-amb'):
            pass
        elif file_type == 'nm-res-cya':
            pass
        elif file_type == 'nm-res-ros':
            pass
        elif file_type == 'nm-res-bio':
            pass
        elif file_type in ('nm-res-gro', 'nm-aux-gro'):
            pass
        elif file_type == 'nm-res-dyn':
            pass
        elif file_type == 'nm-res-syb':
            pass
        elif file_type == 'nm-res-isd':
            pass
        elif file_type == 'nm-res-cha':
            pass
        elif file_type == 'nm-res-ari':
            pass
        else:
            return False

        err_message = err_desc['message']
        err_line_number = err_desc['line_number']
        err_column_position = err_desc['column_position']
        err_input = err_desc.get('input', '')

        xplor_file_type = file_type in ('nm-res-xpl', 'nm-res-cns')
        amber_file_type = file_type == 'nm-res-amb'
        gromacs_file_type = file_type in ('nm-res-gro', 'nm-aux-gro')
        linear_mr_file_types = ('nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-syb')

        xplor_missing_end = xplor_file_type and err_message.startswith(xplor_missing_end_err_msg)
        xplor_ends_wo_statement = xplor_file_type and (bool(xplor_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and xplor_end_pattern.match(err_input)))

        xplor_assi_after_or_tag = xplor_file_type and bool(xplor_extra_assi_err_msg_pattern.match(err_message))
        xplor_assi_incompl_tag = xplor_file_type and bool(xplor_extra_ssi_err_msg_pattern.match(err_message))

        xplor_l_paren_wo_assi = xplor_file_type and bool(xplor_extra_l_paren_err_msg_pattern.match(err_message))
        xplor_00_origin = xplor_file_type and err_message.startswith(no_viable_alt_err_msg) and ' 00' in err_input

        amber_missing_end = amber_file_type and err_message.startswith(amber_missing_end_err_msg)
        amber_ends_wo_statement = amber_file_type and (bool(amber_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and amber_end_pattern.match(err_input)))

        concat_xplor_assi = (xplor_file_type
                             and (err_message.startswith(mismatched_input_err_msg)
                                  or err_message.startswith(extraneous_input_err_msg))
                             and bool(xplor_assi_pattern.search(err_input))
                             and not bool(xplor_class_pattern.search(err_input)))
        concat_xplor_rest = (xplor_file_type
                             and (err_message.startswith(mismatched_input_err_msg)
                                  or err_message.startswith(extraneous_input_err_msg))
                             and bool(xplor_rest_pattern.search(err_input)))
        concat_xplor_set = (xplor_file_type
                            and (err_message.startswith(mismatched_input_err_msg)
                                 or err_message.startswith(extraneous_input_err_msg))
                            and bool(xplor_set_pattern.search(err_input)))
        concat_amber_rst = (amber_file_type
                            and (err_message.startswith(mismatched_input_err_msg)
                                 or err_message.startswith(extraneous_input_err_msg))
                            and bool(amber_rst_pattern.search(err_input))
                            and not bool(amber_rst_pattern.match(err_input)))

        concat_gromacs_tag = not gromacs_file_type and bool(gromacs_tag_pattern.search(err_input))

        concat_comment = (file_type in linear_mr_file_types
                          and err_message.startswith(no_viable_alt_err_msg)
                          and bool(comment_pattern.search(err_input)))

        if concat_xplor_assi and bool(xplor_assi_pattern.match(err_input)):
            if expecting_l_paren in err_message:
                xplor_missing_end = True
                concat_xplor_assi = False
            if concat_xplor_rest or concat_xplor_set:
                concat_xplor_assi = False

        reader = prev_input = next_input = None

        if not (xplor_missing_end or xplor_ends_wo_statement
                or xplor_l_paren_wo_assi or xplor_00_origin
                or amber_missing_end or amber_ends_wo_statement
                or concat_xplor_assi or concat_xplor_rest or concat_xplor_set
                or concat_amber_rst
                or concat_gromacs_tag
                or concat_comment):

            if err_column_position > 0 and not err_input[0:err_column_position].isspace():
                test_line = err_input[0:err_column_position]

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

                if not has_lexer_error and not has_parser_error:

                    concat_input = err_input[err_column_position:]

                    if comment_pattern.match(concat_input) or concat_input[0].isalnum():

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #1-1\n')

                        return self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #1-2\n')

                    return False

                # try to resolve unexcepted concatenation
                test_line = err_input[err_column_position + 1:]

                if len(test_line) > 0:

                    _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                    has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

                    if not has_lexer_error and not has_parser_error:
                        err_desc['column_position'] += 1

                        corrected = self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset)

                        if corrected and os.path.exists(src_path):

                            cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                            if cor_src_path is not None:

                                offset += err_line_number - 1

                                j = 0

                                with open(src_path, 'r') as ifh, \
                                        open(cor_src_path, 'w') as ofh:
                                    for line in ifh:
                                        if j == offset:
                                            ofh.write(line[:err_column_position + 1] + '\n')
                                            ofh.write(line[err_column_position + 1:])
                                        else:
                                            ofh.write(line)
                                        j += 1

                                if cor_test:
                                    os.rename(cor_src_path, src_path)

                                if self.__mr_debug:
                                    self.__lfh.write('DIV-MR-EXIT #2-1\n')

                            else:

                                if self.__mr_debug:
                                    self.__lfh.write('DIV-MR-EXIT #2-2\n')

                                corrected = False

                        else:

                            if self.__mr_debug:
                                self.__lfh.write('DIV-MR-EXIT #2-3\n')

                        return corrected

        i = j = j_offset = 0

        ws_or_comment = True

        interval = []

        with open(file_path, 'r') as ifh, \
                open(div_src_file, 'w') as ofh, \
                open(div_try_file, 'w') as ofh2:
            for line in ifh:
                i += 1
                if i < err_line_number - self.mr_max_spacer_lines:
                    if ws_or_comment:
                        if line.isspace() or comment_pattern.match(line)\
                           or (gromacs_file_type and gromacs_comment_pattern.match(line)):
                            pass
                        else:
                            ws_or_comment = False
                    ofh.write(line)
                    j += 1
                    continue
                if i < err_line_number:
                    if ws_or_comment:
                        if line.isspace() or comment_pattern.match(line)\
                           or (gromacs_file_type and gromacs_comment_pattern.match(line)):
                            pass
                        else:
                            ws_or_comment = False
                    interval.append({'line': line,
                                     'ws_or_comment': line.isspace() or bool(comment_pattern.match(line))
                                     or (gromacs_file_type and bool(gromacs_comment_pattern.match(line)))})
                    if i < err_line_number - 1:
                        continue
                    if i == err_line_number - 1:
                        prev_input = line
                    _k = len(interval) - 1
                    _c = interval[-1]['line'][0]
                    for _interval in reversed(interval):
                        c = _interval['line'][0]
                        if _interval['ws_or_comment'] and {c, _c} != comment_code_mixed_set:
                            _c = c
                            _k -= 1
                            continue
                        break
                    for k, _interval in enumerate(interval):
                        if k <= _k:
                            ofh.write(_interval['line'])
                            j += 1
                        else:
                            ofh2.write(_interval['line'])
                            j_offset += 1
                    continue
                if i == err_line_number + 1:
                    next_input = line
                ofh2.write(line)

        offset += err_line_number - 1

        xplor_missing_end_before = (xplor_file_type and err_message.startswith(mismatched_input_err_msg)
                                    and not bool(xplor_expecting_symbol_pattern.search(err_message))  # exclude syntax errors in a factor
                                    and prev_input is not None and bool(xplor_assi_pattern.search(prev_input)))

        xplor_no_syntax_err_in_fac_or_ann = not bool(xplor_expecting_equ_op_pattern.search(err_message))\
            and not bool(xplor_expecting_seg_id_pattern.search(err_message))\
            and not err_message.startswith(no_viable_alt_err_msg)

        amber_missing_comma_before = (amber_file_type and err_message.startswith(mismatched_input_err_msg)
                                      and bool(amber_expecting_comma_pattern.search(err_message)))

        if (xplor_missing_end or xplor_ends_wo_statement
                or xplor_l_paren_wo_assi or xplor_00_origin
                or xplor_missing_end_before
                or amber_missing_end or amber_ends_wo_statement
                or amber_missing_comma_before
                or concat_xplor_assi or concat_xplor_rest or concat_xplor_set
                or concat_amber_rst
                or concat_gromacs_tag
                or concat_comment) or i <= err_line_number or j == 0:

            corrected = False

            if err_line_number - 1 in (i, j + j_offset) and xplor_l_paren_wo_assi:  # this should be before 'concat_comment' routine

                if comment_pattern.match(prev_input) and xplor_assi_pattern.search(prev_input):

                    k = k2 = 0

                    with open(file_path, 'r') as ifh:
                        for line in ifh:
                            k += 1
                            if k <= err_line_number:
                                continue
                            if k < err_line_number + self.mr_max_spacer_lines:
                                if xplor_assi_pattern.match(line) or comment_pattern.match(line) or line.isspace():
                                    k2 = k
                                    break
                                continue
                            break

                    if k2 != 0:

                        comment_code = prev_input.rstrip()[0]

                        cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                        if cor_src_path is not None:

                            k = 0

                            with open(src_path, 'r') as ifh, \
                                    open(cor_src_path, 'w') as ofh:
                                for line in ifh:
                                    k += 1
                                    if k < err_line_number:
                                        ofh.write(line)
                                    elif k < k2:
                                        ofh.write(comment_code + line)
                                    else:
                                        ofh.write(line)

                            if cor_test:
                                os.rename(cor_src_path, src_path)

                            if self.__mr_debug:
                                self.__lfh.write('DIV-MR-EXIT #3-1\n')

                            return True

                if os.path.exists(div_src_file):
                    os.remove(div_src_file)
                if os.path.exists(div_try_file):
                    os.remove(div_try_file)

                if prev_input is not None:
                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"

                if self.__mr_debug and not corrected:
                    self.__lfh.write('DIV-MR-EXIT #3-2\n')

                return False

            if xplor_00_origin:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            if ' 00' in line:
                                ofh.write(re.sub(r' 00', ' OO', line))
                            else:
                                ofh.write(line)

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #3-3\n')

                    corrected = True

            if xplor_ends_wo_statement or amber_ends_wo_statement:

                has_end_tag = False

                k = 0

                with open(src_path, 'r') as ifh:
                    for line in ifh:
                        if k == offset:
                            if xplor_ends_wo_statement and xplor_end_pattern.match(line):
                                has_end_tag = True
                            if amber_ends_wo_statement and amber_end_pattern.match(line):
                                has_end_tag = True
                            break
                        k += 1

                if has_end_tag:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        k = 0

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if k == offset:
                                    ofh.write('#' + line)
                                else:
                                    ofh.write(line)
                                k += 1

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #3-4\n')

                        corrected = True

            if concat_xplor_assi or concat_xplor_rest or concat_xplor_set or concat_amber_rst:

                code_index = -1

                if concat_xplor_assi:
                    for m in xplor_any_assi_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_xplor_rest:
                    for m in xplor_any_rest_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_xplor_set:
                    for m in xplor_any_set_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_amber_rst:
                    for m in amber_rst_pattern.finditer(err_input):
                        code_index = m.start()

                if code_index != -1:
                    test_line = err_input[0:code_index]

                    if len(test_line.strip()) > 0:
                        typo_for_comment_out = bool(possible_typo_for_comment_out_pattern.match(test_line))

                        if reader is None:
                            reader = self.__getSimpleMrPtFileReader(file_type, False)

                        _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                        has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                        if not has_lexer_error:

                            cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                            if cor_src_path is not None:

                                k = 0

                                with open(src_path, 'r') as ifh, \
                                        open(cor_src_path, 'w') as ofh:
                                    for line in ifh:
                                        if k == offset:
                                            if typo_for_comment_out:
                                                g = possible_typo_for_comment_out_pattern.search(test_line).groups()
                                                if g[0] == '1':
                                                    test_line = re.sub(r'1', '!', test_line)
                                                else:
                                                    test_line = re.sub(r'3', '#', test_line)
                                                ofh.write(f"{test_line}{err_input[code_index:]}\n")
                                            else:
                                                ofh.write(f"{test_line}\n{err_input[code_index:]}\n")
                                        else:
                                            ofh.write(line)
                                        k += 1

                                if cor_test:
                                    os.rename(cor_src_path, src_path)

                                if self.__mr_debug:
                                    self.__lfh.write('DIV-MR-EXIT #3-5\n')

                                corrected = True

            if concat_gromacs_tag:
                test_line = err_input[err_column_position + 1:]

                if len(test_line) > 0:

                    test_reader = self.__getSimpleMrPtFileReader('nm-res-gro', False)

                    _, parser_err_listener, lexer_err_listener = test_reader.parse(test_line, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                    if not has_lexer_error:

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #3-6\n')

                        return self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset)

                    test_reader = self.__getSimpleMrPtFileReader('nm-aux-gro', False)

                    _, parser_err_listener, lexer_err_listener = test_reader.parse(test_line, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                    if not has_lexer_error:

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #3-7\n')

                        return self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset)

            if concat_comment:

                comment_code_index = -1
                if '#' in err_input:
                    comment_code_index = err_input.index('#')
                if '!' in err_input:
                    if comment_code_index == -1:
                        comment_code_index = err_input.index('!')
                    elif err_input.index('!') < comment_code_index:
                        comment_code_index = err_input.index('!')

                if comment_code_index != -1:
                    test_line = err_input[0:comment_code_index]

                    if reader is None:
                        reader = self.__getSimpleMrPtFileReader(file_type, False)

                    _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                    has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

                    if not has_lexer_error and not has_parser_error:

                        cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                        if cor_src_path is not None:

                            k = 0

                            with open(src_path, 'r') as ifh, \
                                    open(cor_src_path, 'w') as ofh:
                                for line in ifh:
                                    if k == offset:
                                        ofh.write(f"{test_line} {err_input[comment_code_index:]}\n")
                                    else:
                                        ofh.write(line)
                                    k += 1

                            if cor_test:
                                os.rename(cor_src_path, src_path)

                            if self.__mr_debug:
                                self.__lfh.write('DIV-MR-EXIT #3-8\n')

                            corrected = True

            if err_line_number - 1 in (i, j + j_offset) and (xplor_missing_end or xplor_missing_end_before):

                if not xplor_missing_end_before or xplor_no_syntax_err_in_fac_or_ann:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        middle = i != err_line_number - 1
                        is_done = False

                        k = 0 if xplor_missing_end else 1

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if middle:
                                    if k == err_line_number - 2 and comment_pattern.match(line):
                                        ofh.write('end\n')
                                        is_done = True
                                    elif k == err_line_number - 1 and not is_done:
                                        ofh.write('end\n')
                                ofh.write(line)
                                k += 1
                            if not middle:
                                ofh.write('end\n')

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #3-9\n')

                        corrected = True

            if err_line_number - 1 in (i, j + j_offset) and amber_missing_comma_before:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    k = 1

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            if k == err_line_number:
                                ofh.write(',' + line)
                            else:
                                ofh.write(line)
                            k += 1

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #3-10\n')

                    corrected = True

            if i == err_line_number - 1 and amber_missing_end:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            ofh.write(line)
                        ofh.write('&end\n')

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #3-11\n')

                    corrected = True

            if not (corrected or concat_xplor_assi or concat_xplor_rest or concat_xplor_set or concat_amber_rst)\
               and (j + j_offset) in (0, err_line_number - 1)\
               and (not xplor_missing_end_before or xplor_no_syntax_err_in_fac_or_ann):
                test_line = err_input

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        k = 0

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if k == offset:
                                    ofh.write('#' + line)
                                else:
                                    ofh.write(line)
                                k += 1

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #3-12\n')

                        corrected = True

            if os.path.exists(div_src_file):
                os.remove(div_src_file)
            if os.path.exists(div_try_file):
                os.remove(div_try_file)

            if prev_input is not None and (err_message.startswith(no_viable_alt_err_msg) or err_message.startswith(extraneous_input_err_msg)):
                if comment_pattern.match(prev_input):
                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"
                elif not xplor_assi_after_or_tag and not xplor_assi_incompl_tag:
                    err_desc['previous_input'] = prev_input

            if self.__mr_debug and not corrected:
                self.__lfh.write('DIV-MR-EXIT #3-13\n')

            return corrected

        if ws_or_comment:
            os.remove(div_src_file)
            os.remove(div_try_file)

            if self.__mr_debug:
                self.__lfh.write('DIV-MR-EXIT #4\n')

            return False

        if not os.path.exists(div_try_file):
            return False

        file_name = os.path.basename(div_try_file)

        _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(div_try_file, file_name, 'nm-res-mr', [], True)

        len_valid_types = len(valid_types)
        len_possible_types = len(possible_types)

        if len_valid_types == 0 and len_possible_types == 0:

            if err_column_position > 0 and not err_input[0:err_column_position].isspace():
                test_line = err_input[0:err_column_position]

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error:
                    os.remove(div_src_file)
                    os.remove(div_try_file)

                    if is_peak_list(err_input):

                        if self.__mr_debug:
                            self.__lfh.write('DIV-MR-EXIT #5-1\n')

                        return self.__peelLegacyMrIfNecessary(file_path, file_type, err_desc, src_path, offset)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #5-2\n')

                    return False  # not split MR file because of the lexer errors to be handled by manual

            if next_input is not None and re.search(r'[A-Za-z]', next_input) is not None:
                test_line = next_input

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error and (prev_input is None or not (prev_input.isspace() or bool(comment_pattern.match(prev_input)))):

                    if err_column_position == 0 and file_type not in linear_mr_file_types:

                        for test_file_type in linear_mr_file_types:

                            test_reader = self.__getSimpleMrPtFileReader(test_file_type, False)

                            listener, parser_err_listener, lexer_err_listener = test_reader.parse(test_line, None, isFilePath=False)

                            has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                            has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None
                            if test_file_type == 'nm-res-ros':
                                _content_subtype = listener.getEffectiveContentSubtype() if listener is not None else None
                            else:
                                _content_subtype = listener.getContentSubtype() if listener is not None else None
                            if _content_subtype is not None and len(_content_subtype) == 0:
                                _content_subtype = None
                            has_content = _content_subtype is not None

                            if not has_lexer_error and not has_parser_error and has_content:

                                if div_src:
                                    os.remove(file_path)

                                os.rename(div_try_file, div_dst_file)

                                is_valid = True  # triggar for more split
                                re_valid = False  # local lexer/parser errors should be handled by manual

                                k = l = 0  # noqa: E741

                                with open(div_dst_file, 'r') as ifh:
                                    for line in ifh:
                                        if k > 0 and not (line.isspace() or bool(comment_pattern.match(line))):
                                            listener, parser_err_listener, lexer_err_listener = test_reader.parse(line, None, isFilePath=False)
                                            has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                                            has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None
                                            if test_file_type == 'nm-res-ros':
                                                _content_subtype = listener.getEffectiveContentSubtype() if listener is not None else None
                                            else:
                                                _content_subtype = listener.getContentSubtype() if listener is not None else None
                                            if _content_subtype is not None and len(_content_subtype) == 0:
                                                _content_subtype = None
                                            has_content = _content_subtype is not None
                                            if has_lexer_error or has_parser_error or not has_content:
                                                if is_valid:
                                                    is_valid = False
                                            elif not is_valid:
                                                re_valid = True
                                                break
                                        if is_valid:
                                            k += 1
                                        else:
                                            l += 1  # noqa: E741
                                            if l >= self.mr_max_spacer_lines:
                                                break

                                if not is_valid and not re_valid:

                                    _src_basename = os.path.splitext(div_dst_file)[0]
                                    _div_src_file = _src_basename + '-div_src.mr'
                                    _div_dst_file = _src_basename + '-div_dst.mr'

                                    l = 0  # noqa: E741

                                    with open(div_dst_file, 'r') as ifh, \
                                            open(_div_src_file, 'w') as ofh, \
                                            open(_div_dst_file, 'w') as ofh2:
                                        for line in ifh:
                                            if l < k:
                                                ofh.write(line)
                                            else:
                                                ofh2.write(line)
                                            l += 1  # noqa: E741

                                    os.remove(div_dst_file)

                                if self.__mr_debug:
                                    self.__lfh.write('DIV-MR-EXIT #6\n')

                                return True

                    os.remove(div_src_file)
                    os.remove(div_try_file)

                    if prev_input is not None:
                        if comment_pattern.match(prev_input):
                            err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"
                        elif not xplor_assi_after_or_tag and not xplor_assi_incompl_tag:
                            err_desc['previous_input'] = prev_input

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #7\n')

                    return False  # not split MR file because of the lexer errors to be handled by manual

            if div_src:
                os.remove(file_path)

            os.rename(div_try_file, div_ext_file)

            if self.__mr_debug:
                self.__lfh.write('DIV-MR-EXIT #8\n')

            return True  # succeeded in eliminating uninterpretable parts

        if len_possible_types > 0:
            os.remove(div_src_file)
            os.remove(div_try_file)

            if prev_input is not None:
                if comment_pattern.match(prev_input):
                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"
                elif not xplor_assi_after_or_tag and not xplor_assi_incompl_tag:
                    err_desc['previous_input'] = prev_input

            if self.__mr_debug:
                self.__lfh.write('DIV-MR-EXIT #9\n')

            return False

        # self.__lfh.write(f"The NMR restraint file {file_name!r} ({mr_format_name} format) is identified as {valid_types}.\n")

        if file_type in valid_types:
            os.remove(div_src_file)
            os.remove(div_try_file)

            if prev_input is not None:
                if comment_pattern.match(prev_input):
                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"
                elif not xplor_assi_after_or_tag and not xplor_assi_incompl_tag:
                    err_desc['previous_input'] = prev_input

            if self.__mr_debug:
                self.__lfh.write('DIV-MR-EXIT #10\n')

            return False  # actual issue in the line before the parser error should be handled by manual

        if prev_input is not None and comment_pattern.match(prev_input)\
           and file_type != 'nm-res-cya' and 'nm-res-cya' not in valid_types:  # CYANA MR grammar is lax to check comment

            try:

                g = comment_pattern.search(prev_input).groups()

                test_line = g[0]

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error:
                    os.remove(div_src_file)
                    os.remove(div_try_file)

                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #11\n')

                    return False  # actual issue in the line before the parser error should be handled by manual

            except AttributeError:
                pass

        if div_src:
            os.remove(file_path)

        os.rename(div_try_file, div_dst_file)

        file_path = div_dst_file

        if len_valid_types == 1:
            file_type = valid_types[0]

        elif len_valid_types == 2 and 'nm-res-cns' in valid_types and 'nm-res-xpl' in valid_types:
            file_type = 'nm-res-xpl'

        elif len_valid_types == 2 and 'nm-res-cya' in valid_types:
            file_type = next(valid_type for valid_type in valid_types if valid_type != 'nm-res-cya')

        elif len_valid_types == 3:
            set_valid_types = set(valid_types)
            if set_valid_types in ({'nm-res-cya', 'nm-res-cns', 'nm-res-xpl'}, {'nm-res-isd', 'nm-res-cns', 'nm-res-xpl'}):
                file_type = 'nm-res-xpl'
            if set_valid_types == {'nm-res-cha', 'nm-res-cns', 'nm-res-xpl'}:
                file_type = 'nm-res-cha'

        self.__testFormatValidityOfLegacyMr(file_path, file_type, src_path, offset)

        if self.__mr_debug:
            self.__lfh.write('DIV-MR-DONE\n')

        return True

    def __peelLegacyMrIfNecessary(self, file_path, file_type, err_desc, src_path, offset):
        """ Peel uninterpretable restraints from the legacy NMR file if necessary.
        """

        src_basename = os.path.splitext(file_path)[0]
        div_src = 'div_dst' in src_basename
        div_src_file = src_basename + '-div_src.mr'
        div_ext_file = src_basename + '-div_ext.mr'
        div_try_file = src_basename + '-div_try.mr'
        div_dst_file = src_basename + '-div_dst.mr'

        if any(_err_desc for _err_desc in self.__peel_mr_error_message
               if err_desc['file_path'] == _err_desc['file_path']
               and err_desc['line_number'] == _err_desc['line_number']
               and err_desc['column_position'] == _err_desc['column_position']
               and err_desc['message'] == _err_desc['message']):
            if os.path.exists(div_src_file):
                os.remove(div_src_file)
            if os.path.exists(div_dst_file):
                os.remove(div_dst_file)
            if os.path.exists(div_ext_file):
                os.remove(div_ext_file)
            return False

        self.__peel_mr_error_message.append(err_desc)

        if self.__mr_debug:
            self.__lfh.write('PEEL-MR\n')

        reader = self.__getSimpleMrPtFileReader(file_type, False)

        if reader is None:
            return False

        err_message = err_desc['message']
        err_line_number = err_desc['line_number']
        err_column_position = err_desc['column_position']
        err_input = err_desc.get('input', '')

        xplor_file_type = file_type in ('nm-res-xpl', 'nm-res-cns')
        amber_file_type = file_type = 'nm-res-amb'
        gromacs_file_type = file_type in ('nm-res-gro', 'nm-aux-gro')

        xplor_ends_wo_statement = xplor_file_type and (bool(xplor_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and xplor_end_pattern.match(err_input)))
        amber_ends_wo_statement = amber_file_type and (bool(amber_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and amber_end_pattern.match(err_input)))

        corrected = False

        if xplor_ends_wo_statement or amber_ends_wo_statement:

            _offset = offset + err_line_number - 1

            has_end_tag = False

            k = 0

            with open(src_path, 'r') as ifh:
                for line in ifh:
                    if k == _offset:
                        if xplor_ends_wo_statement and xplor_end_pattern.match(line):
                            has_end_tag = True
                        if amber_ends_wo_statement and amber_end_pattern.match(line):
                            has_end_tag = True
                        break
                    k += 1

            if has_end_tag:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    k = 0

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            if k == _offset:
                                ofh.write('#' + line)
                            else:
                                ofh.write(line)
                            k += 1

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    corrected = True

        if err_column_position > 0 and not err_input[0:err_column_position].isspace():
            test_line = err_input[err_column_position:]

            if comment_pattern.match(test_line):

                if self.__mr_debug:
                    self.__lfh.write('PEEL-MR-EXIT #1\n')

                return self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset) | corrected

            for test_file_type in ['nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-aux-amb', 'nm-res-cya',
                                   'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-aux-gro', 'nm-res-dyn',
                                   'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari']:

                if test_file_type == file_type:
                    continue

                test_reader = self.__getSimpleMrPtFileReader(test_file_type, False)

                _, parser_err_listener, lexer_err_listener = test_reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

                if not has_lexer_error and not has_parser_error:

                    if self.__mr_debug:
                        self.__lfh.write('PEEL-MR-EXIT #2\n')

                    return self.__divideLegacyMr(file_path, file_type, err_desc, src_path, offset) | corrected

            _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)
            has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
            has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

            if has_lexer_error or not has_parser_error:

                if self.__mr_debug:
                    self.__lfh.write('PEEL-MR-EXIT #3\n')

                return False | corrected

        i = j = j2 = j3 = 0

        interval = []

        is_done = False

        if not xplor_file_type:

            prev_input = None

            with open(file_path, 'r') as ifh:
                for line in ifh:
                    i += 1
                    if i < err_line_number - self.mr_max_spacer_lines:
                        continue
                    if i < err_line_number - 1:
                        interval.append({'line': line,
                                         'ws_or_comment': line.isspace() or bool(comment_pattern.match(line))
                                         or (gromacs_file_type and bool(gromacs_comment_pattern.match(line)))})
                        continue
                    if i == err_line_number - 1:
                        prev_input = line
                        break

            if prev_input is not None:
                listener, parser_err_listener, lexer_err_listener = reader.parse(prev_input, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None
                has_content = bool(listener is not None and len(listener.getContentSubtype()) > 0)

                if has_lexer_error or has_parser_error or not has_content:
                    test_reader = self.__getSimpleMrPtFileReader('nm-res-xpl', False)

                    _, _, lexer_err_listener = test_reader.parse(prev_input, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                    if not has_lexer_error:
                        err_line_number -= 1

                        for _interval in reversed(interval):
                            if _interval['ws_or_comment']:
                                err_line_number -= 1
                            else:
                                break

                        i = 0

                        with open(file_path, 'r') as ifh, \
                                open(div_src_file, 'w') as ofh, \
                                open(div_try_file, 'w') as ofh3:
                            for line in ifh:
                                i += 1
                                if i < err_line_number:
                                    ofh.write(line)
                                    j += 1
                                    continue
                                ofh3.write(line)
                                j3 += 1

                        is_done = True

        if not is_done:

            i = j = j2 = j3 = 0

            interval.clear()

            is_valid = False
            ws_or_comment = True

            with open(file_path, 'r') as ifh, \
                    open(div_src_file, 'w') as ofh, \
                    open(div_ext_file, 'w') as ofh2, \
                    open(div_try_file, 'w') as ofh3:
                for line in ifh:
                    i += 1
                    if i < err_line_number - self.mr_max_spacer_lines:
                        ofh.write(line)
                        j += 1
                        continue
                    if i < err_line_number:
                        interval.append({'line': line,
                                         'ws_or_comment': line.isspace() or bool(comment_pattern.match(line))
                                         or (gromacs_file_type and bool(gromacs_comment_pattern.match(line)))})
                        if i < err_line_number - 1:
                            continue
                        _k = len(interval) - 1
                        _c = interval[-1]['line'][0]
                        for _interval in reversed(interval):
                            c = _interval['line'][0]
                            if _interval['ws_or_comment'] and {c, _c} != comment_code_mixed_set:
                                _c = c
                                _k -= 1
                                continue
                            break
                        for k, _interval in enumerate(interval):
                            if k <= _k:
                                ofh.write(_interval['line'])
                                j += 1
                            else:
                                ofh2.write(_interval['line'])
                                j2 += 1
                        continue
                    if not is_valid:
                        if line.isspace() or comment_pattern.match(line)\
                           or (gromacs_file_type and gromacs_comment_pattern.match(line)):
                            ofh2.write(line)
                            j2 += 1
                            continue
                        _, parser_err_listener, lexer_err_listener = reader.parse(line, None, isFilePath=False)
                        if lexer_err_listener is None or lexer_err_listener.getMessageList() is not None:
                            ofh2.write(line)
                            j2 += 1
                            continue
                        if parser_err_listener is not None:
                            messageList = parser_err_listener.getMessageList()
                            if messageList is not None and messageList[0]['line_number'] == 1:
                                ofh2.write(line)
                                j2 += 1
                                continue
                        is_valid = True
                    if ws_or_comment:
                        if line.isspace() or comment_pattern.match(line)\
                           or (gromacs_file_type and gromacs_comment_pattern.match(line)):
                            ofh2.write(line)
                            j2 += 1
                            continue
                    if cyana_unset_info_pattern.match(line) or cyana_print_pattern.match(line):
                        ofh2.write(line)
                        j2 += 1
                        continue
                    ws_or_comment = False
                    ofh3.write(line)
                    j3 += 1

        offset += j + j2

        if j == 0:  # or j3 == 0:
            if div_src:
                os.remove(file_path)
            if os.path.exists(div_try_file):
                os.remove(div_try_file)

            if j3 > 0:
                if self.__mr_debug:
                    self.__lfh.write('PEEL-MR-EXIT #5\n')

                return False | corrected

            if os.path.getsize(div_src_file) == 0:
                if os.path.exists(div_src_file):  # remove empty file
                    os.remove(div_src_file)

                os.rename(div_ext_file, div_ext_file.replace('dst-div_ext.mr', '_ext.mr'))  # shrink div_ext file name

                if self.__mr_debug:
                    self.__lfh.write('PEEL-MR-EXIT #6\n')

                return True

        if not os.path.exists(div_try_file):
            return False

        file_name = os.path.basename(div_try_file)

        _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(div_try_file, file_name, 'nm-res-mr', [], True)

        len_valid_types = len(valid_types)
        len_possible_types = len(possible_types)

        if len_valid_types == 0 and len_possible_types == 0:

            if err_column_position > 0 and not err_input[0:err_column_position].isspace():
                test_line = err_input[0:err_column_position]

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error:
                    if j3 == 0 and is_peak_list(err_input):
                        shutil.copyfile(div_ext_file, div_ext_file + '-ignored-as-pea-any')
                        os.remove(div_try_file)
                        os.remove(file_path)

                        corrected = True

                    else:
                        os.remove(div_src_file)
                        if os.path.exists(div_ext_file):
                            os.remove(div_ext_file)
                        os.remove(div_try_file)

                    if self.__mr_debug:
                        self.__lfh.write('PEEL-MR-EXIT #7\n')

                    return False | corrected  # not split MR file because of the lexer errors to be handled by manual

            if div_src:
                os.remove(file_path)
            with open(div_try_file, 'r') as ifh, \
                    open(div_ext_file, 'a') as ofh:
                for line in ifh:
                    ofh.write(line)
            os.remove(div_try_file)

            if self.__mr_debug:
                self.__lfh.write('PEEL-MR-EXIT #8\n')

            return True  # succeeded in eliminating uninterpretable parts

        if len_possible_types > 0:
            os.remove(div_src_file)
            os.remove(div_ext_file)
            os.remove(div_try_file)

            if self.__mr_debug:
                self.__lfh.write('PEEL-MR-EXIT #9\n')

            return False | corrected

        # self.__lfh.write(f"The NMR restraint file {file_name!r} ({mr_format_name} format) is identified as {valid_types}.\n")

        if div_src:
            os.remove(file_path)

        os.rename(div_try_file, div_dst_file)

        file_path = div_dst_file

        if len_valid_types == 1:
            file_type = valid_types[0]

        elif len_valid_types == 2 and 'nm-res-cns' in valid_types and 'nm-res-xpl' in valid_types:
            file_type = 'nm-res-xpl'

        elif len_valid_types == 2 and 'nm-res-cya' in valid_types:
            file_type = next(valid_type for valid_type in valid_types if valid_type != 'nm-res-cya')

        elif len_valid_types == 3:
            set_valid_types = set(valid_types)
            if set_valid_types in ({'nm-res-cya', 'nm-res-cns', 'nm-res-xpl'}, {'nm-res-isd', 'nm-res-cns', 'nm-res-xpl'}):
                file_type = 'nm-res-xpl'
            if set_valid_types == {'nm-res-cha', 'nm-res-cns', 'nm-res-xpl'}:
                file_type = 'nm-res-cha'

        self.__testFormatValidityOfLegacyMr(file_path, file_type, src_path, offset)

        if self.__mr_debug:
            self.__lfh.write('PEEL-MR-DONE\n')

        return True

    def __divideLegacyMr(self, file_path, file_type, err_desc, src_path, offset):
        """ Divive legacy NMR restraint file.
        """

        src_basename = os.path.splitext(file_path)[0]
        div_src = 'div_dst' in src_basename
        div_src_file = src_basename + '-div_src.mr'
        div_ext_file = src_basename + '-div_ext.mr'
        div_try_file = src_basename + '-div_try.mr'
        div_dst_file = src_basename + '-div_dst.mr'

        if self.__mr_debug:
            self.__lfh.write('DO-DIV-MR\n')

        if file_type == 'nm-res-xpl':
            pass
        elif file_type == 'nm-res-cns':
            pass
        elif file_type in ('nm-res-amb', 'nm-aux-amb'):
            pass
        elif file_type == 'nm-res-cya':
            pass
        elif file_type == 'nm-res-ros':
            pass
        elif file_type == 'nm-res-bio':
            pass
        elif file_type in ('nm-res-gro', 'nm-aux-gro'):
            pass
        elif file_type == 'nm-res-dyn':
            pass
        elif file_type == 'nm-res-syb':
            pass
        elif file_type == 'nm-res-isd':
            pass
        elif file_type == 'nm-res-cha':
            pass
        elif file_type == 'nm-res-ari':
            pass
        else:
            return False

        err_message = err_desc['message']
        err_line_number = err_desc['line_number']
        err_column_position = err_desc['column_position']
        err_input = err_desc.get('input', '')

        if err_column_position == 0 or len(err_input) == 0:

            if self.__mr_debug:
                self.__lfh.write('DO-DIV-MR-EXIT #1\n')

            return False

        xplor_file_type = file_type in ('nm-res-xpl', 'nm-res-cns')
        amber_file_type = file_type == 'nm-res-amb'
        gromacs_file_type = file_type in ('nm-res-gro', 'nm-aux-gro')
        linear_mr_file_types = ('nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-syb')

        xplor_missing_end = xplor_file_type and err_message.startswith(xplor_missing_end_err_msg)
        xplor_ends_wo_statement = xplor_file_type and (bool(xplor_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and xplor_end_pattern.match(err_input)))

        xplor_l_paren_wo_assi = xplor_file_type and bool(xplor_extra_l_paren_err_msg_pattern.match(err_message))
        xplor_00_origin = xplor_file_type and err_message.startswith(no_viable_alt_err_msg) and ' 00' in err_input

        amber_missing_end = amber_file_type and err_message.startswith(amber_missing_end_err_msg)
        amber_ends_wo_statement = amber_file_type and (bool(amber_extra_end_err_msg_pattern.match(err_message))
                                                       or (err_message.startswith(no_viable_alt_err_msg)
                                                           and amber_end_pattern.match(err_input)))

        concat_xplor_assi = (xplor_file_type
                             and (err_message.startswith(mismatched_input_err_msg)
                                  or err_message.startswith(extraneous_input_err_msg))
                             and bool(xplor_assi_pattern.search(err_input))
                             and not bool(xplor_class_pattern.search(err_input)))
        concat_xplor_rest = (xplor_file_type
                             and (err_message.startswith(mismatched_input_err_msg)
                                  or err_message.startswith(extraneous_input_err_msg))
                             and bool(xplor_rest_pattern.search(err_input)))
        concat_xplor_set = (xplor_file_type
                            and (err_message.startswith(mismatched_input_err_msg)
                                 or err_message.startswith(extraneous_input_err_msg))
                            and bool(xplor_set_pattern.search(err_input)))
        concat_amber_rst = (amber_file_type
                            and (err_message.startswith(mismatched_input_err_msg)
                                 or err_message.startswith(extraneous_input_err_msg))
                            and bool(amber_rst_pattern.search(err_input))
                            and not bool(amber_rst_pattern.match(err_input)))

        concat_comment = (file_type in linear_mr_file_types
                          and err_message.startswith(no_viable_alt_err_msg)
                          and bool(comment_pattern.search(err_input)))

        if concat_xplor_assi and bool(xplor_assi_pattern.match(err_input)):
            if expecting_l_paren in err_message:
                xplor_missing_end = True
                concat_xplor_assi = False
            if concat_xplor_rest or concat_xplor_set:
                concat_xplor_assi = False

        reader = prev_input = None

        i = j = 0

        ws_or_comment = True

        with open(file_path, 'r') as ifh, \
                open(div_src_file, 'w') as ofh, \
                open(div_try_file, 'w') as ofh2:
            for line in ifh:
                i += 1
                if i < err_line_number:
                    if ws_or_comment:
                        if line.isspace() or comment_pattern.match(line)\
                           or (gromacs_file_type and gromacs_comment_pattern.match(line)):
                            pass
                        else:
                            ws_or_comment = False
                    if i == err_line_number - 1:
                        prev_input = line
                    ofh.write(line)
                    j += 1
                    continue
                if i == err_line_number:
                    ofh.write(line[0:err_column_position] + '\n')
                    j += 1
                    ofh2.write(line[err_column_position:])
                    continue
                ofh2.write(line)

        offset += err_line_number - 1

        xplor_missing_end_before = (xplor_file_type and err_message.startswith(mismatched_input_err_msg)
                                    and not bool(xplor_expecting_symbol_pattern.search(err_message))  # exclude syntax errors in a factor
                                    and prev_input is not None and bool(xplor_assi_pattern.search(prev_input)))

        xplor_no_syntax_err_in_fac_or_ann = not bool(xplor_expecting_equ_op_pattern.search(err_message))\
            and not bool(xplor_expecting_seg_id_pattern.search(err_message))\
            and not err_message.startswith(no_viable_alt_err_msg)

        amber_missing_comma_before = (amber_file_type and err_message.startswith(mismatched_input_err_msg)
                                      and bool(amber_expecting_comma_pattern.search(err_message)))

        if (xplor_missing_end or xplor_ends_wo_statement
                or xplor_l_paren_wo_assi or xplor_00_origin
                or xplor_missing_end_before
                or amber_missing_end or amber_ends_wo_statement
                or amber_missing_comma_before
                or concat_xplor_assi or concat_xplor_rest or concat_xplor_set
                or concat_amber_rst
                or concat_comment) or i <= err_line_number or j == 0:

            corrected = False

            if err_line_number - 1 in (i, j) and xplor_l_paren_wo_assi:  # this should be before 'concat_comment' routine

                if os.path.exists(div_src_file):
                    os.remove(div_src_file)
                if os.path.exists(div_try_file):
                    os.remove(div_try_file)

                if prev_input is not None:
                    err_desc['previous_input'] = f"Do you need to comment out the succeeding lines as well?\n{prev_input}"

                if self.__mr_debug and not corrected:
                    self.__lfh.write('DO-DIV-MR-EXIT #2-1\n')

                return False

            if xplor_00_origin:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            if ' 00' in line:
                                ofh.write(re.sub(r' 00', ' OO', line))
                            else:
                                ofh.write(line)

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DO-DIV-MR-EXIT #2-2\n')

                    corrected = True

            if xplor_ends_wo_statement or amber_ends_wo_statement:

                has_end_tag = False

                k = 0

                with open(src_path, 'r') as ifh:
                    for line in ifh:
                        if k == offset:
                            if xplor_ends_wo_statement and xplor_end_pattern.match(line):
                                has_end_tag = True
                            if amber_ends_wo_statement and amber_end_pattern.match(line):
                                has_end_tag = True
                            break
                        k += 1

                if has_end_tag:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        k = 0

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if k == offset:
                                    ofh.write('#' + line)
                                else:
                                    ofh.write(line)
                                k += 1

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DO-DIV-MR-EXIT #2-3\n')

                        corrected = True

            if concat_xplor_assi or concat_xplor_rest or concat_xplor_set or concat_amber_rst:

                code_index = -1

                if concat_xplor_assi:
                    for m in xplor_any_assi_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_xplor_rest:
                    for m in xplor_any_rest_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_xplor_set:
                    for m in xplor_any_set_pattern.finditer(err_input):
                        code_index = m.start()
                elif concat_amber_rst:
                    for m in amber_rst_pattern.finditer(err_input):
                        code_index = m.start()

                if code_index != -1:
                    test_line = err_input[0:code_index]

                    if len(test_line.strip()) > 0:
                        typo_for_comment_out = bool(possible_typo_for_comment_out_pattern.match(test_line))

                        if reader is None:
                            reader = self.__getSimpleMrPtFileReader(file_type, False)

                        _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                        has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                        if not has_lexer_error:

                            cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                            if cor_src_path is not None:

                                k = 0

                                with open(src_path, 'r') as ifh, \
                                        open(cor_src_path, 'w') as ofh:
                                    for line in ifh:
                                        if k == offset:
                                            if typo_for_comment_out:
                                                g = possible_typo_for_comment_out_pattern.search(test_line).groups()
                                                if g[0] == '1':
                                                    test_line = re.sub(r'1', '!', test_line)
                                                else:
                                                    test_line = re.sub(r'3', '#', test_line)
                                                ofh.write(f"{test_line}{err_input[code_index:]}\n")
                                            else:
                                                ofh.write(f"{test_line}\n{err_input[code_index:]}\n")
                                        else:
                                            ofh.write(line)
                                        k += 1

                                if cor_test:
                                    os.rename(cor_src_path, src_path)

                                if self.__mr_debug:
                                    self.__lfh.write('DIV-MR-EXIT #2-4\n')

                                corrected = True

            if concat_comment:

                comment_code_index = -1
                if '#' in err_input:
                    comment_code_index = err_input.index('#')
                if '!' in err_input:
                    if comment_code_index == -1:
                        comment_code_index = err_input.index('!')
                    elif err_input.index('!') < comment_code_index:
                        comment_code_index = err_input.index('!')

                if comment_code_index != -1:
                    test_line = err_input[0:comment_code_index]

                    if reader is None:
                        reader = self.__getSimpleMrPtFileReader(file_type, False)

                    _, parser_err_listener, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                    has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                    has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

                    if not has_lexer_error and not has_parser_error:

                        cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                        if cor_src_path is not None:

                            k = 0

                            with open(src_path, 'r') as ifh, \
                                    open(cor_src_path, 'w') as ofh:
                                for line in ifh:
                                    if k == offset:
                                        ofh.write(f"{test_line} {err_input[comment_code_index:]}\n")
                                    else:
                                        ofh.write(line)
                                    k += 1

                            if cor_test:
                                os.rename(cor_src_path, src_path)

                            if self.__mr_debug:
                                self.__lfh.write('DO-DIV-MR-EXIT #2-5\n')

                            corrected = True

            if err_line_number - 1 in (i, j) and (xplor_missing_end or xplor_missing_end_before):

                if not xplor_missing_end_before or xplor_no_syntax_err_in_fac_or_ann:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        middle = i != err_line_number - 1
                        is_done = False

                        k = 0 if xplor_missing_end else 1

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if middle:
                                    if k == err_line_number - 2 and comment_pattern.match(line):
                                        ofh.write('end\n')
                                        is_done = True
                                    elif k == err_line_number - 1 and not is_done:
                                        ofh.write('end\n')
                                ofh.write(line)
                                k += 1
                            if not middle:
                                ofh.write('end\n')

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DO-DIV-MR-EXIT #2-6\n')

                        corrected = True

            if err_line_number - 1 in (i, j) and amber_missing_comma_before:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    k = 1

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            if k == err_line_number:
                                ofh.write(',' + line)
                            else:
                                ofh.write(line)
                            k += 1

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DIV-MR-EXIT #2-7\n')

                    corrected = True

            if i == err_line_number - 1 and amber_missing_end:

                cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                if cor_src_path is not None:

                    with open(src_path, 'r') as ifh, \
                            open(cor_src_path, 'w') as ofh:
                        for line in ifh:
                            ofh.write(line)
                        ofh.write('&end\n')

                    if cor_test:
                        os.rename(cor_src_path, src_path)

                    if self.__mr_debug:
                        self.__lfh.write('DO-DIV-MR-EXIT #2-8\n')

                    corrected = True

            if not (corrected or concat_xplor_assi or concat_xplor_rest or concat_xplor_set or concat_amber_rst)\
               and j in (0, err_line_number - 1)\
               and (not xplor_missing_end_before or xplor_no_syntax_err_in_fac_or_ann):
                test_line = err_input

                if reader is None:
                    reader = self.__getSimpleMrPtFileReader(file_type, False)

                _, _, lexer_err_listener = reader.parse(test_line, None, isFilePath=False)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None

                if not has_lexer_error:

                    cor_src_path, cor_test = self.__getCorrectedMrFilePath(src_path)

                    if cor_src_path is not None:

                        k = 0

                        with open(src_path, 'r') as ifh, \
                                open(cor_src_path, 'w') as ofh:
                            for line in ifh:
                                if k == offset:
                                    ofh.write('#' + line)
                                else:
                                    ofh.write(line)
                                k += 1

                        if cor_test:
                            os.rename(cor_src_path, src_path)

                        if self.__mr_debug:
                            self.__lfh.write('DO-DIV-MR-EXIT #2-9\n')

                        corrected = True

            if os.path.exists(div_src_file):
                os.remove(div_src_file)
            if os.path.exists(div_try_file):
                os.remove(div_try_file)

            if self.__mr_debug and not corrected:
                self.__lfh.write('DO-DIV-MR-EXIT #2-10\n')

            return corrected

        if ws_or_comment:
            os.remove(div_src_file)
            os.remove(div_try_file)

            if self.__mr_debug:
                self.__lfh.write('DO-DIV-MR-EXIT #3\n')

            return False

        if not os.path.exists(div_try_file):
            return False

        file_name = os.path.basename(div_try_file)

        _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(div_try_file, file_name, 'nm-res-mr', [], True)

        len_valid_types = len(valid_types)
        len_possible_types = len(possible_types)

        if len_valid_types == 0 and len_possible_types == 0:

            if xplor_file_type and bool(xplor_assi_pattern.search(err_input)):

                if prev_input is not None and err_message.startswith(extraneous_input_err_msg):
                    err_desc['previous_input'] = prev_input

                os.remove(div_src_file)
                os.remove(div_try_file)

                if self.__mr_debug:
                    self.__lfh.write('DO-DIV-MR-EXIT #4\n')

                return False

            if div_src:
                os.remove(file_path)
            os.rename(div_try_file, div_ext_file)

            if self.__mr_debug:
                self.__lfh.write('DO-DIV-MR-EXIT #5\n')

            return True  # succeeded in eliminating uninterpretable parts

        if len_possible_types > 0:
            os.remove(div_src_file)
            os.remove(div_try_file)

            if self.__mr_debug:
                self.__lfh.write('DO-DIV-MR-EXIT #6\n')

            return False

        # self.__lfh.write(f"The NMR restraint file {file_name!r} ({mr_format_name} format) is identified as {valid_types}.\n")

        if div_src:
            os.remove(file_path)

        os.rename(div_try_file, div_dst_file)

        file_path = div_dst_file

        if len_valid_types == 1:
            file_type = valid_types[0]

        elif len_valid_types == 2 and 'nm-res-cns' in valid_types and 'nm-res-xpl' in valid_types:
            file_type = 'nm-res-xpl'

        elif len_valid_types == 2 and 'nm-res-cya' in valid_types:
            file_type = next(valid_type for valid_type in valid_types if valid_type != 'nm-res-cya')

        elif len_valid_types == 3:
            set_valid_types = set(valid_types)
            if set_valid_types in ({'nm-res-cya', 'nm-res-cns', 'nm-res-xpl'}, {'nm-res-isd', 'nm-res-cns', 'nm-res-xpl'}):
                file_type = 'nm-res-xpl'
            if set_valid_types == {'nm-res-cha', 'nm-res-cns', 'nm-res-xpl'}:
                file_type = 'nm-res-cha'

        self.__testFormatValidityOfLegacyMr(file_path, file_type, src_path, offset)

        if self.__mr_debug:
            self.__lfh.write('DO-DIV-MR-DONE\n')

        return True

    def __testFormatValidityOfLegacyMr(self, file_path, file_type, src_path, offset):
        """ Perform format check of a given MR file, then split MR recursively if necessary.
        """

        div_test = False

        try:

            reader = self.__getSimpleMrPtFileReader(file_type, False, sll_pred=False)

            listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

            if listener is not None:
                if file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-dyn',
                                 'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari'):
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:
                        reader = self.__getSimpleMrPtFileReader(file_type, False, sll_pred=False, reasons=reasons)

                        listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

            has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
            has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None
            has_content = bool(listener is not None and len(listener.getContentSubtype()) > 0)

            if has_lexer_error and has_parser_error and has_content:
                # parser error occurrs before occurrenece of lexer error that implies mixing of different MR formats in a file
                if lexer_err_listener.getErrorLineNumber()[0] > parser_err_listener.getErrorLineNumber()[0]:
                    self.__peelLegacyMrIfNecessary(file_path, file_type,
                                                   parser_err_listener.getMessageList()[0],
                                                   src_path, offset)
                    div_test = True

            fixed_line_num = -1

            if has_lexer_error:
                messageList = lexer_err_listener.getMessageList()

                for description in messageList:
                    if 'input' in description:
                        enc = detect_encoding(description['input'])
                        if enc is not None and enc != 'ascii':
                            pass
                        elif not div_test and has_content:
                            fixed = self.__divideLegacyMrIfNecessary(file_path, file_type, description, src_path, offset)
                            if fixed:
                                fixed_line_num = description['line_number']
                            div_test = file_type != 'nm-res-amb'  # remediate missing comma issue in AMBER MR

            if has_parser_error:
                messageList = parser_err_listener.getMessageList()

                for description in messageList:
                    if 0 < fixed_line_num <= description['line_number']:
                        div_test = True
                    if 'input' in description:
                        if not div_test and has_content:
                            self.__divideLegacyMrIfNecessary(file_path, file_type, description, src_path, offset)
                            div_test = True
                    elif not div_test and has_content and file_type in ('nm-res-xpl', 'nm-res-cns'):
                        self.__divideLegacyMrIfNecessary(file_path, file_type, description, str(file_path), offset)
                        div_test = True

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testFormatValidityOfLegacyMr() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testFormatValidityOfLegacyMr() ++ Error  - {str(e)}\n")

    def __detectOtherPossibleFormatAsErrorOfLegacyMr(self, file_path, file_name, file_type, dismiss_err_lines, multiple_check=False):
        """ Report other possible format as error of a given legacy NMR restraint file.
        """

        is_valid = False
        err = ''
        genuine_type = []
        valid_types = {}
        possible_types = {}

        agreed_w_cns = False

        if (not is_valid or multiple_check) and file_type != 'nm-res-cns':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-cns')

            is_valid |= is_valid
            agreed_w_cns = _is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-xpl':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-xpl',
                                                                    agreed_w_cns=agreed_w_cns)

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-amb':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-amb')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-aux-amb':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-aux-amb')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        # prevent 'nm-pea-any' occasionally matches with 'nm-res-cya' (DAOTHER-9425)
        if (not is_valid or multiple_check) and file_type not in ('nm-res-cya', 'nm-pea-any'):
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-cya')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-ros':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-ros')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-bio':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-bio')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-gro':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-gro')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-aux-gro':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-aux-gro')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-dyn':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-dyn')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-syb':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-syb')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-isd':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-isd')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
                valid_types.update(_valid_types)
                possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-cha':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-cha',
                                                                    agreed_w_cns=agreed_w_cns)

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
                valid_types.update(_valid_types)
                possible_types.update(_possible_types)

        if (not is_valid or multiple_check) and file_type != 'nm-res-ari':
            _is_valid, _err, _genuine_type, _valid_types, _possible_types =\
                self.__detectOtherPossibleFormatAsErrorOfLegacyMr__(file_path, file_name, file_type, dismiss_err_lines, 'nm-res-ari')

            is_valid |= is_valid
            err += _err
            if _genuine_type is not None:
                genuine_type.append(_genuine_type)
            valid_types.update(_valid_types)
            possible_types.update(_possible_types)

        if len(genuine_type) != 1:
            _valid_types = [k for k, v in sorted(valid_types.items(), key=itemgetter(1), reverse=True)]
            _possible_types = [k for k, v in sorted(possible_types.items(), key=itemgetter(1), reverse=True)]
        else:
            _valid_types = [genuine_type[0]]
            _possible_types = []

        return is_valid, err, _valid_types, _possible_types

    def __detectOtherPossibleFormatAsErrorOfLegacyMr__(self, file_path, file_name, file_type, dismiss_err_lines, _file_type,
                                                       agreed_w_cns=False):
        """ Report other possible format as error of a given legacy NMR restraint file.
        """

        _mr_format_name = getRestraintFormatName(file_type)
        mr_format_name = _mr_format_name.split()[0]

        __mr_format_name = getRestraintFormatName(_file_type, True)
        _mr_format_name = __mr_format_name.split()[0]
        _a_mr_format_name = ('an ' if _mr_format_name[0] in ('AINMX') else 'a ') + __mr_format_name

        is_valid = False
        err = ''
        genuine_type = None
        valid_types = {}
        possible_types = {}

        try:

            sll_pred = not agreed_w_cns

            reader = self.__getSimpleMrPtFileReader(_file_type, False, sll_pred=sll_pred)

            listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

            has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
            has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

            if (has_lexer_error or has_parser_error) and sll_pred\
               and _file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-cha'):
                sll_pred = False

                reader.setSllPredMode(sll_pred)

                listener, parser_err_listener, lexer_err_listener = reader.parse(file_path, None)

                has_lexer_error = lexer_err_listener is not None and lexer_err_listener.getMessageList() is not None
                has_parser_error = parser_err_listener is not None and parser_err_listener.getMessageList() is not None

            if file_path not in self.__sll_pred_holder:
                self.__sll_pred_holder[file_path] = {}

            if not has_lexer_error and not has_parser_error:
                self.__sll_pred_holder[file_path][_file_type] = sll_pred

            # 'rdc_restraint' occasionally matches with CYANA restraints
            # 'geo_restraint' include CS-ROSETTA disulfide bond linkage, which matches any integer array
            if _file_type == 'nm-res-ros':
                _content_subtype = listener.getEffectiveContentSubtype() if listener is not None else None
            else:
                _content_subtype = listener.getContentSubtype() if listener is not None else None
            if _content_subtype is not None and len(_content_subtype) == 0:
                _content_subtype = None
            has_content = _content_subtype is not None

            if lexer_err_listener is not None and parser_err_listener is not None and listener is not None\
               and ((lexer_err_listener.getMessageList() is None and parser_err_listener.getMessageList() is None) or has_content):

                if has_content or file_type != 'nm-res-oth':

                    is_valid = True

                    err = f"The NMR restraint file {file_name!r} ({mr_format_name}) looks like {_a_mr_format_name} file, "\
                          f"which has {concat_nmr_restraint_names(_content_subtype)}. "\
                          "Did you accidentally select the wrong format? Please re-upload the NMR restraint file."

                    if has_content:
                        _err = ''
                        if lexer_err_listener is not None:
                            messageList = lexer_err_listener.getMessageList()

                            if messageList is not None:
                                for description in messageList:
                                    if description['line_number'] in dismiss_err_lines:
                                        continue
                                    _err = f"[Syntax error as {_a_mr_format_name} file] "\
                                           f"line {description['line_number']}:{description['column_position']} {description['message']}\n"
                                    if 'input' in description:
                                        enc = detect_encoding(description['input'])
                                        is_not_ascii = False
                                        if enc is not None and enc != 'ascii':
                                            _err += f"{description['input']}\n".encode().decode('ascii', 'backslashreplace')
                                            is_not_ascii = True
                                        else:
                                            _err += f"{description['input']}\n"
                                        _err += f"{description['marker']}\n"
                                        if is_not_ascii:
                                            _err += f"[Unexpected text encoding] Encoding used in the above line is {enc!r} and must be 'ascii'.\n"

                        if parser_err_listener is not None and len(_err) == 0:
                            messageList = parser_err_listener.getMessageList()

                            if messageList is not None:
                                for description in messageList:
                                    if description['line_number'] in dismiss_err_lines:
                                        continue
                                    _err += f"[Syntax error as {_a_mr_format_name} file] "\
                                            f"line {description['line_number']}:{description['column_position']} {description['message']}\n"
                                    if 'input' in description:
                                        _err += f"{description['input']}\n"
                                        _err += f"{description['marker']}\n"

                        if len(_err) > 0:
                            err += f"\nEven assuming that the format is the {_mr_format_name!r}, the following issues need to be fixed.\n" + _err[:-1]
                        elif file_type != 'nm-res-oth' and (lexer_err_listener.getMessageList() is not None or parser_err_listener.getMessageList() is not None):
                            is_valid = False
                            err = ''

                        if is_valid:
                            if has_content and lexer_err_listener.getMessageList() is None and parser_err_listener.getMessageList() is None:
                                genuine_type = _file_type
                            valid_types[_file_type] = len(_content_subtype)
                        else:
                            possible_types[_file_type] = len(_content_subtype)

                    if file_type == 'nm-res-oth':
                        self.report.error.appendDescription('content_mismatch',
                                                            {'file_name': file_name, 'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detectOtherPossibleFormatAsErrorOfLegacyMr() ++ Error  - {err}\n")

        except ValueError:
            pass

        return is_valid, err, genuine_type, valid_types, possible_types

    def __extractPublicMrFileIntoLegacyMr(self):
        """ Extract/split public MR file into legacy NMR restraint files for NMR restraint remediation.
        """

        if self.__combined_mode or not self.__remediation_mode:
            return True

        linear_mr_file_types = ('nm-res-cya', 'nm-res-ros', 'nm-res-bio', 'nm-res-syb')

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        fileListId = self.__file_path_list_len

        dir_path = '.'
        mr_file_name = '.'
        split_file_list = []
        peak_file_list = []

        self.__mr_atom_name_mapping = []

        remediated = False
        aborted = False
        src_basename = mr_core_path = None
        mr_file_path = mr_file_link = None
        mr_part_paths = []
        pk_list_paths = []

        for ar in self.__inputParamDict[ar_file_path_list]:

            src_file = ar['file_name']

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            fileListId += 1

            if file_type != 'nm-res-mr':

                if file_type in linear_mr_file_types:
                    with open(src_file, 'r') as ifh:
                        for line in ifh:
                            if 'Submitted Coord H atom name' in line:
                                input_source.setItemValue('file_type', 'nm-res-mr')
                                return self.__extractPublicMrFileIntoLegacyMr()

                continue

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])
                if file_name != original_file_name and original_file_name is not None:
                    file_name = f"{original_file_name} ({file_name})"

            mr_file_name = file_name

            self.__cur_original_ar_file_name = original_file_name

            if is_binary_file(src_file):

                if not src_file.endswith('.gz'):

                    err = f"The NMR restraint file {src_file!r} (MR format) is neither ASCII file nor gzip compressed file."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                    return False

                dst_file = os.path.splitext(src_file)[0]

                if not os.path.exists(dst_file):

                    try:

                        uncompress_gzip_file(src_file, dst_file)

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {str(e)}\n")

                        return False

                src_file = dst_file

            dir_path = os.path.dirname(src_file)

            _div_file_names = {div_file_name: len(div_file_name) + (0 if div_file_name.endswith('-div_src.mr') else (1 if div_file_name.endswith('-div_ext.mr') else 2))
                               for div_file_name in os.listdir(dir_path)
                               if os.path.isfile(os.path.join(dir_path, div_file_name))
                               and (div_file_name.endswith('-div_src.mr')
                                    or div_file_name.endswith('-div_dst.mr')
                                    or div_file_name.endswith('-div_ext.mr'))}

            div_file_names = [k for k, v in sorted(_div_file_names.items(), key=itemgetter(1))]

            src_basename = os.path.splitext(src_file)[0]
            ar['original_file_name'] = src_basename + '.mr'

            dst_file = src_basename + '-trimmed.mr'
            header_file = src_basename + '-header.mr'
            footer_file = src_basename + '-footer.mr'
            cor_dst_file = src_basename + '-corrected.mr'
            ign_dst_file = src_basename + '-ignored.mr'

            if os.path.exists(ign_dst_file):  # in case the MR file can be ignored
                continue

            ign_pk_file = src_basename + '-ignored-as-pea-any.mr'

            if os.path.exists(ign_pk_file):  # in case the MR file can be ignored as peak list file
                _ar = ar.copy()

                _ar['file_name'] = ign_pk_file
                _ar['file_type'] = 'nm-pea-any'
                peak_file_list.append(_ar)

                pk_list_paths.append({'nm-pea-any': src_basename + '.mr'})

                touch_file = os.path.join(dir_path, '.entry_with_pk')
                if not os.path.exists(touch_file):
                    with open(touch_file, 'w') as ofh:
                        ofh.write('')

                continue

            designated = False

            for _file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-res-cya',
                               'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-res-dyn',
                               'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari',
                               'nm-res-sax'):

                sel_res_file = src_basename + f'-selected-as-res-{_file_type[-3:]}.mr'

                if os.path.exists(sel_res_file):
                    _ar = ar.copy()

                    _ar['file_name'] = sel_res_file
                    _ar['file_type'] = _file_type
                    split_file_list.append(_ar)

                    designated = True

                    break

            if designated:
                continue

            if mr_file_path is None:

                rem_dir = os.path.join(dir_path, 'remediation')

                try:

                    if not os.path.isdir(rem_dir):
                        os.makedirs(rem_dir)

                except OSError:
                    pass

                mr_file_path = src_basename + '-remediated.mr'
                mr_file_link = os.path.join(rem_dir, os.path.basename(src_basename) + '.mr')

                mr_part_paths.append({'header': header_file})
                mr_part_paths.append({'footer': footer_file})

            has_mr_header = False
            has_pdb_format = False
            has_cif_format = False
            has_str_format = False
            has_cs_str = False
            has_mr_str = False

            try:

                header = True
                in_header = False
                pdb_record = False
                footer = False

                has_datablock = False
                has_anonymous_saveframe = False
                has_save = False
                has_loop = False
                has_stop = False

                first_str_line_num = -1
                last_str_line_num = -1

                i = 0

                with open(src_file, 'r') as ifh, \
                        open(dst_file, 'w') as ofh, \
                        open(header_file, 'w') as hofh, \
                        open(footer_file, 'w') as fofh:
                    for line in ifh:
                        i += 1

                        # skip MR header
                        if header:
                            if line.startswith('*'):
                                hofh.write(line)
                                continue
                            if startsWithPdbRecord(line):
                                continue
                            header = False

                        if not footer:
                            if line.startswith('*') and startsWithPdbRecord(line[1:]):
                                hofh.write(line)
                                in_header = True
                                continue

                            if in_header and line.startswith('*END'):
                                hofh.write(line)
                                in_header = False
                                continue

                        if mr_file_header_pattern.match(line):
                            has_mr_header = True

                        # skip legacy PDB
                        if startsWithPdbRecord(line):
                            has_pdb_format = pdb_record = True
                            continue
                        if pdb_record:
                            pdb_record = False
                            if line.startswith('END'):
                                continue

                        # check STAR
                        str_syntax = False
                        if datablock_pattern.match(line):
                            str_syntax = has_datablock = True
                        elif sf_anonymous_pattern.match(line):
                            str_syntax = has_anonymous_saveframe = True
                        elif save_pattern.match(line):
                            str_syntax = has_save = True
                        elif loop_pattern.match(line):
                            str_syntax = has_loop = True
                        elif stop_pattern.match(line):
                            str_syntax = has_stop = True

                        if str_syntax:
                            if first_str_line_num < 0:
                                first_str_line_num = i
                            last_str_line_num = i
                            if (has_anonymous_saveframe and has_save) or (has_loop and has_stop):
                                has_str_format = True
                            elif has_datablock and has_loop and not has_stop:
                                has_cif_format = True

                        # skip MR footer
                        if 'Submitted Coord H atom name' in line:
                            fofh.write(line)
                            footer = True
                            continue

                        if footer:
                            fofh.write(line)
                            col = line.split()
                            len_col = len(col)
                            if len_col == 10:
                                original_comp_id = col[5].upper()
                                if original_comp_id not in monDict3:  # extract non-standard residues
                                    try:
                                        atom_map = {'auth_atom_id': col[1],
                                                    'auth_comp_id': col[2],
                                                    'auth_seq_id': int(col[3]),
                                                    'original_atom_id': col[4].upper(),
                                                    'original_comp_id': original_comp_id,
                                                    'original_seq_id': int(col[3])}
                                        if atom_map not in self.__mr_atom_name_mapping:
                                            self.__mr_atom_name_mapping.append(atom_map)
                                    except ValueError:
                                        pass
                            elif len_col >= 8:  # 2liw 4 digits residue number

                                def split_concat_comp_id_seq_id(string):
                                    if not string[-1].isdigit():
                                        return None, None
                                    idx = len(string) - 1
                                    while idx >= 0 and string[idx].isdigit():
                                        idx -= 1
                                    if idx == 0:
                                        return None, None
                                    if string[idx] != '-':
                                        idx += 1
                                    return string[:idx], int(string[idx:])

                                if len(col[2]) > 4:
                                    auth_comp_id, auth_seq_id = split_concat_comp_id_seq_id(col[2])

                                    if len(col[4]) > 4 and len_col == 8:
                                        orig_comp_id, orig_seq_id = split_concat_comp_id_seq_id(col[4])
                                        if auth_comp_id is not None and orig_comp_id is not None and orig_comp_id.upper() not in monDict3:
                                            atom_map = {'auth_atom_id': col[1],
                                                        'auth_comp_id': auth_comp_id,
                                                        'auth_seq_id': auth_seq_id,
                                                        'original_atom_id': col[3].upper(),
                                                        'original_comp_id': orig_comp_id.upper(),
                                                        'original_seq_id': orig_seq_id}
                                            if atom_map not in self.__mr_atom_name_mapping:
                                                self.__mr_atom_name_mapping.append(atom_map)
                                    elif len_col == 9:
                                        if auth_comp_id is not None and col[4] not in monDict3:
                                            try:
                                                atom_map = {'auth_atom_id': col[1],
                                                            'auth_comp_id': auth_comp_id,
                                                            'auth_seq_id': auth_seq_id,
                                                            'original_atom_id': col[3].upper(),
                                                            'original_comp_id': col[4].upper(),
                                                            'original_seq_id': int(col[5])}
                                                if atom_map not in self.__mr_atom_name_mapping:
                                                    self.__mr_atom_name_mapping.append(atom_map)
                                            except ValueError:
                                                pass
                                elif len(col[5]) > 4 and len_col == 9:
                                    orig_comp_id, orig_seq_id = split_concat_comp_id_seq_id(col[5])
                                    if orig_comp_id is not None and orig_comp_id.upper() not in monDict3:
                                        try:
                                            atom_map = {'auth_atom_id': col[1],
                                                        'auth_comp_id': col[2],
                                                        'auth_seq_id': int(col[3]),
                                                        'original_atom_id': col[4].upper(),
                                                        'original_comp_id': orig_comp_id.upper(),
                                                        'original_seq_id': orig_seq_id}
                                            if atom_map not in self.__mr_atom_name_mapping:
                                                self.__mr_atom_name_mapping.append(atom_map)
                                        except ValueError:
                                            pass

                        else:
                            ofh.write(line)

                if last_str_line_num - first_str_line_num < 10:
                    has_str_format = has_cif_format = False

                # split STAR and others
                if has_str_format and not has_mr_header:

                    remediated = True

                    mrPath = os.path.splitext(src_file)[0] + '-ignored.str'

                    if not os.path.exists(mrPath):
                        mrPath = os.path.splitext(src_file)[0] + '-trimmed.str'

                        header = True
                        in_header = False
                        pdb_record = False

                        i = 0

                        with open(src_file, 'r') as ifh, \
                                open(dst_file, 'w') as ofh, \
                                open(mrPath, 'w') as ofh2:
                            for line in ifh:
                                i += 1

                                # skip MR header
                                if header:
                                    if line.startswith('*'):
                                        continue
                                    header = False

                                if line.startswith('*') and startsWithPdbRecord(line[1:]):
                                    in_header = True
                                    continue

                                if in_header and line.startswith('*END'):
                                    in_header = False
                                    continue

                                # skip legacy PDB
                                if has_pdb_format:
                                    if startsWithPdbRecord(line):
                                        pdb_record = True
                                        continue
                                    if pdb_record:
                                        pdb_record = False
                                        if line.startswith('END'):
                                            continue

                                if first_str_line_num <= i <= last_str_line_num:
                                    ofh2.write(line)
                                    continue

                                # skip MR footer
                                if 'Submitted Coord H atom name' in line:
                                    break

                                ofh.write(line)

                        _mrPath = os.path.splitext(src_file)[0] + '-corrected.str'

                        if os.path.exists(_mrPath):  # in case manually corrected NMR-STAR file exists
                            mrPath = _mrPath

                        mr_file_path_list = 'restraint_file_path_list'

                        if mr_file_path_list not in self.__inputParamDict:
                            self.__inputParamDict[mr_file_path_list] = [mrPath]
                        else:
                            self.__inputParamDict[mr_file_path_list].append(mrPath)

                        insert_index = self.__file_path_list_len

                        if insert_index > len(self.__star_data):
                            self.__star_data.append(None)
                            self.__star_data_type.append(None)

                        self.report.insertInputSource(insert_index)

                        self.__file_path_list_len += 1

                        input_source = self.report.input_sources[insert_index]

                        file_type = 'nmr-star'
                        file_name = os.path.basename(mrPath)

                        input_source.setItemValue('file_name', file_name)
                        input_source.setItemValue('file_type', file_type)
                        input_source.setItemValue('content_type', 'nmr-restraints')

                        codec = detect_bom(mrPath, 'utf-8')

                        _mrPath = None

                        if codec != 'utf-8':
                            _mrPath = mrPath + '~'
                            convert_codec(mrPath, _mrPath, codec, 'utf-8')
                            mrPath = _mrPath

                        if is_rtf_file(mrPath):
                            _mrPath = mrPath + '.rtf2txt'
                            convert_rtf_to_ascii(mrPath, _mrPath)
                            mrPath = _mrPath

                        file_subtype = 'O'

                        is_valid, message = self.__nefT.validate_file(mrPath, file_subtype)

                        if not is_valid or not self.__has_star_chem_shift:
                            _is_valid, _ = self.__nefT.validate_file(mrPath, 'S')
                            if _is_valid:
                                has_cs_str = True

                        self.__original_error_message.append(message)

                        _file_type = message['file_type']  # nef/nmr-star/unknown

                        if is_valid:

                            if _file_type != file_type:

                                err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                                    f"but recognized as {self.readable_file_type[_file_type]} file."
                                # DAOTHER-5673
                                err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                                if len(message['error']) > 0:
                                    for err_message in message['error']:
                                        if 'No such file or directory' not in err_message:
                                            err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                                self.report.error.appendDescription('content_mismatch',
                                                                    {'file_name': file_name, 'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            else:

                                # NEFTranslator.validate_file() generates this object internally, but not re-used.
                                _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                self.__has_legacy_sf_issue = False

                                if star_data_type == 'Saveframe':
                                    self.__has_legacy_sf_issue = True
                                    self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message)
                                    _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):

                                    if len(self.__star_data_type) == self.__file_path_list_len:
                                        del self.__star_data_type[-1]
                                        del self.__star_data[-1]

                                    self.__star_data_type.append(star_data_type)
                                    self.__star_data.append(star_data)

                                    self.__rescueFormerNef(insert_index)
                                    self.__rescueImmatureStr(insert_index)

                                if _is_done:
                                    self.__detectContentSubType__(insert_index, input_source, dir_path)
                                    input_source_dic = input_source.get()
                                    if 'content_subtype' in input_source_dic:
                                        content_subtype = input_source_dic['content_subtype']
                                        if any(mr_content_subtype for mr_content_subtype in self.mr_content_subtypes if mr_content_subtype in content_subtype):
                                            has_mr_str = True
                                            mr_part_paths.append({'nmr-star': mrPath})

                        elif not self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message):
                            pass

                        if _mrPath is not None:
                            try:
                                os.remove(_mrPath)
                            except OSError:
                                pass

                elif has_cif_format and not has_mr_header:

                    remediated = True

                    mrPath = os.path.splitext(src_file)[0] + '-ignored.cif'

                    if not os.path.exists(mrPath):
                        mrPath = os.path.splitext(src_file)[0] + '-trimmed.cif'

                        header = True
                        in_header = False
                        pdb_record = False
                        has_sharp = False

                        i = 0

                        with open(src_file, 'r') as ifh, \
                                open(dst_file, 'w') as ofh, \
                                open(mrPath, 'w') as ofh2:
                            for line in ifh:
                                i += 1

                                # skip MR header
                                if header:
                                    if line.startswith('*'):
                                        continue
                                    header = False

                                if line.startswith('*') and startsWithPdbRecord(line[1:]):
                                    in_header = True
                                    continue

                                if in_header and line.startswith('*END'):
                                    in_header = False
                                    continue

                                if first_str_line_num <= i and not has_sharp:
                                    if i <= last_str_line_num:
                                        ofh2.write(line)
                                        continue
                                    ofh2.write(line)
                                    if line.startswith('#'):
                                        has_sharp = True
                                    continue

                                # skip legacy PDB
                                if has_pdb_format:
                                    if startsWithPdbRecord(line):
                                        pdb_record = True
                                        continue
                                    if pdb_record:
                                        pdb_record = False
                                        if line.startswith('END'):
                                            continue

                                # skip MR footer
                                if 'Submitted Coord H atom name' in line:
                                    break

                                ofh.write(line)

                        _mrPath = os.path.splitext(mrPath)[0] + '.cif2str'

                        if not self.__c2S.convert(mrPath, _mrPath):
                            _mrPath = mrPath

                        mrPath = _mrPath

                        mr_file_path_list = 'restraint_file_path_list'

                        if mr_file_path_list not in self.__inputParamDict:
                            self.__inputParamDict[mr_file_path_list] = [mrPath]
                        else:
                            self.__inputParamDict[mr_file_path_list].append(mrPath)

                        insert_index = self.__file_path_list_len

                        if insert_index > len(self.__star_data):
                            self.__star_data.append(None)
                            self.__star_data_type.append(None)

                        self.report.insertInputSource(insert_index)

                        self.__file_path_list_len += 1

                        input_source = self.report.input_sources[insert_index]

                        file_type = 'nmr-star'
                        file_name = os.path.basename(mrPath)

                        input_source.setItemValue('file_name', file_name)
                        input_source.setItemValue('file_type', file_type)
                        input_source.setItemValue('content_type', 'nmr-restraints')

                        codec = detect_bom(mrPath, 'utf-8')

                        _mrPath = None

                        if codec != 'utf-8':
                            _mrPath = mrPath + '~'
                            convert_codec(mrPath, _mrPath, codec, 'utf-8')
                            mrPath = _mrPath

                        if is_rtf_file(mrPath):
                            _mrPath = mrPath + '.rtf2txt'
                            convert_rtf_to_ascii(mrPath, _mrPath)
                            mrPath = _mrPath

                        file_subtype = 'O'

                        is_valid, message = self.__nefT.validate_file(mrPath, file_subtype)

                        if not is_valid or not self.__has_star_chem_shift:
                            _is_valid, _ = self.__nefT.validate_file(mrPath, 'S')
                            if _is_valid:
                                has_cs_str = True

                        self.__original_error_message.append(message)

                        _file_type = message['file_type']  # nef/nmr-star/unknown

                        if is_valid:

                            if _file_type != file_type:

                                err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                                    f"but recognized as {self.readable_file_type[_file_type]} file."
                                # DAOTHER-5673
                                err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                                if len(message['error']) > 0:
                                    for err_message in message['error']:
                                        if 'No such file or directory' not in err_message:
                                            err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                                self.report.error.appendDescription('content_mismatch',
                                                                    {'file_name': file_name, 'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            else:

                                # NEFTranslator.validate_file() generates this object internally, but not re-used.
                                _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                self.__has_legacy_sf_issue = False

                                if star_data_type == 'Saveframe':
                                    self.__has_legacy_sf_issue = True
                                    self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message)
                                    _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):

                                    if len(self.__star_data_type) == self.__file_path_list_len:
                                        del self.__star_data_type[-1]
                                        del self.__star_data[-1]

                                    self.__star_data_type.append(star_data_type)
                                    self.__star_data.append(star_data)

                                    self.__rescueFormerNef(insert_index)
                                    self.__rescueImmatureStr(insert_index)

                                if _is_done:
                                    self.__detectContentSubType__(insert_index, input_source, dir_path)
                                    input_source_dic = input_source.get()
                                    if 'content_subtype' in input_source_dic:
                                        content_subtype = input_source_dic['content_subtype']
                                        if any(mr_content_subtype for mr_content_subtype in self.mr_content_subtypes if mr_content_subtype in content_subtype):
                                            has_mr_str = True
                                            mr_part_paths.append({'nmr-star': mrPath})

                        elif not self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message):
                            pass

                        if _mrPath is not None:
                            try:
                                os.remove(_mrPath)
                            except OSError:
                                pass

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {str(e)}\n")

                return False

            has_content = False
            with open(dst_file, 'r') as ifh:
                for line in ifh:
                    if line.isspace() or comment_pattern.match(line):
                        continue
                    has_content = True
                    break

            if not has_content and not has_mr_str:
                with open(os.path.join(dir_path, '.entry_without_mr'), 'w') as ofh:
                    ofh.write('')

                remediated = False

            if os.path.exists(cor_dst_file):  # in case manually corrected MR file exists
                dst_file = cor_dst_file

                with open(dst_file, 'r') as ifh:
                    for line in ifh:
                        if line.isspace() or comment_pattern.match(line):
                            continue
                        has_content = True
                        break

                remediated = True

            if not has_content:
                continue

            mr_core_path = dst_file

            # has no MR haeder
            if not has_mr_header:

                dst_name_prefix = os.path.splitext(os.path.basename(dst_file))[0]

                dst_file_list = [os.path.join(dir_path, div_name) for div_name in div_file_names if div_name.startswith(dst_name_prefix)]

                if (not file_name.endswith('str') or mr_file_path.endswith('-remediated.mr')) and len(dst_file_list) == 0:
                    dst_file_list.append(dst_file)

                for dst_file in dst_file_list:

                    if dst_file.endswith('-div_ext.mr'):

                        ign_dst_file = dst_file + '-ignored'

                        if os.path.exists(ign_dst_file):  # in case the MR file can be ignored
                            remediated = True
                            continue

                        ign_pk_file = dst_file + '-ignored-as-pea-any'

                        if os.path.exists(ign_pk_file):  # in case the MR file can be ignored as peak list file
                            _ar = ar.copy()

                            _ar['file_name'] = dst_file
                            _ar['file_type'] = 'nm-pea-any'
                            peak_file_list.append(_ar)

                            pk_list_paths.append({'nm-pea-any': dst_file})

                            remediated = True

                            continue

                        designated = False

                        for _file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-res-cya',
                                           'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-res-dyn',
                                           'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari',
                                           'nm-res-sax'):

                            sel_res_file = dst_file + f'-selected-as-res-{_file_type[-3:]}'

                            if os.path.exists(sel_res_file):
                                _ar = ar.copy()

                                _ar['file_name'] = dst_file
                                _ar['file_type'] = _file_type
                                split_file_list.append(_ar)

                                mr_part_paths.append({_file_type: dst_file})

                                designated = True

                                break

                        if designated:
                            continue

                        _ar = ar.copy()

                        _ar['file_name'] = dst_file
                        _ar['file_type'] = 'nm-res-oth'
                        split_file_list.append(_ar)

                        mr_part_paths.append({_ar['file_type']: dst_file})

                        continue

                    if dst_file.endswith('-div_dst.mr'):

                        ign_dst_file = dst_file + '-ignored'

                        if os.path.exists(ign_dst_file):  # in case the MR file can be ignored
                            remediated = True
                            continue

                        ign_pk_file = dst_file + '-ignored-as-pea-any'

                        if os.path.exists(ign_pk_file):  # in case the MR file can be ignored as peak list file
                            _ar = ar.copy()

                            _ar['file_name'] = dst_file
                            _ar['file_type'] = 'nm-pea-any'
                            peak_file_list.append(_ar)

                            pk_list_paths.append({'nm-pea-any': dst_file})

                            remediated = True

                            continue

                    _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(dst_file, file_name, 'nm-res-mr', [], True)

                    len_valid_types = len(valid_types)
                    len_possible_types = len(possible_types)

                    if len_valid_types == 0 and len_possible_types == 0:

                        ins_msg = ''
                        if has_pdb_format and has_cs_str:
                            ins_msg = 'unexpectedly contains PDB coordinates and assigned chemical shifts, but '
                        elif has_pdb_format:
                            ins_msg = 'unexpectedly contains PDB coordinates, but '
                        elif has_cs_str:
                            ins_msg = 'unexpectedly contains assigned chemical shifts, but '

                        _file_name = os.path.basename(dst_file)
                        if file_name != _file_name:
                            _file_name = f'({_file_name}) '
                        else:
                            _file_name = ''

                        err = f"The NMR restraint file {file_name!r} {_file_name}{ins_msg}does not match with any known restraint format. "\
                            "@todo: It needs to be reviewed or marked as entry without NMR restraints."

                        self.report.error.appendDescription('internal_error',
                                                            {'file_name': file_name, 'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                        aborted = True

                        continue

                    if len_possible_types == 0:
                        # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types}.\n")

                        _ar = ar.copy()

                        if len_valid_types == 1:
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = valid_types[0]
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: dst_file})

                        elif len_valid_types == 2 and 'nm-res-cns' in valid_types and 'nm-res-xpl' in valid_types:
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = 'nm-res-xpl'
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: dst_file})

                        elif len_valid_types == 2 and 'nm-res-cya' in valid_types:
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = next(valid_type for valid_type in valid_types if valid_type != 'nm-res-cya')
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: dst_file})

                        elif len_valid_types == 3\
                                and (set(valid_types) == {'nm-res-cya', 'nm-res-cns', 'nm-res-xpl'}
                                     or set(valid_types) == {'nm-res-isd', 'nm-res-cns', 'nm-res-xpl'}):
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = 'nm-res-xpl'
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: dst_file})

                        elif len_valid_types == 3\
                                and set(valid_types) == {'nm-res-cha', 'nm-res-cns', 'nm-res-xpl'}:
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = 'nm-res-cha'
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: dst_file})

                        else:
                            _ar['file_name'] = dst_file
                            _ar['file_type'] = valid_types[0]
                            split_file_list.append(_ar)

                            err = f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types}. "\
                                "@todo: It needs to be split properly."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            aborted = True

                    elif len_valid_types == 0:
                        # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) can be {possible_types}.\n")

                        _ar = ar.copy()

                        _ar['file_name'] = dst_file
                        _ar['file_type'] = possible_types[0]
                        split_file_list.append(_ar)

                        err = f"The NMR restraint file {file_name!r} (MR format) can be {possible_types}. "\
                            "@todo: It needs to be reviewed."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                        aborted = True

                    else:
                        # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types} and can be {possible_types} as well.\n")

                        _ar['file_name'] = dst_file
                        _ar['file_type'] = valid_types[0]
                        split_file_list.append(_ar)

                        err = f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types} and can be {possible_types} as well. "\
                            "@todo: It needs to be reviewed."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                        aborted = True

            # has MR header
            else:

                original_file_path_list = []

                ofh = None
                j = 0

                with open(dst_file, 'r') as ifh:
                    for line in ifh:

                        if mr_file_header_pattern.match(line):
                            g = mr_file_header_pattern.search(line).groups()

                            if ofh is not None:
                                if len(g[0]) > 0:
                                    j += 1
                                    ofh.write(g[0] + '\n')
                                ofh.close()
                                if j == 0:
                                    os.remove(original_file_path_list.pop())

                            j = 0
                            _dst_file = os.path.join(dir_path, g[2])
                            original_file_path_list.append(_dst_file)
                            ofh = open(_dst_file, 'w')  # pylint: disable=consider-using-with

                        elif not line.isspace() and not comment_pattern.match(line):
                            j += 1
                            if ofh is None:
                                _dst_file = os.path.join(dir_path, src_basename + '-noname.mr')
                                original_file_path_list.append(_dst_file)
                                ofh = open(_dst_file, 'w')  # pylint: disable=consider-using-with
                            ofh.write(line)

                        elif ofh is not None:
                            ofh.write(line)

                if ofh is not None:
                    ofh.close()
                    if j == 0:
                        os.remove(original_file_path_list.pop())

                distict = True
                if len(original_file_path_list) == 0:
                    distict = False
                    original_file_path_list.append(dst_file)

                for dst_file in original_file_path_list:
                    ign_dst_file = dst_file + '-ignored'

                    if os.path.exists(ign_dst_file):  # in case the MR file can be ignored
                        remediated = True
                        continue

                    split_ext = os.path.splitext(dst_file)

                    if len(split_ext) == 2:
                        if len(split_ext[1]) > 0:
                            file_ext = split_ext[1][1:].lower()
                        else:
                            file_ext = os.path.basename(split_ext[0]).lower()
                    else:
                        file_ext = os.path.basename(split_ext[0]).lower()

                    if file_ext in ('x', 'rc', 'crd', 'rst', 'inp', 'inpcrd', 'restrt')\
                       or 'rc' in file_ext or 'crd' in file_ext or 'rst' in file_ext or 'inp' in file_ext:  # AMBER coordinate file extensions
                        is_crd = False
                        with open(dst_file, 'r') as ifh:
                            for pos, line in enumerate(ifh, start=1):
                                if pos == 1:
                                    if line.isdigit():
                                        break
                                elif pos == 2:
                                    try:
                                        int(line.lstrip().split()[0])
                                    except (ValueError, IndexError):
                                        break
                                elif pos == 3:
                                    if line.count('.') == 6:
                                        is_crd = True
                                    break

                        if is_crd:
                            shutil.copyfile(dst_file, ign_dst_file)  # ignore AMBER input coordinate file for the next time
                            remediated = True
                            continue

                    if file_ext in ('frc', 'known') or 'frc' in file_ext:
                        is_frc = False
                        with open(dst_file, 'r') as ifh:
                            for pos, line in enumerate(ifh, start=1):
                                if pos == 1:
                                    if not line.startswith('FRCMOD'):
                                        break
                                elif pos == 2:
                                    if line.startswith('MASS'):
                                        is_frc = True
                                    break

                        if is_frc:
                            shutil.copyfile(dst_file, ign_dst_file)  # ignore AMBER frcmod file for the next time
                            remediated = True
                            continue

                    if file_ext == 'seq':
                        is_seq = False
                        _len_seq = None
                        with open(dst_file, 'r') as ifh:
                            for line in ifh:
                                if line.isspace() or comment_pattern.match(line):
                                    continue
                                seq = line.upper().split()
                                len_seq = len(seq)
                                if len_seq > 2:
                                    is_seq = False
                                    break
                                if _len_seq is None:
                                    _len_seq = len_seq
                                elif len_seq != _len_seq:
                                    is_seq = False
                                    break
                                if len_seq == 2:
                                    if (translateToStdResName(seq[0], ccU=self.__ccU) in monDict3 and seq[1].isdigit())\
                                       or (translateToStdResName(seq[1], ccU=self.__ccU) in monDict3 and seq[0].isdigit()):
                                        is_seq = True
                                    else:
                                        is_seq = False
                                        break
                                elif len_seq == 1:
                                    if seq[0] in monDict3:
                                        is_seq = True
                                    else:
                                        is_seq = False
                                        break

                        if is_seq:
                            shutil.copyfile(dst_file, ign_dst_file)  # ignore sequence file for the next time
                            remediated = True
                            continue

                    if file_ext == 'cor':
                        is_cor = False
                        with open(dst_file, 'r') as ifh:
                            for pos, line in enumerate(ifh, start=1):
                                if pos == 1:
                                    if 'Structures from CYANA' not in line:
                                        break
                                elif pos == 2:
                                    if 'CYANA' not in line:
                                        break
                                elif pos == 3:
                                    if line.count('Number') < 3:
                                        break
                                elif pos == 4:
                                    if line.count('.') >= 3:
                                        is_cor = True
                                    break

                        if is_cor:
                            shutil.copyfile(dst_file, ign_dst_file)  # ignore CYANA coordinate file for the next time
                            remediated = True
                            continue

                    ign_pk_file = dst_file + '-ignored-as-pea-any'

                    if os.path.exists(ign_pk_file):  # in case the MR file can be ignored as peak list file
                        _ar = ar.copy()

                        _ar['file_name'] = dst_file
                        _ar['file_type'] = 'nm-pea-any'
                        peak_file_list.append(_ar)

                        pk_list_paths.append({'nm-pea-any': dst_file,
                                              'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                        remediated = True

                        continue

                    ign_ext_file = dst_file + '-ignored-as-res-oth'

                    if os.path.exists(ign_ext_file):  # in case the MR files can not be parsed
                        _ar = ar.copy()

                        _ar['file_name'] = dst_file
                        _ar['file_type'] = 'nm-res-oth'
                        split_file_list.append(_ar)

                        mr_part_paths.append({_ar['file_type']: dst_file,
                                              'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                        continue

                    designated = False

                    for _file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-res-cya',
                                       'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-res-dyn',
                                       'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari',
                                       'nm-res-sax'):

                        sel_res_file = dst_file + f'-selected-as-res-{_file_type[-3:]}'

                        if os.path.exists(sel_res_file):
                            _ar = ar.copy()

                            _ar['file_name'] = dst_file
                            _ar['file_type'] = _file_type
                            split_file_list.append(_ar)

                            mr_part_paths.append({_file_type: dst_file,
                                                  'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                            designated = True

                            break

                    if designated:
                        continue

                    cor_dst_file = dst_file + '-corrected'

                    if os.path.exists(cor_dst_file):  # in case manually corrected MR file exists
                        dst_file = cor_dst_file

                        remediated = True

                    has_spectral_peak = False

                    with open(dst_file, 'r') as ifh:
                        has_header = False
                        for line in ifh:
                            if line.isspace() or comment_pattern.match(line):
                                if line.startswith('#INAME'):
                                    has_header = True
                                continue
                            if is_peak_list(line, has_header):
                                has_spectral_peak = True

                                shutil.copyfile(dst_file, ign_pk_file)

                                _ar = ar.copy()

                                _ar['file_name'] = dst_file
                                _ar['file_type'] = 'nm-pea-any'
                                peak_file_list.append(_ar)

                                pk_list_paths.append({'nm-pea-any': dst_file,
                                                      'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                                remediated = True

                            break

                    if has_spectral_peak:
                        continue

                    if has_str_format or has_cif_format:

                        dst_file_type = get_type_of_star_file(dst_file)

                        if dst_file_type == 'str':

                            mrPath = dst_file

                            mr_file_path_list = 'restraint_file_path_list'

                            if mr_file_path_list not in self.__inputParamDict:
                                self.__inputParamDict[mr_file_path_list] = [mrPath]
                            else:
                                self.__inputParamDict[mr_file_path_list].append(mrPath)

                            insert_index = self.__file_path_list_len

                            if insert_index > len(self.__star_data):
                                self.__star_data.append(None)
                                self.__star_data_type.append(None)

                            self.report.insertInputSource(insert_index)

                            self.__file_path_list_len += 1

                            input_source = self.report.input_sources[insert_index]

                            file_type = 'nmr-star'
                            file_name = os.path.basename(mrPath)

                            input_source.setItemValue('file_name', file_name)
                            input_source.setItemValue('file_type', file_type)
                            input_source.setItemValue('content_type', 'nmr-restraints')

                            codec = detect_bom(mrPath, 'utf-8')

                            _mrPath = None

                            if codec != 'utf-8':
                                _mrPath = mrPath + '~'
                                convert_codec(mrPath, _mrPath, codec, 'utf-8')
                                mrPath = _mrPath

                            if is_rtf_file(mrPath):
                                _mrPath = mrPath + '.rtf2txt'
                                convert_rtf_to_ascii(mrPath, _mrPath)
                                mrPath = _mrPath

                            file_subtype = 'O'

                            is_valid, message = self.__nefT.validate_file(mrPath, file_subtype)

                            if not is_valid or not self.__has_star_chem_shift:
                                _is_valid, _ = self.__nefT.validate_file(mrPath, 'S')
                                if _is_valid:
                                    has_cs_str = True

                            self.__original_error_message.append(message)

                            _file_type = message['file_type']  # nef/nmr-star/unknown

                            if is_valid:

                                if _file_type != file_type:

                                    err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                                        f"but recognized as {self.readable_file_type[_file_type]} file."
                                    # DAOTHER-5673
                                    err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                                    if len(message['error']) > 0:
                                        for err_message in message['error']:
                                            if 'No such file or directory' not in err_message:
                                                err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                                    self.report.error.appendDescription('content_mismatch',
                                                                        {'file_name': file_name, 'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                                else:

                                    # NEFTranslator.validate_file() generates this object internally, but not re-used.
                                    _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                    self.__has_legacy_sf_issue = False

                                    if star_data_type == 'Saveframe':
                                        self.__has_legacy_sf_issue = True
                                        self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message)
                                        _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                    if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):

                                        if len(self.__star_data_type) == self.__file_path_list_len:
                                            del self.__star_data_type[-1]
                                            del self.__star_data[-1]

                                        self.__star_data_type.append(star_data_type)
                                        self.__star_data.append(star_data)

                                        self.__rescueFormerNef(insert_index)
                                        self.__rescueImmatureStr(insert_index)

                                    if _is_done:
                                        self.__detectContentSubType__(insert_index, input_source, dir_path)
                                        input_source_dic = input_source.get()
                                        if 'content_subtype' in input_source_dic:
                                            content_subtype = input_source_dic['content_subtype']
                                            if any(mr_content_subtype for mr_content_subtype in self.mr_content_subtypes if mr_content_subtype in content_subtype):
                                                mr_part_paths.append({'nmr-star': mrPath,
                                                                      'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                            elif not self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message):
                                pass

                            if _mrPath is not None:
                                try:
                                    os.remove(_mrPath)
                                except OSError:
                                    pass

                            continue

                        if dst_file_type == 'cif':

                            mrPath = dst_file

                            _mrPath = os.path.splitext(mrPath)[0] + '.cif2str'

                            if not self.__c2S.convert(mrPath, _mrPath):
                                _mrPath = mrPath

                            mrPath = _mrPath

                            mr_file_path_list = 'restraint_file_path_list'

                            if mr_file_path_list not in self.__inputParamDict:
                                self.__inputParamDict[mr_file_path_list] = [mrPath]
                            else:
                                self.__inputParamDict[mr_file_path_list].append(mrPath)

                            insert_index = self.__file_path_list_len

                            if insert_index > len(self.__star_data):
                                self.__star_data.append(None)
                                self.__star_data_type.append(None)

                            self.report.insertInputSource(insert_index)

                            self.__file_path_list_len += 1

                            input_source = self.report.input_sources[insert_index]

                            file_type = 'nmr-star'
                            file_name = os.path.basename(mrPath)

                            input_source.setItemValue('file_name', file_name)
                            input_source.setItemValue('file_type', file_type)
                            input_source.setItemValue('content_type', 'nmr-restraints')

                            codec = detect_bom(mrPath, 'utf-8')

                            _mrPath = None

                            if codec != 'utf-8':
                                _mrPath = mrPath + '~'
                                convert_codec(mrPath, _mrPath, codec, 'utf-8')
                                mrPath = _mrPath

                            if is_rtf_file(mrPath):
                                _mrPath = mrPath + '.rtf2txt'
                                convert_rtf_to_ascii(mrPath, _mrPath)
                                mrPath = _mrPath

                            file_subtype = 'O'

                            is_valid, message = self.__nefT.validate_file(mrPath, file_subtype)

                            if not is_valid or not self.__has_star_chem_shift:
                                _is_valid, _ = self.__nefT.validate_file(mrPath, 'S')
                                if _is_valid:
                                    has_cs_str = True

                            self.__original_error_message.append(message)

                            _file_type = message['file_type']  # nef/nmr-star/unknown

                            if is_valid:

                                if _file_type != file_type:

                                    err = f"{file_name!r} was selected as {self.readable_file_type[file_type]} file, "\
                                        f"but recognized as {self.readable_file_type[_file_type]} file."
                                    # DAOTHER-5673
                                    err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef' else " Please re-upload the file."

                                    if len(message['error']) > 0:
                                        for err_message in message['error']:
                                            if 'No such file or directory' not in err_message:
                                                err += ' ' + re.sub('not in list', 'unknown item.', err_message)

                                    self.report.error.appendDescription('content_mismatch',
                                                                        {'file_name': file_name, 'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                                else:

                                    # NEFTranslator.validate_file() generates this object internally, but not re-used.
                                    _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                    self.__has_legacy_sf_issue = False

                                    if star_data_type == 'Saveframe':
                                        self.__has_legacy_sf_issue = True
                                        self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message)
                                        _is_done, star_data_type, star_data = self.__nefT.read_input_file(mrPath)

                                    if not (self.__has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):

                                        if len(self.__star_data_type) == self.__file_path_list_len:
                                            del self.__star_data_type[-1]
                                            del self.__star_data[-1]

                                        self.__star_data_type.append(star_data_type)
                                        self.__star_data.append(star_data)

                                        self.__rescueFormerNef(insert_index)
                                        self.__rescueImmatureStr(insert_index)

                                    if _is_done:
                                        self.__detectContentSubType()
                                        input_source_dic = input_source.get()
                                        if 'content_subtype' in input_source_dic:
                                            content_subtype = input_source_dic['content_subtype']
                                            if any(mr_content_subtype for mr_content_subtype in self.mr_content_subtypes if mr_content_subtype in content_subtype):
                                                mr_part_paths.append({'nmr-star': mrPath,
                                                                      'original_file_name': None if dst_file.endswith('-noname.mr') else os.path.basename(dst_file)})

                            elif not self.__fixFormatIssueOfInputSource(insert_index, file_name, file_type, mrPath, file_subtype, message):
                                pass

                            if _mrPath is not None:
                                try:
                                    os.remove(_mrPath)
                                except OSError:
                                    pass

                            continue

                    file_name = os.path.basename(dst_file)

                    dst_file_list = [os.path.join(dir_path, div_name) for div_name in div_file_names if div_name.startswith(file_name)]

                    if len(dst_file_list) == 0:
                        dst_file_list.append(dst_file)

                    for _dst_file in dst_file_list:

                        ign_pk_file = _dst_file + '-ignored-as-pea-any'

                        if os.path.exists(ign_pk_file):  # in case the MR file can be ignored as peak list file
                            _ar = ar.copy()

                            _ar['file_name'] = _dst_file
                            _ar['file_type'] = 'nm-pea-any'
                            peak_file_list.append(_ar)

                            pk_list_paths.append({'nm-pea-any': _dst_file,
                                                  'original_file_name': None if file_name.endswith('-noname.mr') else os.path.basename(file_name)})

                            remediated = True

                            continue

                        designated = False

                        for _file_type in ('nm-res-xpl', 'nm-res-cns', 'nm-res-amb', 'nm-res-cya',
                                           'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-res-dyn',
                                           'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari',
                                           'nm-res-sax'):

                            sel_res_file = _dst_file + f'-selected-as-res-{_file_type[-3:]}'

                            if os.path.exists(sel_res_file):
                                _ar = ar.copy()

                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = _file_type
                                split_file_list.append(_ar)

                                mr_part_paths.append({_file_type: _dst_file,
                                                      'original_file_name': None if file_name.endswith('-noname.mr') else os.path.basename(file_name)})

                                designated = True

                                break

                        if designated:
                            continue

                        if _dst_file.endswith('-div_ext.mr'):
                            _ar = ar.copy()

                            _ar['file_name'] = _dst_file
                            _ar['file_type'] = 'nm-res-oth'
                            split_file_list.append(_ar)

                            mr_part_paths.append({_ar['file_type']: _dst_file,
                                                  'original_file_name': None if file_name.endswith('-noname.mr') else os.path.basename(file_name)})

                            continue

                        _, _, valid_types, possible_types = self.__detectOtherPossibleFormatAsErrorOfLegacyMr(_dst_file, file_name, 'nm-res-mr', [], True)

                        len_valid_types = len(valid_types)
                        len_possible_types = len(possible_types)

                        if len_valid_types == 0 and len_possible_types == 0:

                            ins_msg = ''
                            if not distict or len(original_file_path_list) == 1:
                                if has_pdb_format and has_cs_str:
                                    ins_msg = 'unexpectedly contains PDB coordinates and assigned chemical shifts, but '
                                elif has_pdb_format:
                                    ins_msg = 'unexpectedly contains PDB coordinates, but '
                                elif has_cs_str:
                                    ins_msg = 'unexpectedly contains assigned chemical shifts, but '

                            _file_name = os.path.basename(dst_file)
                            if file_name != _file_name:
                                _file_name = f'({_file_name}) '
                            else:
                                _file_name = ''

                            err = f"The NMR restraint file {file_name!r} {_file_name}{ins_msg}does not match with any known restraint format. "\
                                "@todo: It needs to be reviewed or marked as entry without NMR restraints."

                            self.report.error.appendDescription('internal_error',
                                                                {'file_name': file_name, 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            aborted = True

                            continue

                        if len_possible_types == 0:
                            # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types}.\n")

                            _ar = ar.copy()

                            if len_valid_types == 1:
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = valid_types[0]
                                if distict:
                                    _ar['original_file_name'] = file_name
                                split_file_list.append(_ar)

                                mr_part_paths.append({_ar['file_type']: _dst_file,
                                                      'original_file_name': None if _dst_file.endswith('-noname.mr') else os.path.basename(_dst_file)})

                            elif len_valid_types == 2 and 'nm-res-cns' in valid_types and 'nm-res-xpl' in valid_types:
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = 'nm-res-xpl'
                                if distict:
                                    _ar['original_file_name'] = file_name
                                split_file_list.append(_ar)

                                mr_part_paths.append({_ar['file_type']: _dst_file,
                                                      'original_file_name': None if _dst_file.endswith('-noname.mr') else os.path.basename(_dst_file)})

                            elif len_valid_types == 2 and 'nm-res-cya' in valid_types:
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = next(valid_type for valid_type in valid_types if valid_type != 'nm-res-cya')
                                if distict:
                                    _ar['original_file_name'] = file_name
                                split_file_list.append(_ar)

                                mr_part_paths.append({_ar['file_type']: _dst_file,
                                                      'original_file_name': None if _dst_file.endswith('-noname.mr') else os.path.basename(_dst_file)})

                            elif len_valid_types == 3\
                                    and (set(valid_types) == {'nm-res-cya', 'nm-res-cns', 'nm-res-xpl'}
                                         or set(valid_types) == {'nm-res-isd', 'nm-res-cns', 'nm-res-xpl'}):
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = 'nm-res-xpl'
                                if distict:
                                    _ar['original_file_name'] = file_name
                                split_file_list.append(_ar)

                                mr_part_paths.append({_ar['file_type']: _dst_file,
                                                      'original_file_name': None if _dst_file.endswith('-noname.mr') else os.path.basename(_dst_file)})

                            elif len_valid_types == 3\
                                    and set(valid_types) == {'nm-res-cha', 'nm-res-cns', 'nm-res-xpl'}:
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = 'nm-res-cha'
                                if distict:
                                    _ar['original_file_name'] = file_name
                                split_file_list.append(_ar)

                                mr_part_paths.append({_ar['file_type']: _dst_file,
                                                      'original_file_name': None if _dst_file.endswith('-noname.mr') else os.path.basename(_dst_file)})

                            else:
                                _ar['file_name'] = _dst_file
                                _ar['file_type'] = valid_types[0]
                                if distict:
                                    _ar['original_file_name'] = file_name

                                err = f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types}. "\
                                    "@todo: It needs to be split properly."

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                                aborted = True

                        elif len_valid_types == 0:
                            # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) can be {possible_types}.\n")

                            _ar['file_name'] = _dst_file
                            _ar['file_type'] = possible_types[0]
                            if distict:
                                _ar['original_file_name'] = file_name

                            err = f"The NMR restraint file {file_name!r} (MR format) can be {possible_types}. "\
                                "@todo: It needs to be reviewed."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            aborted = True

                        else:
                            # self.__lfh.write(f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types} and can be {possible_types} as well.\n")

                            _ar['file_name'] = _dst_file
                            _ar['file_type'] = valid_types[0]
                            if distict:
                                _ar['original_file_name'] = file_name

                            err = f"The NMR restraint file {file_name!r} (MR format) is identified as {valid_types} and can be {possible_types} as well. "\
                                "@todo: It needs to be reviewed."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

                            aborted = True

        len_peak_file_list = len(peak_file_list)
        has_spectral_peak = len_peak_file_list > 0

        if len(split_file_list) > 0:
            self.__inputParamDict[ar_file_path_list].extend(split_file_list)

            for _ar in split_file_list:

                self.report.appendInputSource()

                input_source = self.report.input_sources[-1]

                input_source.setItemValue('file_name', os.path.basename(_ar['file_name']))
                input_source.setItemValue('file_type', _ar['file_type'])
                input_source.setItemValue('content_type', 'nmr-restraints')
                if 'original_file_name' in _ar:
                    input_source.setItemValue('original_file_name', os.path.basename(_ar['original_file_name']))

        else:

            has_restraint = False

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_name = input_source_dic['file_name']
                file_type = input_source_dic['file_type']
                content_type = input_source_dic['content_type']

                if content_type != 'nmr-restraints':
                    continue

                content_subtype = input_source_dic['content_subtype']

                if content_subtype is None:
                    continue

                if 'dist_restraint' in content_subtype or 'dihed_restraint' in content_subtype or 'rdc_restraint' in content_subtype:
                    has_restraint = True

                if 'spectral_peak' in content_subtype or 'spectral_peak_alt' in content_subtype:
                    has_spectral_peak = True

            if not has_restraint:

                if mr_file_name == '.':

                    if self.__dstPath is not None:

                        dir_path = os.path.dirname(self.__dstPath)

                        rem_dir = os.path.join(dir_path, 'remediation')

                        if os.path.isdir(rem_dir):

                            try:
                                os.rmdir(rem_dir)
                            except OSError:
                                pass

                else:

                    touch_file = os.path.join(dir_path, '.entry_without_mr')
                    if not os.path.exists(touch_file):
                        with open(touch_file, 'w') as ofh:
                            ofh.write('')

                    hint = ' or is not recognized properly'

                    if len_peak_file_list > 0:
                        hint = f', except for {len_peak_file_list} peak list file(s)'

                    err = f"NMR restraint file contains no restraints{hint}. "\
                        "Please re-upload the NMR restraint file."

                    self.__suspended_errors_for_lazy_eval.append({'content_mismatch':
                                                                 {'file_name': mr_file_name, 'description': err}})

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPublicMrFileIntoLegacyMr() ++ Error  - {err}\n")

        if has_spectral_peak:

            if len_peak_file_list > 0:

                self.__inputParamDict[ar_file_path_list].extend(peak_file_list)

                for _ar in peak_file_list:

                    self.report.appendInputSource()

                    input_source = self.report.input_sources[-1]

                    input_source.setItemValue('file_name', os.path.basename(_ar['file_name']))
                    input_source.setItemValue('file_type', _ar['file_type'])
                    input_source.setItemValue('content_type', 'nmr-peaks')
                    if 'original_file_name' in _ar:
                        input_source.setItemValue('original_file_name', os.path.basename(_ar['original_file_name']))

            touch_file = os.path.join(dir_path, '.entry_with_pk')
            if not os.path.exists(touch_file):
                with open(touch_file, 'w') as ofh:
                    ofh.write('')

        if not aborted and remediated and mr_file_path is not None:
            with open(mr_file_path, 'w') as ofh:

                header_file = next(mr_part_path['header'] for mr_part_path in mr_part_paths if 'header' in mr_part_path)
                with open(header_file, 'r') as ifh:
                    for line in ifh:
                        ofh.write(line)

                file_idx = 1
                for mr_part_path in mr_part_paths:
                    if 'header' in mr_part_path or 'footer' in mr_part_path:
                        continue

                    for file_type in ['nmr-star',
                                      'nm-res-amb', 'nm-res-cns', 'nm-res-cya', 'nm-res-xpl', 'nm-res-oth',
                                      'nm-aux-amb', 'nm-res-ros', 'nm-res-bio', 'nm-res-gro', 'nm-aux-gro',
                                      'nm-res-dyn', 'nm-res-syb', 'nm-res-isd', 'nm-res-cha', 'nm-res-ari',
                                      'nm-res-sax']:
                        if file_type in mr_part_path:
                            file_path = mr_part_path[file_type]
                            if 'original_file_name' in mr_part_path and mr_part_path['original_file_name'] is not None:
                                original_file_name = mr_part_path['original_file_name']
                            else:
                                original_file_name = f'{os.path.basename(src_basename)}-division_P{file_idx}.mr'

                            ofh.write(f'# Restraints file {file_idx}: {original_file_name}\n')
                            ofh.write(f'# Restraint file format: {getRestraintFormatName(file_type).split()[0]}\n')

                            with open(file_path, 'r') as ifh:
                                for line in ifh:
                                    ofh.write(line)

                            break

                    file_idx += 1

                if file_idx == 1 and len(split_file_list) > 0:

                    ofh.write(f'# Restraints file {file_idx}: {os.path.basename(src_basename)}.mr\n')
                    file_type = split_file_list[0]['file_type']
                    ofh.write(f'# Restraint file format: {getRestraintFormatName(file_type).split()[0]}\n')

                    with open(mr_core_path, 'r') as ifh:
                        for line in ifh:
                            ofh.write(line)

                footer_file = next(mr_part_path['footer'] for mr_part_path in mr_part_paths if 'footer' in mr_part_path)
                with open(footer_file, 'r') as ifh:
                    for line in ifh:
                        ofh.write(line)

            try:

                if os.path.exists(mr_file_link):
                    os.remove(mr_file_link)

                os.symlink(mr_file_path, mr_file_link)

                if len(pk_list_paths) > 0:

                    pk_dir = os.path.join(dir_path, 'nmr_peak_lists')

                    try:

                        if not os.path.isdir(pk_dir):
                            os.makedirs(pk_dir)

                    except OSError:
                        pass

                    for pk_list_path in pk_list_paths:

                        pk_file_path = pk_list_path['nm-pea-any']
                        if 'original_file_name' in pk_list_path and pk_list_path['original_file_name'] is not None:
                            original_file_name = pk_list_path['original_file_name']
                        else:
                            original_file_name = os.path.basename(file_path)

                        rem_pk_file_path = os.path.join(pk_dir, original_file_name)

                        if os.path.exists(rem_pk_file_path):
                            os.remove(rem_pk_file_path)

                        os.symlink(pk_file_path, rem_pk_file_path)

            except OSError:
                pass

        return not self.report.isError()

    def __getPolymerSequence(self, file_list_id, sf, content_subtype):
        """ Wrapper function to retrieve polymer sequence from loop of a specified saveframe and content subtype via NEFTranslator.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        check_identity = content_subtype not in self.mr_content_subtypes

        if file_type == 'nef':  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
            return self.__nefT.get_nef_seq(sf, lp_category=self.lp_categories[file_type][content_subtype],
                                           allow_empty=(content_subtype in ('chem_shift', 'spectral_peak')),
                                           allow_gap=(content_subtype not in ('poly_seq', 'entity')),
                                           check_identity=check_identity)

        if content_subtype == 'spectral_peak_alt':
            return self.__nefT.get_star_seq(sf, lp_category='_Assigned_peak_chem_shift',
                                            allow_empty=True,
                                            allow_gap=True,
                                            check_identity=check_identity)

        if not self.__bmrb_only or not self.__internal_mode:
            if self.__caC is None:
                self.__retrieveCoordAssemblyChecker()

        # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
        return self.__nefT.get_star_seq(sf, lp_category=self.lp_categories[file_type][content_subtype],
                                        allow_empty=(content_subtype in ('chem_shift', 'spectral_peak')),
                                        allow_gap=(content_subtype not in ('poly_seq', 'entity')),
                                        check_identity=check_identity,
                                        coord_assembly_checker=self.__caC)

    def __extractPolymerSequence(self):
        """ Extract reference polymer sequence.
        """

        is_done = True

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                is_done = False
                continue

            content_subtype = 'poly_seq'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                try:

                    poly_seq = self.__getPolymerSequence(fileListId, sf, content_subtype)[0]

                    input_source.setItemValue('polymer_sequence', poly_seq)

                    if file_type == 'nmr-star':

                        auth_poly_seq = self.__nefT.get_star_auth_seq(sf, lp_category)[0]

                        for ps in poly_seq:
                            chain_id, seq_ids, comp_ids = ps['chain_id'], ps['seq_id'], ps['comp_id']

                            for aps in auth_poly_seq:

                                if aps['chain_id'] != chain_id:
                                    continue

                                _seq_ids = aps['seq_id']

                                auth_asym_ids, auth_seq_ids, auth_comp_ids = aps['auth_asym_id'], aps['auth_seq_id'], aps['auth_comp_id']

                                auth_asym_id_set = sorted(set(auth_asym_ids))

                                for auth_asym_id in auth_asym_id_set:

                                    offsets = []
                                    total = 0

                                    for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                        if _auth_asym_id != auth_asym_id or auth_seq_id in emptyValue:
                                            continue

                                        try:

                                            _auth_seq_id = int(auth_seq_id)

                                            offsets.append(_auth_seq_id - _seq_id)
                                            total += 1

                                        except ValueError:

                                            if self.__check_auth_seq:
                                                warn = f"Auth_seq_ID {str(auth_seq_id)!r} "\
                                                    f"(Auth_asym_ID {auth_asym_id}, Auth_comp_ID {auth_comp_id}) should be an integer."

                                                self.report.warning.appendDescription('sequence_mismatch',
                                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                       'description': warn})
                                                self.report.setWarning()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ Warning  - {warn}\n")

                                    if total > 1:

                                        offset = collections.Counter(offsets).most_common()[0][0]

                                        for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                            if _auth_asym_id != auth_asym_id or auth_seq_id in emptyValue:
                                                continue

                                            try:

                                                _auth_seq_id = int(auth_seq_id)

                                            except ValueError:
                                                continue

                                            if _auth_seq_id - _seq_id != offset:

                                                if self.__check_auth_seq:
                                                    warn = f"Auth_seq_ID {str(auth_seq_id)!r} is inconsistent with {str(_seq_id + offset)!r} "\
                                                        f"(Auth_asym_ID {auth_asym_id}, Auth_comp_ID {auth_comp_id})."

                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                           'description': warn})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ Warning  - {warn}\n")

                                for seq_id, comp_id in zip(seq_ids, comp_ids):

                                    for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                        if _seq_id != seq_id:
                                            continue

                                        if comp_id == auth_comp_id:
                                            continue

                                        if self.__check_auth_seq:
                                            warn = f"Auth_comp_ID {auth_comp_id!r} (Auth_asym_ID {_auth_asym_id}, Auth_seq_ID {auth_seq_id}) is inconsistent with {comp_id} "\
                                                f"(Entity_assembly_ID {chain_id}, Seq_ID {seq_id})."

                                            self.report.warning.appendDescription('sequence_mismatch',
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                   'description': warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ Warning  - {warn}\n")

                                        break

                    continue

                except KeyError as e:

                    self.report.error.appendDescription('sequence_mismatch',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': str(e).strip("'")})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ KeyError  - {str(e)}\n")

                except LookupError:
                    # """
                    # self.report.error.appendDescription('missing_mandatory_item',
                    #                                     {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                    #                                      'description': str(e).strip("'")})
                    # self.report.setError()

                    # self.__lfh.write("+NmrDpUtility.__extractPolymerSequence() ++ LookupError  - "
                    #                  f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")
                    # """
                    pass
                except ValueError as e:

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': str(e).strip("'")})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ ValueError  - {str(e)}\n")

                except UserWarning as e:

                    errs = str(e).strip("'").split('\n')

                    for err in errs:

                        if len(err) == 0:
                            continue

                        if err.startswith('[Invalid data]'):

                            p = err.index(']') + 2
                            err = err[p:]

                            self.report.error.appendDescription('format_issue',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ ValueError  - {err}\n")

                        else:

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPolymerSequence() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ Error  - {err}\n")

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPolymerSequence() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ Error  - {str(e)}\n")

                is_done = False

        return is_done

    def __extractPolymerSequenceInLoop(self):
        """ Extract polymer sequence in interesting loops.
        """

        is_done = True

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                is_done = False
                continue

            poly_seq_list_set = {}

            for content_subtype in self.nmr_content_subtypes:

                if content_subtype in ('entry_info', 'poly_seq', 'entity') or (not has_key_value(input_source_dic['content_subtype'], content_subtype)):
                    continue

                poly_seq_list_set[content_subtype] = []

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                    lp_category = '_Assigned_peak_chem_shift'

                has_poly_seq = False

                list_id = 1

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf,
                                                                          list_id, sf_framecode, lp_category, poly_seq_list_set)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf,
                                                                          list_id, sf_framecode, lp_category, poly_seq_list_set)

                else:

                    for list_id, sf in enumerate(self.__star_data[fileListId].get_saveframes_by_category(sf_category), start=1):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf,
                                                                              list_id, sf_framecode, lp_category, poly_seq_list_set)

                if not has_poly_seq:
                    poly_seq_list_set.pop(content_subtype)

            if len(poly_seq_list_set) > 0:
                input_source.setItemValue('polymer_sequence_in_loop', poly_seq_list_set)

        return is_done

    def __extractPolymerSequenceInLoop__(self, file_list_id, file_name, file_type, content_subtype, sf,
                                         list_id, sf_framecode, lp_category, poly_seq_list_set):
        """ Extract polymer sequence in interesting loops.
        """

        has_poly_seq = False

        try:

            poly_seq = self.__getPolymerSequence(file_list_id, sf, content_subtype)[0]

            if len(poly_seq) > 0:

                poly_seq_list_set[content_subtype].append({'list_id': list_id, 'sf_framecode': sf_framecode, 'polymer_sequence': poly_seq})

                has_poly_seq = True

                if file_type == 'nmr-star':

                    auth_poly_seq = self.__nefT.get_star_auth_seq(sf, lp_category)[0]

                    for ps in poly_seq:
                        chain_id, seq_ids, comp_ids = ps['chain_id'], ps['seq_id'], ps['comp_id']

                        for aps in auth_poly_seq:

                            if aps['chain_id'] != chain_id:
                                continue

                            _seq_ids = aps['seq_id']

                            auth_asym_ids, auth_seq_ids, auth_comp_ids = aps['auth_asym_id'], aps['auth_seq_id'], aps['auth_comp_id']

                            auth_asym_id_set = sorted(set(auth_asym_ids))

                            for auth_asym_id in auth_asym_id_set:

                                offsets = []
                                total = 0

                                for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                    if _auth_asym_id != auth_asym_id or auth_seq_id in emptyValue:
                                        continue

                                    try:

                                        _auth_seq_id = int(auth_seq_id)

                                        offsets.append(_auth_seq_id - _seq_id)
                                        total += 1

                                    except ValueError:

                                        if self.__check_auth_seq:
                                            warn = f"Auth_seq_ID {str(auth_seq_id)!r} (Auth_asym_ID {auth_asym_id}, "\
                                                f"Auth_comp_ID {auth_comp_id}) should be an integer."

                                            self.report.warning.appendDescription('sequence_mismatch',
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                   'description': warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Warning  - {warn}\n")

                                if total > 1:

                                    offset = collections.Counter(offsets).most_common()[0][0]

                                    for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                        if _auth_asym_id != auth_asym_id or auth_seq_id in emptyValue:
                                            continue

                                        try:

                                            _auth_seq_id = int(auth_seq_id)

                                        except ValueError:
                                            continue

                                        if _auth_seq_id - _seq_id != offset:

                                            if self.__check_auth_seq:
                                                warn = f"Auth_seq_ID {str(auth_seq_id)!r} is inconsistent with {str(_seq_id + offset)!r} "\
                                                    f"(Auth_asym_ID {auth_asym_id}, Auth_comp_ID {auth_comp_id})."

                                                self.report.warning.appendDescription('sequence_mismatch',
                                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                       'description': warn})
                                                self.report.setWarning()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Warning  - {warn}\n")

                            for seq_id, comp_id in zip(seq_ids, comp_ids):

                                for _seq_id, _auth_asym_id, auth_seq_id, auth_comp_id in zip(_seq_ids, auth_asym_ids, auth_seq_ids, auth_comp_ids):

                                    if _seq_id != seq_id:
                                        continue

                                    if comp_id == auth_comp_id:
                                        continue

                                    if self.__check_auth_seq:
                                        warn = f"Auth_comp_ID {auth_comp_id!r} (Auth_asym_ID {_auth_asym_id}, Auth_seq_ID {auth_seq_id}) is inconsistent with {comp_id!r} "\
                                            f"(Entity_assembly_ID {chain_id}, Seq_ID {seq_id})."

                                        self.report.warning.appendDescription('sequence_mismatch',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Warning  - {warn}\n")

                                    break

        except KeyError as e:

            if 'Auth' not in str(e) or self.__check_auth_seq:
                self.report.error.appendDescription('sequence_mismatch',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ KeyError  - {str(e)}\n")

        except LookupError:
            # """
            # self.report.error.appendDescription('missing_mandatory_item',
            #                                     {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
            #                                      'description': str(e).strip("'")})
            # self.report.setError()

            # self.__lfh.write("+NmrDpUtility.__extractPolymerSequenceInLoop() ++ LookupError  - "
            #                  f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")
            # """
            pass

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')

            for err in errs:

                if len(err) == 0:
                    continue

                if err.startswith('[Invalid data]'):

                    _err = err.split('#')[0]

                    if 'Auth' not in _err or self.__check_auth_seq:

                        p = err.index(']') + 2
                        err = err[p:]

                        self.report.error.appendDescription('format_issue',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ ValueError  - {err}\n")

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ Error  - {str(e)}\n")

        return has_poly_seq

    def __isConsistentSequence(self):
        """ Perform sequence consistency test among extracted polymer sequences.
            @return: True for valid sequence, False otherwise
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            # file_type = input_source_dic['file_type']

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if (not has_poly_seq) or (not has_poly_seq_in_loop):
                continue

            polymer_sequence = input_source_dic['polymer_sequence']
            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            poly_seq = 'poly_seq'

            subtype_with_poly_seq = [poly_seq if has_poly_seq else None]

            for subtype in polymer_sequence_in_loop.keys():
                subtype_with_poly_seq.append(subtype)

            for subtype_pair in itertools.combinations_with_replacement(subtype_with_poly_seq, 2):

                # poly_seq is reference sequence and suppress tests on combinations of two sequences in loop
                if has_poly_seq and ((poly_seq not in subtype_pair) or subtype_pair == (poly_seq, poly_seq)):
                    continue

                subtype1 = subtype_pair[0]  # poly_seq will appear only on subtype1
                subtype2 = subtype_pair[1]

                if subtype1 is None or subtype2 is None:
                    continue

                # reference polymer sequence exists
                if has_poly_seq and subtype1 == poly_seq:
                    ps1 = polymer_sequence

                    ref_chain_ids = {s1['chain_id'] for s1 in ps1}

                    for ps_in_loop in polymer_sequence_in_loop[subtype2]:
                        ps2 = ps_in_loop['polymer_sequence']
                        # sf_framecode2 = ps_in_loop['sf_framecode']

                        for s2 in ps2:

                            chain_id = s2['chain_id']

                            if chain_id not in ref_chain_ids and not ('identical_chain_id' in s2 and chain_id not in s2['identical_chain_id']):
                                return False

                            for s1 in ps1:

                                if s1['chain_id'] != chain_id and not ('identical_chain_id' in s2 and s1['chain_id'] in s2['identical_chain_id']):
                                    continue

                                for seq_id, comp_id in zip(s2['seq_id'], s2['comp_id']):

                                    if seq_id not in s1['seq_id']:

                                        if comp_id != '.':
                                            return False

                                    else:
                                        i = s1['seq_id'].index(seq_id)
                                        _comp_id = s1['comp_id'][i]

                                        if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:
                                            return False

                #  brute force check
                else:

                    for ps_in_loop in polymer_sequence_in_loop[subtype1]:
                        ps1 = ps_in_loop['polymer_sequence']
                        # sf_framecode1 = ps_in_loop['sf_framecode']

                        for ps_in_loop2 in polymer_sequence_in_loop[subtype2]:
                            ps2 = ps_in_loop2['polymer_sequence']
                            # sf_framecode2 = ps_in_loop2['sf_framecode']

                            # suppress redundant tests inside the same subtype
                            if subtype1 == subtype2 and ps_in_loop['list_id'] >= ps_in_loop2['list_id']:
                                continue

                            for s2 in ps2:

                                chain_id = s2['chain_id']

                                for s1 in ps1:

                                    if chain_id != s1['chain_id']:
                                        continue

                                    for seq_id, comp_id in zip(s2['seq_id'], s2['comp_id']):

                                        if seq_id in s1['seq_id']:
                                            i = s1['seq_id'].index(seq_id)
                                            _comp_id = s1['comp_id'][i]

                                            if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:
                                                return False

                            # inverse check required for unverified sequences
                            for s1 in ps1:

                                chain_id = s1['chain_id']

                                for s2 in ps2:

                                    if chain_id != s2['chain_id']:
                                        continue

                                    for seq_id, comp_id in zip(s1['seq_id'], s1['comp_id']):

                                        if seq_id in s2['seq_id']:
                                            j = s2['seq_id'].index(seq_id)
                                            _comp_id = s2['comp_id'][j]

                                            if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:
                                                return False

        return True

    def __testSequenceConsistency(self):
        """ Perform sequence consistency test among extracted polymer sequences.
        """

        if self.__valid_seq:
            return True

        update_poly_seq = False

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if (not has_poly_seq) or (not has_poly_seq_in_loop):
                continue

            polymer_sequence = input_source_dic['polymer_sequence']
            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            poly_seq = 'poly_seq'

            subtype_with_poly_seq = [poly_seq if has_poly_seq else None]

            for subtype in polymer_sequence_in_loop.keys():
                subtype_with_poly_seq.append(subtype)

            if self.__bmrb_only and self.__internal_mode:

                to_entity_id = {}

                for sf in self.__star_data[fileListId].get_saveframes_by_category('assembly'):

                    try:

                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop('_Entity_assembly')
                        else:
                            loop = sf.get_loop_by_category('_Entity_assembly')

                        if loop is not None:
                            dat = get_lp_tag(loop, ['ID', 'Entity_ID'])

                            for row in dat:
                                to_entity_id[row[0]] = row[1]

                    except KeyError:
                        pass

            for subtype_pair in itertools.combinations_with_replacement(subtype_with_poly_seq, 2):

                # poly_seq is reference sequence and suppress tests on combinations of two sequences in loop
                if has_poly_seq and ((poly_seq not in subtype_pair) or subtype_pair == (poly_seq, poly_seq)):
                    continue

                subtype1 = subtype_pair[0]  # poly_seq will appear only on subtype1
                subtype2 = subtype_pair[1]

                if subtype1 is None or subtype2 is None:
                    continue

                # lp_category1 = self.lp_categories[file_type][subtype1]
                lp_category2 = self.lp_categories[file_type][subtype2]

                if file_type == 'nmr-star':
                    # if subtype1 == 'spectral_peak_alt':
                    #    lp_category1 = '_Assigned_peak_chem_shift'
                    if subtype2 == 'spectral_peak_alt':
                        lp_category2 = '_Assigned_peak_chem_shift'

                # reference polymer sequence exists
                if has_poly_seq and subtype1 == poly_seq:
                    ps1 = polymer_sequence

                    ref_chain_ids = {s1['chain_id'] for s1 in ps1}

                    for ps_in_loop in polymer_sequence_in_loop[subtype2]:
                        ps2 = ps_in_loop['polymer_sequence']
                        sf_framecode2 = ps_in_loop['sf_framecode']

                        for s2 in ps2:

                            chain_id = s2['chain_id']

                            if self.__bmrb_only and self.__internal_mode\
                               and chain_id not in ref_chain_ids and not ('identical_chain_id' in s2 and chain_id not in s2['identical_chain_id']):

                                chain_id = to_entity_id.get(chain_id, chain_id)

                            if chain_id not in ref_chain_ids and not chain_id.isdigit() and self.__combined_mode:

                                if self.__caC is None:
                                    self.__retrieveCoordAssemblyChecker()

                                chain_id = next((str(item['entity_assembly_id']) for item in self.__caC['entity_assembly']
                                                 if chain_id in item['auth_asym_id'].split(',')), chain_id)

                            if chain_id not in ref_chain_ids and not ('identical_chain_id' in s2 and chain_id not in s2['identical_chain_id']):

                                err = f"Invalid chain_id {chain_id!r} in a loop {lp_category2}."

                                if self.__remediation_mode:

                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                           'description': err})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {err}\n")

                                else:

                                    self.report.error.appendDescription('sequence_mismatch',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Error  - {err}\n")

                            else:

                                for s1 in ps1:

                                    if s1['chain_id'] != chain_id and not ('identical_chain_id' in s2 and s1['chain_id'] in s2['identical_chain_id']):
                                        continue

                                    if 'identical_chain_id' in s2:
                                        _s1 = next((_s1 for _s1 in ps1 if _s1['chain_id'] == chain_id), None)
                                        __s1 = next((_s1 for _s1 in ps1 if _s1['chain_id'] in s2['identical_chain_id']), None)
                                        if _s1 is not None and len(s1['seq_id']) != len(_s1['seq_id']):
                                            continue
                                        if __s1 is not None and len(s1['seq_id']) != len(__s1['seq_id']):
                                            continue

                                    for seq_id, comp_id in zip(s2['seq_id'], s2['comp_id']):

                                        if seq_id not in s1['seq_id']:

                                            if comp_id != '.':

                                                if self.__target_framecode not in emptyValue:
                                                    self.__lfh.write(f"{sf_framecode2} s1:{s1['chain_id']} {s1['seq_id']} {s1['comp_id']} "
                                                                     f"s2: {s2['chain_id']} {s2['seq_id']} {s2['comp_id']} {seq_id} {comp_id}\n")
                                                    sys.exit(1)

                                                if not self.__remediation_mode\
                                                   and ((min(set(s2['seq_id']) - set(s1['seq_id'])) > 0 and seq_id > 0) or not self.__nonblk_bad_nterm):

                                                    err = f"Invalid seq_id {str(seq_id)!r} (chain_id {chain_id}) in a loop {lp_category2}."

                                                    self.report.error.appendDescription('sequence_mismatch',
                                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                         'description': err})
                                                    self.report.setError()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Error  - {err}\n")

                                                else:

                                                    warn = f"Unmapped seq_id {str(seq_id)!r} (chain_id {chain_id}) in a loop {lp_category2}. "\
                                                        "Please update the sequence in the Macromolecules page."

                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                           'description': warn})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {warn}\n")

                                        else:
                                            i = s1['seq_id'].index(seq_id)
                                            _comp_id = s1['comp_id'][i]

                                            if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:

                                                if self.__target_framecode not in emptyValue:
                                                    self.__lfh.write(f"{sf_framecode2} s1:{s1['chain_id']} {s1['seq_id']} {s1['comp_id']} "
                                                                     f"s2: {s2['chain_id']} {s2['seq_id']} {s2['comp_id']} {seq_id} {comp_id}\n")
                                                    sys.exit(1)

                                                err = f"Invalid comp_id {comp_id!r} vs {_comp_id!r} (seq_id {seq_id}, chain_id {chain_id}) in a loop {lp_category2}."

                                                if self.__tolerant_seq_align and self.__equalsRepCompId(comp_id, _comp_id):
                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                           'description': err})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {err}\n")

                                                elif self.__tolerant_seq_align and getOneLetterCodeCan(comp_id) == getOneLetterCodeCan(_comp_id):
                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                           'description': err})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {err}\n")

                                                    comp_id_conv_dict = {comp_id: _comp_id}

                                                    self.__fixCompIdInLoop(fileListId, file_type, subtype2, sf_framecode2, chain_id, seq_id, comp_id_conv_dict)

                                                    update_poly_seq = True

                                                else:
                                                    self.report.error.appendDescription('sequence_mismatch',
                                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                         'description': err})
                                                    self.report.setError()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Error  - {err}\n")

                # brute force check
                else:

                    for ps_in_loop in polymer_sequence_in_loop[subtype1]:
                        ps1 = ps_in_loop['polymer_sequence']
                        sf_framecode1 = ps_in_loop['sf_framecode']

                        for ps_in_loop2 in polymer_sequence_in_loop[subtype2]:
                            ps2 = ps_in_loop2['polymer_sequence']
                            sf_framecode2 = ps_in_loop2['sf_framecode']

                            # suppress redundant tests inside the same subtype
                            if subtype1 == subtype2 and ps_in_loop['list_id'] >= ps_in_loop2['list_id']:
                                continue

                            for s2 in ps2:

                                chain_id = s2['chain_id']

                                for s1 in ps1:

                                    if chain_id != s1['chain_id']:
                                        continue

                                    for seq_id, comp_id in zip(s2['seq_id'], s2['comp_id']):

                                        if seq_id in s1['seq_id']:
                                            i = s1['seq_id'].index(seq_id)
                                            _comp_id = s1['comp_id'][i]

                                            if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:

                                                err = f"Unmatched comp_id {comp_id!r} vs {_comp_id!r} (seq_id {seq_id}, chain_id {chain_id}) exists "\
                                                    f"against {sf_framecode1!r} saveframe."

                                                if self.__tolerant_seq_align and self.__equalsRepCompId(comp_id, _comp_id):
                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                           'description': err})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {err}\n")

                                                else:
                                                    self.report.error.appendDescription('sequence_mismatch',
                                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                         'description': err})
                                                    self.report.setError()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Error  - {err}\n")

                            # inverse check required for unverified sequences
                            for s1 in ps1:

                                chain_id = s1['chain_id']

                                for s2 in ps2:

                                    if chain_id != s2['chain_id']:
                                        continue

                                    for seq_id, comp_id in zip(s1['seq_id'], s1['comp_id']):

                                        if seq_id in s2['seq_id']:
                                            j = s2['seq_id'].index(seq_id)
                                            _comp_id = s2['comp_id'][j]

                                            if comp_id not in emptyValue and _comp_id not in emptyValue and comp_id != _comp_id:

                                                err = f"Unmatched comp_id {comp_id!r} vs {_comp_id!r} (seq_id {seq_id}, chain_id {chain_id}) exists "\
                                                    f"against {sf_framecode2!r} saveframe."

                                                if self.__tolerant_seq_align and self.__equalsRepCompId(comp_id, _comp_id):
                                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                           'description': err})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Warning  - {err}\n")

                                                else:
                                                    self.report.error.appendDescription('sequence_mismatch',
                                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode2, 'category': lp_category2,
                                                                                         'description': err})
                                                    self.report.setError()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testSequenceConsistency() ++ Error  - {err}\n")

        if update_poly_seq:
            self.__extractPolymerSequenceInLoop()
            self.__depositNmrData()

        return not self.report.isError()

    def __equalsRepCompId(self, comp_id, ref_comp_id):
        """ Return whether given representative comp IDs are equal.
            @return: True for representative comp IDs are matched, False otherwise
        """

        if comp_id in emptyValue or ref_comp_id in emptyValue:
            return False

        if '_' in comp_id:
            comp_id = comp_id.split('_')[0]

        elif comp_id not in monDict3 and self.__ccU.updateChemCompDict(comp_id):
            if '_chem_comp.mon_nstd_parent_comp_id' in self.__ccU.lastChemCompDict:  # matches with comp_id in CCD
                if self.__ccU.lastChemCompDict['_chem_comp.mon_nstd_parent_comp_id'] not in emptyValue:
                    comp_id = self.__ccU.lastChemCompDict['_chem_comp.mon_nstd_parent_comp_id']
                    if comp_id in ('A', 'C', 'G', 'T', 'I', 'U') and len(ref_comp_id) == 2 and ref_comp_id.startswith('D'):
                        comp_id = 'D' + comp_id
                    elif ref_comp_id in ('A', 'C', 'G', 'T', 'I', 'U') and len(comp_id) == 2 and comp_id.startswith('D'):
                        comp_id = comp_id[1]

        if '_' in ref_comp_id:
            ref_comp_id = ref_comp_id.split('_')[0]

        elif ref_comp_id not in monDict3 and self.__ccU.updateChemCompDict(ref_comp_id):
            if '_chem_comp.mon_nstd_parent_comp_id' in self.__ccU.lastChemCompDict:  # matches with comp_id in CCD
                if self.__ccU.lastChemCompDict['_chem_comp.mon_nstd_parent_comp_id'] not in emptyValue:
                    ref_comp_id = self.__ccU.lastChemCompDict['_chem_comp.mon_nstd_parent_comp_id']
                    if ref_comp_id in ('A', 'C', 'G', 'T', 'I', 'U') and len(comp_id) == 2 and comp_id.startswith('D'):
                        ref_comp_id = 'D' + ref_comp_id
                    elif comp_id in ('A', 'C', 'G', 'T', 'I', 'U') and len(ref_comp_id) == 2 and ref_comp_id.startswith('D'):
                        ref_comp_id = ref_comp_id[1]

        return comp_id == ref_comp_id

    def __fixCompIdInLoop(self, file_list_id, file_type, content_subtype, sf_framecode, chain_id, seq_id, comp_id_conv_dict):
        """ Fix comp ID of interesting loop.
        """

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
            lp_category = '_Assigned_peak_chem_shift'

        if self.__star_data_type[file_list_id] == 'Loop':

            sf = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id, comp_id_conv_dict)

        elif self.__star_data_type[file_list_id] == 'Saveframe':
            sf = self.__star_data[file_list_id]

            if get_first_sf_tag(sf, 'sf_framecode') == sf_framecode:
                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id, comp_id_conv_dict)

        else:

            for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                if get_first_sf_tag(sf, 'sf_framecode') != sf_framecode:
                    continue

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id, comp_id_conv_dict)

    def __fixCompIdInLoop__(self, file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id, comp_id_conv_dict):
        """ Fix sequence ID of interesting loop.
        """

        chain_id_name = 'chain_code' if file_type == 'nef' else 'Entity_assembly_ID'
        seq_id_name = 'sequence_code' if file_type == 'nef' else 'Comp_index_ID'
        comp_id_name = 'residue_name' if file_type == 'nef' else 'Comp_ID'

        max_dim = 2

        if content_subtype in ('poly_seq', 'dist_restraint', 'rdc_restraint'):
            max_dim = 3

        elif content_subtype == 'dihed_restraint':
            max_dim = 5

        elif content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                num_dim = int(_num_dim)

                if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    raise ValueError()

            except ValueError:  # raised error already at __testIndexConsistency()
                return

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if max_dim == 2:

            chain_id_col = loop.tags.index(chain_id_name) if chain_id_name in loop.tags else -1
            seq_id_col = loop.tags.index(seq_id_name) if seq_id_name in loop.tags else -1
            comp_id_col = loop.tags.index(comp_id_name) if comp_id_name in loop.tags else -1

            if chain_id_col == -1 or seq_id_col == -1 or comp_id_col == -1:
                return

            for row in loop:

                if row[chain_id_col] != chain_id:
                    continue

                _seq_id = row[seq_id_col]

                if _seq_id in emptyValue or int(_seq_id) != seq_id:
                    continue

                comp_id = row[comp_id_col]

                if comp_id in comp_id_conv_dict:
                    row[comp_id_col] = comp_id_conv_dict[comp_id]

        else:

            for i in range(1, max_dim):

                _chain_id_name = chain_id_name + '_' + str(i)
                _seq_id_name = seq_id_name + '_' + str(i)
                _comp_id_name = comp_id_name + '_' + str(i)

                chain_id_col = loop.tags.index(_chain_id_name) if _chain_id_name in loop.tags else -1
                seq_id_col = loop.tags.index(_seq_id_name) if _seq_id_name in loop.tags else -1
                comp_id_col = loop.tags.index(_comp_id_name) if _comp_id_name in loop.tags else -1

                if chain_id_col == -1 or seq_id_col == -1 or comp_id_col == -1:
                    continue

                for row in loop:

                    if row[chain_id_col] != chain_id:
                        continue

                    _seq_id = row[seq_id_col]

                    if _seq_id in emptyValue or int(_seq_id) != seq_id:
                        continue

                    comp_id = row[comp_id_col]

                    if comp_id in comp_id_conv_dict:
                        row[comp_id_col] = comp_id_conv_dict[comp_id]

    def __extractCommonPolymerSequence(self):
        """ Extract common polymer sequence if required.
        """

        common_poly_seq = {}

        cs_has_alt_comp_id = False

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            # file_type = input_source_dic['file_type']
            content_type = input_source_dic['content_type']

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            # pass if poly_seq exists
            if has_poly_seq or (not has_poly_seq_in_loop):

                if not has_poly_seq or not has_poly_seq_in_loop:
                    continue

                self.__mergePolymerSequenceInCsLoop(fileListId)

                continue

            if self.__extractPolymerSequenceInEntityAssembly(fileListId):
                continue

            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            content_subtype = 'chem_shift'

            if content_subtype not in polymer_sequence_in_loop or content_type == 'nmr-restraints':  # DAOTHER-7545 NMR-STAR formatted MR has no chem_shift

                if 'dist_restraint' in polymer_sequence_in_loop:
                    content_subtype = 'dist_restraint'
                elif 'dihed_restraint' in polymer_sequence_in_loop:
                    content_subtype = 'dihed_restraint'
                elif 'rdc_restraint' in polymer_sequence_in_loop:
                    content_subtype = 'rdc_restraint'
                else:
                    continue

            # for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                polymer_sequence = ps_in_loop['polymer_sequence']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']

                    if chain_id not in common_poly_seq:
                        common_poly_seq[chain_id] = set()

            chain_ids = common_poly_seq.keys()
            offset_seq_ids = {c: 0 for c in chain_ids}

            # for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                polymer_sequence = ps_in_loop['polymer_sequence']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']

                    min_seq_id = min(ps['seq_id'])
                    if min_seq_id < 0:
                        offset_seq_ids[chain_id] = min_seq_id * -1

                    if 'alt_comp_id' in ps:
                        for seq_id, comp_id, alt_comp_id in zip(ps['seq_id'], ps['comp_id'], ps['alt_comp_id']):
                            common_poly_seq[chain_id].add((seq_id + offset_seq_ids[chain_id], comp_id, alt_comp_id))
                            if (seq_id + offset_seq_ids[chain_id], comp_id) in common_poly_seq[chain_id]:
                                common_poly_seq[chain_id].remove((seq_id + offset_seq_ids[chain_id], comp_id))
                            cs_has_alt_comp_id = True
                    else:
                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):
                            if not any(len(item) == 3 for item in common_poly_seq[chain_id]
                                       if item[0] == seq_id + offset_seq_ids[chain_id] and item[1] == comp_id):
                                common_poly_seq[chain_id].add((seq_id + offset_seq_ids[chain_id], comp_id))

        asm = []  # molecular assembly of a loop

        for chain_id in sorted(common_poly_seq.keys()):

            if len(common_poly_seq[chain_id]) > 0:
                seq_ids = sorted(set(item[0] - offset_seq_ids[chain_id] for item in common_poly_seq[chain_id]))
                comp_ids = []
                if cs_has_alt_comp_id:
                    alt_comp_ids = []

                for seq_id in seq_ids:
                    _comp_ids = [item[1] for item in common_poly_seq[chain_id] if item[0] - offset_seq_ids[chain_id] == seq_id]
                    if cs_has_alt_comp_id:
                        _alt_comp_ids = [item[len(item) - 1] for item in common_poly_seq[chain_id] if item[0] - offset_seq_ids[chain_id] == seq_id]
                    if len(_comp_ids) == 1:
                        comp_ids.append(_comp_ids[0])
                        if cs_has_alt_comp_id:
                            alt_comp_ids.append(_alt_comp_ids[0])
                    else:
                        comp_ids.append(next(comp_id for comp_id in _comp_ids if comp_id not in emptyValue))
                        if cs_has_alt_comp_id:
                            alt_comp_ids.append(next(alt_comp_id for alt_comp_id in _alt_comp_ids if alt_comp_id not in emptyValue))

                if self.__combined_mode and self.__has_star_entity:
                    ent = self.__extractPolymerSequenceInEntityLoopOfChain(fileListId, chain_id)

                    if ent is not None:
                        asm.append(ent)
                        continue

                ent = {'chain_id': chain_id, 'seq_id': seq_ids, 'comp_id': comp_ids}
                if cs_has_alt_comp_id:
                    ent['alt_comp_id'] = alt_comp_ids

                asm.append(ent)

        if len(asm) > 0:

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
                has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

                # pass if poly_seq exists
                if has_poly_seq or (not has_poly_seq_in_loop):
                    continue

                if self.__extractPolymerSequenceInEntityAssembly(fileListId):
                    continue

                input_source.setItemValue('polymer_sequence', asm)

        return True

    def __mergePolymerSequenceInCsLoop(self, file_list_id):
        """ Merge polymer sequence in CS loops.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
        has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if not has_poly_seq or not has_poly_seq_in_loop:
            return False

        polymer_sequence = input_source_dic['polymer_sequence']
        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        content_subtype = 'chem_shift'

        if content_subtype not in polymer_sequence_in_loop:
            return False

        ext_seq_key_set = set()

        for polymer_sequence_in_cs_loop in polymer_sequence_in_loop[content_subtype]:
            for ps_in_cs_loop in polymer_sequence_in_cs_loop['polymer_sequence']:
                _chain_id = ps_in_cs_loop['chain_id']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']

                    if chain_id == _chain_id\
                       or 'identical_chain_id' in ps and _chain_id in ps['identical_chain_id']:

                        if 'alt_comp_id' in ps_in_cs_loop:
                            for _seq_id, _comp_id, _alt_comp_id in zip(ps_in_cs_loop['seq_id'], ps_in_cs_loop['comp_id'],
                                                                       ps_in_cs_loop['alt_comp_id']):
                                if _seq_id not in ps['seq_id'] and _seq_id is not None:
                                    ext_seq_key_set.add((chain_id, _seq_id, _comp_id, _alt_comp_id))
                        else:
                            for _seq_id, _comp_id in zip(ps_in_cs_loop['seq_id'], ps_in_cs_loop['comp_id']):
                                if _seq_id not in ps['seq_id'] and _seq_id is not None:
                                    ext_seq_key_set.add((chain_id, _seq_id, _comp_id))

        if len(ext_seq_key_set) > 0:

            cs_has_alt_comp_id = False

            for ext_seq_key in ext_seq_key_set:
                ps = next(ps for ps in polymer_sequence if ps['chain_id'] == ext_seq_key[0])

                seq_id = ext_seq_key[1]
                comp_id = ext_seq_key[2]

                if len(ext_seq_key) > 3:
                    cs_has_alt_comp_id = True

                if seq_id in ps['seq_id']:
                    continue

                pos = None

                if ps['seq_id'][0] is not None and seq_id < ps['seq_id'][0]:
                    pos = 0
                elif ps['seq_id'][-1] is not None and seq_id > ps['seq_id'][-1]:
                    pos = len(ps['seq_id'])
                else:
                    for idx, _seq_id in enumerate(ps['seq_id']):
                        if _seq_id is None or seq_id > _seq_id:
                            continue
                        pos = idx
                        break

                if pos is not None:
                    ps['seq_id'].insert(pos, seq_id)
                    ps['comp_id'].insert(pos, comp_id)

            if cs_has_alt_comp_id:
                chain_ids = []
                for ext_seq_key in ext_seq_key_set:
                    if ext_seq_key[0] not in chain_ids:
                        chain_ids.append(ext_seq_key[0])

                for chain_id in chain_ids:
                    ps = next(ps for ps in polymer_sequence if ps['chain_id'] == chain_id)
                    if 'alt_comp_id' not in ps or len(ps['alt_comp_id']) != len(ps['comp_id']):
                        ps['alt_comp_id'] = copy.deepcopy(ps['comp_id'])
                    for ext_seq_key in ext_seq_key_set:
                        if ext_seq_key[0] != chain_id:
                            continue
                        if ext_seq_key[1] in ps['seq_id']:
                            cs_has_alt_comp_id = len(ext_seq_key) > 3
                            pos = ps['seq_id'].index(ext_seq_key[1])
                            ps['alt_comp_id'][pos] = ext_seq_key[3 if cs_has_alt_comp_id else 2]

        return True

    def __extractPolymerSequenceInEntityAssembly(self, file_list_id):
        """ Extract polymer sequence in entity loops. (NMR combined deposition)
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type != 'nmr-star' or (not self.__has_star_entity):
            return False

        if not self.__combined_mode:
            return self.__extractPolymerSequenceInEntityLoop(file_list_id)

        for sf in self.__star_data[file_list_id].get_saveframes_by_category('assembly'):

            try:
                if __pynmrstar_v3_2__:
                    loop = sf.get_loop('_Entity_assembly')
                else:
                    loop = sf.get_loop_by_category('_Entity_assembly')
            except KeyError:
                return False

            if loop is None:
                return False

            tags = ['ID', 'Entity_assembly_name', 'Entity_ID']

            if 'Entity_label' in loop.tags:
                tags.append('Entity_label')

            if set(tags) & set(loop.tags) != set(tags):
                return False

            dat = get_lp_tag(loop, tags)

            asm = []  # molecular assembly of a loop

            chain_ids = set()
            entity_sfs = {}

            for c in dat:

                if c[0] in emptyValue or c[1] in emptyValue or c[2] in emptyValue:
                    return False

                try:
                    chain_id = str(c[0])
                    entity_sf = c[1] if len(c) < 4 else (c[3][1:] if c[3][0] == '$' else c[3])  # Entity_assemble_name or Entity_label
                    entity_id = int(c[2])

                    if chain_id in chain_ids:
                        return False

                    chain_ids.add(chain_id)

                    for k, v in entity_sfs.items():
                        if (k != entity_sf and v == entity_id) or (k == entity_sf and v != entity_id):
                            return False

                    entity_sfs[entity_sf] = entity_id

                except ValueError:
                    return False

                _sf = self.__getSaveframeByName(file_list_id, entity_sf)

                if _sf is None:
                    return False

                content_subtype = 'entity'

                try:
                    if __pynmrstar_v3_2__:
                        _loop = _sf.get_loop(self.lp_categories[file_type][content_subtype])
                    else:
                        _loop = _sf.get_loop_by_category(self.lp_categories[file_type][content_subtype])
                except KeyError:
                    return False

                if _loop is None:
                    return False

                _tags = ['ID', 'Comp_ID', 'Entity_ID']

                if set(_tags) & set(_loop.tags) != set(_tags):
                    return False

                _dat = get_lp_tag(_loop, _tags)

                seq = set()

                for s in _dat:

                    if s[0] in emptyValue or s[1] in emptyValue or s[2] in emptyValue:
                        return False

                    try:
                        seq_id = int(s[0])
                        comp_id = s[1]
                        _entity_id = int(s[2])
                    except ValueError:
                        return False

                    if entity_id != _entity_id:
                        return False

                    seq.add((seq_id, comp_id))

                sorted_seq = sorted(seq, key=itemgetter(0))

                asm.append({'chain_id': chain_id,
                            'seq_id': [x[0] for x in sorted_seq],
                            'comp_id': [x[1] for x in sorted_seq]})

            if len(asm) > 0:
                input_source.setItemValue('polymer_sequence', asm)
                return True

            break

        return False

    def __extractPolymerSequenceInEntityLoop(self, file_list_id):
        """ Extract polymer sequence in entity loops. (NMR conventional deposition)
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type != 'nmr-star' or (not self.__has_star_entity):
            return False

        if self.__combined_mode:
            return self.__extractPolymerSequenceInEntityAssembly(file_list_id)

        star_data = self.__star_data[file_list_id]

        content_subtype = 'entity'

        if self.__star_data_type[file_list_id] != 'Loop':
            for sf in self.__star_data[file_list_id].get_saveframes_by_category(content_subtype):
                entity_id = get_first_sf_tag(sf, 'ID')
                if len(entity_id) == 0 or entity_id in emptyValue or not entity_id.isdigit():
                    entity_id = 1
                    set_sf_tag(sf, 'ID', entity_id)
                else:
                    entity_id = int(entity_id)
                self.__c2S.set_local_sf_id(sf, str(entity_id))

        lp_category = self.lp_categories[file_type][content_subtype]

        try:
            loops = star_data.get_loops_by_category(lp_category)
        except AttributeError:
            try:
                if __pynmrstar_v3_2__:
                    loops = [star_data.get_loop(lp_category)]
                else:
                    loops = [star_data.get_loop_by_category(lp_category)]
            except AttributeError:
                return False

        for sf in self.__star_data[file_list_id].get_saveframes_by_category('assembly'):

            try:
                if __pynmrstar_v3_2__:
                    loop = sf.get_loop('_Entity_assembly')
                else:
                    loop = sf.get_loop_by_category('_Entity_assembly')
            except KeyError:
                return False

            if loop is None:
                return False

            dat = get_lp_tag(loop, ['Entity_ID'])

            entity_id_set = set()

            for row in dat:
                if row not in emptyValue:
                    entity_id_set.add(row)

            if len(loops) != len(entity_id_set):  # DAOTHER-8800: Make sure all _Entity saveframes have an _Entity Comp Index loop before relying on these loops
                return False

        asm = []  # molecular assembly of a loop

        chain_ids = set()
        seq = {}

        for loop in loops:

            if loop is None:
                continue

            tags = ['ID', 'Comp_ID', 'Entity_ID']
            tags_ = ['ID', 'Comp_ID']

            dat = []

            if set(tags) & set(loop.tags) == set(tags):
                dat = get_lp_tag(loop, tags)
                for row in dat:
                    if row[2] in emptyValue:
                        row[2] = '1'
            elif set(tags_) & set(loop.tags) == set(tags_):  # No Entity_ID tag case
                dat = get_lp_tag(loop, tags_)
                for row in dat:
                    row.append('1')

            for row in dat:

                if row[0] in emptyValue or row[1] in emptyValue or row[2] in emptyValue:
                    return False

                try:
                    c = str(row[2])

                    chain_ids.add(c)
                    if c not in seq:
                        seq[c] = set()
                    seq[c].add((int(row[0]), row[1]))
                except (ValueError, TypeError):
                    return False

        for chain_id in chain_ids:

            sorted_seq = sorted(seq[chain_id], key=itemgetter(0))

            asm.append({'chain_id': chain_id,
                        'seq_id': [x[0] for x in sorted_seq],
                        'comp_id': [x[1] for x in sorted_seq]})

        if len(asm) > 0:
            input_source.setItemValue('polymer_sequence', asm)
            return True

        return False

    def __extractPolymerSequenceInEntityLoopOfChain(self, file_list_id, chain_id):
        """ Extract polymer sequence in entity loops of a given chain id.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type != 'nmr-star' or (not self.__has_star_entity):
            return None

        star_data = self.__star_data[file_list_id]

        content_subtype = 'entity'

        lp_category = self.lp_categories[file_type][content_subtype]

        try:
            loops = star_data.get_loops_by_category(lp_category)
        except AttributeError:
            try:
                if __pynmrstar_v3_2__:
                    loops = [star_data.get_loop(lp_category)]
                else:
                    loops = [star_data.get_loop_by_category(lp_category)]
            except AttributeError:
                return None

        chain_ids = set()
        seq = {}

        for loop in loops:

            if loop is None:
                continue

            tags = ['ID', 'Comp_ID', 'Entity_ID']
            tags_ = ['ID', 'Comp_ID']

            dat = []

            if set(tags) & set(loop.tags) == set(tags):
                dat = get_lp_tag(loop, tags)
                for row in dat:
                    if row[2] in emptyValue:
                        row[2] = '1'
            elif set(tags_) & set(loop.tags) == set(tags_):  # No Entity_ID tag case
                dat = get_lp_tag(loop, tags_)
                for row in dat:
                    row.append('1')

            for row in dat:

                if row[0] in emptyValue or row[1] in emptyValue or row[2] in emptyValue:
                    return None

                try:
                    c = str(row[2])

                    chain_ids.add(c)
                    if c not in seq:
                        seq[c] = set()
                    seq[c].add((int(row[0]), row[1]))
                except (ValueError, TypeError):
                    return None

        if chain_id in chain_ids:

            sorted_seq = sorted(seq[chain_id], key=itemgetter(0))

            return {'chain_id': chain_id,
                    'seq_id': [x[0] for x in sorted_seq],
                    'comp_id': [x[1] for x in sorted_seq]}

        return None

    def __extractNonStandardResidue(self):
        """ Extract non-standard residue.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            content_subtype = 'poly_seq'

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')

            if not has_poly_seq:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                self.__extractNonStandardResidue__(file_name, sf_framecode, lp_category, input_source)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__extractNonStandardResidue__(file_name, sf_framecode, lp_category, input_source)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__extractNonStandardResidue__(file_name, sf_framecode, lp_category, input_source)

        return True

    def __extractNonStandardResidue__(self, file_name, sf_framecode, lp_category, input_source):
        """ Extract non-standard residue.
        """

        input_source_dic = input_source.get()

        polymer_sequence = input_source_dic['polymer_sequence']

        asm = []

        for ps in polymer_sequence:

            has_nstd_res = False

            ent = {'chain_id': ps['chain_id'], 'seq_id': [], 'comp_id': [], 'chem_comp_name': [], 'exptl_data': []}

            for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                if comp_id not in monDict3:
                    has_nstd_res = True

                    ent['seq_id'].append(seq_id)
                    ent['comp_id'].append(comp_id)

                    is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(comp_id)

                    if is_valid:  # matches with comp_id in CCD
                        if cc_rel_status == 'REL' or cc_name is not None:
                            ent['chem_comp_name'].append(cc_name)
                        else:
                            ent['chem_comp_name'].append(f"(Not available due to CCD status code {cc_rel_status})")

                    else:
                        ent['chem_comp_name'].append(cc_name)

                        if comp_id != '.':
                            warn = f"Non standard residue ({ps['chain_id']}:{seq_id}:{comp_id}) did not match with chemical component dictionary (CCD)."

                            self.report.warning.appendDescription('ccd_mismatch',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractNonStandardResidue() ++ Warning  - {warn}\n")

                        # DAOTHER-9065
                        else:
                            warn = f"Residue ({ps['chain_id']}:{seq_id}:{comp_id}) was not specified. "\
                                   "Please update the sequence in the Macromolecules page."

                            self.report.warning.appendDescription('sequence_mismatch',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractNonStandardResidue() ++ Warning  - {warn}\n")

                    ent['exptl_data'].append({'chem_shift': False, 'dist_restraint': False, 'dihed_restraint': False,
                                              'rdc_restraint': False, 'spectral_peak': False, 'coordinate': False})

            if has_nstd_res:
                asm.append(ent)

        if len(asm) > 0:
            input_source.setItemValue('non_standard_residue', asm)

    def __getChemCompNameAndStatusOf(self, comp_id):
        """ Return _chem_comp.name and release status a given CCD ID, if possible.
        """

        cc_name = cc_rel_status = processing_site = None

        if len(self.__star_data_type) > 0 and self.__star_data_type[0] == 'Entry' and 'chem_comp' in self.__sf_category_list:
            chem_comp_sf = next((sf for sf in self.__star_data[0].frame_list if sf.name == f'chem_comp_{comp_id}'), None)

            if chem_comp_sf is not None:
                cc_name = get_first_sf_tag(chem_comp_sf, 'Name')
                if cc_name in emptyValue:
                    cc_name = None
                processing_site = get_first_sf_tag(chem_comp_sf, 'Processing_site')
                if processing_site in emptyValue:
                    processing_site = None

        if self.__ccU.updateChemCompDict(comp_id):  # matches with comp_id in CCD
            is_valid = True

            if cc_name is None:
                cc_name = self.__ccU.lastChemCompDict['_chem_comp.name']

            if processing_site is not None and processing_site.startswith('BMRB'):
                is_valid = False
                cc_name += f', processing site {processing_site}'
            else:
                cc_rel_status = self.__ccU.lastChemCompDict['_chem_comp.pdbx_release_status']

        else:
            is_valid = False

        return is_valid, cc_name, cc_rel_status

    def __appendPolymerSequenceAlignment(self):
        """ Append polymer sequence alignment of interesting loops.
        """

        is_done = True

        update_poly_seq = False

        self.__alt_chain = False
        self.__valid_seq = False

        if not self.__tolerant_seq_align:
            self.__valid_seq = self.__isConsistentSequence()

            if not self.__valid_seq:
                self.__tolerant_seq_align = True

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            # file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if not has_poly_seq:
                is_done = False
                continue

            if not has_poly_seq_in_loop:
                continue

            polymer_sequence = input_source_dic['polymer_sequence']
            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            for content_subtype in polymer_sequence_in_loop.keys():

                seq_align_set = []

                dst_chain_ids = {}
                ref_chain_ids = {}
                map_chain_ids = {}
                map_seq_ids = {}

                proc_chain_ids = {}

                for s1 in polymer_sequence:
                    chain_id = s1['chain_id']

                    for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                        ps2 = ps_in_loop['polymer_sequence']
                        sf_framecode2 = ps_in_loop['sf_framecode']

                        for s2 in ps2:

                            if sf_framecode2 in ref_chain_ids and chain_id in ref_chain_ids[sf_framecode2]:
                                continue

                            chain_id2 = s2['chain_id']

                            if chain_id != chain_id2:
                                continue

                            _s2 = fillBlankCompIdWithOffset(s2, 0)

                            if len(_s2['seq_id']) > len(s2['seq_id']) and len(_s2['seq_id']) < len(s1['seq_id']):
                                s2 = _s2

                            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                            self.__pA.addTestSequence(s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            if length == 0:
                                continue

                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                            alt_chain = False

                            if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0)\
                               or (len(polymer_sequence) > 1 and _matched < 4 and offset_1 > 0):

                                if self.__tolerant_seq_align and _matched <= conflict + (1 if length > 1 else 0) and len(polymer_sequence) > 1:

                                    __length = length
                                    __matched = _matched
                                    __unmapped = unmapped
                                    __conflict = conflict
                                    __chain_id = None
                                    __s1 = None
                                    __offset_1 = None
                                    __offset_2 = None

                                    for _s1 in polymer_sequence:

                                        if _s1 == s1:
                                            continue

                                        chain_id_ = _s1['chain_id']

                                        if sf_framecode2 in ref_chain_ids and chain_id_ in ref_chain_ids[sf_framecode2]:
                                            continue

                                        self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id_)
                                        self.__pA.addTestSequence(s2['comp_id'], chain_id_)
                                        self.__pA.doAlign()

                                        myAlign = self.__pA.getAlignment(chain_id_)

                                        length = len(myAlign)

                                        if length == 0:
                                            continue

                                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                                        if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0):
                                            continue

                                        if _matched - conflict < __matched - __conflict or unmapped + conflict > __unmapped + __conflict:
                                            continue

                                        __length = length
                                        __matched = _matched
                                        __unmapped = unmapped
                                        __conflict = conflict
                                        __chain_id = chain_id_
                                        __offset_1 = offset_1
                                        __offset_2 = offset_2
                                        __s1 = copy.copy(_s1)

                                        alt_chain = True

                                        break

                                if not alt_chain or\
                                   (sf_framecode2 in dst_chain_ids and __chain_id in dst_chain_ids[sf_framecode2]) or\
                                   (sf_framecode2 in map_chain_ids and chain_id in map_chain_ids[sf_framecode2]):
                                    continue

                                if sf_framecode2 not in dst_chain_ids:
                                    dst_chain_ids[sf_framecode2] = set()

                                dst_chain_ids[sf_framecode2].add(__chain_id)

                                if sf_framecode2 not in map_chain_ids:
                                    map_chain_ids[sf_framecode2] = {}

                                map_chain_ids[sf_framecode2][chain_id] = __chain_id

                                if sf_framecode2 == self.__target_framecode:
                                    self.__lfh.write(f"+NmrDpUtility.__appendPolymerSequenceAlignment() map-chain {chain_id} -> {__chain_id}, "
                                                     f"{__length} {__matched} {__unmapped} {__conflict} {__offset_1} {__offset_2}\n")

                                length = __length
                                _matched = __matched
                                unmapped = __unmapped
                                conflict = __conflict
                                chain_id = __s1['chain_id']
                                chain_id = __chain_id
                                offset_1 = __offset_1
                                offset_2 = __offset_2
                                s1 = __s1

                                # s2['chain_id'] = __chain_id

                                update_poly_seq = True

                            if conflict == 0 and self.__alt_chain and not alt_chain and chain_id != s2['chain_id'] and\
                               (sf_framecode2 not in dst_chain_ids or chain_id not in dst_chain_ids[sf_framecode2]) and\
                               (sf_framecode2 not in map_chain_ids or s2['chain_id'] not in map_chain_ids[sf_framecode2]) and\
                               unmapped != offset_1 + 1 and unmapped != offset_2 + 1 and\
                               unmapped <= _matched + offset_1 and unmapped <= _matched + offset_2:

                                if sf_framecode2 not in dst_chain_ids:
                                    dst_chain_ids[sf_framecode2] = set()

                                dst_chain_ids[sf_framecode2].add(chain_id)

                                if sf_framecode2 not in map_chain_ids:
                                    map_chain_ids[sf_framecode2] = {}

                                map_chain_ids[sf_framecode2][s2['chain_id']] = chain_id

                                if sf_framecode2 == self.__target_framecode:
                                    self.__lfh.write(f"+NmrDpUtility.__appendPolymerSequenceAlignment() map-chain-alt {s2['chain_id']} -> {chain_id}, "
                                                     f"{length} {_matched} {unmapped} {conflict} {offset_1} {offset_2}\n")

                                alt_chain = True

                            _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                            if conflict > 0 and _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:  # pylint: disable=chained-comparison
                                continue

                            if self.__tolerant_seq_align:  # and not alt_chain:
                                seq_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                   in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                   if __s1 != '.' and __s2 != '.' and __s1 != __s2 and __c1 != '.' and __c2 != '.' and __c1 == __c2)
                                comp_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                    in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                    if __c1 != '.' and __c2 != '.' and __c1 != __c2)

                                if 0 < _matched < 4 and unmapped // _matched > 20 and seq_mismatch and len(polymer_sequence) > 1:
                                    continue

                            if not alt_chain:
                                if sf_framecode2 not in dst_chain_ids:
                                    dst_chain_ids[sf_framecode2] = set()

                                dst_chain_ids[sf_framecode2].add(chain_id)

                            if sf_framecode2 not in ref_chain_ids:
                                ref_chain_ids[sf_framecode2] = []

                            ref_chain_ids[sf_framecode2].append(chain_id)

                            ref_length = len(s1['seq_id'])

                            ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                            test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                            mid_code = getMiddleCode(ref_code, test_code)
                            ref_gauge_code = getGaugeCode(_s1['seq_id'])
                            test_gauge_code = getGaugeCode(_s2['seq_id'])

                            self.__alt_chain |= alt_chain

                            if self.__tolerant_seq_align and (seq_mismatch or comp_mismatch):  # and not alt_chain:
                                if sf_framecode2 not in map_seq_ids:
                                    map_seq_ids[sf_framecode2] = set()
                                map_seq_ids[sf_framecode2].add(chain_id)
                                if _s2['seq_id'] == list(range(_s2['seq_id'][0], _s2['seq_id'][-1] + 1)):
                                    seq_id_conv_dict = {str(__s2): str(__s1) for __s1, __s2
                                                        in zip(_s1['seq_id'], _s2['seq_id']) if __s2 != '.'}
                                    if comp_mismatch:
                                        _seq_align = self.__getSeqAlignCode(fileListId, file_type, content_subtype, sf_framecode2,
                                                                            chain_id, _s1, _s2, myAlign,
                                                                            map_chain_ids.get(sf_framecode2),
                                                                            ref_gauge_code, ref_code, mid_code, test_code, test_gauge_code)
                                        _s2['seq_id'] = _seq_align['test_seq_id']
                                        if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                            continue
                                        ref_gauge_code = _seq_align['ref_gauge_code']
                                        ref_code = _seq_align['ref_code']
                                        mid_code = _seq_align['mid_code']
                                        test_code = _seq_align['test_code']
                                        test_gauge_code = _seq_align['test_gauge_code']
                                    else:
                                        # if _s1['seq_id'][0] < 0:
                                        #    continue
                                        chain_id2 = chain_id
                                        if sf_framecode2 in map_chain_ids and chain_id in map_chain_ids[sf_framecode2].values():
                                            chain_id2 = next(k for k, v in map_chain_ids[sf_framecode2].items() if v == chain_id)

                                        if sf_framecode2 == self.__target_framecode:
                                            self.__lfh.write("+NmrDpUtility.__appendPolymerSequenceAlignment() test "
                                                             f"{chain_id2} {_matched} {offset_1} {offset_2} {seq_id_conv_dict}\n")

                                        if sf_framecode2 not in proc_chain_ids:
                                            proc_chain_ids[sf_framecode2] = set()

                                        if chain_id2 not in proc_chain_ids[sf_framecode2]:
                                            self.__fixSeqIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, chain_id2, seq_id_conv_dict)
                                            proc_chain_ids[sf_framecode2].add(chain_id2)

                                            if 'identical_chain_id' in s2:
                                                for chain_id2_ in s2['identical_chain_id']:
                                                    if chain_id2_ not in proc_chain_ids[sf_framecode2]:
                                                        self.__fixSeqIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, chain_id2_, seq_id_conv_dict)
                                                        proc_chain_ids[sf_framecode2].add(chain_id2_)

                                        _s2['seq_id'] = _s1['seq_id']
                                        mid_code = getMiddleCode(ref_code, test_code)
                                        test_gauge_code = ref_gauge_code
                                else:
                                    if seq_mismatch:
                                        _seq_align = self.__getSeqAlignCode(fileListId, file_type, content_subtype, sf_framecode2,
                                                                            chain_id, _s1, _s2, myAlign,
                                                                            map_chain_ids.get(sf_framecode2),
                                                                            ref_gauge_code, ref_code, mid_code, test_code, test_gauge_code)
                                        _s2['seq_id'] = _seq_align['test_seq_id']
                                        if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                            continue
                                        ref_gauge_code = _seq_align['ref_gauge_code']
                                        ref_code = _seq_align['ref_code']
                                        mid_code = _seq_align['mid_code']
                                        test_code = _seq_align['test_code']
                                        test_gauge_code = _seq_align['test_gauge_code']
                                    else:
                                        _s2 = fillBlankCompId(_s1, _s2)
                                        if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                            continue
                                        test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                                        mid_code = getMiddleCode(ref_code, test_code)
                                        test_gauge_code = ref_gauge_code

                                update_poly_seq = True

                            matched = mid_code.count('|')

                            if self.__tolerant_seq_align and len(polymer_sequence) > 1:  # and not alt_chain:
                                if 0 < matched < 4 and unmapped // matched > 20:
                                    continue

                            seq_align = {'list_id': ps_in_loop['list_id'], 'sf_framecode': sf_framecode2, 'chain_id': chain_id, 'length': ref_length,
                                         'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                                         'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                                         'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                                         'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                                         'test_code': test_code, 'test_gauge_code': test_gauge_code}

                            if seq_align in seq_align_set:
                                continue

                            seq_align_set.append(seq_align)

                            if not self.__combined_mode and input_source_dic['non_standard_residue'] is None:  # no polymer sequence
                                has_nstd_res = False
                                for j, rc in enumerate(ref_code):
                                    if rc == 'X' and j < len(test_code) and test_code[j] == 'X':
                                        has_nstd_res = True
                                        break

                                if not has_nstd_res:
                                    continue

                                asm = []

                                for _s in polymer_sequence:

                                    ent = {'chain_id': _s['chain_id'], 'seq_id': [], 'comp_id': [], 'chem_comp_name': [], 'exptl_data': []}

                                    for _seq_id, _comp_id in zip(_s['seq_id'], _s['comp_id']):

                                        if _comp_id not in monDict3:

                                            ent['seq_id'].append(_seq_id)
                                            ent['comp_id'].append(_comp_id)

                                            is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(_comp_id)

                                            if is_valid:  # matches with comp_id in CCD
                                                if cc_rel_status == 'REL' or cc_name is not None:
                                                    ent['chem_comp_name'].append(cc_name)
                                                else:
                                                    ent['chem_comp_name'].append(f"(Not available due to CCD status code {cc_rel_status})")

                                            else:
                                                ent['chem_comp_name'].append(cc_name)

                                            ent['exptl_data'].append({'coordinate': False})

                                    asm.append(ent)

                                input_source.setItemValue('non_standard_residue', asm)

                            for r_code, t_code, seq_id in zip(ref_code, test_code, s1['seq_id']):
                                if r_code == 'X' and t_code == 'X':
                                    input_source.updateNonStandardResidueByExptlData(chain_id, seq_id, content_subtype)

                for s1 in polymer_sequence:

                    chain_id = s1['chain_id']

                    for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                        ps2 = ps_in_loop['polymer_sequence']
                        sf_framecode2 = ps_in_loop['sf_framecode']

                        for s2 in ps2:

                            if sf_framecode2 in ref_chain_ids and chain_id in ref_chain_ids[sf_framecode2]:
                                continue

                            chain_id2 = s2['chain_id']

                            if sf_framecode2 in dst_chain_ids and chain_id2 in dst_chain_ids[sf_framecode2]:
                                continue

                            if chain_id != chain_id2 and not self.__tolerant_seq_align:
                                continue

                            _s2 = fillBlankCompIdWithOffset(s2, 0)

                            if len(_s2['seq_id']) > len(s2['seq_id']) and len(_s2['seq_id']) < len(s1['seq_id']):
                                s2 = _s2

                            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                            self.__pA.addTestSequence(s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            if length == 0:
                                continue

                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                            alt_chain = False

                            if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0)\
                               or (len(polymer_sequence) > 1 and _matched < 4 and offset_1 > 0):

                                if self.__tolerant_seq_align and _matched <= conflict + (1 if length > 1 else 0) and len(polymer_sequence) > 1:

                                    __length = length
                                    __matched = _matched
                                    __unmapped = unmapped
                                    __conflict = conflict
                                    __chain_id = None
                                    __s1 = None
                                    __offset_1 = None
                                    __offset_2 = None

                                    for _s1 in polymer_sequence:

                                        if _s1 == s1:
                                            continue

                                        chain_id_ = _s1['chain_id']

                                        if sf_framecode2 in ref_chain_ids and chain_id_ in ref_chain_ids[sf_framecode2]:
                                            continue

                                        self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id_)
                                        self.__pA.addTestSequence(s2['comp_id'], chain_id_)
                                        self.__pA.doAlign()

                                        myAlign = self.__pA.getAlignment(chain_id_)

                                        length = len(myAlign)

                                        if length == 0:
                                            continue

                                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                                        if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0):
                                            continue

                                        if _matched - conflict < __matched - __conflict or (unmapped + conflict > __unmapped + __conflict and __matched > 0):
                                            continue

                                        __length = length
                                        __matched = _matched
                                        __unmapped = unmapped
                                        __conflict = conflict
                                        __chain_id = chain_id_
                                        __offset_1 = offset_1
                                        __offset_2 = offset_2
                                        __s1 = copy.copy(_s1)

                                        alt_chain = True

                                        break

                                if not alt_chain or\
                                   (sf_framecode2 in dst_chain_ids and __chain_id in dst_chain_ids[sf_framecode2]) or\
                                   (sf_framecode2 in map_chain_ids and chain_id in map_chain_ids[sf_framecode2]):
                                    continue

                                if sf_framecode2 not in dst_chain_ids:
                                    dst_chain_ids[sf_framecode2] = set()

                                dst_chain_ids[sf_framecode2].add(__chain_id)

                                if sf_framecode2 not in map_chain_ids:
                                    map_chain_ids[sf_framecode2] = {}

                                map_chain_ids[sf_framecode2][chain_id] = __chain_id

                                if sf_framecode2 == self.__target_framecode:
                                    self.__lfh.write(f"+NmrDpUtility.__appendPolymerSequenceAlignment() map-chain-rest {chain_id} -> {__chain_id}, "
                                                     f"{__length} {__matched} {__unmapped} {__conflict} {__offset_1} {__offset_2}\n")

                                length = __length
                                _matched = __matched
                                unmapped = __unmapped
                                conflict = __conflict
                                chain_id = __s1['chain_id']
                                chain_id = __chain_id
                                offset_1 = __offset_1
                                offset_2 = __offset_2
                                s1 = __s1

                                # s2['chain_id'] = __chain_id

                                update_poly_seq = True

                            _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                            if conflict > 0 and _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:  # pylint: disable=chained-comparison
                                continue

                            if self.__tolerant_seq_align:  # and not alt_chain:
                                seq_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                   in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                   if __s1 != '.' and __s2 != '.' and __s1 != __s2 and __c1 != '.' and __c2 != '.' and __c1 == __c2)
                                comp_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                    in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                    if __c1 != '.' and __c2 != '.' and __c1 != __c2)

                                if 0 < _matched < 4 and unmapped // _matched > 20 and seq_mismatch and len(polymer_sequence) > 1:
                                    continue

                            if sf_framecode2 not in ref_chain_ids:
                                ref_chain_ids[sf_framecode2] = []

                            if sf_framecode2 not in map_chain_ids:
                                map_chain_ids[sf_framecode2] = {}

                            if sf_framecode2 not in ref_chain_ids or chain_id not in ref_chain_ids[sf_framecode2]:
                                map_chain_ids[sf_framecode2][s2['chain_id']] = chain_id

                            ref_chain_ids[sf_framecode2].append(chain_id)

                            ref_length = len(s1['seq_id'])

                            ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                            test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                            mid_code = getMiddleCode(ref_code, test_code)
                            ref_gauge_code = getGaugeCode(_s1['seq_id'])
                            test_gauge_code = getGaugeCode(_s2['seq_id'])

                            self.__alt_chain |= not alt_chain

                            matched = mid_code.count('|')

                            if self.__tolerant_seq_align and len(polymer_sequence) > 1:  # and not alt_chain:
                                if 0 < matched < 4 and unmapped // matched > 20:
                                    continue

                            seq_align = {'list_id': ps_in_loop['list_id'], 'sf_framecode': sf_framecode2, 'chain_id': chain_id, 'length': ref_length,
                                         'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                                         'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                                         'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                                         'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                                         'test_code': test_code, 'test_gauge_code': test_gauge_code}

                            if seq_align in seq_align_set:
                                continue

                            seq_align_set.append(seq_align)

                            if not self.__combined_mode and input_source_dic['non_standard_residue'] is None:  # no polymer sequence
                                has_nstd_res = False
                                for j, rc in enumerate(ref_code):
                                    if rc == 'X' and j < len(test_code) and test_code[j] == 'X':
                                        has_nstd_res = True
                                        break

                                if not has_nstd_res:
                                    continue

                                asm = []

                                for _s in polymer_sequence:

                                    ent = {'chain_id': _s['chain_id'], 'seq_id': [], 'comp_id': [], 'chem_comp_name': [], 'exptl_data': []}

                                    for _seq_id, _comp_id in zip(_s['seq_id'], _s['comp_id']):

                                        if _comp_id not in monDict3:

                                            ent['seq_id'].append(_seq_id)
                                            ent['comp_id'].append(_comp_id)

                                            is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(_comp_id)

                                            if is_valid:  # matches with comp_id in CCD
                                                if cc_rel_status == 'REL' or cc_name is not None:
                                                    ent['chem_comp_name'].append(cc_name)
                                                else:
                                                    ent['chem_comp_name'].append(f"(Not available due to CCD status code {cc_rel_status})")

                                            else:
                                                ent['chem_comp_name'].append(cc_name)

                                            ent['exptl_data'].append({'coordinate': False})

                                    asm.append(ent)

                                input_source.setItemValue('non_standard_residue', asm)

                            for r_code, t_code, seq_id in zip(ref_code, test_code, s1['seq_id']):
                                if r_code == 'X' and t_code == 'X':
                                    input_source.updateNonStandardResidueByExptlData(chain_id, seq_id, content_subtype)

                if len(seq_align_set) > 0:
                    self.report.sequence_alignment.setItemValue('nmr_poly_seq_vs_' + content_subtype, seq_align_set)

                if self.__alt_chain:

                    for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                        ps2 = ps_in_loop['polymer_sequence']
                        sf_framecode2 = ps_in_loop['sf_framecode']

                        if sf_framecode2 in map_chain_ids:
                            mapping = map_chain_ids[sf_framecode2]

                            total = set(mapping.keys()) | set(mapping.values())

                            k_rests = list(total - set(mapping.keys()))
                            v_rests = list(total - set(mapping.values()))

                            circular = False
                            cross = False

                            for k, v in mapping.items():
                                for _k, _v in mapping.items():
                                    if v == _k:
                                        circular = True
                                        break
                                if circular:
                                    break

                            if len(k_rests) == 1 and len(v_rests) == 1:

                                src_chain = k_rests[0]
                                dst_chain = v_rests[0]

                                if circular:
                                    mapping[src_chain] = dst_chain

                                else:

                                    for s1 in polymer_sequence:
                                        chain_id = s1['chain_id']

                                        if chain_id != dst_chain:
                                            continue

                                        for s2 in ps2:

                                            # if chain_id != dst_chain:
                                            #    continue

                                            _s2 = fillBlankCompIdWithOffset(s2, 0)

                                            if len(_s2['seq_id']) > len(s2['seq_id']) and len(_s2['seq_id']) < len(s1['seq_id']):
                                                s2 = _s2

                                            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                                            self.__pA.addTestSequence(s2['comp_id'], chain_id)
                                            self.__pA.doAlign()

                                            myAlign = self.__pA.getAlignment(chain_id)

                                            length = len(myAlign)

                                            if length == 0:
                                                break

                                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                                            if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0):
                                                break

                                            cross = True
                                            mapping[src_chain] = dst_chain

                                            break

                            if sf_framecode2 == self.__target_framecode:
                                self.__lfh.write(f"+NmrDpUtility.__appendPolymerSequenceAlignment() alt-chain {mapping} {cross} {circular}\n")

                            for s1 in polymer_sequence:
                                chain_id = s1['chain_id']

                                for s2 in ps2:

                                    if chain_id != s2['chain_id']:
                                        continue

                                    _s2 = fillBlankCompIdWithOffset(s2, 0)

                                    if len(_s2['seq_id']) > len(s2['seq_id']) and len(_s2['seq_id']) < len(s1['seq_id']):
                                        s2 = _s2

                                    self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                                    self.__pA.addTestSequence(s2['comp_id'], chain_id)
                                    self.__pA.doAlign()

                                    myAlign = self.__pA.getAlignment(chain_id)

                                    length = len(myAlign)

                                    if length == 0:
                                        continue

                                    _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                                    if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0):
                                        continue

                                    _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                                    _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                                    if conflict > 0 and _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:  # pylint: disable=chained-comparison
                                        continue

                                    if self.__tolerant_seq_align:
                                        seq_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                           in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                           if __s1 != '.' and __s2 != '.' and __s1 != __s2 and __c1 != '.' and __c2 != '.' and __c1 == __c2)
                                        comp_mismatch = any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                                                            in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                                                            if __c1 != '.' and __c2 != '.' and __c1 != __c2)

                                        if 0 < _matched < 4 and unmapped // _matched > 20 and seq_mismatch and len(polymer_sequence) > 1:
                                            continue

                                    ref_length = len(s1['seq_id'])

                                    ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                                    test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                                    mid_code = getMiddleCode(ref_code, test_code)
                                    ref_gauge_code = getGaugeCode(_s1['seq_id'])
                                    test_gauge_code = getGaugeCode(_s2['seq_id'])

                                    if self.__tolerant_seq_align and (seq_mismatch or comp_mismatch):
                                        if sf_framecode2 in map_seq_ids and chain_id in map_seq_ids[sf_framecode2]:
                                            continue
                                        if _s2['seq_id'] == list(range(_s2['seq_id'][0], _s2['seq_id'][-1] + 1)):
                                            seq_id_conv_dict = {str(__s2): str(__s1) for __s1, __s2
                                                                in zip(_s1['seq_id'], _s2['seq_id']) if __s2 != '.'}
                                            if comp_mismatch:
                                                _seq_align = self.__getSeqAlignCode(fileListId, file_type, content_subtype, sf_framecode2,
                                                                                    chain_id, _s1, _s2, myAlign, mapping,
                                                                                    ref_gauge_code, ref_code, mid_code, test_code, test_gauge_code)
                                                _s2['seq_id'] = _seq_align['test_seq_id']
                                                if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                                    continue
                                                ref_gauge_code = _seq_align['ref_gauge_code']
                                                ref_code = _seq_align['ref_code']
                                                mid_code = _seq_align['mid_code']
                                                test_code = _seq_align['test_code']
                                                test_gauge_code = _seq_align['test_gauge_code']
                                            else:
                                                # if _s1['seq_id'][0] < 0:
                                                #    continue
                                                chain_id2 = chain_id
                                                if chain_id in mapping.values():
                                                    chain_id2 = next(k for k, v in mapping.items() if v == chain_id)

                                                if sf_framecode2 == self.__target_framecode:
                                                    self.__lfh.write("+NmrDpUtility.__appendPolymerSequenceAlignment() test-alt "
                                                                     f"{chain_id2} {_matched} {offset_1} {offset_2} {seq_id_conv_dict}\n")

                                                if sf_framecode2 not in proc_chain_ids:
                                                    proc_chain_ids[sf_framecode2] = set()

                                                if chain_id2 not in proc_chain_ids[sf_framecode2]:
                                                    self.__fixSeqIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, chain_id2, seq_id_conv_dict)
                                                    proc_chain_ids[sf_framecode2].add(chain_id2)

                                                    if 'identical_chain_id' in s2:
                                                        for chain_id2_ in s2['identical_chain_id']:
                                                            if chain_id2_ not in proc_chain_ids[sf_framecode2]:
                                                                self.__fixSeqIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, chain_id2_, seq_id_conv_dict)
                                                                proc_chain_ids[sf_framecode2].add(chain_id2_)

                                                _s2['seq_id'] = _s1['seq_id']
                                                mid_code = getMiddleCode(ref_code, test_code)
                                                test_gauge_code = ref_gauge_code
                                        else:
                                            if seq_mismatch:
                                                _seq_align = self.__getSeqAlignCode(fileListId, file_type, content_subtype, sf_framecode2,
                                                                                    chain_id, _s1, _s2, myAlign, mapping,
                                                                                    ref_gauge_code, ref_code, mid_code, test_code, test_gauge_code)
                                                _s2['seq_id'] = _seq_align['test_seq_id']
                                                if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                                    continue
                                                ref_gauge_code = _seq_align['ref_gauge_code']
                                                ref_code = _seq_align['ref_code']
                                                mid_code = _seq_align['mid_code']
                                                test_code = _seq_align['test_code']
                                                test_gauge_code = _seq_align['test_gauge_code']
                                            else:
                                                _s2 = fillBlankCompId(_s1, _s2)
                                                if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                                    continue
                                                test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                                                mid_code = getMiddleCode(ref_code, test_code)
                                                test_gauge_code = ref_gauge_code

                                    matched = mid_code.count('|')

                                    _seq_align = next((_seq_align for _seq_align in seq_align_set
                                                       if _seq_align['list_id'] == ps_in_loop['list_id']
                                                       and _seq_align['sf_framecode'] == sf_framecode2
                                                       and _seq_align['chain_id'] == chain_id), None)

                                    if _seq_align is not None:
                                        seq_align_set.remove(_seq_align)

                                    seq_align = {'list_id': ps_in_loop['list_id'], 'sf_framecode': sf_framecode2, 'chain_id': chain_id, 'length': ref_length,
                                                 'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                                                 'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                                                 'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                                                 'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                                                 'test_code': test_code, 'test_gauge_code': test_gauge_code}

                                    if seq_align in seq_align_set:
                                        continue

                                    seq_align_set.append(seq_align)

                            if circular or cross:
                                for k, v in mapping.items():

                                    for s2 in ps2:

                                        if s2['chain_id'] != k:
                                            continue

                                        s2['chain_id'] = v + '_'

                                        break

                                    self.__fixChainIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, k, v + '_')

                                for v in mapping.values():

                                    for s2 in ps2:

                                        if s2['chain_id'] != v + '_':
                                            continue

                                        s2['chain_id'] = v

                                        break

                                    self.__fixChainIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, v + '_', v)

                            else:
                                for k, v in mapping.items():

                                    for s2 in ps2:

                                        if s2['chain_id'] != k:
                                            continue

                                        s2['chain_id'] = v

                                        break

                                    self.__fixChainIdInLoop(fileListId, file_type, content_subtype, sf_framecode2, k, v)

        if update_poly_seq:
            self.__extractPolymerSequenceInLoop()
            self.__depositNmrData()

        return is_done

    def __getSeqAlignCode(self, file_list_id, file_type, content_subtype, sf_framecode, chain_id, s1, s2, myAlign, mapping,
                          ref_gauge_code, ref_code, mid_code, test_code, test_gauge_code):
        """ Return human-readable seq align codes.
        """

        len_s1 = len(s1['seq_id'])
        len_s2 = len(s2['seq_id'])

        length = len(myAlign)

        seq_id1 = []
        seq_id2 = []
        comp_id1 = []
        comp_id2 = []

        idx1 = 0
        idx2 = 0
        for i in range(length):
            myPr = myAlign[i]
            myPr0 = str(myPr[0])
            myPr1 = str(myPr[1])
            if myPr0 != '.':
                while idx1 < len_s1:
                    if s1['comp_id'][idx1] == myPr0:
                        seq_id1.append(s1['seq_id'][idx1])
                        comp_id1.append(myPr0)
                        idx1 += 1
                        break
                    idx1 += 1
            else:
                seq_id1.append(None)
                comp_id1.append('.')
            if myPr1 != '.':
                while idx2 < len_s2:
                    if s2['comp_id'][idx2] == myPr1:
                        seq_id2.append(s2['seq_id'][idx2])
                        comp_id2.append(myPr1)
                        idx2 += 1
                        break
                    idx2 += 1
            else:
                seq_id2.append(None)
                comp_id2.append('.')
        seq_id_conv_dict = {str(_s2): str(_s1) for _s1, _s2
                            in zip(seq_id1, seq_id2) if _s1 is not None and _s2 is not None}
        if s1['seq_id'] != list(range(s1['seq_id'][0], s1['seq_id'][-1] + 1))\
           and not any(k for k in seq_id_conv_dict.keys() if seq_id_conv_dict[k] != k):
            s2['seq_id'] = s1['seq_id']
            ref_code = test_code
            mid_code = getMiddleCode(ref_code, test_code)
            ref_gauge_code = test_gauge_code
        else:
            chain_id2 = chain_id
            if mapping is not None and chain_id in mapping.values():
                chain_id2 = next(k for k, v in mapping.items() if v == chain_id)
            self.__fixSeqIdInLoop(file_list_id, file_type, content_subtype, sf_framecode, chain_id2, seq_id_conv_dict)
            s2['seq_id'] = s1['seq_id']
            ref_code = getOneLetterCodeCanSequence(comp_id1)
            test_code = getOneLetterCodeCanSequence(comp_id2)
            mid_code = getMiddleCode(ref_code, test_code)
            ref_gauge_code = getGaugeCode(seq_id1)
            test_gauge_code = ref_gauge_code
            if ' ' in ref_gauge_code:
                for p, g in enumerate(ref_gauge_code):
                    if g == ' ':
                        ref_code = ref_code[0:p] + '-' + ref_code[p + 1:]
            if ' ' in test_gauge_code:
                for p, g in enumerate(test_gauge_code):
                    if g == ' ':
                        test_code = test_code[0:p] + '-' + test_code[p + 1:]

        return {'ref_seq_id': s1['seq_id'], 'test_seq_id': s2['seq_id'],
                'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                'test_code': test_code, 'test_gauge_code': test_gauge_code}

    def __fixChainIdInLoop(self, file_list_id, file_type, content_subtype, sf_framecode, chain_id, _chain_id):
        """ Fix chain ID of interesting loop.
        """

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
            lp_category = '_Assigned_peak_chem_shift'

        if self.__star_data_type[file_list_id] == 'Loop':

            sf = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, _chain_id)

        elif self.__star_data_type[file_list_id] == 'Saveframe':
            sf = self.__star_data[file_list_id]

            if get_first_sf_tag(sf, 'sf_framecode') == sf_framecode:
                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, _chain_id)

        else:

            for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                if get_first_sf_tag(sf, 'sf_framecode') != sf_framecode:
                    continue

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, _chain_id)

    def __fixChainIdInLoop__(self, file_list_id, file_type, content_subtype, sf, lp_category, chain_id, _chain_id):
        """ Fix sequence ID of interesting loop.
        """

        uniq_chain_ids = self.report.getChainIdsForSameEntity() is None

        chain_id_name = 'chain_code' if file_type == 'nef' else 'Entity_assembly_ID'
        entity_id_name = None if file_type == 'nef' else 'Entity_ID'

        max_dim = 2

        if content_subtype in ('poly_seq', 'dist_restraint', 'rdc_restraint'):
            max_dim = 3

        elif content_subtype == 'dihed_restraint':
            max_dim = 5

        elif content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                num_dim = int(_num_dim)

                if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    raise ValueError()

            except ValueError:  # raised error already at __testIndexConsistency()
                return

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if max_dim == 2:

            chain_id_col = loop.tags.index(chain_id_name) if chain_id_name in loop.tags else -1
            entity_id_col = -1
            if entity_id_name is not None:
                entity_id_col = loop.tags.index(entity_id_name) if entity_id_name in loop.tags else -1

            if chain_id_col == -1:
                return

            for row in loop:

                if row[chain_id_col] != chain_id:
                    continue

                row[chain_id_col] = _chain_id

                if uniq_chain_ids and entity_id_col != -1:
                    row[entity_id_col] = _chain_id

        else:

            for i in range(1, max_dim):

                _chain_id_name = chain_id_name + '_' + str(i)
                _entity_id_name = None if entity_id_name is None else entity_id_name + '_' + str(i)

                chain_id_col = loop.tags.index(_chain_id_name) if _chain_id_name in loop.tags else -1
                entity_id_col = -1
                if _entity_id_name is not None:
                    entity_id_col = loop.tags.index(_entity_id_name) if _entity_id_name in loop.tags else -1

                if chain_id_col == -1:
                    continue

                for row in loop:

                    if row[chain_id_col] != chain_id:
                        continue

                    row[chain_id_col] = _chain_id

                    if uniq_chain_ids and entity_id_col != -1:
                        row[entity_id_col] = _chain_id

    def __fixSeqIdInLoop(self, file_list_id, file_type, content_subtype, sf_framecode, chain_id, seq_id_conv_dict):
        """ Fix sequence ID of interesting loop.
        """

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
            lp_category = '_Assigned_peak_chem_shift'

        if self.__star_data_type[file_list_id] == 'Loop':

            sf = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id_conv_dict)

        elif self.__star_data_type[file_list_id] == 'Saveframe':
            sf = self.__star_data[file_list_id]

            if get_first_sf_tag(sf, 'sf_framecode') == sf_framecode:
                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id_conv_dict)

        else:

            for sf in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                if get_first_sf_tag(sf, 'sf_framecode') != sf_framecode:
                    continue

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id_conv_dict)

    def __fixSeqIdInLoop__(self, file_list_id, file_type, content_subtype, sf, lp_category, chain_id, seq_id_conv_dict):
        """ Fix sequence ID of interesting loop.
        """

        chain_id_name = 'chain_code' if file_type == 'nef' else 'Entity_assembly_ID'
        seq_id_name = 'sequence_code' if file_type == 'nef' else 'Comp_index_ID'
        seq_id_alt_name = None if file_type == 'nef' else 'Seq_ID'

        max_dim = 2

        if content_subtype in ('poly_seq', 'dist_restraint', 'rdc_restraint'):
            max_dim = 3

        elif content_subtype == 'dihed_restraint':
            max_dim = 5

        elif content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                num_dim = int(_num_dim)

                if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    raise ValueError()

            except ValueError:  # raised error already at __testIndexConsistency()
                return

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if max_dim == 2:

            chain_id_col = loop.tags.index(chain_id_name) if chain_id_name in loop.tags else -1
            seq_id_col = loop.tags.index(seq_id_name) if seq_id_name in loop.tags else -1
            seq_id_alt_col = -1
            if seq_id_alt_name is not None:
                seq_id_alt_col = loop.tags.index(seq_id_alt_name) if seq_id_alt_name in loop.tags else -1

            if chain_id_col == -1 or seq_id_col == -1:
                return

            for row in loop:

                if row[chain_id_col] != chain_id:
                    continue

                seq_id = row[seq_id_col]

                if seq_id in seq_id_conv_dict:
                    row[seq_id_col] = seq_id_conv_dict[seq_id]

                if seq_id_alt_col == -1:
                    continue

                seq_id_alt = row[seq_id_alt_col]

                if seq_id_alt in seq_id_conv_dict:
                    row[seq_id_alt_col] = seq_id_conv_dict[seq_id_alt]

        else:

            for i in range(1, max_dim):

                _chain_id_name = chain_id_name + '_' + str(i)
                _seq_id_name = seq_id_name + '_' + str(i)

                chain_id_col = loop.tags.index(_chain_id_name) if _chain_id_name in loop.tags else -1
                seq_id_col = loop.tags.index(_seq_id_name) if _seq_id_name in loop.tags else -1
                seq_id_alt_col = -1
                if seq_id_alt_name is not None:
                    _seq_id_alt_name = seq_id_alt_name + '_' + str(i)
                    seq_id_alt_col = loop.tags.index(_seq_id_alt_name) if _seq_id_alt_name in loop.tags else -1

                if chain_id_col == -1 or seq_id_col == -1:
                    continue

                for row in loop:

                    if row[chain_id_col] != chain_id:
                        continue

                    seq_id = row[seq_id_col]

                    if seq_id in seq_id_conv_dict:
                        row[seq_id_col] = seq_id_conv_dict[seq_id]

                    if seq_id_alt_col == -1:
                        continue

                    seq_id_alt = row[seq_id_alt_col]

                    if seq_id_alt in seq_id_conv_dict:
                        row[seq_id_alt_col] = seq_id_conv_dict[seq_id_alt]

    def __validateAtomNomenclature(self):
        """ Validate atom nomenclature using NEFTranslator and CCD.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if not has_poly_seq_in_loop:
                continue

            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            content_subtypes = ['poly_seq']
            content_subtypes.extend(polymer_sequence_in_loop.keys())

            for content_subtype in content_subtypes:

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if content_subtype == 'poly_seq':
                    lp_category = self.aux_lp_categories[file_type][content_subtype][0]

                    if lp_category not in self.__lp_category_list:
                        continue

                if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                    lp_category = '_Assigned_peak_chem_shift'

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)  # , first_comp_ids)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)  # , first_comp_ids)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)  # , first_comp_ids)

        return not self.report.isError()

    def __isNmrAtomName(self, comp_id, atom_id):
        """ Return whether a given atom_id uses NMR conventional atom name.
        """

        return ((atom_id in ('HN', 'CO') and self.__csStat.peptideLike(comp_id))
                or atom_id.startswith('Q') or atom_id.startswith('M')
                or atom_id.endswith('%') or atom_id.endswith('#')
                or self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id) == 0)

    def __getRepAtomIdInXplor(self, comp_id, atom_id):
        """ Return a representative atom ID in IUPAC atom nomenclature for a given atom_id in XPLOR atom nomenclautre.
        """

        _atom_id = self.__nefT.get_valid_star_atom_in_xplor(comp_id, atom_id, leave_unmatched=False)[0]

        return atom_id if len(_atom_id) == 0 else _atom_id[0]

    def __getAtomIdListInXplor(self, comp_id, atom_id):
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id in XPLOR atom nomenclature.
        """

        return self.__nefT.get_valid_star_atom_in_xplor(comp_id, atom_id, leave_unmatched=False)[0]

    def __getAtomIdListInXplorForLigandRemap(self, comp_id, atom_id, coord_atom_site):
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id in XPLOR atom nomenclature
            in reference to coordinates' alternative atom IDs. (DAOTHER-9286)
        """

        return self.__nefT.get_valid_star_atom_in_xplor_for_ligand_remap(comp_id, atom_id, coord_atom_site)[0]

    def __getRepAtomId(self, comp_id, atom_id):
        """ Return a representative atom ID in IUPAC atom nomenclature for a given atom_id.
        """

        _atom_id = self.__nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

        return atom_id if len(_atom_id) == 0 else _atom_id[0]

    def __getAtomIdList(self, comp_id, atom_id):
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id.
        """

        return self.__nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

    def __getAtomIdListWithAmbigCode(self, comp_id, atom_id, leave_unmatched=True):
        """ Return lists of atom ID, ambiguity_code, details in IUPAC atom nomenclature for a given conventional NMR atom name.
            @see: NEFTranslator.get_valid_star_atom()
        """

        return self.__nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=leave_unmatched)

    def __validateAtomNomenclature__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category):  # , first_comp_ids):
        """ Validate atom nomenclature using NEFTranslator and CCD.
        """

        try:

            if file_type == 'nef':  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
                pairs = self.__nefT.get_nef_comp_atom_pair(sf, lp_category,
                                                           allow_empty=content_subtype in ('chem_shift', 'spectral_peak'))[0]
            else:  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
                pairs = self.__nefT.get_star_comp_atom_pair(sf, lp_category,
                                                            allow_empty=content_subtype in ('chem_shift', 'spectral_peak'))[0]

            for pair in pairs:
                comp_id = pair['comp_id']
                atom_ids = pair['atom_id']

                # standard residue
                if comp_id in monDict3:

                    if file_type == 'nef':

                        _atom_ids = []
                        for atom_id in atom_ids:

                            if atom_id in emptyValue:
                                continue

                            _atom_id = self.__nefT.get_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

                            if len(_atom_id) == 0:

                                if self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                   and atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                    continue

                                if self.__remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                    continue

                                if self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                   and atom_id == "HO5'":
                                    continue

                                err = f"Invalid atom_id {atom_id!r} (comp_id {comp_id!r}) in a loop {lp_category}."

                                self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {err}\n")

                            else:
                                _atom_ids.extend(_atom_id)

                        atom_ids = sorted(set(_atom_ids))

                    for atom_id in atom_ids:

                        if atom_id in emptyValue:
                            continue

                        if self.__remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                            continue

                        if self.__csStat.peptideLike(comp_id):
                            if atom_id.upper() == 'HN':
                                self.__fixAtomNomenclature(comp_id, {atom_id: 'H'})
                                continue
                            if atom_id.upper() == 'CO':
                                self.__fixAtomNomenclature(comp_id, {atom_id: 'C'})
                                continue

                            _atom_id = self.__nefT.get_star_atom(comp_id, translateToStdAtomName(atom_id, comp_id, ccU=self.__ccU), leave_unmatched=False)[0]
                            if len(_atom_id) == 1 and atom_id != _atom_id[0]:
                                self.__fixAtomNomenclature(comp_id, {atom_id: _atom_id[0]})
                                continue

                        elif len(atom_id) > 2 and atom_id.endswith('"') and atom_id[-2].isdigit():  # 7zew, 7zex: H5" -> H5''
                            self.__fixAtomNomenclature(comp_id, {atom_id: atom_id[:-1] + "''"})
                            continue

                        atom_id_ = atom_id

                        if (file_type == 'nef' or not self.__combined_mode or self.__transl_pseudo_name) and self.__isNmrAtomName(comp_id, atom_id):
                            atom_id_ = self.__getRepAtomId(comp_id, atom_id)

                            if file_type == 'nmr-star' and self.__combined_mode and self.__transl_pseudo_name and atom_id != atom_id_:

                                warn = f"Conventional psuedo atom {comp_id}:{atom_id} is translated to {atom_id_!r} "\
                                    "according to the IUPAC atom nomenclature."

                                self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                                self.__fixAtomNomenclature(comp_id, {atom_id: atom_id_})

                        if not self.__nefT.validate_comp_atom(comp_id, atom_id_):

                            if self.__csStat.peptideLike(comp_id) and atom_id_.startswith('H') and atom_id_.endswith('1') and\
                               self.__nefT.validate_comp_atom(comp_id, atom_id_[:-1] + '2') and self.__nefT.validate_comp_atom(comp_id, atom_id_[:-1] + '3'):

                                _atom_id_ = atom_id_[:-1]
                                _atom_id_1 = _atom_id_ + '1'
                                _atom_id_2 = _atom_id_ + '2'
                                _atom_id_3 = _atom_id_ + '3'

                                warn = f"{comp_id}:{_atom_id_1}/{_atom_id_2} should be {comp_id}:{_atom_id_3}/{_atom_id_2} "\
                                    "according to the IUPAC atom nomenclature, respectively."

                                self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                                # @see: https://bmrb.io/ref_info/atom_nom.tbl
                                self.__fixAtomNomenclature(comp_id, {_atom_id_1: _atom_id_3})

                            elif self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                    and atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                pass

                            elif self.__remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                pass

                            elif self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                    and atom_id == "HO5'":
                                pass

                            else:
                                is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(comp_id)

                                if is_valid:
                                    if cc_rel_status != 'REL':
                                        cc_name = f"(Not available due to CCD status code {cc_rel_status})"
                                cc_name = '' if cc_name is None else ', ' + cc_name

                                err = f"Invalid atom_id {atom_id!r} (comp_id {comp_id!r}{cc_name}) in a loop {lp_category}."

                                self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {err}\n")

                # non-standard residue
                else:

                    if self.__ccU.updateChemCompDict(comp_id):  # matches with comp_id in CCD

                        ref_atom_ids = [a[self.__ccU.ccaAtomId] for a in self.__ccU.lastAtomList]  # if a[self.__ccU.ccaLeavingAtomFlag] != 'Y']
                        unk_atom_ids = []

                        for atom_id in atom_ids:

                            if atom_id in emptyValue:
                                continue

                            if file_type == 'nef':
                                _atom_id = self.__nefT.get_star_atom(comp_id, atom_id, leave_unmatched=False)[0]
                                if len(_atom_id) > 0:
                                    atom_id = _atom_id[0]

                            if atom_id not in ref_atom_ids:

                                if self.__remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                    continue

                                unk_atom_ids.append(atom_id)

                        if len(unk_atom_ids) > 0:
                            is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(comp_id)

                            if is_valid:
                                if cc_rel_status != 'REL':
                                    cc_name = f"(Not available due to CCD status code {cc_rel_status})"
                            cc_name = '' if cc_name is None else ', ' + cc_name

                            warn = f"Unknown atom_id {unk_atom_ids!r} (comp_id {comp_id!r}{cc_name})."

                            self.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                        ref_elems = set(a[self.__ccU.ccaTypeSymbol] for a in self.__ccU.lastAtomList if a[self.__ccU.ccaLeavingAtomFlag] != 'Y')

                        for elem in ref_elems:
                            if elem in PARAMAGNETIC_ELEMENTS or elem in FERROMAGNETIC_ELEMENTS:
                                self.report.setDiamagnetic(False)
                                break

                        for atom_id in atom_ids:

                            if atom_id in emptyValue:
                                continue

                            if self.__remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                continue

                            if self.__csStat.peptideLike(comp_id):
                                if atom_id.upper() == 'HN':
                                    self.__fixAtomNomenclature(comp_id, {atom_id: 'H'})
                                    continue
                                if atom_id.upper() == 'CO':
                                    self.__fixAtomNomenclature(comp_id, {atom_id: 'C'})
                                    continue

                            elif len(atom_id) > 2 and atom_id.endswith('"') and atom_id[-2].isdigit():  # 7zew, 7zex: H5" -> H5''
                                self.__fixAtomNomenclature(comp_id, {atom_id: atom_id[:-1] + "''"})
                                continue

                            atom_id_ = atom_id

                            if (file_type == 'nef' or not self.__combined_mode or self.__transl_pseudo_name) and self.__isNmrAtomName(comp_id, atom_id):
                                atom_id_ = self.__getRepAtomId(comp_id, atom_id)

                                if file_type == 'nmr-star' and self.__combined_mode and self.__transl_pseudo_name and atom_id != atom_id_:

                                    warn = f"Conventional psuedo atom {comp_id}:{atom_id} is translated to {atom_id_!r} "\
                                        "according to the IUPAC atom nomenclature."

                                    self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                                    self.__fixAtomNomenclature(comp_id, {atom_id: atom_id_})

                    else:
                        pass

            if file_type == 'nmr-star':

                try:

                    peptide_only = all(len(pair['comp_id']) == 3 and pair['comp_id'] in monDict3 for pair in pairs)

                    auth_pairs = self.__nefT.get_star_auth_comp_atom_pair(sf, lp_category)[0]

                    for auth_pair in auth_pairs:
                        auth_comp_id = auth_pair['comp_id']
                        if peptide_only and len(auth_comp_id) == 1:
                            comp_id = next((k for k, v in monDict3.items() if v == auth_comp_id), auth_comp_id)
                        else:
                            comp_id = auth_comp_id
                        comp_id = translateToStdResName(comp_id, ccU=self.__ccU)
                        auth_atom_ids = auth_pair['atom_id']

                        # standard residue
                        if comp_id in monDict3:

                            self.__ccU.updateChemCompDict(comp_id)
                            ref_atom_ids = [a[self.__ccU.ccaAtomId] for a in self.__ccU.lastAtomList]

                            _auth_atom_ids = []
                            for auth_atom_id in auth_atom_ids:

                                if auth_atom_id in emptyValue:
                                    continue

                                _auth_atom_id = translateToStdAtomName(auth_atom_id, comp_id, ref_atom_ids)

                                auth_atom_ids = self.__getAtomIdList(comp_id, _auth_atom_id)

                                if len(auth_atom_ids) > 0:
                                    _auth_atom_ids.extend(auth_atom_ids)

                                else:

                                    if self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                       and _auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                        continue

                                    if self.__remediation_mode and _auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    auth_atom_ids = self.__getAtomIdListInXplor(comp_id, _auth_atom_id)

                                    if len(auth_atom_ids) > 0:
                                        _auth_atom_ids.extend(auth_atom_ids)

                                    else:

                                        warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {auth_comp_id})."

                                        self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                            auth_atom_ids = sorted(set(_auth_atom_ids))

                            for auth_atom_id in auth_atom_ids:

                                if auth_atom_id in emptyValue:
                                    continue

                                if not self.__nefT.validate_comp_atom(comp_id,
                                                                      translateToStdAtomName(auth_atom_id, comp_id, ref_atom_ids)):

                                    if self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                       and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                        continue

                                    if self.__remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {auth_comp_id})."

                                    self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                        # non-standard residue
                        else:
                            has_comp_id = False

                            for pair in pairs:

                                if pair['comp_id'] != comp_id:
                                    continue

                                has_comp_id = True

                                atom_ids = pair['atom_id']

                                if (set(auth_atom_ids) | set(atom_ids)) != set(atom_ids):

                                    for auth_atom_id in (set(auth_atom_ids) | set(atom_ids)) - set(atom_ids):

                                        if auth_atom_id in emptyValue:
                                            continue

                                        if self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                           and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                            continue

                                        if self.__remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                            continue

                                        if self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                           and atom_id == "HO5'":
                                            continue

                                        warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {comp_id}, non-standard residue)."

                                        self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                                break

                            if not has_comp_id:

                                for auth_atom_id in auth_atom_ids:

                                    if auth_atom_id in emptyValue:
                                        continue

                                    if self.__nonblk_bad_nterm and self.__csStat.peptideLike(comp_id)\
                                       and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):  # and comp_id in first_comp_ids:
                                        continue

                                    if self.__remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__remediation_mode and self.__csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {comp_id}, non-standard residue)."

                                    self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Warning  - {warn}\n")

                except LookupError:
                    # """
                    # self.report.error.appendDescription('missing_mandatory_item',
                    #                                     {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                    #                                      'description': str(e).strip("'")})
                    # self.report.setError()

                    # self.__lfh.write("+NmrDpUtility.__validateAtomNomenclature() ++ LookupError  - "
                    #                  f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")
                    # """
                    pass

                except ValueError as e:

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': str(e).strip("'")})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ ValueError  - {str(e)}\n")

                except UserWarning as e:

                    errs = str(e).strip("'").split('\n')

                    for err in errs:

                        if len(err) == 0:
                            continue

                        if err.startswith('[Invalid data]'):

                            p = err.index(']') + 2
                            err = err[p:]

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ ValueError  - {err}\n")

                        else:

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomNomenclature() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {err}\n")

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomNomenclature() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {str(e)}\n")

        except LookupError as e:

            self.report.error.appendDescription('missing_mandatory_item',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            self.__lfh.write("+NmrDpUtility.__validateAtomNomenclature() ++ LookupError  - "
                             f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')

            for err in errs:

                if len(err) == 0:
                    continue

                if err.startswith('[Invalid data]'):

                    p = err.index(']') + 2
                    err = err[p:]

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ ValueError  - {err}\n")

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomNomenclature() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomNomenclature() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ Error  - {str(e)}\n")

    def __fixAtomNomenclature(self, comp_id, atom_id_conv_dict):
        """ Fix atom nomenclature.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            # file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype == ['entry_info', 'entity', 'chem_shift_ref']:
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if content_subtype == 'poly_seq':
                    lp_category = self.aux_lp_categories[file_type][content_subtype][0]

                if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                    lp_category = '_Assigned_peak_chem_shift'

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    # sf_framecode = ''

                    self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf, lp_category, comp_id, atom_id_conv_dict)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    # sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf, lp_category, comp_id, atom_id_conv_dict)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        # sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf, lp_category, comp_id, atom_id_conv_dict)

    def __fixAtomNomenclature__(self, file_list_id, file_type, content_subtype, sf, lp_category, comp_id, atom_id_conv_dict):
        """ Fix atom nomenclature.
        """

        comp_id_name = 'residue_name' if file_type == 'nef' else 'Comp_ID'
        atom_id_name = 'atom_name' if file_type == 'nef' else 'Atom_ID'

        max_dim = 2

        if content_subtype in ('poly_seq', 'dist_restraint', 'rdc_restraint'):
            max_dim = 3

        elif content_subtype == 'dihed_restraint':
            max_dim = 5

        elif content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                num_dim = int(_num_dim)

                if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    raise ValueError()

            except ValueError:  # raised error already at __testIndexConsistency()
                return

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if max_dim == 2:

            comp_id_col = loop.tags.index(comp_id_name) if comp_id_name in loop.tags else -1
            atom_id_col = loop.tags.index(atom_id_name) if atom_id_name in loop.tags else -1

            if comp_id_col == -1 or atom_id_col == -1:
                return

            for row in loop:

                _comp_id = row[comp_id_col].upper()

                if _comp_id != comp_id:
                    continue

                atom_id = row[atom_id_col]

                if atom_id in atom_id_conv_dict:
                    row[atom_id_col] = atom_id_conv_dict[atom_id]

        else:

            for j in range(1, max_dim):

                _comp_id_name = comp_id_name + '_' + str(j)
                _atom_id_name = atom_id_name + '_' + str(j)

                comp_id_col = loop.tags.index(_comp_id_name) if _comp_id_name in loop.tags else -1
                atom_id_col = loop.tags.index(_atom_id_name) if _atom_id_name in loop.tags else -1

                if comp_id_col == -1 or atom_id_col == -1:
                    continue

                for row in loop:

                    _comp_id = row[comp_id_col].upper()

                    if _comp_id != comp_id:
                        continue

                    atom_id = row[atom_id_col]

                    if atom_id in atom_id_conv_dict:
                        row[atom_id_col] = atom_id_conv_dict[atom_id]

    def __validateAtomTypeOfCsLoop(self):
        """ Validate atom type, isotope number on assigned chemical shifts.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'chem_shift'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                self.__validateAtomTypeOfCsLoop__(file_name, file_type, sf, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__validateAtomTypeOfCsLoop__(file_name, file_type, sf, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__validateAtomTypeOfCsLoop__(file_name, file_type, sf, sf_framecode, lp_category)

        return not self.report.isError()

    def __validateAtomTypeOfCsLoop__(self, file_name, file_type, sf, sf_framecode, lp_category):
        """ Validate atom type, isotope number on assigned chemical shifts.
        """

        if not self.__combined_mode:
            return

        try:

            # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
            if file_type == 'nef':
                a_types = self.__nefT.get_nef_atom_type_from_cs_loop(sf, allow_empty=True)[0]
            else:
                a_types = self.__nefT.get_star_atom_type_from_cs_loop(sf, allow_empty=True)[0]

            for a_type in a_types:
                atom_type = a_type['atom_type']
                isotope_nums = a_type['isotope_number']
                atom_ids = a_type['atom_id']

                if atom_type not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys():

                    err = f"Invalid atom_type {atom_type!r} in a loop {lp_category}."

                    self.report.error.appendDescription('invalid_atom_type',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

                else:

                    for isotope_num in isotope_nums:
                        if isotope_num not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type]:

                            err = f"Invalid isotope number {str(isotope_num)!r} (atom_type {atom_type}, "\
                                f"allowed isotope number {ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type]}) in a loop {lp_category}."

                            self.report.error.appendDescription('invalid_isotope_number',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

                    for atom_id in atom_ids:
                        if not atom_id.startswith(atom_type):

                            if self.__remediation_mode and 1 in isotope_nums and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751, 9520
                                continue

                            err = f"Invalid atom_id {atom_id!r} (atom_type {atom_type!r}) in a loop {lp_category}."

                            self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

        except LookupError as e:

            if not self.__resolve_conflict:
                self.report.error.appendDescription('missing_mandatory_item',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                self.__lfh.write("+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ LookupError  - "
                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')

            for err in errs:

                if len(err) == 0:
                    continue

                if err.startswith('[Invalid data]'):

                    p = err.index(']') + 2
                    err = err[p:]

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ ValueError  - {err}\n")

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCsLoop() ++ Error  - {str(e)}\n")

    def __validateAmbigCodeOfCsLoop(self):
        """ Validate ambiguity code on assigned chemical shifts.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'chem_shift'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            # NEF file has no ambiguity code
            if file_type == 'nef':
                continue

            if not self.__combined_mode:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            modified = False

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                modified |= self.__validateAmbigCodeOfCsLoop__(file_name, sf, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                modified |= self.__validateAmbigCodeOfCsLoop__(file_name, sf, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    modified |= self.__validateAmbigCodeOfCsLoop__(file_name, sf, sf_framecode, lp_category)

            if modified:
                self.__depositNmrData()

        return not self.report.isError()

    def __validateAmbigCodeOfCsLoop__(self, file_name, sf, sf_framecode, lp_category):
        """ Validate ambiguity code on assigned chemical shifts.
        """

        try:

            need_set_id = False
            valid = True

            a_codes = self.__nefT.get_star_ambig_code_from_cs_loop(sf)[0]

            comp_ids_wo_ambig_code = []

            for a_code in a_codes:
                comp_id = a_code['comp_id']
                ambig_code = a_code['ambig_code']
                atom_ids = a_code['atom_id']

                if ambig_code is None:
                    comp_ids_wo_ambig_code.append(comp_id)

                elif ambig_code == 1 or ambig_code >= 4:
                    need_set_id |= ambig_code in (4, 5, 6, 9)

                # ambig_code is 2 (geminal atoms) or 3 (aromatic ring atoms in opposite side)
                else:

                    for atom_id in atom_ids:

                        _atom_id = atom_id

                        if self.__isNmrAtomName(comp_id, atom_id):
                            _atom_id = self.__getRepAtomId(comp_id, atom_id)

                        allowed_ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)

                        if ambig_code > allowed_ambig_code > 0:

                            if allowed_ambig_code < 1:

                                if self.__remediation_mode:
                                    pass

                                else:

                                    warn = f"Ambiguity code {str(ambig_code)!r} (comp_id {comp_id}, atom_id {atom_id}) "\
                                        "should be '1' according to the BMRB definition."

                                    self.report.warning.appendDescription('ambiguity_code_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Warning  - {warn}\n")

                                    valid = False

                            else:

                                if self.__remediation_mode:
                                    pass

                                else:

                                    err = f"Invalid ambiguity code {str(ambig_code)!r} (comp_id {comp_id}, atom_id {atom_id}, "\
                                        f"allowed ambig_code {[1, allowed_ambig_code, 4, 5, 6, 9]}) in a loop {lp_category}."

                                    self.report.error.appendDescription('invalid_ambiguity_code',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Error  - {err}\n")

                                    valid = False

            if len(comp_ids_wo_ambig_code) > 0:

                warn = f"Missing ambiguity code for the following residues {comp_ids_wo_ambig_code}."

                self.report.warning.appendDescription('missing_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                       'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Warning  - {warn}\n")

                valid = False

            if need_set_id and valid:

                list_id = get_first_sf_tag(sf, 'ID')

                try:

                    if __pynmrstar_v3_2__:
                        lp = sf.get_loop(lp_category)
                    else:
                        lp = sf.get_loop_by_category(lp_category)

                    ambig_code_col = lp.tags.index('Ambiguity_code')
                    ambig_set_id_col = lp.tags.index('Ambiguity_set_ID')

                    id_col = lp.tags.index('ID')
                    chain_id_col = lp.tags.index('Entity_assembly_ID')
                    seq_id_col = lp.tags.index('Comp_index_ID')
                    atom_type_col = lp.tags.index('Atom_type')

                    aux_lp_category = self.aux_lp_categories['nmr-star']['chem_shift'][0]

                    if any(aux_loop for aux_loop in sf if aux_loop.category == aux_lp_category):

                        if __pynmrstar_v3_2__:
                            aux_loop = sf.get_loop(aux_lp_category)
                        else:
                            aux_loop = sf.get_loop_by_category(aux_lp_category)

                        del sf[aux_loop]

                    aux_lp = pynmrstar.Loop.from_scratch(aux_lp_category)

                    aux_items = ['Ambiguous_shift_set_ID', 'Atom_chem_shift_ID', 'Entry_ID', 'Assigned_chem_shift_list_ID']

                    aux_tags = [aux_lp_category + '.' + item for item in aux_items]

                    for tag in aux_tags:
                        aux_lp.add_tag(tag)

                    inter_residue_seq_id = {}

                    for _row in lp:

                        ambig_code = _row[ambig_code_col]

                        if ambig_code in emptyValue:
                            continue

                        if isinstance(ambig_code, str):
                            ambig_code = int(ambig_code)

                        if ambig_code not in (5, 6, 9):
                            continue

                        chain_id = _row[chain_id_col]
                        seq_id = _row[seq_id_col]

                        if chain_id not in inter_residue_seq_id:
                            inter_residue_seq_id[chain_id] = set()

                        inter_residue_seq_id[chain_id].add(seq_id)

                    aux_index_id = 0
                    ambig_shift_set_id = {}

                    for _idx, _row in enumerate(lp):

                        ambig_code = _row[ambig_code_col]

                        if ambig_code in emptyValue:
                            continue

                        if isinstance(ambig_code, str):
                            ambig_code = int(ambig_code)

                        if ambig_code not in (4, 5):
                            continue

                        chain_id = _row[chain_id_col]
                        seq_id = _row[seq_id_col]
                        atom_type = _row[atom_type_col]

                        if ambig_code == 4:
                            key = (chain_id, str(seq_id), atom_type, ambig_code)
                        else:
                            key = (chain_id, str(inter_residue_seq_id[chain_id]), atom_type, ambig_code)

                        if key not in ambig_shift_set_id:
                            aux_index_id += 1
                            ambig_shift_set_id[key] = aux_index_id

                        lp.data[_idx][ambig_set_id_col] = ambig_shift_set_id[key]

                        _aux_row = [None] * 4
                        _aux_row[0], _aux_row[1], _aux_row[2], _aux_row[3] =\
                            ambig_shift_set_id[key], _row[id_col], self.__entry_id, list_id

                        aux_lp.add_data(_aux_row)

                    if len(aux_lp) > 0:
                        sf.add_loop(aux_lp)
                        return True

                except (KeyError, IndexError, ValueError):
                    pass

        except LookupError as e:

            if not self.__resolve_conflict:
                self.report.error.appendDescription('missing_mandatory_item',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                self.__lfh.write("+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ LookupError  - "
                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')

            for err in errs:

                if len(err) == 0:
                    continue

                if err.startswith('[Invalid data]'):

                    p = err.index(']') + 2
                    err = err[p:]

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ ValueError  - {err}\n")

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCsLoop() ++ Error  - {str(e)}\n")

        return False

    def __testIndexConsistency(self):
        """ Perform consistency test on index of interesting loops.
        """

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_type'] == 'nmr-restraints' or input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype == 'entity':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                index_tag = self.index_tags[file_type][content_subtype]

                if index_tag is None:
                    continue

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__testIndexConsistency__(file_name, sf, sf_framecode, lp_category, index_tag)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__testIndexConsistency__(file_name, sf, sf_framecode, lp_category, index_tag)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        self.__testIndexConsistency__(file_name, sf, sf_framecode, lp_category, index_tag)

        return not self.report.isError()

    def __testIndexConsistency__(self, file_name, sf, sf_framecode, lp_category, index_tag):
        """ Perform consistency test on index of interesting loops.
        """

        try:

            indices = self.__nefT.get_index(sf, lp_category, index_id=index_tag)[0]

            if indices != list(range(1, len(indices) + 1)):

                warn = f"Index of loop, '{lp_category}.{index_tag}', should be ordinal numbers."

                self.report.warning.appendDescription('disordered_index',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                       'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ Warning  - {warn}\n")

        except KeyError as e:

            self.report.error.appendDescription('duplicated_index',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ KeyError  - {str(e)}\n")

        except LookupError:
            # """
            # self.report.error.appendDescription('missing_mandatory_item',
            #                                     {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
            #                                      'description': str(e).strip("'")})
            # self.report.setError()

            # self.__lfh.write("+NmrDpUtility.__testIndexConsistency() ++ LookupError  - "
            #                  f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")
            # """
            pass

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')

            for err in errs:

                if len(err) == 0:
                    continue

                if err.startswith('[Invalid data]'):

                    p = err.index(']') + 2
                    err = err[p:]

                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ ValueError  - {err}\n")

                elif err.startswith('[Too big loop]'):
                    continue

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testIndexConsistency() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testIndexConsistency() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ Error  - {str(e)}\n")

    def __testDataConsistencyInLoop(self):
        """ Perform consistency test on data of interesting loops.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('entry_info', 'entity'):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, 1)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, 1)

                else:

                    parent_pointer = 0

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        parent_pointer += 1

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, parent_pointer)

        return self.report.getTotalErrors() == __errors

    def __testDataConsistencyInLoop__(self, file_list_id, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, parent_pointer):
        """ Perform consistency test on data of interesting loops.
        """

        allowed_tags = self.allowed_tags[file_type][content_subtype]
        disallowed_tags = None

        if content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                num_dim = int(_num_dim)

                if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    raise ValueError()

            except ValueError:  # raised error already at __testIndexConsistency()
                return

            max_dim = num_dim + 1

            key_items = []
            for dim in range(1, max_dim):
                for k in self.pk_key_items[file_type]:
                    if k['type'] == 'float':  # position
                        _k = copy.copy(k)
                        if '%s' in k['name']:
                            _k['name'] = k['name'] % dim
                        key_items.append(_k)
            for k in self.pk_key_items[file_type]:
                if k['type'] == 'positive-int':  # peak_id
                    key_items.append(k)

            data_items = []
            for d in self.data_items[file_type][content_subtype]:
                data_items.append(d)
            for dim in range(1, max_dim):
                for d in self.pk_data_items[file_type]:
                    _d = copy.copy(d)
                    if '%s' in d['name']:
                        _d['name'] = d['name'] % dim
                    if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                        _d['default-from'] = d['default-from'] % dim
                    data_items.append(_d)

            if max_dim < MAX_DIM_NUM_OF_SPECTRA:
                disallowed_tags = []
                for dim in range(max_dim, MAX_DIM_NUM_OF_SPECTRA):
                    for t in self.spectral_peak_disallowed_tags[file_type]:
                        if '%s' in t:
                            t = t % dim
                        disallowed_tags.append(t)

                if self.__bmrb_only:
                    loop = sf.get_loop_by_category(lp_category)
                    disallowed_tags = list(set(loop.tags) & set(disallowed_tags))
                    loop.remove_tag(disallowed_tags)

        else:

            key_items = self.key_items[file_type][content_subtype]
            data_items = self.data_items[file_type][content_subtype]

            if file_type == 'nmr-star' and content_subtype == 'ccr_dd_restraint':
                loop = sf.get_loop_by_category(lp_category)
                if 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                    key_items = copy.copy(key_items)
                    key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                    if key_item is not None:
                        key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

        lp_data = None

        try:

            lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                             allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                             test_on_index=True, enforce_non_zero=True, enforce_sign=True, enforce_range=True, enforce_enum=True,
                                             enforce_allowed_tags=(file_type == 'nmr-star' and not self.__bmrb_only),
                                             excl_missing_data=self.__excl_missing_data)[0]

            self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

        except KeyError as e:

            self.report.error.appendDescription('multiple_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ KeyError  - {str(e)}\n")

        except LookupError as e:

            item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

            self.report.error.appendDescription(item,
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            self.__lfh.write("+NmrDpUtility.__testDataConsistencyInLoop() ++ LookupError  - "
                             f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            warns = str(e).strip("'").split('\n')

            has_multiple_data = False
            has_bad_pattern = False

            for warn in warns:

                if len(warn) == 0:
                    continue

                zero = warn.startswith('[Zero value error]')
                nega = warn.startswith('[Negative value error]')
                rang = warn.startswith('[Range value error]')
                enum = warn.startswith('[Enumeration error]')
                mult = warn.startswith('[Multiple data]')
                remo = warn.startswith('[Remove bad pattern]')
                clea = warn.startswith('[Clear bad pattern]')

                if zero or nega or range or enum or mult or remo or clea:

                    p = warn.index(']') + 2
                    warn = warn[p:]

                    if zero or nega or rang:
                        item = 'unusual_data'
                    elif enum:
                        item = 'enum_mismatch'
                    elif remo:
                        if content_subtype == 'chem_shift':
                            warn += ' Your unassigned chemical shifts have been removed.'
                            item = 'incompletely_assigned_chemical_shift'
                        else:
                            item = 'insufficient_data'
                        has_bad_pattern = True
                    elif clea:
                        if content_subtype == 'chem_shift':
                            warn += ' Partially assiged chemical shifts should be resolved or removed.'
                            item = 'incompletely_assigned_chemical_shift'
                        elif content_subtype.startswith('spectral_peak'):
                            warn += ' Unassigned spectral peaks can be included in your peak list(s).'
                            item = 'incompletely_assigned_spectral_peak'
                        else:
                            item = 'insufficient_data'
                    elif self.__resolve_conflict:
                        item = 'redundant_data'
                        has_multiple_data = True
                    else:
                        item = 'multiple_data'

                    if zero or nega or rang or enum or remo or clea or self.__resolve_conflict:

                        self.report.warning.appendDescription(item,
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ Warning  - {warn}\n")

                    else:

                        self.report.error.appendDescription(item,
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ KeyError  - {warn}\n")

                else:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInLoop() ++ Error  - " + warn)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ Error  - {warn}\n")

            # try to parse data without constraints

            if has_multiple_data:
                conflict_id = self.__nefT.get_conflict_id(sf, lp_category, key_items)[0]

                if len(conflict_id) > 0:
                    if __pynmrstar_v3_2__:
                        loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
                    else:
                        loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

                    for lcid in conflict_id:
                        del loop.data[lcid]

                    index_tag = self.index_tags[file_type][content_subtype]
                    if index_tag is not None:
                        index_col = loop.tags.index(index_tag) if index_tag in loop.tags else -1
                        if index_col != -1:
                            for idx, row in enumerate(loop, start=1):
                                row[index_col] = idx

            # try to parse data without bad patterns

            if has_bad_pattern:
                conflict_id = self.__nefT.get_bad_pattern_id(sf, lp_category, key_items, data_items)[0]

                if len(conflict_id) > 0:
                    if __pynmrstar_v3_2__:
                        loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
                    else:
                        loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

                    for lcid in conflict_id:
                        del loop.data[lcid]

            try:

                lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                                 allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                                 enforce_allowed_tags=(file_type == 'nmr-star' and not self.__bmrb_only),
                                                 excl_missing_data=self.__excl_missing_data)[0]

                self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

            except Exception:
                pass

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ Error  - {str(e)}\n")

    def __detectConflictDataInLoop(self):
        """ Detect redundant/inconsistent data of interesting loops.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    if self.__star_data_type[fileListId] == 'Loop':
                        sf = self.__star_data[fileListId]
                        sf_framecode = ''

                        self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

                    elif self.__star_data_type[fileListId] == 'Saveframe':
                        sf = self.__star_data[fileListId]
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

                    else:

                        for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                            if not any(loop for loop in sf.loops if loop.category == lp_category):
                                continue

                            self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

        return self.report.getTotalErrors() == __errors

    def __detectConflictDataInLoop__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category):
        """ Detect redundant/inconsistent data of interesting loops.
        """

        lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                        if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

        if lp_data is None or len(lp_data) == 0:
            return

        key_items = self.consist_key_items[file_type][content_subtype]

        if file_type == 'nmr-star' and content_subtype == 'ccr_dd_restraint':
            loop = sf.get_loop_by_category(lp_category)
            if 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                key_items = copy.copy(key_items)
                key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                if key_item is not None:
                    key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

        conflict_id_set = self.__nefT.get_conflict_id_set(sf, lp_category, key_items)[0]

        if conflict_id_set is None:
            return

        data_items = self.consist_data_items[file_type][content_subtype]
        index_tag = self.index_tags[file_type][content_subtype]
        id_tag = self.consist_id_tags[file_type][content_subtype]

        data_unit_name = 'atom pair'

        if content_subtype == 'dist_restraint':
            max_inclusive = DIST_UNCERT_MAX

        elif content_subtype == 'dihed_restraint':
            max_inclusive = ANGLE_UNCERT_MAX

            data_unit_name = 'dihedral angle'

            dh_item_names = self.item_names_in_dh_loop[file_type]
            chain_id_1_name = dh_item_names['chain_id_1']
            chain_id_2_name = dh_item_names['chain_id_2']
            chain_id_3_name = dh_item_names['chain_id_3']
            chain_id_4_name = dh_item_names['chain_id_4']
            seq_id_1_name = dh_item_names['seq_id_1']
            seq_id_2_name = dh_item_names['seq_id_2']
            seq_id_3_name = dh_item_names['seq_id_3']
            seq_id_4_name = dh_item_names['seq_id_4']
            comp_id_1_name = dh_item_names['comp_id_1']
            atom_id_1_name = dh_item_names['atom_id_1']
            atom_id_2_name = dh_item_names['atom_id_2']
            atom_id_3_name = dh_item_names['atom_id_3']
            atom_id_4_name = dh_item_names['atom_id_4']
            angle_type_name = dh_item_names['angle_type']

        elif content_subtype == 'rdc_restraint':
            max_inclusive = RDC_UNCERT_MAX

            data_unit_name = 'bond vector'

        for id_set in conflict_id_set:
            len_id_set = len(id_set)

            if len_id_set < 2:
                continue

            redundant = True

            for i in range(len_id_set - 1):

                for j in range(i + 1, len_id_set):

                    try:
                        row_1 = lp_data[id_set[i]]
                        row_2 = lp_data[id_set[j]]
                    except IndexError:
                        continue

                    conflict = False
                    inconsist = False

                    discrepancy = ''

                    for d in data_items:
                        dname = d['name']

                        if dname not in row_1:
                            continue

                        val_1 = row_1[dname]
                        val_2 = row_2[dname]

                        if val_1 is None and val_2 is None:
                            continue

                        if val_1 is None or val_2 is None:
                            redundant = False
                            continue

                        if val_1 == val_2:
                            continue

                        redundant = False

                        _val_1 = str(val_1) if val_1 >= 0.0 else '(' + str(val_1) + ')'
                        _val_2 = str(val_2) if val_2 >= 0.0 else '(' + str(val_2) + ')'

                        if content_subtype == 'dist_restraint':

                            r = abs(val_1 - val_2) / abs(val_1 + val_2)

                            if r >= self.r_conflicted_dist_restraint:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}|/|{_val_1}+{_val_2}| = {r:.1%} is out of acceptable range, "\
                                               f"{int(self.r_conflicted_dist_restraint * 100)} %, "
                                conflict = True

                            elif r >= self.r_inconsistent_dist_restraint:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}|/|{_val_1}+{_val_2}| = {r:.1%} is out of typical range, "\
                                               f"{int(self.r_inconsistent_dist_restraint * 100)} %, "
                                inconsist = True

                        else:

                            r = abs(val_1 - val_2)

                            if content_subtype == 'dihed_restraint':

                                if r > 180.0:
                                    if val_1 < val_2:
                                        r = abs(val_1 - (val_2 - 360.0))
                                    if val_1 > val_2:
                                        r = abs(val_1 - (val_2 + 360.0))

                                chain_id_1 = row_1[chain_id_1_name]
                                chain_id_2 = row_1[chain_id_2_name]
                                chain_id_3 = row_1[chain_id_3_name]
                                chain_id_4 = row_1[chain_id_4_name]
                                seq_id_1 = row_1[seq_id_1_name]
                                seq_id_2 = row_1[seq_id_2_name]
                                seq_id_3 = row_1[seq_id_3_name]
                                seq_id_4 = row_1[seq_id_4_name]
                                comp_id_1 = row_1[comp_id_1_name]
                                atom_id_1 = row_1[atom_id_1_name]
                                atom_id_2 = row_1[atom_id_2_name]
                                atom_id_3 = row_1[atom_id_3_name]
                                atom_id_4 = row_1[atom_id_4_name]
                                data_type = row_1[angle_type_name]

                                peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                                data_type = self.__getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                              chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                                                              chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4)[0]

                                if not data_type.startswith('phi') and not data_type.startswith('psi') and not data_type.startswith('omega'):
                                    continue

                            if r > max_inclusive:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}| = {r:.1f} is out of acceptable range, "\
                                               f"{max_inclusive}{'°' if content_subtype == 'dihed_restraint' else 'Hz'}, "
                                conflict = True

                            elif r > max_inclusive * self.inconsist_over_conflicted:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}| = {r:.1f} is out of typical range, "\
                                               f"{max_inclusive * self.inconsist_over_conflicted}{'°' if content_subtype == 'dihed_restraint' else 'Hz'}, "
                                inconsist = True

                    if conflict:

                        msg = '' if content_subtype != 'dihed_restraint' else angle_type_name + f" {row_1[angle_type_name]}, "
                        msg += self.__getReducedAtomNotations(key_items, row_1)

                        if index_tag in row_1:
                            warn = f"[Check rows of {index_tag} {row_1[index_tag]} vs {row_2[index_tag]}, {id_tag} {row_1[id_tag]} vs {row_2[id_tag]}] "
                        else:
                            warn = f"[Check rows of {index_tag} {id_set[i] + 1} vs {id_set[j] + 1}, {id_tag} {row_1[id_tag]} vs {row_2[id_tag]}] "
                        warn += f"Found conflict on restraints ({discrepancy[:-2]}) for the same {data_unit_name} ({msg})."

                        self.report.warning.appendDescription('conflicted_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn,
                                                               'sigma': float(f"{r / max_inclusive:.2f}")})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detetConflictDataInLoop() ++ Warning  - {warn}\n")

                    elif inconsist:

                        msg = '' if content_subtype != 'dihed_restraint' else angle_type_name + f" {row_1[angle_type_name]}, "
                        msg += self.__getReducedAtomNotations(key_items, row_1)

                        if index_tag in row_1:
                            warn = f"[Check rows of {index_tag} {row_1[index_tag]} vs {row_2[index_tag]}, {id_tag} {row_1[id_tag]} vs {row_2[id_tag]}] "
                        else:
                            warn = f"[Check rows of {index_tag} {id_set[i] + 1} vs {id_set[j] + 1}, {id_tag} {row_1[id_tag]} vs {row_2[id_tag]}] "
                        warn += f"Found discrepancy in restraints ({discrepancy[:-2]}) for the same {data_unit_name} ({msg})."

                        self.report.warning.appendDescription('inconsistent_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn,
                                                               'sigma': float(f"{r / max_inclusive:.2f}")})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detetConflictDataInLoop() ++ Warning  - {warn}\n")

            if redundant:

                idx_msg = index_tag + ' '
                if index_tag in lp_data[0]:
                    for row_id in id_set:
                        try:
                            idx_msg += f"{lp_data[row_id][index_tag]} vs "
                        except IndexError:
                            continue
                else:
                    for row_id in id_set:
                        idx_msg += f"{row_id + 1} vs "
                idx_msg = idx_msg[:-4] + ', '
                idx_msg += id_tag + ' '
                for row_id in id_set:
                    try:
                        idx_msg += f"{lp_data[row_id][id_tag]} vs "
                    except IndexError:
                        continue

                if not idx_msg.endswith(' vs '):
                    continue

                warn = f"[Check rows of {idx_msg[:-4]}] Found redundant restraints for the same {data_unit_name}."

                self.report.warning.appendDescription('redundant_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                       'description': warn})
                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detetConflictDataInLoop() ++ Warning  - {warn}\n")

    def __testNmrCovalentBond(self):
        """ Perform consistency test on data of auxiliary loops.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            if fileListId >= len(self.__star_data_type) or self.__star_data_type[fileListId] != 'Entry':
                continue

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if file_type not in self.item_names_in_cs_loop:
                continue

            item_names = self.item_names_in_cs_loop[file_type]
            chain_id_name = item_names['chain_id']
            seq_id_name = item_names['seq_id']
            comp_id_name = item_names['comp_id']
            atom_id_name = item_names['atom_id']
            value_name = item_names['value']

            item_names = self.item_names_in_rdc_loop[file_type]
            chain_id_1_name = item_names['chain_id_1']
            chain_id_2_name = item_names['chain_id_2']
            seq_id_1_name = item_names['seq_id_1']
            seq_id_2_name = item_names['seq_id_2']
            comp_id_1_name = item_names['comp_id_1']
            comp_id_2_name = item_names['comp_id_2']
            atom_id_1_name = item_names['atom_id_1']
            atom_id_2_name = item_names['atom_id_2']

            auth_chain_id_1_name = 'Auth_asym_ID_1'
            auth_chain_id_2_name = 'Auth_asym_ID_2'
            auth_seq_id_1_name = 'Auth_seq_ID_1'
            auth_seq_id_2_name = 'Auth_seq_ID_2'
            auth_atom_id_1_name = 'Auth_atom_ID_1'
            auth_atom_id_2_name = 'Auth_atom_ID_2'

            content_subtype = 'chem_shift'

            cs_sf_category = self.sf_categories[file_type][content_subtype]
            cs_lp_category = self.lp_categories[file_type][content_subtype]

            cs_lp_data = None

            for cs_sf in self.__star_data[fileListId].get_saveframes_by_category(cs_sf_category):
                cs_sf_framecode = get_first_sf_tag(cs_sf, 'sf_framecode')

                if not any(loop for loop in cs_sf.loops if loop.category == cs_lp_category):
                    continue

                cs_lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                   if lp['file_name'] == file_name and lp['sf_framecode'] == cs_sf_framecode), None)

                break

            content_subtype = 'poly_seq'

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            parent_pointer = 0

            for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                parent_pointer += 1

                for loop in sf.loops:

                    lp_category = loop.category

                    if lp_category is None:
                        continue

                    if (file_type == 'nef' and lp_category == '_nef_covalent_links') or (file_type == 'nmr-star' and lp_category == '_Bond'):

                        key_items = self.aux_key_items[file_type][content_subtype][lp_category]
                        data_items = self.aux_data_items[file_type][content_subtype][lp_category]
                        allowed_tags = self.aux_allowed_tags[file_type][content_subtype][lp_category]

                        try:

                            aux_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                                              allowed_tags, None, parent_pointer=parent_pointer,
                                                              test_on_index=False, enforce_non_zero=False, enforce_sign=False, enforce_range=False, enforce_enum=False,
                                                              enforce_allowed_tags=(file_type == 'nmr-star'),
                                                              excl_missing_data=self.__excl_missing_data)[0]

                            disulf_asm = []
                            other_asm = []

                            item_names = self.item_names_in_rdc_loop[file_type]
                            chain_id_1_name = item_names['chain_id_1']
                            chain_id_2_name = item_names['chain_id_2']
                            seq_id_1_name = item_names['seq_id_1']
                            seq_id_2_name = item_names['seq_id_2']
                            comp_id_1_name = item_names['comp_id_1']
                            comp_id_2_name = item_names['comp_id_2']
                            atom_id_1_name = item_names['atom_id_1']
                            atom_id_2_name = item_names['atom_id_2']

                            for row in aux_data:
                                chain_id_1 = row[chain_id_1_name]
                                seq_id_1 = row[seq_id_1_name]
                                comp_id_1 = row[comp_id_1_name]
                                atom_id_1 = row[atom_id_1_name]
                                chain_id_2 = row[chain_id_2_name]
                                seq_id_2 = row[seq_id_2_name]
                                comp_id_2 = row[comp_id_2_name]
                                atom_id_2 = row[atom_id_2_name]

                                if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                                    continue

                                atom_id_1_ = atom_id_1[0]
                                atom_id_2_ = atom_id_2[0]

                                if atom_id_1_ == 'S' and atom_id_2_ == 'S' and not atom_id_1.startswith('SE') and not atom_id_2.startswith('SE'):

                                    disulf = {}
                                    disulf['chain_id_1'] = chain_id_1
                                    disulf['seq_id_1'] = seq_id_1
                                    disulf['comp_id_1'] = comp_id_1
                                    disulf['atom_id_1'] = atom_id_1
                                    disulf['chain_id_2'] = chain_id_2
                                    disulf['seq_id_2'] = seq_id_2
                                    disulf['comp_id_2'] = comp_id_2
                                    disulf['atom_id_2'] = atom_id_2
                                    disulf['distance_value'] = None
                                    bond = self.__getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)
                                    if bond is not None:
                                        disulf['distance_value'] = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)
                                    if disulf['distance_value'] is None and file_type == 'nmr-star':
                                        cif_chain_id_1 = row[auth_chain_id_1_name]
                                        cif_chain_id_2 = row[auth_chain_id_2_name]
                                        cif_seq_id_1 = row[auth_seq_id_1_name]
                                        cif_seq_id_2 = row[auth_seq_id_2_name]
                                        cif_atom_id_1 = row[auth_atom_id_1_name]
                                        cif_atom_id_2 = row[auth_atom_id_2_name]
                                        bond = self.__getCoordBondLength(cif_chain_id_1, cif_seq_id_1, cif_atom_id_1,
                                                                         cif_chain_id_2, cif_seq_id_2, cif_atom_id_2,
                                                                         label_scheme=False)
                                        if bond is not None:
                                            disulf['distance_value'] = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)
                                    disulf['warning_description_1'] = None
                                    disulf['warning_description_2'] = None

                                    if cs_lp_data is not None:

                                        ca_chem_shift_1 = None
                                        cb_chem_shift_1 = None

                                        for _row in cs_lp_data:

                                            atom_id = _row[atom_id_name]

                                            if _row[chain_id_name] == chain_id_1 and _row[seq_id_name] == seq_id_1 and _row[comp_id_name] in ('CYS', 'DCY'):
                                                if atom_id == 'CA':
                                                    ca_chem_shift_1 = _row[value_name]
                                                elif atom_id == 'CB':
                                                    cb_chem_shift_1 = _row[value_name]

                                            if ca_chem_shift_1 is None or cb_chem_shift_1 is None:
                                                if _row[chain_id_name] == chain_id_1 and _row[seq_id_name] > seq_id_1:
                                                    break
                                            else:
                                                break

                                        disulf['ca_chem_shift_1'] = ca_chem_shift_1
                                        disulf['cb_chem_shift_1'] = cb_chem_shift_1

                                        ca_chem_shift_2 = None
                                        cb_chem_shift_2 = None

                                        for _row in cs_lp_data:

                                            atom_id = _row[atom_id_name]

                                            if _row[chain_id_name] == chain_id_2 and _row[seq_id_name] == seq_id_2 and _row[comp_id_name] in ('CYS', 'DCY'):
                                                if atom_id == 'CA':
                                                    ca_chem_shift_2 = _row[value_name]
                                                elif atom_id == 'CB':
                                                    cb_chem_shift_2 = _row[value_name]

                                            if ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                                                if _row[chain_id_name] == chain_id_2 and _row[seq_id_name] > seq_id_2:
                                                    break
                                            else:
                                                break

                                        disulf['ca_chem_shift_2'] = ca_chem_shift_2
                                        disulf['cb_chem_shift_2'] = cb_chem_shift_2

                                        if cb_chem_shift_1 is not None:
                                            if cb_chem_shift_1 < 32.0:
                                                disulf['redox_state_pred_1'] = 'reduced'
                                            elif cb_chem_shift_1 > 35.0:
                                                disulf['redox_state_pred_1'] = 'oxidized'
                                            elif cb_chem_shift_2 is not None:
                                                if cb_chem_shift_2 < 32.0:
                                                    disulf['redox_state_pred_1'] = 'reduced'
                                                elif cb_chem_shift_2 > 35.0:
                                                    disulf['redox_state_pred_1'] = 'oxidized'
                                                else:
                                                    disulf['redox_state_pred_1'] = 'ambiguous'
                                            else:
                                                disulf['redox_state_pred_1'] = 'ambiguous'
                                        else:
                                            disulf['redox_state_pred_1'] = 'unknown'

                                        if cb_chem_shift_2 is not None:
                                            if cb_chem_shift_2 < 32.0:
                                                disulf['redox_state_pred_2'] = 'reduced'
                                            elif cb_chem_shift_2 > 35.0:
                                                disulf['redox_state_pred_2'] = 'oxidized'
                                            elif cb_chem_shift_1 is not None:
                                                if cb_chem_shift_1 < 32.0:
                                                    disulf['redox_state_pred_2'] = 'reduced'
                                                elif cb_chem_shift_1 > 35.0:
                                                    disulf['redox_state_pred_2'] = 'oxidized'
                                                else:
                                                    disulf['redox_state_pred_2'] = 'ambiguous'
                                            else:
                                                disulf['redox_state_pred_2'] = 'ambiguous'
                                        else:
                                            disulf['redox_state_pred_2'] = 'unknown'

                                        if disulf['redox_state_pred_1'] == 'ambiguous' and ((ca_chem_shift_1 is not None) or (cb_chem_shift_1 is not None)):
                                            oxi, red = predict_redox_state_of_cystein(ca_chem_shift_1, cb_chem_shift_1)
                                            disulf['redox_state_pred_1'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                                        if disulf['redox_state_pred_2'] == 'ambiguous' and ((ca_chem_shift_2 is not None) or (cb_chem_shift_2 is not None)):
                                            oxi, red = predict_redox_state_of_cystein(ca_chem_shift_2, cb_chem_shift_2)
                                            disulf['redox_state_pred_2'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                                        if disulf['redox_state_pred_1'] != 'oxidized' and disulf['redox_state_pred_1'] != 'unknown':

                                            warn = "Disulfide bond "\
                                                f"({chain_id_1}:{seq_id_1}:{comp_id_1} - {chain_id_2}:{seq_id_2}:{comp_id_2}) can not be verified with "\
                                                f"the assigned chemical shift values ({chain_id_1}:{seq_id_1}:{comp_id_1}:CA {ca_chem_shift_1} ppm, "\
                                                f"{chain_id_1}:{seq_id_1}:{comp_id_1}:CB {cb_chem_shift_1} ppm, redox_state_pred {disulf['redox_state_pred_1']})."

                                            item = 'anomalous_chemical_shift' if disulf['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                                            disulf['warning_description_1'] = item + ': ' + warn

                                        if disulf['redox_state_pred_2'] != 'oxidized' and disulf['redox_state_pred_2'] != 'unknown':

                                            warn = "Disulfide bond "\
                                                f"({chain_id_1}:{seq_id_1}:{comp_id_1} - {chain_id_2}:{seq_id_2}:{comp_id_2}) can not be verified with "\
                                                f"the assigned chemical shift values ({chain_id_2}:{seq_id_2}:{comp_id_2}:CA {ca_chem_shift_2} ppm, "\
                                                f"{chain_id_2}:{seq_id_2}:{comp_id_2}:CB {cb_chem_shift_2} ppm, redox_state_pred {disulf['redox_state_pred_2']})."

                                            item = 'anomalous_chemical_shift' if disulf['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                                            disulf['warning_description_2'] = item + ': ' + warn

                                    disulf_asm.append(disulf)

                                elif chain_id_1 == chain_id_2 and seq_id_1 == 1 and atom_id_1 == 'N' and seq_id_2 > 1 and atom_id_2 == 'C':
                                    self.report.setCyclicPolymer(True)

                                elif atom_id_1 == 'SE' and atom_id_2 == 'SE':  # diselenide bond
                                    pass

                                # hydrogen bond begins

                                elif (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):
                                    pass

                                elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):
                                    pass

                                elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):
                                    pass

                                elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):
                                    pass

                                elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):
                                    pass

                                elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):
                                    pass

                                elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):
                                    pass

                                # hydrogen bond ends

                                else:

                                    other = {}
                                    other['chain_id_1'] = chain_id_1
                                    other['seq_id_1'] = seq_id_1
                                    other['comp_id_1'] = comp_id_1
                                    other['atom_id_1'] = atom_id_1
                                    other['chain_id_2'] = chain_id_2
                                    other['seq_id_2'] = seq_id_2
                                    other['comp_id_2'] = comp_id_2
                                    other['atom_id_2'] = atom_id_2
                                    other['distance_value'] = None
                                    bond = self.__getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)
                                    if bond is not None:
                                        other['distance_value'] = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)
                                    if other['distance_value'] is None and file_type == 'nmr-star':
                                        cif_chain_id_1 = row[auth_chain_id_1_name]
                                        cif_chain_id_2 = row[auth_chain_id_2_name]
                                        cif_seq_id_1 = row[auth_seq_id_1_name]
                                        cif_seq_id_2 = row[auth_seq_id_2_name]
                                        cif_atom_id_1 = row[auth_atom_id_1_name]
                                        cif_atom_id_2 = row[auth_atom_id_2_name]
                                        bond = self.__getCoordBondLength(cif_chain_id_1, cif_seq_id_1, cif_atom_id_1,
                                                                         cif_chain_id_2, cif_seq_id_2, cif_atom_id_2,
                                                                         label_scheme=False)
                                        if bond is not None:
                                            other['distance_value'] = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)
                                    other['warning_description_1'] = None
                                    other['warning_description_2'] = None

                                    if cs_lp_data is not None:

                                        ca_chem_shift_1 = None
                                        cb_chem_shift_1 = None

                                        for _row in cs_lp_data:

                                            atom_id = _row[atom_id_name]

                                            if _row[chain_id_name] == chain_id_1 and _row[seq_id_name] == seq_id_1 and _row[comp_id_name] in ('CYS', 'DCY'):
                                                if atom_id == 'CA':
                                                    ca_chem_shift_1 = _row[value_name]
                                                elif atom_id == 'CB':
                                                    cb_chem_shift_1 = _row[value_name]

                                            if ca_chem_shift_1 is None or cb_chem_shift_1 is None:
                                                if _row[chain_id_name] == chain_id_1 and _row[seq_id_name] > seq_id_1:
                                                    break
                                            else:
                                                break

                                        other['ca_chem_shift_1'] = ca_chem_shift_1
                                        other['cb_chem_shift_1'] = cb_chem_shift_1

                                        ca_chem_shift_2 = None
                                        cb_chem_shift_2 = None

                                        for _row in cs_lp_data:

                                            atom_id = _row[atom_id_name]

                                            if _row[chain_id_name] == chain_id_2 and _row[seq_id_name] == seq_id_2 and _row[comp_id_name] in ('CYS', 'DCY'):
                                                if atom_id == 'CA':
                                                    ca_chem_shift_2 = _row[value_name]
                                                elif atom_id == 'CB':
                                                    cb_chem_shift_2 = _row[value_name]

                                            if ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                                                if _row[chain_id_name] == chain_id_2 and _row[seq_id_name] > seq_id_2:
                                                    break
                                            else:
                                                break

                                        other['ca_chem_shift_2'] = ca_chem_shift_2
                                        other['cb_chem_shift_2'] = cb_chem_shift_2

                                        if cb_chem_shift_1 is not None:
                                            if cb_chem_shift_1 < 32.0:
                                                other['redox_state_pred_1'] = 'reduced'
                                            elif cb_chem_shift_1 > 35.0:
                                                other['redox_state_pred_1'] = 'oxidized'
                                            elif cb_chem_shift_2 is not None:
                                                if cb_chem_shift_2 < 32.0:
                                                    other['redox_state_pred_1'] = 'reduced'
                                                elif cb_chem_shift_2 > 35.0:
                                                    other['redox_state_pred_1'] = 'oxidized'
                                                else:
                                                    other['redox_state_pred_1'] = 'ambiguous'
                                            else:
                                                other['redox_state_pred_1'] = 'ambiguous'
                                        else:
                                            other['redox_state_pred_1'] = 'unknown'

                                        if cb_chem_shift_2 is not None:
                                            if cb_chem_shift_2 < 32.0:
                                                other['redox_state_pred_2'] = 'reduced'
                                            elif cb_chem_shift_2 > 35.0:
                                                other['redox_state_pred_2'] = 'oxidized'
                                            elif cb_chem_shift_1 is not None:
                                                if cb_chem_shift_1 < 32.0:
                                                    other['redox_state_pred_2'] = 'reduced'
                                                elif cb_chem_shift_1 > 35.0:
                                                    other['redox_state_pred_2'] = 'oxidized'
                                                else:
                                                    other['redox_state_pred_2'] = 'ambiguous'
                                            else:
                                                other['redox_state_pred_2'] = 'ambiguous'
                                        else:
                                            other['redox_state_pred_2'] = 'unknown'

                                        if other['redox_state_pred_1'] == 'ambiguous' and ((ca_chem_shift_1 is not None) or (cb_chem_shift_1 is not None)):
                                            oxi, red = predict_redox_state_of_cystein(ca_chem_shift_1, cb_chem_shift_1)
                                            other['redox_state_pred_1'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                                        if other['redox_state_pred_2'] == 'ambiguous' and ((ca_chem_shift_2 is not None) or (cb_chem_shift_2 is not None)):
                                            oxi, red = predict_redox_state_of_cystein(ca_chem_shift_2, cb_chem_shift_2)
                                            other['redox_state_pred_2'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                                        if other['redox_state_pred_1'] != 'oxidized' and other['redox_state_pred_1'] != 'unknown':

                                            warn = "Other bond "\
                                                f"({chain_id_1}:{seq_id_1}:{comp_id_1} - {chain_id_2}:{seq_id_2}:{comp_id_2}) can not be verified with "\
                                                f"the assigned chemical shift values ({chain_id_1}:{seq_id_1}:{comp_id_1}:CA {ca_chem_shift_1} ppm, "\
                                                f"{chain_id_1}:{seq_id_1}:{comp_id_1}:CB {cb_chem_shift_1} ppm, redox_state_pred {other['redox_state_pred_1']})."

                                            item = 'anomalous_chemical_shift' if other['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                                            other['warning_description_1'] = item + ': ' + warn

                                        if other['redox_state_pred_2'] != 'oxidized' and other['redox_state_pred_2'] != 'unknown':

                                            warn = "Other bond "\
                                                f"({chain_id_1}:{seq_id_1}:{comp_id_1} - {chain_id_2}:{seq_id_2}:{comp_id_2}) can not be verified with "\
                                                f"the assigned chemical shift values ({chain_id_2}:{seq_id_2}:{comp_id_2}:CA {ca_chem_shift_2} ppm, "\
                                                f"{chain_id_2}:{seq_id_2}:{comp_id_2}:CB {cb_chem_shift_2} ppm, redox_state_pred {other['redox_state_pred_2']})."

                                            item = 'anomalous_chemical_shift' if other['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                                            other['warning_description_2'] = item + ': ' + warn

                                    other_asm.append(other)

                            if len(disulf_asm) > 0:
                                input_source.setItemValue('disulfide_bond', disulf_asm)

                                self.report.setDisulfideBond(True)

                            if len(other_asm) > 0:
                                input_source.setItemValue('other_bond', other_asm)

                                self.report.setOtherBond(True)

                        except KeyError as e:

                            self.report.error.appendDescription('multiple_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': str(e).strip("'")})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testNmrCovalentBond() ++ KeyError  - {str(e)}\n")

                        except LookupError as e:

                            item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

                            self.report.error.appendDescription(item,
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': str(e).strip("'")})
                            self.report.setError()

                            self.__lfh.write("+NmrDpUtility.__testNmrCovalentBond() ++ LookupError  - "
                                             f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

                        except ValueError as e:

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': str(e).strip("'")})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testNmrCovalentBond() ++ ValueError  - {str(e)}\n")

                        except UserWarning:
                            pass

                        except Exception as e:

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testNmrCovalentBond() ++ Error  - " + str(e))
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testNmrCovalentBond() ++ Error  - {str(e)}\n")

        return self.report.getTotalErrors() == __errors

    def __testDataConsistencyInAuxLoop(self):
        """ Perform consistency test on data of auxiliary loops.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            if fileListId >= len(self.__star_data_type) or self.__star_data_type[fileListId] != 'Entry':
                continue

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype == 'entity':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                parent_pointer = 0

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                    parent_pointer += 1

                    if content_subtype.startswith('spectral_peak'):

                        try:

                            _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                            num_dim = int(_num_dim)

                            if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                                raise ValueError()

                        except ValueError:

                            err = f"{self.num_dim_items[file_type]} {str(_num_dim)!r} must be in {set(range(1, MAX_DIM_NUM_OF_SPECTRA))}."

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ ValueError  - {err}\n")

                            continue

                    for loop in sf.loops:

                        lp_category = loop.category

                        if lp_category is None:
                            continue

                        # main content of loop has been processed in __testDataConsistencyInLoop()
                        if lp_category in self.lp_categories[file_type][content_subtype]:
                            continue

                        if self.aux_lp_categories[file_type][content_subtype] is None:
                            continue

                        if lp_category in self.aux_lp_categories[file_type][content_subtype]:

                            key_items = self.aux_key_items[file_type][content_subtype][lp_category]
                            data_items = self.aux_data_items[file_type][content_subtype][lp_category]
                            allowed_tags = self.aux_allowed_tags[file_type][content_subtype][lp_category]

                            try:

                                aux_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                                                  allowed_tags, None, parent_pointer=parent_pointer,
                                                                  test_on_index=True, enforce_non_zero=True, enforce_sign=True, enforce_range=True, enforce_enum=True,
                                                                  enforce_allowed_tags=(file_type == 'nmr-star'),
                                                                  excl_missing_data=self.__excl_missing_data)[0]

                                self.__aux_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category, 'data': aux_data})

                                if content_subtype == 'spectral_peak':
                                    self.__testDataConsistencyInAuxLoopOfSpectralPeak(file_name, file_type, sf_framecode, num_dim, lp_category, aux_data)
                                if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                                    self.__testDataConsistencyInAuxLoopOfSpectralPeakAlt(file_name, file_type, sf_framecode, num_dim, lp_category,
                                                                                         aux_data, sf, parent_pointer)

                            except KeyError as e:

                                self.report.error.appendDescription('multiple_data',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': str(e).strip("'")})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ KeyError  - {str(e)}\n")

                            except LookupError as e:

                                item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

                                self.report.error.appendDescription(item,
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': str(e).strip("'")})
                                self.report.setError()

                                self.__lfh.write("+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ LookupError  - "
                                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

                            except ValueError as e:

                                self.report.error.appendDescription('invalid_data',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': str(e).strip("'")})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ ValueError  - {str(e)}\n")

                            except UserWarning as e:

                                warns = str(e).strip("'").split('\n')

                                has_multiple_data = False
                                has_bad_pattern = False

                                for warn in warns:

                                    if len(warn) == 0:
                                        continue

                                    zero = warn.startswith('[Zero value error]')
                                    nega = warn.startswith('[Negative value error]')
                                    rang = warn.startswith('[Range value error]')
                                    enum = warn.startswith('[Enumeration error]')
                                    mult = warn.startswith('[Multiple data]')
                                    remo = warn.startswith('[Remove bad pattern]')
                                    clea = warn.startswith('[Clear bad pattern]')

                                    if zero or nega or rang or enum or mult or remo or clea:

                                        p = warn.index(']') + 2
                                        warn = warn[p:]

                                        if zero or nega or rang:
                                            item = 'unusual_data'
                                        elif enum:
                                            item = 'enum_mismatch'
                                        elif remo:
                                            if content_subtype == 'chem_shift':
                                                warn += ' Your unassigned chemical shifts have been removed.'
                                                item = 'incompletely_assigned_chemical_shift'
                                            else:
                                                item = 'insufficient_data'
                                            has_bad_pattern = True
                                        elif clea:
                                            if content_subtype.startswith('spectral_peak'):
                                                warn += ' Unassigned spectral peaks can be included in your peak list(s).'
                                                item = 'incompletely_assigned_spectral_peak'
                                            else:
                                                item = 'insufficient_data'
                                        elif self.__resolve_conflict:
                                            item = 'redundant_data'
                                            has_multiple_data = True
                                        else:
                                            item = 'multiple_data'

                                        if zero or nega or rang or enum or remo or clea or self.__resolve_conflict:

                                            self.report.warning.appendDescription(item,
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                   'description': warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Warning  - {warn}\n")

                                        else:

                                            self.report.error.appendDescription(item,
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ KeyError  - {warn}\n")

                                    else:

                                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Error  - " + warn)
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Error  - {warn}\n")

                                # try to parse data without constraints

                                if has_multiple_data:
                                    conflict_id = self.__nefT.get_conflict_id(sf, lp_category, key_items)[0]

                                    if len(conflict_id) > 0:
                                        if __pynmrstar_v3_2__:
                                            _loop = sf.get_loop(lp_category)
                                        else:
                                            _loop = sf.get_loop_by_category(lp_category)

                                        for lcid in conflict_id:
                                            del _loop.data[lcid]

                                        index_tag = self.index_tags[file_type][content_subtype]
                                        if index_tag is not None:
                                            index_col = loop.tags.index(index_tag) if index_tag in loop.tags else -1
                                            if index_col != -1:
                                                for idx, row in enumerate(loop, start=1):
                                                    row[index_col] = idx

                                # try to parse data without bad patterns

                                if has_bad_pattern:
                                    conflict_id = self.__nefT.get_bad_pattern_id(sf, lp_category, key_items, data_items)[0]

                                    if len(conflict_id) > 0:
                                        if __pynmrstar_v3_2__:
                                            _loop = sf.get_loop(lp_category)
                                        else:
                                            _loop = sf.get_loop_by_category(lp_category)

                                        for lcid in conflict_id:
                                            del _loop.data[lcid]

                                try:

                                    aux_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                                                      allowed_tags, None, parent_pointer=parent_pointer,
                                                                      enforce_allowed_tags=(file_type == 'nmr-star'),
                                                                      excl_missing_data=self.__excl_missing_data)[0]

                                    self.__aux_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'data': aux_data})

                                    if content_subtype == 'spectral_peak':
                                        self.__testDataConsistencyInAuxLoopOfSpectralPeak(file_name, file_type, sf_framecode, num_dim, lp_category, aux_data)
                                    if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                                        self.__testDataConsistencyInAuxLoopOfSpectralPeakAlt(file_name, file_type, sf_framecode, num_dim, lp_category,
                                                                                             aux_data, sf, parent_pointer)

                                except Exception:
                                    pass

                            except Exception as e:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Error  - " + str(e))
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Error  - {str(e)}\n")

                        elif lp_category in self.linked_lp_categories[file_type][content_subtype]:

                            if not self.__bmrb_only:

                                warn = f"Ignored {lp_category!r} loop in {sf_framecode!r} saveframe."

                                self.report.warning.appendDescription('skipped_loop_category',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Warning  - {warn}\n")

                        else:

                            if not self.__bmrb_only:

                                if file_type == 'nef':
                                    warn = f"Ignored third party software's loop {lp_category!r} in {sf_framecode!r} saveframe."
                                else:
                                    warn = f"Ignored {lp_category!r} loop in {sf_framecode!r} saveframe."

                                self.report.warning.appendDescription('skipped_loop_category',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Warning  - {warn}\n")

        return self.report.getTotalErrors() == __errors

    def __testDataConsistencyInAuxLoopOfSpectralPeak(self, file_name, file_type, sf_framecode, num_dim, lp_category, aux_data):
        """ Perform consistency test on data of spectral peak loops.
        """

        content_subtype = 'spectral_peak'

        max_dim = num_dim + 1

        if (file_type == 'nef' and lp_category == '_nef_spectrum_dimension') or (file_type == 'nmr-star' and lp_category == '_Spectral_dim'):

            err = f"The number of dimension {str(num_dim)!r} and the number of rows {str(len(aux_data))!r} are not matched."

            if len(aux_data) != num_dim:
                self.report.error.appendDescription('missing_data',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ Error  - {err}\n")

            try:

                min_points = [None] * num_dim
                max_points = [None] * num_dim
                min_limits = [None] * num_dim
                max_limits = [None] * num_dim
                abs_positions = [None] * num_dim

                for i in range(1, max_dim):

                    for sp_dim in aux_data:

                        if file_type == 'nef':

                            if sp_dim['dimension_id'] != i:
                                continue

                            first_point = sp_dim.get('value_first_point')
                            sp_width = sp_dim.get('spectral_width')
                            # acq = sp_dim['is_acquisition']
                            sp_freq = sp_dim.get('spectrometer_frequency')
                            abs_positions[i - 1] = False if 'absolute_peak_positions' not in sp_dim else sp_dim['absolute_peak_positions']

                            if 'axis_unit' in sp_dim and sp_dim['axis_unit'] == 'Hz'\
                               and sp_freq is not None and first_point is not None and sp_width is not None:
                                first_point /= sp_freq
                                sp_width /= sp_freq

                        else:

                            if sp_dim['ID'] != i:
                                continue

                            first_point = sp_dim.get('Value_first_point')
                            sp_width = sp_dim.get('Sweep_width')
                            # acq = sp_dim['Acquisition']
                            sp_freq = sp_dim.get('Spectrometer_frequency')
                            abs_positions[i - 1] = False if 'Absolute_peak_positions' not in sp_dim else sp_dim['Absolute_peak_positions']

                            if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                               and sp_freq is not None and first_point is not None and sp_width is not None:
                                first_point /= sp_freq
                                sp_width /= sp_freq

                        min_point = None
                        max_point = None
                        min_limit = None
                        max_limit = None

                        if first_point is not None and sp_width is not None:

                            last_point = first_point - sp_width

                            # DAOTHER-7389, issue #1, relax expected range of peak position by three times of spectral width if absolute_peak_positios are true
                            min_point = last_point - (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)
                            max_point = first_point + (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)

                            min_limit = min_point
                            max_limit = max_point

                            if sp_freq is not None and min_point is not None and max_point is not None:
                                center_point = (max_point - min_point) / 2.0
                                min_limit = center_point - self.hard_probe_limit / 2.0 / sp_freq
                                max_limit = center_point + self.hard_probe_limit / 2.0 / sp_freq

                        if min_point is not None:
                            min_points[i - 1] = float(f"{min_point:.7f}")
                        if max_point is not None:
                            max_points[i - 1] = float(f"{max_point:.7f}")

                        if min_limit is not None:
                            min_limits[i - 1] = float(f"{min_limit:.7f}")
                        if max_limit is not None:
                            max_limits[i - 1] = float(f"{max_limit:.7f}")

                        break

                key_items = []
                for dim in range(1, max_dim):
                    for k in self.pk_key_items[file_type]:
                        if k['type'] == 'float':  # position
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            key_items.append(_k)

                position_names = [k['name'] for k in key_items]
                index_tag = self.index_tags[file_type][content_subtype]

                lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                if lp_data is not None:

                    for row in lp_data:
                        for j in range(num_dim):

                            if min_points[j] is None or max_points[j] is None:
                                continue

                            position = row[position_names[j]]

                            if position < min_points[j] or position > max_points[j]:

                                err = f"[Check row of {index_tag} {row[index_tag]}] {position_names[j]} {position} is not within expected range "\
                                    f"(min_position {min_points[j]}, max_position {max_points[j]}, absolute_peak_positions {abs_positions[j]}). "\
                                    "Please check for reference frequency and spectral width."

                                self.report.warning.appendDescription('anomalous_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': err})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ Warning  - {err}\n")

                            if min_limits[j] is None or max_limits[j] is None:
                                continue

                            if position < min_limits[j] or position > max_limits[j]:

                                err = f"[Check row of {index_tag} {row[index_tag]}] {position_names[j]} {position} is not within expected range "\
                                    f"(min_position {min_limits[j]}, max_position {max_limits[j]}, absolute_peak_positions {abs_positions[j]}), "\
                                    f"which exceeds limit of current probe design ({self.hard_probe_limit / 1000.0} kHz). "\
                                    "Please check for reference frequency and spectral width."

                                self.report.error.appendDescription('invalid_data',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ ValueError  - {err}\n")

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ Error  - {str(e)}\n")

        if (file_type == 'nef' and lp_category == '_nef_spectrum_dimension_transfer') or (file_type == 'nmr-star' and lp_category == '_Spectral_dim_transfer'):

            for row in aux_data:
                for name in [key['name'] for key in self.aux_key_items[file_type][content_subtype][lp_category]]:
                    if row[name] not in range(1, max_dim):

                        err = f"{name} {row[name]!r} must be one of {range(1, max_dim)}."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeak() ++ ValueError  - {err}\n")

    def __testDataConsistencyInAuxLoopOfSpectralPeakAlt(self, file_name, file_type, sf_framecode, num_dim, lp_category, aux_data, sf, parent_pointer):
        """ Perform consistency test on data of spectral peak loops.
        """

        content_subtype = 'spectral_peak_alt'

        max_dim = num_dim + 1

        if lp_category == '_Spectral_dim':

            err = f"The number of dimension {str(num_dim)!r} and the number of rows {str(len(aux_data))!r} are not matched."

            if len(aux_data) != num_dim:
                self.report.error.appendDescription('missing_data',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ Error  - {err}\n")

            try:

                min_points = [None] * num_dim
                max_points = [None] * num_dim
                min_limits = [None] * num_dim
                max_limits = [None] * num_dim
                abs_positions = [None] * num_dim

                for i in range(1, max_dim):

                    for sp_dim in aux_data:

                        if sp_dim['ID'] != i:
                            continue

                        first_point = sp_dim.get('Value_first_point')
                        sp_width = sp_dim.get('Sweep_width')
                        sp_freq = sp_dim.get('Spectrometer_frequency')
                        # acq = sp_dim['Acquisition']
                        abs_positions[i - 1] = False if 'Absolute_peak_positions' not in sp_dim else sp_dim['Absolute_peak_positions']

                        if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                           and sp_freq is not None and first_point is not None and sp_width is not None:
                            first_point /= sp_freq
                            sp_width /= sp_freq

                        min_point = None
                        max_point = None
                        min_limit = None
                        max_limit = None

                        if first_point is not None and sp_width is not None:

                            last_point = first_point - sp_width

                            # DAOTHER-7389, issue #1, relax expected range of peak position by three times of spectral width if absolute_peak_positios are true
                            min_point = last_point - (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)
                            max_point = first_point + (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)

                            min_limit = min_point
                            max_limit = max_point

                            if sp_freq is not None and min_point is not None and max_point is not None:
                                center_point = (max_point - min_point) / 2.0
                                min_limit = center_point - self.hard_probe_limit / 2.0 / sp_freq
                                max_limit = center_point + self.hard_probe_limit / 2.0 / sp_freq

                        if min_point is not None:
                            min_points[i - 1] = float(f"{min_point:.7f}")
                        if max_point is not None:
                            max_points[i - 1] = float(f"{max_point:.7f}")

                        if min_limit is not None:
                            min_limits[i - 1] = float(f"{min_limit:.7f}")
                        if max_limit is not None:
                            max_limits[i - 1] = float(f"{max_limit:.7f}")

                        break

                _pk_char_category = '_Peak_char'

                _pk_char_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                      if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode and lp['category'] == _pk_char_category), None)

                if _pk_char_data is None and any(loop for loop in sf.loops if loop.category == _pk_char_category):

                    key_items = self.aux_key_items[file_type][content_subtype][_pk_char_category]
                    data_items = self.aux_data_items[file_type][content_subtype][_pk_char_category]
                    allowed_tags = self.aux_allowed_tags[file_type][content_subtype][_pk_char_category]

                    _pk_char_data = self.__nefT.check_data(sf, _pk_char_category, key_items, data_items,
                                                           allowed_tags, None, parent_pointer=parent_pointer,
                                                           enforce_allowed_tags=(file_type == 'nmr-star'),
                                                           excl_missing_data=self.__excl_missing_data)[0]

                pk_id_name = 'Peak_ID'
                dim_id_name = 'Spectral_dim_ID'
                position_name = 'Chem_shift_val'

                if _pk_char_data is not None:

                    for row in _pk_char_data:

                        j = row[dim_id_name] - 1

                        if j >= num_dim or min_points[j] is None or max_points[j] is None:
                            continue

                        position = row[position_name]

                        if position < min_points[j] or position > max_points[j]:

                            warn = f"[Check row of {pk_id_name} {row[pk_id_name]}] {position_name} {position} is not within expected range "\
                                f"(min_position {min_points[j]}, max_position {max_points[j]}, absolute_peak_positions {abs_positions[j]}). "\
                                "Please check for reference frequency and spectral width."

                            self.report.warning.appendDescription('anomalous_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ Warning  - {warn}\n")

                        if min_limits[j] is None or max_limits[j] is None:
                            continue

                        if position < min_limits[j] or position > max_limits[j]:

                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] {position_name} {position} is not within expected range "\
                                f"(min_position {min_limits[j]}, max_position {max_limits[j]}, absolute_peak_positions {abs_positions[j]}), "\
                                f"which exceeds limit of current probe design ({self.hard_probe_limit / 1000.0} kHz). "\
                                "Please check for reference frequency and spectral width."

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ ValueError  - {err}\n")

            except LookupError as e:

                item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

                self.report.error.appendDescription(item,
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                self.__lfh.write("+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ LookupError  - "
                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

            except ValueError as e:

                self.report.error.appendDescription('invalid_data',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ ValueError  - {str(e)}\n")

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ Error  - {str(e)}\n")

        if lp_category == '_Spectral_dim_transfer':

            for row in aux_data:
                for name in [key['name'] for key in self.aux_key_items[file_type][content_subtype][lp_category]]:
                    if row[name] not in range(1, max_dim):

                        err = f"{name} {row[name]!r} must be one of {range(1, max_dim)}."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ ValueError  - {err}\n")

    def __testSfTagConsistency(self):
        """ Perform consistency test on saveframe tags.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            if self.__star_data_type[fileListId] != 'Entry':
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if self.__op == 'nmr-cs-mr-merge' and content_subtype == 'entity':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]

                parent_keys = set()
                sf_framecode_dict = {}

                list_id = 1  # tentative parent key if not exists

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if self.__combined_mode and sf.tag_prefix != self.sf_tag_prefixes[file_type][content_subtype]:

                        err = f"Saveframe tag prefix {sf.tag_prefix!r} did not match with "\
                            f"{self.sf_tag_prefixes[file_type][content_subtype]!r} in {sf_framecode!r} saveframe."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testSfTagConsistency() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ Error  - {err}\n")

                    try:

                        sf_tag_items = copy.copy(self.sf_tag_items[file_type][content_subtype])

                        if not self.__combined_mode:
                            for sf_tag_item in sf_tag_items:
                                if sf_tag_item['name'] == 'sf_framecode' if file_type == 'nef' else 'Sf_framecode':
                                    sf_tag_item['mandatory'] = False

                        sf_tag_data = self.__nefT.check_sf_tag(sf, file_type, sf_category, sf_tag_items, self.sf_allowed_tags[file_type][content_subtype],
                                                               enforce_non_zero=True, enforce_sign=True, enforce_range=True, enforce_enum=True)

                        self.__testParentChildRelation(file_name, file_type, content_subtype, parent_keys, list_id, sf_framecode, sf_framecode_dict, sf_tag_data)

                        self.__sf_tag_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': sf_tag_data})

                    except LookupError as e:

                        self.report.error.appendDescription('missing_mandatory_item',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                             'description': str(e).strip("'")})
                        self.report.setError()

                        self.__lfh.write("+NmrDpUtility.__testSfTagConsistency() ++ LookupError  - "
                                         f"{file_name} {sf_framecode} {str(e)}\n")

                    except ValueError as e:

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                             'description': str(e).strip("'")})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ ValueError  - {str(e)}\n")

                    except UserWarning as e:

                        warns = str(e).strip("'").split('\n')

                        for warn in warns:

                            if len(warn) == 0:
                                continue

                            zero = warn.startswith('[Zero value error]')
                            nega = warn.startswith('[Negative value error]')
                            rang = warn.startswith('[Range value error]')
                            enum = warn.startswith('[Enumeration error]')

                            ignorable = False

                            if zero or nega or rang or enum:

                                p = warn.index(']') + 2
                                warn = warn[p:]

                                if zero or nega or rang:
                                    item = 'unusual_data'
                                else:  # enum

                                    if warn.startswith('The mandatory type'):
                                        try:
                                            g = self.chk_desc_pat_mand.search(warn).groups()
                                        except AttributeError:
                                            g = self.chk_desc_pat_mand_one.search(warn).groups()
                                    else:
                                        try:
                                            g = self.chk_desc_pat.search(warn).groups()
                                        except AttributeError:
                                            g = self.chk_desc_pat_one.search(warn).groups()

                                    if has_key_value(self._sf_tag_items[file_type], content_subtype):

                                        if any(item for item in self._sf_tag_items[file_type][content_subtype] if item == g[0]):
                                            if not self.__nefT.is_mandatory_tag('_' + sf_category + '.' + g[0], file_type):
                                                ignorable = True  # author provides the meta data through DepUI after upload

                                    item = 'enum_mismatch_ignorable' if ignorable else 'enum_mismatch'

                                self.report.warning.appendDescription(item,
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ Warning  - {warn}\n")

                            else:

                                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testSfTagConsistency() ++ Error  - " + warn)
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ Error  - {warn}\n")

                        # try to parse data without constraints

                        try:

                            sf_tag_data = self.__nefT.check_sf_tag(sf, file_type, sf_category, sf_tag_items, self.sf_allowed_tags[file_type][content_subtype],
                                                                   enforce_non_zero=False, enforce_sign=False, enforce_range=False, enforce_enum=False)

                            self.__testParentChildRelation(file_name, file_type, content_subtype, parent_keys, list_id, sf_framecode, sf_framecode_dict, sf_tag_data)

                            self.__sf_tag_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': sf_tag_data})

                        except Exception:
                            pass

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testSfTagConsistency() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ Error  - {str(e)}\n")

                    parent_keys.add(list_id)
                    if str(list_id) not in sf_framecode_dict:
                        sf_framecode_dict = {list_id: sf_framecode}

                    list_id += 1

        return self.report.getTotalErrors() == __errors

    def __testParentChildRelation(self, file_name, file_type, content_subtype, parent_keys, list_id, sf_framecode, sf_framecode_dict, sf_tag_data):
        """ Perform consistency test on saveframe category and loop category relationship of interesting loops.
        """

        if file_type == 'nef' or content_subtype in ('entry_info', 'entity'):
            return True

        __errors = self.report.getTotalErrors()

        key_base = self.sf_tag_prefixes['nmr-star'][content_subtype].lstrip('_')

        parent_key_name = key_base + '.ID'
        child_key_name = key_base + '_ID'

        try:

            if parent_key_name in sf_tag_data:
                parent_key = sf_tag_data[parent_key_name]
            else:
                parent_key = list_id

            if parent_key in parent_keys:

                err = f"{parent_key_name} {str(parent_key)!r} must be unique."

                self.report.error.appendDescription('duplicated_index',
                                                    {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                     'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testParentChildRelation() ++ KeyError  - {err}\n")

            index_tag = self.index_tags[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is not None:

                for row in lp_data:
                    if child_key_name in row and row[child_key_name] != parent_key:

                        if index_tag is None:
                            err = f"{child_key_name} {str(row[child_key_name])!r} must be {parent_key}."
                        else:
                            err = f"[Check row of {index_tag} {row[index_tag]}] {child_key_name} {row[child_key_name]!r} must be {parent_key}."

                        if row[child_key_name] in sf_framecode_dict:
                            err = err[0:-1] + f" to point the parent {sf_framecode!r} saveframe. "\
                                f"The pointer has been reserved for the {sf_framecode_dict[row[child_key_name]]!r} saveframe."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testParentChildRelation() ++ ValueError  - {err}\n")

                        break

            if self.aux_lp_categories[file_type][content_subtype] is not None:

                for lp_category in self.aux_lp_categories[file_type][content_subtype]:

                    aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                     if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode and lp['category'] == lp_category), None)

                    if aux_data is None:
                        continue

                    for row in aux_data:
                        if child_key_name in row and row[child_key_name] != parent_key:

                            if index_tag is None:
                                err = f"{child_key_name} {str(row[child_key_name])!r} must be {parent_key}."
                            else:
                                err = f"[Check row of {index_tag} {row[index_tag]}] {child_key_name} {row[child_key_name]!r} must be {parent_key}."

                            if row[child_key_name] in sf_framecode_dict:
                                err = err[0:-1] + f" to point the parent {sf_framecode!r} saveframe. "\
                                    f"The pointer has been reserved for the {sf_framecode_dict[row[child_key_name]]!r} saveframe."

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testParentChildRelation() ++ ValueError  - {err}\n")

                            break

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testParentChildRelation() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testParentChildRelation() ++ Error  - {str(e)}\n")

        return self.report.getTotalErrors() == __errors

    def __validateCsValue(self):
        """ Validate assigned chemical shift value based on BMRB chemical shift statistics.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'chem_shift'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            modified = False

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                modified |= self.__validateCsValue__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                modified |= self.__validateCsValue__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    modified |= self.__validateCsValue__(fileListId, file_name, file_type, content_subtype, sf, sf_framecode, lp_category)

            if modified:
                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __validateCsValue__(self, file_list_id, file_name, file_type, content_subtype, sf, sf_framecode, lp_category):
        """ Validate assigned chemical shift value based on BMRB chemical shift statistics.
        """

        no_reason_message = " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."
        fold_warn_message = " Please check for folded/aliased signals."

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        value_name = item_names['value']
        ambig_code_name = 'Ambiguity_code'  # NMR-STAR specific
        occupancy_name = 'Occupancy'  # NMR-STAR specific

        full_value_name = lp_category + '.' + value_name

        max_inclusive = 0.01

        modified = False

        has_mr_atom_name_mapping = file_type == 'nmr-star' and self.__remediation_mode and self.__mr_atom_name_mapping is not None and len(self.__mr_atom_name_mapping) > 0

        try:

            details_col = -1

            if file_type == 'nmr-star':

                if __pynmrstar_v3_2__:
                    loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
                else:
                    loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

                if has_mr_atom_name_mapping:
                    auth_seq_id_col = loop.tags.index('Auth_seq_ID') if 'Auth_seq_ID' in loop.tags else -1
                    auth_comp_id_col = loop.tags.index('Auth_comp_ID') if 'Auth_comp_ID' in loop.tags else -1
                    auth_atom_id_col = loop.tags.index('Auth_atom_ID') if 'Auth_atom_ID' in loop.tags else -1
                    orig_atom_name_col = loop.tags.index('Original_PDB_atom_name') if 'Original_PDB_atom_name' in loop.tags else -1
                    if -1 in (auth_seq_id_col, auth_comp_id_col, auth_atom_id_col, orig_atom_name_col):
                        has_mr_atom_name_mapping = False

                if 'Details' in loop.tags:
                    details_col = loop.tags.index('Details')

                if ambig_code_name in loop.tags:
                    ambig_code_col = loop.tags.index(ambig_code_name)
                    ambig_code_dat = get_lp_tag(loop, ambig_code_name)
                    if len(ambig_code_dat) > 0:
                        ambig_code_set = set()
                        invalid_ambig_code_set = set()
                        for row in ambig_code_dat:
                            if row not in emptyValue:
                                if row.isdigit() and int(row) in ALLOWED_AMBIGUITY_CODES:
                                    ambig_code_set.add(int(row))
                                else:
                                    invalid_ambig_code_set.add(row)
                        if len(invalid_ambig_code_set) > 0:
                            if seq_id_name in loop.tags and comp_id_name in loop.tags:
                                seq_key_set = set()
                                seq_key_dat = get_lp_tag(loop, [seq_id_name, comp_id_name])
                                for row in seq_key_dat:
                                    seq_key = (row[0], row[1])
                                    seq_key_set.add(seq_key)
                                if len(invalid_ambig_code_set) > len(seq_key_set) * 2:
                                    for row in loop.data:
                                        row[ambig_code_col] = '.'
                                else:
                                    for row in loop.data:
                                        if row[ambig_code_col] in invalid_ambig_code_set:
                                            row[ambig_code_col] = '.'
                        if len(ambig_code_set) == 1:
                            if 1 not in ambig_code_set:  # 2lrk
                                comp_id_col = loop.tags.index(comp_id_name)
                                atom_id_col = loop.tags.index(atom_id_name)
                                for row in loop.data:
                                    comp_id = row[comp_id_col]
                                    _atom_id = atom_id = row[atom_id_col]
                                    if self.__isNmrAtomName(comp_id, atom_id):
                                        _atom_id = self.__getRepAtomId(comp_id, atom_id)
                                    allowed_ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)
                                    if allowed_ambig_code in (0, 1):
                                        row[ambig_code_col] = '1'

            if file_type == 'nef' or (not self.__nonblk_anomalous_cs):
                lp_data = next(lp['data'] for lp in self.__lp_data[content_subtype]
                               if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode)

            else:

                key_items = self.key_items[file_type][content_subtype]
                data_items = self.data_items[file_type][content_subtype]

                try:

                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                     excl_missing_data=self.__excl_missing_data)[0]

                except Exception:

                    err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. Please fix problems reported."

                    self.report.error.appendDescription('missing_mandatory_content',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Error  - {err}\n")

                    return False

            chk_row_tmp = f"[Check row of {chain_id_name} %s, {seq_id_name} %s, {comp_id_name} %s, {atom_id_name} %s"
            row_tmp = f"{chain_id_name} %s, {seq_id_name} %s, {comp_id_name} %s, {atom_id_name} %s"

            methyl_cs_vals = {}
            failed_methyl_cs_keys = []

            for idx, row in enumerate(lp_data):
                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]
                comp_id = row[comp_id_name]
                atom_id = row[atom_id_name]
                value = row[value_name]
                occupancy = '.' if file_type == 'nef' else row[occupancy_name]

                alt_chain_id = set(emptyValue)
                alt_chain_id.add(chain_id)
                if chain_id.isalpha():
                    alt_chain_id.add(str(letterToDigit(chain_id)))

                if value in emptyValue:
                    continue

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id, ambig_code, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id
                        atom_name = atom_id

                        if details is not None:
                            atom_name += f", where {details.rstrip('.')}"

                    else:
                        atom_name = f'{atom_id} (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += f'{atom_id_} '

                        atom_name = f'{atom_name.rstrip()})'

                        # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id
                    atom_name = atom_id

                has_cs_stat = False

                # non-standard residue
                if comp_id not in monDict3:

                    if has_mr_atom_name_mapping and atom_id_[0] == 'H':
                        try:
                            _row_ = loop.data[idx]
                            auth_seq_id, auth_comp_id, auth_atom_id, orig_atom_name =\
                                int(_row_[auth_seq_id_col]), _row_[auth_comp_id_col], _row_[auth_atom_id_col], _row_[orig_atom_name_col].upper()
                            if auth_comp_id not in emptyValue and auth_atom_id not in emptyValue and orig_atom_name not in emptyValue\
                               and auth_atom_id != orig_atom_name:
                                try:
                                    atom_map = next(atom_map for atom_map in self.__mr_atom_name_mapping
                                                    if atom_map['auth_seq_id'] == auth_seq_id and atom_map['auth_comp_id'] == auth_comp_id
                                                    and atom_map['auth_atom_id'] == auth_atom_id and atom_map['original_atom_id'] == auth_atom_id)
                                    atom_map['original_atom_id'] = orig_atom_name
                                except StopIteration:
                                    pass
                        except (ValueError, TypeError):
                            pass

                    neighbor_comp_ids = set(_row[comp_id_name] for _row in lp_data
                                            if _row[chain_id_name] == chain_id and abs(_row[seq_id_name] - seq_id) < 4 and _row[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__csStat.peptideLike(comp_id2)

                    cs_stats = self.__csStat.get(comp_id)
                    if len(cs_stats) == 0:
                        if self.__ccU.updateChemCompDict(comp_id):
                            parent_comp_id = self.__ccU.lastChemCompDict['_chem_comp.mon_nstd_parent_comp_id']
                            if parent_comp_id in monDict3:  # DAOTHER-9198: retrieve BMRB chemical shift statittics from parent comp_id if possible (i.e. DNR -> DC)
                                cs_stats = self.__csStat.get(parent_comp_id)

                    cs_stat = next((cs_stat for cs_stat in cs_stats if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0), None)

                    if cs_stat is not None:
                        min_value = cs_stat['min']
                        max_value = cs_stat['max']
                        avg_value = cs_stat['avg']
                        std_value = cs_stat['std']

                        has_cs_stat = True

                        if atom_id_[0] in protonBeginCode and 'methyl' in cs_stat['desc']:
                            methyl_h_list = self.__csStat.getProtonsInSameGroup(comp_id, atom_id)
                            _atom_id = methyl_h_list[0] if len(methyl_h_list) > 0 else atom_id
                            methyl_cs_key = (chain_id, seq_id, _atom_id, occupancy)

                            if methyl_cs_key not in methyl_cs_vals:
                                methyl_cs_vals[methyl_cs_key] = value

                            elif value != methyl_cs_vals[methyl_cs_key] and methyl_cs_key not in failed_methyl_cs_keys:
                                failed_methyl_cs_keys.append(methyl_cs_key)

                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + "] Chemical shift values in the same methyl group "\
                                    f"({full_value_name} {value} vs {methyl_cs_vals[methyl_cs_key]}) are inconsistent."

                                if self.__combined_mode and not self.__remediation_mode:

                                    self.report.error.appendDescription('invalid_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                else:

                                    self.report.warning.appendDescription('conflicted_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': err,
                                                                           'sigma': float(f"{abs(value - methyl_cs_vals[methyl_cs_key]) / max_inclusive:.2f}")})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                        if std_value is None or std_value <= 0.0:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} is available "\
                                f"to verify {full_value_name} {value} (avg {avg_value})."

                            self.report.warning.appendDescription('unusual_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            continue

                        if avg_value is None:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} is available to verify {full_value_name} {value}."

                            self.report.warning.appendDescription('unusual_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            continue

                        z_score = float(f"{(value - avg_value) / std_value:.2f}")
                        sigma = abs(z_score)

                        if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):
                            tolerance = std_value

                            if (value < min_value - tolerance or value > max_value + tolerance)\
                               and sigma > self.cs_anomalous_error_scaled_by_sigma\
                               and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                                if na is None and pa is None:

                                    err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) is not within expected range "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                    err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__cifChecked:
                                        err += no_reason_message
                                        err_alt += no_reason_message

                                    err += fold_warn_message
                                    err_alt += fold_warn_message

                                    if self.__nonblk_anomalous_cs or self.__remediation_mode:

                                        self.report.warning.appendDescription('anomalous_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': err,
                                                                               'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                                        if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."\
                                                f"{no_reason_message if self.__cifChecked else ''}"\
                                                f"{fold_warn_message}\n"
                                            if _details in emptyValue or (details not in _details):
                                                if _details in emptyValue:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.report.error.appendDescription('anomalous_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                             'description': err,
                                                                             'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                elif pa is None:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    if (na['ring_angle'] - self.magic_angle) * z_score > 0.0 or self.__nonblk_anomalous_cs or self.__remediation_mode:

                                        self.report.warning.appendDescription('anomalous_data'
                                                                              if (na['ring_angle'] - self.magic_angle) * z_score < 0.0
                                                                              or na['ring_distance'] > self.vicinity_aromatic
                                                                              else 'unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                        if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1\
                                           and ((na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic):
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                                f"The nearest aromatic ring {na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']} "\
                                                f"is located at a distance of {na['ring_distance']}Å, "\
                                                f"and has an elevation angle of {na['ring_angle']}° with the ring plane.\n"
                                            if _details in emptyValue or (details not in _details):
                                                if _details in emptyValue:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.report.error.appendDescription('anomalous_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                             'description': warn,
                                                                             'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {warn}\n")

                                else:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                        f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    self.report.warning.appendDescription('anomalous_data' if pa['distance'] > self.vicinity_paramagnetic else 'unusual_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                    if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1\
                                       and pa['distance'] > self.vicinity_paramagnetic:
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom {pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']} "\
                                            f"is located at a distance of {pa['distance']}Å.\n"
                                        if _details in emptyValue or (details not in _details):
                                            if _details in emptyValue:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                            elif sigma > self.cs_anomalous_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                                if na is None and pa is None:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) "\
                                        f"must be verified (avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__cifChecked:
                                        warn += no_reason_message
                                        warn_alt += no_reason_message

                                    self.report.warning.appendDescription('anomalous_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                elif pa is None:

                                    if (na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic:

                                        warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                            + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                        warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                            f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                            f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                            f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                        self.report.warning.appendDescription('unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                else:

                                    if pa['distance'] > self.vicinity_paramagnetic:

                                        warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                            + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                            f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                            f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.report.warning.appendDescription('unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            elif sigma > self.cs_unusual_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                                warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                    f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                if na is not None:

                                    if (na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic:
                                        warn += f" The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."
                                        warn_alt += f" The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."
                                    else:
                                        warn = None
                                        warn_alt = None

                                elif pa is not None:

                                    if pa['distance'] > self.vicinity_paramagnetic:
                                        warn += f" The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."
                                        warn_alt += f" The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."
                                    else:
                                        warn = None
                                        warn_alt = None

                                elif self.__cifChecked:
                                    warn += no_reason_message
                                    warn_alt += no_reason_message

                                if warn is not None:
                                    self.report.warning.appendDescription('unusual_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            elif not cs_stat['primary'] and cs_stat['norm_freq'] < 0.03:

                                warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} is an unusual/rare assignment. "\
                                    f"Occurrence of {atom_name} in {comp_id} is {cs_stat['norm_freq']:.1%} in BMRB archive."

                                self.report.warning.appendDescription('unusual/rare_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                        else:

                            tolerance = std_value * 10.0  # rare residue/ligand

                            if min_value < max_value and (value < min_value - tolerance or value > max_value + tolerance)\
                               and sigma > self.cs_anomalous_error_scaled_by_sigma\
                               and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                                if na is None and pa is None:

                                    err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) is not within expected range "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                    err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__cifChecked:
                                        err += no_reason_message
                                        err_alt += no_reason_message

                                    err += fold_warn_message
                                    err_alt += fold_warn_message

                                    if self.__nonblk_anomalous_cs or self.__remediation_mode:

                                        self.report.warning.appendDescription('anomalous_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': err,
                                                                               'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                                        if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."\
                                                f"{no_reason_message if self.__cifChecked else ''}"\
                                                f"{fold_warn_message}\n"
                                            if _details in emptyValue or (details not in _details):
                                                if _details in emptyValue:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.report.error.appendDescription('anomalous_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                             'description': err,
                                                                             'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                elif pa is None:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    if (na['ring_angle'] - self.magic_angle) * z_score > 0.0 or self.__nonblk_anomalous_cs or self.__remediation_mode:

                                        if (na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic:

                                            self.report.warning.appendDescription('anomalous_data',
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                   'description': warn,
                                                                                   'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                            if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1:
                                                _details = loop.data[idx][details_col]
                                                details = f"{full_value_name} {value} is not within expected range "\
                                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                                    f"The nearest aromatic ring {na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']} "\
                                                    f"is located at a distance of {na['ring_distance']}Å, "\
                                                    f"and has an elevation angle of {na['ring_angle']}° with the ring plane.\n"
                                                if _details in emptyValue or (details not in _details):
                                                    if _details in emptyValue:
                                                        loop.data[idx][details_col] = details
                                                    else:
                                                        loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                    modified = True

                                    else:

                                        self.report.error.appendDescription('anomalous_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                             'description': warn,
                                                                             'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {warn}\n")

                                else:

                                    if pa['distance'] > self.vicinity_paramagnetic:

                                        warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                            + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                            f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                            f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.report.warning.appendDescription('unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                        if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                                f"The nearest paramagnetic/ferromagnetic atom {pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']} "\
                                                f"is located at a distance of {pa['distance']}Å.\n"
                                            if _details in emptyValue or (details not in _details):
                                                if _details in emptyValue:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                            elif sigma > self.cs_anomalous_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                                if na is None and pa is None:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) "\
                                        f"must be verified (avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__cifChecked:
                                        warn += no_reason_message
                                        warn_alt += no_reason_message

                                    self.report.warning.appendDescription('anomalous_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                elif pa is None:

                                    if (na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic:

                                        warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                            + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                        warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                            f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                            f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                            f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                        self.report.warning.appendDescription('unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                else:

                                    if pa['distance'] > self.vicinity_paramagnetic:

                                        warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                            + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                            f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                            f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                            f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.report.warning.appendDescription('unusual_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn,
                                                                               'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                # standard residue
                else:

                    cs_stat = next((cs_stat for cs_stat in self.__csStat.get(comp_id, self.report.isDiamagnetic())
                                    if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0), None)

                    if cs_stat is not None:
                        min_value = cs_stat['min']
                        max_value = cs_stat['max']
                        avg_value = cs_stat['avg']
                        std_value = cs_stat['std']

                        has_cs_stat = True

                        if atom_id_[0] in protonBeginCode and 'methyl' in cs_stat['desc']:
                            methyl_cs_key = (chain_id, seq_id, atom_id_[:-1], occupancy)

                            if methyl_cs_key not in methyl_cs_vals:
                                methyl_cs_vals[methyl_cs_key] = value

                            elif value != methyl_cs_vals[methyl_cs_key] and methyl_cs_key not in failed_methyl_cs_keys:
                                failed_methyl_cs_keys.append(methyl_cs_key)

                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + "] Chemical shift values in the same methyl group "\
                                    f"({full_value_name} {value} vs {methyl_cs_vals[methyl_cs_key]}) are inconsistent."

                                if self.__combined_mode and not self.__remediation_mode:

                                    self.report.error.appendDescription('invalid_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                else:

                                    self.report.warning.appendDescription('conflicted_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': err,
                                                                           'sigma': float(f"{abs(value - methyl_cs_vals[methyl_cs_key]) / max_inclusive:.2f}")})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                        if std_value is None or std_value <= 0.0:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} is available "\
                                f"to verify {full_value_name} {value} (avg {avg_value})."

                            self.report.warning.appendDescription('unusual_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            continue

                        if avg_value is None:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} is available to verify {full_value_name} {value}."

                            self.report.warning.appendDescription('unusual_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            continue

                        z_score = float(f"{(value - avg_value) / std_value:.2f}")
                        sigma = abs(z_score)
                        tolerance = std_value

                        if (value < min_value - tolerance or value > max_value + tolerance)\
                           and sigma > self.cs_unusual_error_scaled_by_sigma\
                           and std_value > max_inclusive:

                            na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                            pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                            if na is None and pa is None:

                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) is not within expected range "\
                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                    f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                if self.__cifChecked:
                                    err += no_reason_message
                                    err_alt += no_reason_message

                                err += fold_warn_message
                                err_alt += fold_warn_message

                                if self.__nonblk_anomalous_cs or self.__remediation_mode:

                                    self.report.warning.appendDescription('anomalous_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': err,
                                                                           'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                                    if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1:
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."\
                                            f"{no_reason_message if self.__cifChecked else ''}"\
                                            f"{fold_warn_message}\n"
                                        if _details in emptyValue or (details not in _details):
                                            if _details in emptyValue:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                                else:

                                    self.report.error.appendDescription('anomalous_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err,
                                                                         'value': value, 'z_score': z_score, 'description_alt': err_alt, 'sigma': sigma})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                            elif pa is None:

                                warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                    f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                    f"is located at a distance of {na['ring_distance']}Å, "\
                                    f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                    f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                    f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                    f"is located at a distance of {na['ring_distance']}Å, "\
                                    f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                if (na['ring_angle'] - self.magic_angle) * z_score > 0.0 or self.__nonblk_anomalous_cs or self.__remediation_mode:

                                    self.report.warning.appendDescription('anomalous_data'
                                                                          if (na['ring_angle'] - self.magic_angle) * z_score < 0.0
                                                                          or na['ring_distance'] > self.vicinity_aromatic
                                                                          else 'unusual_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                    if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1\
                                       and ((na['ring_angle'] - self.magic_angle) * z_score > 0.0 or self.__nonblk_anomalous_cs):
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                            f"The nearest aromatic ring {na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']} "\
                                            f"is located at a distance of {na['ring_distance']}Å, "\
                                            f"and has an elevation angle of {na['ring_angle']}° with the ring plane.\n"
                                        if _details in emptyValue or (details not in _details):
                                            if _details in emptyValue:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                                else:

                                    self.report.error.appendDescription('anomalous_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': warn,
                                                                         'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {warn}\n")

                            else:

                                warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                    f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                    f"is located at a distance of {pa['distance']}Å."

                                warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                    f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                    f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                    f"is located at a distance of {pa['distance']}Å."

                                self.report.warning.appendDescription('anomalous_data' if pa['distance'] > self.vicinity_paramagnetic else 'unusual_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn,
                                                                       'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                if self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and details_col != -1\
                                   and pa['distance'] > self.vicinity_paramagnetic:
                                    _details = loop.data[idx][details_col]
                                    details = f"{full_value_name} {value} is not within expected range "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest paramagnetic/ferromagnetic atom {pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']} "\
                                        f"is located at a distance of {pa['distance']}Å.\n"
                                    if _details in emptyValue or (details not in _details):
                                        if _details in emptyValue:
                                            loop.data[idx][details_col] = details
                                        else:
                                            loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                        modified = True

                        elif sigma > self.cs_unusual_error_scaled_by_sigma and std_value > max_inclusive:  # Set 5.0 to be consistent with validation report

                            na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_, self.cutoff_aromatic)
                            pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_, self.cutoff_paramagnetic)

                            if na is None and pa is None:

                                warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) "\
                                    f"must be verified (avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f})."

                                warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                    f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                if self.__cifChecked:
                                    warn += no_reason_message
                                    warn_alt += no_reason_message

                                self.report.warning.appendDescription('anomalous_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn,
                                                                       'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            elif pa is None:

                                if (na['ring_angle'] - self.magic_angle) * z_score < 0.0 or na['ring_distance'] > self.vicinity_aromatic:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                        f"The nearest aromatic ring ({na['chain_id']}:{na['seq_id']}:{na['comp_id']}:{na['ring_atoms']}) "\
                                        f"is located at a distance of {na['ring_distance']}Å, "\
                                        f"and has an elevation angle of {na['ring_angle']}° with the ring plane."

                                    self.report.warning.appendDescription('unusual_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            else:

                                if pa['distance'] > self.vicinity_paramagnetic:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                        + f"] {full_value_name} {value} ({chain_id}:{seq_id}:{comp_id}:{atom_name}) should be verified "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, Z_score {z_score:.2f}). "\
                                        f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} ({value} ppm, {sigma:.2f} sigma), "\
                                        f"which is outside of expected range ({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value}). "\
                                        f"The nearest paramagnetic/ferromagnetic atom ({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    self.report.warning.appendDescription('unusual_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn,
                                                                           'value': value, 'z_score': z_score, 'description_alt': warn_alt, 'sigma': sigma})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                        elif not cs_stat['primary'] and cs_stat['norm_freq'] < 0.03:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] {full_value_name} {value} is an unusual/rare assignment. "\
                                f"Occurrence of {atom_name} in {comp_id} is {cs_stat['norm_freq']:.1%} in BMRB archive."

                            self.report.warning.appendDescription('unusual/rare_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                if not has_cs_stat:

                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                        + f"] No chemical shift statistics is available to verify {full_value_name} {value}."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                # check ambiguity code
                if file_type == 'nmr-star' and ambig_code_name in row:
                    ambig_code = row[ambig_code_name]

                    if ambig_code in emptyValue or ambig_code == 1:
                        continue

                    _atom_id = atom_id

                    if self.__isNmrAtomName(comp_id, atom_id):
                        _atom_id = self.__getRepAtomId(comp_id, atom_id)

                    allowed_ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)

                    if ambig_code in (2, 3):

                        ambig_code_desc = 'ambiguity of geminal atoms or geminal methyl proton groups' if ambig_code == 2\
                            else 'aromatic atoms on opposite sides of symmetrical rings'

                        _atom_id2 = self.__csStat.getGeminalAtom(comp_id, _atom_id)

                        if ambig_code != allowed_ambig_code:

                            if allowed_ambig_code == 1:
                                pass

                            elif allowed_ambig_code > 0:

                                if self.__remediation_mode:
                                    pass

                                else:

                                    err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] Invalid {ambig_code_name} {str(ambig_code)!r} "\
                                        f"(allowed ambig_code {[1, allowed_ambig_code, 4, 5, 6, 9]}) in a loop {lp_category}."

                                    self.report.error.appendDescription('invalid_ambiguity_code',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                        try:

                            _row = next(_row for _row in lp_data
                                        if _row[chain_id_name] == chain_id
                                        and _row[seq_id_name] == seq_id
                                        and _row[comp_id_name] == comp_id
                                        and _row[atom_id_name] == _atom_id2)

                            ambig_code2 = _row[ambig_code_name]

                            if ambig_code2 is not None and ambig_code2 != ambig_code:

                                if ambig_code2 < 4:
                                    loop.data[lp_data.index(_row)][loop.tags.index(ambig_code_name)] = ambig_code

                                if self.__remediation_mode:
                                    pass

                                else:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} indicates {ambig_code_desc}. "\
                                        f"However, {ambig_code_name} {ambig_code2} of {atom_id_name} {_atom_id2} is inconsistent."

                                    self.report.warning.appendDescription('ambiguity_code_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                        except StopIteration:
                            pass

                    elif ambig_code in (4, 5, 6, 9):

                        ambig_set_id_name = 'Ambiguity_set_ID'

                        if ambig_set_id_name not in row:

                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                + f"] {ambig_code_name} {str(ambig_code)!r} requires {ambig_set_id_name} loop tag."

                            if self.__remediation_mode:

                                self.report.warning.appendDescription('missing_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': err})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {err}\n")

                            else:

                                self.report.error.appendDescription('missing_mandatory_item',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write("+NmrDpUtility.__validateCsValue() ++ LookupError  - "
                                                     f"{file_name} {sf_framecode} {lp_category} {err}\n")

                        else:

                            ambig_set_id = row[ambig_set_id_name]

                            if ambig_set_id in emptyValue:

                                if ambig_code in (4, 5):

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} requires {ambig_set_id_name} value."

                                    self.report.warning.appendDescription('missing_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                            else:

                                ambig_set = [_row for _row in lp_data if _row[ambig_set_id_name] == ambig_set_id and _row != row]

                                if len(ambig_set) == 0:

                                    if ambig_code == 4:
                                        ambig_desc = 'of intra-residue atoms '
                                    elif ambig_code == 5:
                                        ambig_desc = 'of inter-residue atoms '
                                    else:
                                        ambig_desc = ''

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} requires other rows {ambig_desc}sharing {ambig_set_id_name} {ambig_set_id}."

                                    self.report.warning.appendDescription('missing_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Warning  - {warn}\n")

                                # Intra-residue ambiguities
                                elif ambig_code == 4:

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepAtomId(comp_id2, atom_id2)

                                        if (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id) and _atom_id < _atom_id2:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                "It indicates intra-residue ambiguities. However, row of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2) + ' exists.'

                                            self.report.error.appendDescription('invalid_ambiguity_code',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                # Inter-residue ambiguities
                                elif ambig_code == 5:

                                    inter_residue_seq_id = False

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepAtomId(comp_id2, atom_id2)

                                        if chain_id2 != chain_id or seq_id2 != seq_id:
                                            inter_residue_seq_id = True
                                            break

                                    if not inter_residue_seq_id:

                                        for _row in ambig_set:
                                            chain_id2 = _row[chain_id_name]
                                            seq_id2 = _row[seq_id_name]
                                            comp_id2 = _row[comp_id_name]
                                            atom_id2 = _row[atom_id_name]

                                            _atom_id2 = atom_id2

                                            if self.__isNmrAtomName(comp_id2, atom_id2):
                                                _atom_id2 = self.__getRepAtomId(comp_id2, atom_id2)

                                            if chain_id2 == chain_id and seq_id2 == seq_id and _atom_id < _atom_id2:

                                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                    + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                    "It indicates inter-residue ambiguities. However, row of "\
                                                    + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2) + ' exists.'

                                                self.report.error.appendDescription('invalid_ambiguity_code',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                # Inter-molecular ambiguities
                                elif ambig_code == 6:

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepAtomId(comp_id2, atom_id2)

                                        if chain_id2 == chain_id and (seq_id < seq_id2 or (seq_id == seq_id2 and _atom_id < _atom_id2)):

                                            if chain_id == chain_id2 and seq_id == seq_id2:
                                                if _atom_id2 in self.__csStat.getProtonsInSameGroup(comp_id, _atom_id):
                                                    continue

                                            if not any(_row_ for _row_ in ambig_set if _row_[chain_id_name] != chain_id
                                               and _row_[seq_id_name] == seq_id and _row_[comp_id_name] == comp_id
                                               and _row_[atom_id_name] == atom_id):

                                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                    + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                    "It indicates inter-molecular ambiguities. However, row of "\
                                                    + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2) + ' exists.'

                                                self.report.error.appendDescription('invalid_ambiguity_code',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                for _row in ambig_set:
                                    chain_id2 = _row[chain_id_name]
                                    seq_id2 = _row[seq_id_name]
                                    comp_id2 = _row[comp_id_name]
                                    atom_id2 = _row[atom_id_name]
                                    value2 = _row[value_name]

                                    if comp_id2 not in monDict3:
                                        continue

                                    _atom_id2 = atom_id2

                                    if self.__isNmrAtomName(comp_id2, atom_id2):
                                        _atom_id2 = self.__getRepAtomId(comp_id2, atom_id2)

                                    if _atom_id[0] != _atom_id2[0] and _atom_id < _atom_id2:

                                        if self.__remediation_mode:

                                            chain_id_col = loop.tags.index(chain_id_name)
                                            seq_id_col = loop.tags.index(seq_id_name)
                                            comp_id_col = loop.tags.index(comp_id_name)
                                            atom_id_col = loop.tags.index(atom_id_name)
                                            ambig_code_col = loop.tags.index(ambig_code_name)

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id
                                                       and row[comp_id_col] == comp_id and row[atom_id_col] == atom_id)

                                            row[ambig_code_col] = allowed_ambig_code

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id2
                                                       and row[comp_id_col] == comp_id2 and row[atom_id_col] == atom_id2)

                                            row[ambig_code_col] = allowed_ambig_code

                                            modified = True

                                        else:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                "However, observation nucleus of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2)\
                                                + f" is different in the set that share the same ambiguity code ({_atom_id[0]!r} vs {_atom_id2[0]!r})."

                                            self.report.error.appendDescription('invalid_ambiguity_code',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                                    elif abs(value2 - value) > CS_UNCERT_MAX and value < value2 and ambig_code <= 4:

                                        if self.__remediation_mode:

                                            chain_id_col = loop.tags.index(chain_id_name)
                                            seq_id_col = loop.tags.index(seq_id_name)
                                            comp_id_col = loop.tags.index(comp_id_name)
                                            atom_id_col = loop.tags.index(atom_id_name)
                                            ambig_code_col = loop.tags.index(ambig_code_name)

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id
                                                       and row[comp_id_col] == comp_id and row[atom_id_col] == atom_id)

                                            row[ambig_code_col] = allowed_ambig_code

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id2
                                                       and row[comp_id_col] == comp_id2 and row[atom_id_col] == atom_id2)

                                            row[ambig_code_col] = allowed_ambig_code

                                            modified = True

                                        else:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {value_name} {value}, {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                f"However, {value_name} {value2} of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2)\
                                                + " is noticeably diffrent from others in the set that share the same ambiguity code "\
                                                f"by {value2 - value:.3f} (tolerance {CS_UNCERT_MAX})."

                                            self.report.error.appendDescription('invalid_ambiguity_code',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

                    else:

                        err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                            + f"] Invalid ambiguity code {str(ambig_code)!r} (allowed ambig_code {ALLOWED_AMBIGUITY_CODES}) in a loop."

                        self.report.error.appendDescription('invalid_ambiguity_code',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ ValueError  - {err}\n")

        except StopIteration:

            err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. Please fix problems reported."

            self.report.error.appendDescription('missing_mandatory_content',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Error  - {err}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateCsValue() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateCsValue() ++ Error  - {str(e)}\n")

        return modified

    def __cleanUpSf(self):
        """ Clean-up third-party saveframes.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            category_order = self.__c2S.category_order if file_type == 'nmr-star' else self.__c2S.category_order_nef

            if self.__star_data_type[fileListId] == 'Entry':

                for sf in reversed(self.__star_data[fileListId].frame_list):

                    if sf.tag_prefix not in category_order:
                        del self.__star_data[fileListId][sf]

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                sf_category = self.sf_categories[file_type][content_subtype]

                if self.__star_data_type[fileListId] == 'Loop':
                    pass

                elif self.__star_data_type[fileListId] == 'Saveframe':

                    sf = self.__star_data[fileListId]

                    self.__cleanUpSfTag__(file_type, content_subtype, sf)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        self.__cleanUpSfTag__(file_type, content_subtype, sf)

        return self.report.getTotalErrors() == __errors

    def __cleanUpSfTag__(self, file_type, content_subtype, sf):
        """ Remediate assigned chemical shift loop based on coordinates.
        """

        tags_to_be_removed = [t[0] for t in sf.tags if t[0] not in self.sf_allowed_tags[file_type][content_subtype]]

        if len(tags_to_be_removed) == 0:
            return True

        sf.remove_tag(tags_to_be_removed)

        return True

    def __remediateCsLoop(self):
        """ Remediate assigned chemical shift loop based on coordinates.
        """

        __errors = self.report.getTotalErrors()

        content_subtype = 'chem_shift'

        if not self.__native_combined:
            self.__lp_data[content_subtype] = []

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            original_file_name = input_source_dic['original_file_name']

            if input_source_dic['content_subtype'] is None:
                continue

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            modified = False

            list_id = 1

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                if file_type == 'nmr-star' and original_file_name not in emptyValue:
                    set_sf_tag(sf, 'Data_file_name', original_file_name)

                modified |= self.__remediateCsLoop__(fileListId, file_type, content_subtype, sf, list_id, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                if file_type == 'nmr-star':
                    data_file_name = get_first_sf_tag(sf, 'Data_file_name')
                    len_data_file_name = len(data_file_name)
                    if len_data_file_name > 0:
                        data_file_name.strip("'").strip('"')
                        _len_data_file_name = len(data_file_name)
                        if _len_data_file_name > 0 and _len_data_file_name != len_data_file_name:
                            set_sf_tag(sf, 'Data_file_name', data_file_name)
                    elif original_file_name not in emptyValue:
                        set_sf_tag(sf, 'Data_file_name', original_file_name)

                modified |= self.__remediateCsLoop__(fileListId, file_type, content_subtype, sf, list_id, sf_framecode, lp_category)

            else:

                for list_id, sf in enumerate(self.__star_data[fileListId].get_saveframes_by_category(sf_category), start=1):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    if file_type == 'nmr-star':
                        data_file_name = get_first_sf_tag(sf, 'Data_file_name')
                        len_data_file_name = len(data_file_name)
                        if len_data_file_name > 0:
                            data_file_name.strip("'").strip('"')
                            _len_data_file_name = len(data_file_name)
                            if _len_data_file_name > 0 and _len_data_file_name != len_data_file_name:
                                set_sf_tag(sf, 'Data_file_name', data_file_name)
                        elif original_file_name not in emptyValue:
                            set_sf_tag(sf, 'Data_file_name', original_file_name)

                    modified |= self.__remediateCsLoop__(fileListId, file_type, content_subtype, sf, list_id, sf_framecode, lp_category)

            if modified:

                # update _Entity_assembly.Experimental_data_reported

                if file_type == 'nmr-star' and len(self.__ent_asym_id_with_exptl_data) > 0:

                    _content_subtype = 'poly_seq'

                    _sf_category = self.sf_categories[file_type][_content_subtype]

                    try:

                        _sf = self.__star_data[fileListId].get_saveframes_by_category(_sf_category)[0]

                        try:
                            if __pynmrstar_v3_2__:
                                _loop = _sf.get_loop('_Entity_assembly')
                            else:
                                _loop = _sf.get_loop_by_category('_Entity_assembly')

                            if 'Experimental_data_reported' in _loop.tags:
                                id_col = _loop.tags.index('ID')
                                exptl_data_rep_col = _loop.tags.index('Experimental_data_reported')

                                for _row in _loop:
                                    if _row[id_col] in self.__ent_asym_id_with_exptl_data:
                                        _row[exptl_data_rep_col] = 'yes'

                        except KeyError:
                            pass

                    except IndexError:
                        pass

                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __remediateCsLoop__(self, file_list_id, file_type, content_subtype, sf, list_id, sf_framecode, lp_category):
        """ Remediate assigned chemical shift loop based on coordinates.
        """

        has_coordinate = self.report.getInputSourceIdOfCoord() >= 0

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if not has_poly_seq_in_loop:
            return False

        try:

            if file_type == 'nmr-star':

                _lp_category = '_Systematic_chem_shift_offset'

                if __pynmrstar_v3_2__:
                    _loop = sf.get_loop(_lp_category)
                else:
                    _loop = sf.get_loop_by_category(_lp_category)

                    if 'Type' in _loop.tags:
                        type_col = _loop.tags.index('Type')
                        for _row in _loop:
                            if _row[type_col] in emptyValue:
                                continue
                            if _row[type_col] == 'SAIL isotope labeling':
                                self.__sail_flag = True
                                break

                if 'sample' in self.__sf_category_list\
                   and '_Sample_component' in self.__lp_category_list:

                    _lp_category = '_Sample_component'

                    for _sf in self.__star_data[file_list_id].get_saveframes_by_category('sample'):

                        if __pynmrstar_v3_2__:
                            _loop = _sf.get_loop(_lp_category)
                        else:
                            _loop = _sf.get_loop_by_category(_lp_category)

                        if 'Isotopic_labeling' in _loop.tags:
                            isotopic_labeling_col = _loop.tags.index('Isotopic_labeling')
                            for _row in _loop:
                                if _row[isotopic_labeling_col] in emptyValue:
                                    continue
                                text = _row[isotopic_labeling_col].lower()
                                if 'sail' in text or 'stereo-array isotope labeling' in text:
                                    self.__sail_flag = True
                                    break

        except KeyError:
            pass

        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        ps = None
        ps_common = input_source_dic['polymer_sequence']

        seq_align = chain_assign = None
        br_seq_align = br_chain_assign = None
        np_seq_align = np_chain_assign = None

        if content_subtype in polymer_sequence_in_loop and self.__caC is not None:
            ps_in_loop = next((ps for ps in polymer_sequence_in_loop[content_subtype] if ps['sf_framecode'] == sf_framecode), None)

            if ps_in_loop is not None:
                list_id = ps_in_loop['list_id']
                ps = ps_in_loop['polymer_sequence']

                seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['polymer_sequence'], ps, conservative=False)
                chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['polymer_sequence'], ps, seq_align)

                if self.__caC['branched'] is not None:
                    br_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['branched'], ps, conservative=False)
                    br_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['branched'], ps, br_seq_align)

                if self.__caC['non_polymer'] is not None:
                    np_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['non_polymer'], ps, conservative=False)
                    np_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['non_polymer'], ps, np_seq_align)

        def get_auth_seq_scheme(chain_id, seq_id):
            auth_asym_id = auth_seq_id = None

            if seq_id is not None:

                if chain_assign is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if (auth_asym_id is None or auth_seq_id is None) and br_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in br_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if (auth_asym_id is None or auth_seq_id is None) and np_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in np_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

            return auth_asym_id, auth_seq_id

        def get_label_seq_scheme(chain_id, seq_id):
            auth_asym_id = auth_seq_id = label_seq_id = None

            if seq_id is not None:

                if chain_assign is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id, label_seq_id = next(((ref_seq_id, test_seq_id)
                                                              for ref_seq_id, test_seq_id
                                                              in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                              if ref_seq_id == seq_id), (None, None))

                if (auth_asym_id is None or auth_seq_id is None) and br_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in br_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id, label_seq_id = next(((ref_seq_id, test_seq_id)
                                                              for ref_seq_id, test_seq_id
                                                              in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                              if ref_seq_id == seq_id), (None, None))

                if (auth_asym_id is None or auth_seq_id is None) and np_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in np_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id, label_seq_id = next(((ref_seq_id, test_seq_id)
                                                              for ref_seq_id, test_seq_id
                                                              in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                              if ref_seq_id == seq_id), (None, None))

            return auth_asym_id, auth_seq_id, label_seq_id

        has_ins_code = False

        if ps is not None:

            for s in ps:

                if has_ins_code:
                    break

                auth_asym_id, _ = get_auth_seq_scheme(s['chain_id'], s['seq_id'][0])

                if self.__caC['polymer_sequence'] is not None\
                   and any(cif_ps for cif_ps in self.__caC['polymer_sequence']
                           if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                    has_ins_code = True

                if self.__caC['branched'] is not None\
                   and any(cif_ps for cif_ps in self.__caC['branched']
                           if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                    has_ins_code = True

                if self.__caC['non_polymer'] is not None\
                   and any(cif_ps for cif_ps in self.__caC['non_polymer']
                           if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                    has_ins_code = True

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        aux_lp = None

        aux_lp_category = self.aux_lp_categories[file_type][content_subtype][0] if file_type == 'nmr-star' else ''

        def delete_aux_loop():

            if file_type == 'nmr-star' and not isinstance(sf, pynmrstar.Loop):

                try:

                    if __pynmrstar_v3_2__:
                        aux_loop = sf.get_loop(aux_lp_category)
                    else:
                        aux_loop = sf.get_loop_by_category(aux_lp_category)

                    del sf[aux_loop]

                except KeyError:
                    pass

        if file_type == 'nef':

            items = ['chain_code', 'sequence_code', 'residue_name', 'atom_name', 'value', 'value_uncertainty', 'element', 'isotope_number']

            mandatory_items = [item['name'] for item in self.key_items[file_type][content_subtype]
                               if 'remove-bad-pattern' in item]

            if not all(tag in loop.tags for tag in mandatory_items):

                err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. Please fix problems reported."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__remediateCsLoop() ++ Error  - {err}\n")

                return False

            mandatory_items = [item['name'] for item in self.key_items[file_type][content_subtype]]
            for item in self.data_items[file_type][content_subtype]:
                if item['mandatory']:
                    mandatory_items.append(item['name'])

            if not all(tag for tag in mandatory_items if tag in loop.tags):
                return False

            coord_atom_site = self.__caC['coord_atom_site'] if self.__caC is not None else {}

            chain_id_col = loop.tags.index('chain_code')
            seq_id_col = loop.tags.index('sequence_code')
            comp_id_col = loop.tags.index('residue_name')
            atom_id_col = loop.tags.index('atom_name')
            val_col = loop.tags.index('value')
            val_err_col = loop.tags.index('value_uncertainty') if 'value_uncertainty' in loop.tags else -1

            lp = pynmrstar.Loop.from_scratch(lp_category)

            tags = [lp_category + '.' + item for item in items]

            for tag in tags:
                lp.add_tag(tag)

            for idx, row in enumerate(loop):

                _row = [None] * len(tags)

                try:
                    seq_key = (row[chain_id_col], int(row[seq_id_col]))
                except (ValueError, TypeError):
                    continue

                if seq_key in self.__seq_id_map_for_remediation:
                    seq_key = self.__seq_id_map_for_remediation[seq_key]

                _row[0], _row[1] = seq_key

                if seq_key in coord_atom_site:
                    _row[2] = coord_atom_site[seq_key]['comp_id']
                else:
                    _row[2] = row[comp_id_col].upper()

                _row[3] = row[atom_id_col]
                atom_id = row[atom_id_col].upper()

                _row[4] = row[val_col]

                try:
                    float(_row[4])
                except ValueError:
                    continue

                if val_err_col != -1:
                    val_err = row[val_err_col]
                    _row[5] = val_err

                    if val_err not in emptyValue:
                        try:
                            _val_err = float(val_err)
                            if _val_err < 0.0:
                                _row[5] = abs(_val_err)
                        except ValueError:
                            pass

                _row[6] = 'H' if _row[3][0] in protonBeginCode else atom_id[0]
                if _row[6] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                    _row[7] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[6]][0]

                lp.add_data(_row)

            key_items = self.key_items[file_type][content_subtype]

            conflict_id = self.__nefT.get_conflict_id(lp, lp_category, key_items)[0]

            if len(conflict_id) > 0:
                conflict_id_set = self.__nefT.get_conflict_id_set(lp, lp_category, key_items)[0]

                for _id in conflict_id:
                    _id_set = next(id_set for id_set in conflict_id_set if _id in id_set)

                    if len(set(str(lp.data[_id_]) for _id_ in _id_set)) == 1:
                        continue

                    msg = ' vs '.join([str(lp.data[_id_]).replace('None', '.').replace(',', '').replace("'", '') for _id_ in _id_set])

                    warn = f"Resolved redundancy of assigned chemical shifts ({msg}) by deletion of the latter one."

                    self.report.warning.appendDescription('redundant_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__remediateCsLoop() ++ Warning  - {warn}\n")

                for _id in conflict_id:
                    del lp.data[_id]

        else:

            if 'original_file_name' in input_source_dic:
                tagNames = [t[0] for t in sf.tags]
                if 'Data_file_name' not in tagNames:
                    sf.add_tag('Data_file_name', input_source_dic['original_file_name'])

            items = ['ID', 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Atom_ID', 'Atom_type', 'Atom_isotope_number',
                     'Val', 'Val_err', 'Assign_fig_of_merit', 'Ambiguity_code', 'Ambiguity_set_ID', 'Occupancy', 'Resonance_ID',
                     'Auth_asym_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID',
                     'Original_PDB_strand_ID', 'Original_PDB_residue_no', 'Original_PDB_residue_name', 'Original_PDB_atom_name',
                     'Details', 'Entry_ID', 'Assigned_chem_shift_list_ID']

            if has_ins_code:
                items.append('PDB_ins_code')

            mandatory_items = [item['name'] for item in self.key_items[file_type][content_subtype]
                               if 'remove-bad-pattern' in item]

            if not all(tag in loop.tags for tag in mandatory_items):

                err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. Please fix problems reported."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__remediateCsLoop() ++ Error  - {err}\n")

                return False

            mandatory_items = [item['name'] for item in self.key_items[file_type][content_subtype]]
            for item in self.data_items[file_type][content_subtype]:
                if item['mandatory']:
                    mandatory_items.append(item['name'])

            if not all(tag for tag in mandatory_items if tag in loop.tags):
                return False

            auth_pdb_tags = ['Auth_asym_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID']
            orig_pdb_tags = ['Original_PDB_strand_ID', 'Original_PDB_residue_no', 'Original_PDB_residue_name', 'Original_PDB_atom_name']

            entity_assembly = self.__caC['entity_assembly'] if self.__caC is not None else []
            auth_to_entity_type = self.__caC['auth_to_entity_type'] if self.__caC is not None else {}
            auth_to_star_seq = self.__caC['auth_to_star_seq'] if self.__caC is not None else {}
            auth_to_orig_seq = self.__caC['auth_to_orig_seq'] if self.__caC is not None else {}
            auth_to_ins_code = self.__caC['auth_to_ins_code'] if self.__caC is not None else {}
            auth_to_star_seq_ann = self.__caC['auth_to_star_seq_ann'] if self.__caC is not None else {}
            coord_atom_site = self.__caC['coord_atom_site'] if self.__caC is not None else {}
            auth_atom_name_to_id = self.__caC['auth_atom_name_to_id'] if self.__caC is not None else {}
            auth_atom_name_to_id_ext = self.__caC['auth_atom_name_to_id_ext'] if self.__caC is not None else {}
            mis_poly_link = self.__caC['missing_polymer_linkage'] if self.__caC is not None else []

            _auth_to_orig_seq = {}

            has_auth_seq = valid_auth_seq = has_auth_chain = False
            aux_auth_seq_id_col = aux_auth_comp_id_col = aux_auth_atom_id_col = -1
            auth_asym_id_col = loop.tags.index('Auth_asym_ID') if 'Auth_asym_ID' in loop.tags else -1
            auth_seq_id_col = loop.tags.index('Auth_seq_ID') if 'Auth_seq_ID' in loop.tags else -1
            auth_comp_id_col = loop.tags.index('Auth_comp_ID') if 'Auth_comp_ID' in loop.tags else -1
            auth_atom_id_col = loop.tags.index('Auth_atom_ID') if 'Auth_atom_ID' in loop.tags else -1

            valid_auth_seq_per_chain = []

            # if self.__remediation_mode or self.__annotation_mode:
            if set(auth_pdb_tags) & set(loop.tags) == set(auth_pdb_tags):
                auth_dat = get_lp_tag(loop, auth_pdb_tags)
                if len(auth_dat) > 0:
                    has_auth_seq = valid_auth_seq = True
                    if not self.__annotation_mode:
                        for row in auth_dat:
                            try:
                                seq_key = (row[0], int(row[1]), row[2])
                                if seq_key not in auth_to_star_seq_ann:
                                    valid_auth_seq = False
                                    break
                            except (ValueError, TypeError):
                                has_auth_seq = valid_auth_seq = False
                                break

            # DAOTHER-9281
            elif auth_asym_id_col != -1:
                has_auth_chain = True
                _auth_pdb_tags = ['Auth_asym_ID']
                if 'Auth_seq_ID' in loop.tags:
                    _auth_pdb_tags.append('Auth_seq_ID')
                elif 'Comp_index_ID' in loop.tags:
                    _auth_pdb_tags.append('Comp_index_ID')
                elif 'Seq_ID' in loop.tags:
                    _auth_pdb_tags.append('Seq_ID')

                if 'Auth_comp_ID' in loop.tags:
                    _auth_pdb_tags.append('Auth_comp_ID')
                elif 'Comp_ID' in loop.tags:
                    _auth_pdb_tags.append('Comp_ID')

                if 'Auth_atom_ID' in loop.tags:
                    _auth_pdb_tags.append('Auth_atom_ID')
                elif 'Atom_ID' in loop.tags:
                    _auth_pdb_tags.append('Atom_ID')

                if len(_auth_pdb_tags) == 4:
                    auth_dat = get_lp_tag(loop, _auth_pdb_tags)
                    if len(auth_dat) > 0:
                        aux_auth_seq_id_col = loop.tags.index(_auth_pdb_tags[1])
                        aux_auth_comp_id_col = loop.tags.index(_auth_pdb_tags[2])
                        aux_auth_atom_id_col = loop.tags.index(_auth_pdb_tags[3])

                        valid_auth_seq = True
                        if not self.__annotation_mode:
                            for row in auth_dat:
                                try:
                                    seq_key = (row[0], int(row[1]), row[2])
                                    if seq_key not in auth_to_star_seq_ann:
                                        valid_auth_seq = False
                                        break
                                except (ValueError, TypeError):
                                    valid_auth_seq = False
                                    break
                        if not valid_auth_seq:
                            for row in auth_dat:
                                if row[0] not in valid_auth_seq_per_chain:
                                    valid_auth_seq_per_chain.append(row[0])
                            if not self.__annotation_mode:
                                for row in auth_dat:
                                    try:
                                        seq_key = (row[0], int(row[1]), row[2])
                                        if seq_key not in auth_to_star_seq_ann:
                                            if row[0] in valid_auth_seq_per_chain:
                                                valid_auth_seq_per_chain.remove(row[0])
                                    except (ValueError, TypeError):
                                        if row[0] in valid_auth_seq_per_chain:
                                            valid_auth_seq_per_chain.remove(row[0])

            has_orig_seq = False
            ch2_name_in_xplor = ch3_name_in_xplor = False

            if self.__remediation_mode:
                if set(orig_pdb_tags) & set(loop.tags) == set(orig_pdb_tags):
                    orig_dat = get_lp_tag(loop, orig_pdb_tags)
                    if len(orig_dat) > 0:
                        for row in orig_dat:
                            if all(d not in emptyValue for d in row):
                                has_orig_seq = True
                                break
                        if has_orig_seq:
                            orig_pdb_tags.append('Comp_ID')
                            orig_pdb_tags.append('Atom_ID')
                            dat = get_lp_tag(loop, orig_pdb_tags)
                            for row in dat:
                                if row[3] in emptyValue:
                                    continue
                                orig_atom_id = row[3].upper()
                                comp_id = row[4]
                                atom_id = row[5]
                                if orig_atom_id == atom_id:
                                    continue
                                ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                if ambig_code == 0 or atom_id[0] not in protonBeginCode:
                                    continue
                                len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                if len_in_grp == 2 and ambig_code == 2:
                                    ch2_name_in_xplor = any(r for r, o in zip(atom_id, orig_atom_id) if r == '3' and o == '1')
                                elif len_in_grp == 3 and atom_id[-1] == orig_atom_id[0]:
                                    ch3_name_in_xplor = True
            else:
                if set(orig_pdb_tags) & set(loop.tags) == set(orig_pdb_tags):
                    orig_dat = get_lp_tag(loop, orig_pdb_tags)
                    if len(orig_dat) > 0:
                        for row in orig_dat:
                            if all(d not in emptyValue for d in row):
                                has_orig_seq = True
                                break

            chain_id_col = loop.tags.index('Entity_assembly_ID')
            entity_id_col = loop.tags.index('Entity_ID') if 'Entity_ID' in loop.tags else -1
            seq_id_col = loop.tags.index('Comp_index_ID')
            comp_id_col = loop.tags.index('Comp_ID')
            atom_id_col = loop.tags.index('Atom_ID')
            val_col = loop.tags.index('Val')
            val_err_col = loop.tags.index('Val_err') if 'Val_err' in loop.tags else -1
            fig_of_merit_col = loop.tags.index('Assign_fig_of_merit') if 'Assign_fig_of_merit' in loop.tags else -1
            ambig_code_col = loop.tags.index('Ambiguity_code') if 'Ambiguity_code' in loop.tags else -1
            ambig_set_id_col = loop.tags.index('Ambiguity_set_ID') if 'Ambiguity_set_ID' in loop.tags else -1
            occupancy_col = loop.tags.index('Occupancy') if 'Occupancy' in loop.tags else -1
            reson_id_col = loop.tags.index('Resonance_ID') if 'Resonance_ID' in loop.tags else -1
            details_col = loop.tags.index('Details') if 'Details' in loop.tags else -1

            if self.__annotation_mode and details_col != -1:
                for row in loop.data:
                    if row[details_col] == 'UNMAPPED':
                        row[details_col] = None

            trial = 0

            incomplete_comp_id_annotation = []  # DAOTHER-9286
            truncated_loop_sequence = []  # DAOTHER-9644
            for mis in mis_poly_link:
                auth_chain_id = mis['auth_chain_id']
                auth_seq_id_1 = mis['auth_seq_id_1']
                auth_seq_id_2 = mis['auth_seq_id_2']

                cif_ps = next((cif_ps for cif_ps in self.__caC['polymer_sequence'] if cif_ps['auth_chain_id'] == auth_chain_id), None)

                if cif_ps is not None and auth_seq_id_1 in cif_ps['auth_seq_id'] and auth_seq_id_2 in cif_ps['auth_seq_id']\
                   and auth_seq_id_1 < auth_seq_id_2:
                    for auth_seq_id in range(auth_seq_id_1 + 1, auth_seq_id_2):
                        _seq_key = (auth_chain_id, auth_seq_id)
                        truncated_loop_sequence.append(_seq_key)

            def fill_cs_row(lp, index, _row, prefer_auth_atom_name, coord_atom_site, _seq_key, comp_id, atom_id, src_lp, src_idx):
                _src_idx = src_idx
                if src_idx > 0:
                    src_idx -= 1
                fill_auth_atom_id = self.__annotation_mode or (_row[19] in emptyValue and _row[18] not in emptyValue)
                fill_orig_atom_id = _row[23] not in emptyValue

                if _seq_key is not None:
                    if _seq_key in truncated_loop_sequence and _row[24] in emptyValue:
                        _row[24] = 'UNMAPPED'
                    seq_key = (_seq_key[0], _seq_key[1], comp_id)
                    _seq_key = seq_key if seq_key in coord_atom_site else _seq_key
                if _seq_key in coord_atom_site:
                    _coord_atom_site = coord_atom_site[_seq_key]
                    _atom_site_atom_id = _coord_atom_site['atom_id']
                    # DAOTHER-8817
                    if 'chain_id' in _coord_atom_site:
                        _row[16] = _coord_atom_site['chain_id']
                    _row[5] = _row[18] = comp_id = _coord_atom_site['comp_id']
                    valid = True
                    missing_ch3 = []
                    if not self.__annotation_mode and atom_id in self.__csStat.getRepMethylProtons(comp_id):
                        missing_ch3 = self.__csStat.getProtonsInSameGroup(comp_id, atom_id, True)
                        valid = self.__sail_flag
                        row_src = src_lp.data[src_idx]
                        for offset in range(1, 10):
                            if src_idx + offset < len(src_lp.data):
                                row = src_lp.data[src_idx + offset]
                                if (row[seq_id_col] == str(_row[3]) or (_row[3] != row_src[3] and row[seq_id_col] == row_src[seq_id_col]))\
                                   and row[comp_id_col].upper() == comp_id\
                                   and row[atom_id_col] in missing_ch3:
                                    valid = True
                                    missing_ch3.remove(row[atom_id_col])
                                    if len(missing_ch3) == 0:
                                        break
                            if src_idx - offset >= 0:
                                row = src_lp.data[src_idx - offset]
                                if (row[seq_id_col] == str(_row[3]) or (_row[3] != row_src[3] and row[seq_id_col] == row_src[seq_id_col]))\
                                   and row[comp_id_col].upper() == comp_id\
                                   and row[atom_id_col] in missing_ch3:
                                    valid = True
                                    missing_ch3.remove(row[atom_id_col])
                                    if len(missing_ch3) == 0:
                                        break
                    if atom_id in _atom_site_atom_id and valid and len(missing_ch3) == 0\
                       and (not self.__annotation_mode or comp_id not in incomplete_comp_id_annotation):
                        _row[6] = atom_id
                        if fill_auth_atom_id or _row[6] != _row[19]:
                            _row[19] = _row[6]
                        _row[7] = _coord_atom_site['type_symbol'][_atom_site_atom_id.index(atom_id)]
                        if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                            _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                        if fill_orig_atom_id and _row[6] != _row[23] and _row[23] in _atom_site_atom_id:
                            if _row[23] in self.__csStat.getProtonsInSameGroup(comp_id, atom_id, True):
                                _row[23] = copy.copy(atom_id)
                    else:
                        if atom_id in ('H1', 'HT1') and 'H' in _atom_site_atom_id\
                           and atom_id not in _atom_site_atom_id:
                            if self.__ccU.updateChemCompDict(comp_id):
                                cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id and cca[self.__ccU.ccaLeavingAtomFlag] == 'N'), None)
                                if cca is None:
                                    atom_id = 'H'
                                    if fill_auth_atom_id:
                                        _row[19] = atom_id
                            else:
                                atom_id = 'H'
                                if fill_auth_atom_id:
                                    _row[19] = atom_id
                        elif atom_id in ('H', 'HT1') and 'H1' in _atom_site_atom_id\
                                and atom_id not in _atom_site_atom_id:
                            if self.__ccU.updateChemCompDict(comp_id):
                                cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id and cca[self.__ccU.ccaLeavingAtomFlag] == 'N'), None)
                                if cca is None:
                                    atom_id = 'H1'
                                    if fill_auth_atom_id:
                                        _row[19] = atom_id
                            else:
                                atom_id = 'H1'
                                if fill_auth_atom_id:
                                    _row[19] = atom_id
                        elif atom_id in aminoProtonCode and 'C' + atom_id[1:] in _atom_site_atom_id:
                            bonded = self.__ccU.getBondedAtoms(comp_id, 'C' + atom_id[1:], onlyProton=True)
                            if len(bonded) == 1 and bonded[0] in _atom_site_atom_id:
                                atom_id = bonded[0]
                                if fill_auth_atom_id:
                                    _row[19] = atom_id
                        if len(missing_ch3) > 0 and (_row[9] in emptyValue or float(_row[9]) >= 4.0):
                            heme = False
                            if _row[9] not in emptyValue:
                                if self.__ccU.updateChemCompDict(comp_id):
                                    heme = comp_id == 'HEM' or 'HEME' in self.__ccU.lastChemCompDict['_chem_comp.name']
                            if not heme:
                                missing_ch3 = []
                        _atom_id = atom_id
                        if not valid and len(missing_ch3) > 0 and atom_id not in _atom_site_atom_id:
                            atom_id = atom_id[:-1]
                            if _atom_id in self.__csStat.getRepMethylProtons(comp_id):
                                atom_id = _atom_id
                        if (valid and atom_id in _atom_site_atom_id)\
                           or ((prefer_auth_atom_name or _row[24] == 'UNMAPPED') and atom_id[0] not in ('Q', 'M')):
                            atom_ids = [atom_id]
                            if self.__annotation_mode and comp_id in incomplete_comp_id_annotation and trial > 0:  # DAOTHER-9286
                                atom_ids = self.__getAtomIdListInXplorForLigandRemap(comp_id, _row[23] if fill_orig_atom_id else atom_id, _coord_atom_site)
                        else:
                            atom_ids = self.__getAtomIdListInXplor(comp_id, atom_id)
                            if len(atom_ids) == 0 or atom_ids[0] not in _atom_site_atom_id:
                                atom_ids = self.__getAtomIdListInXplor(comp_id, translateToStdAtomName(atom_id, comp_id, _atom_site_atom_id, ccU=self.__ccU))
                                if len(atom_ids) == 1 and atom_ids[0] in _atom_site_atom_id and atom_id not in _atom_site_atom_id:
                                    atom_id = atom_ids[0]
                            if self.__annotation_mode and atom_ids[0] not in _atom_site_atom_id:  # DAOTHER-9286
                                atom_ids = self.__getAtomIdListInXplorForLigandRemap(comp_id, atom_id, _coord_atom_site)
                                if comp_id not in incomplete_comp_id_annotation:
                                    incomplete_comp_id_annotation.append(comp_id)
                        if valid and len(missing_ch3) > 0:
                            if not fill_orig_atom_id or not any(c in ('x', 'y', 'X', 'Y') for c in _row[23])\
                               and len(self.__getAtomIdListInXplor(comp_id, _row[23])) > 1 and _row[24] != 'UNMAPPED':
                                atom_ids = self.__getAtomIdListInXplor(comp_id, _row[23])
                            else:
                                missing_ch3.clear()
                        if not valid and len(missing_ch3) > 0 and atom_id in _atom_site_atom_id:
                            atom_ids.extend(missing_ch3)
                        len_atom_ids = len(atom_ids)
                        if len_atom_ids == 0:
                            _row[6] = atom_id
                            if fill_auth_atom_id:
                                _row[19] = _row[6]
                            _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                            if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                        else:
                            methyl_atoms = self.__csStat.getMethylAtoms(comp_id)
                            _row[6] = atom_ids[0]
                            _row[19] = None
                            fill_auth_atom_id = _row[18] not in emptyValue
                            if self.__ccU.updateChemCompDict(comp_id):
                                cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == _row[6]), None)
                                if cca is not None:
                                    _row[7] = cca[self.__ccU.ccaTypeSymbol]
                                    if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                        _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                                else:
                                    _row[7] = 'H' if _row[6][0] in protonBeginCode else atom_id[0]
                                    if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                        _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                            else:
                                _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                                if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                    _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]

                            ambig_code = _row[12]
                            if ambig_code == 0:
                                _row[12] = None

                            elif ambig_code in (2, 3):
                                _ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _row[6])
                                if _ambig_code not in (0, ambig_code):
                                    if _ambig_code != 1:
                                        _row[12] = _ambig_code
                                    else:
                                        _row[12] = ambig_code = 4

                            elif ambig_code == 4:
                                if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                    row_src = src_lp.data[_src_idx]
                                    for offset in range(1, 10):
                                        if src_idx + offset < len(src_lp.data):
                                            row = src_lp.data[src_idx + offset]
                                            if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                               and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                                if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                    _row[12] = ambig_code = 6
                                                    break
                                                _seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
                                                if _seq_id != _row[3]:
                                                    _row[12] = ambig_code = 5
                                        if src_idx - offset >= 0:
                                            row = src_lp.data[src_idx - offset]
                                            if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                               and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                                if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                    _row[12] = ambig_code = 6
                                                    break
                                                _seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
                                                if _seq_id != _row[3]:
                                                    _row[12] = ambig_code = 5

                            elif ambig_code == 5:
                                if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                    row_src = src_lp.data[_src_idx]
                                    for offset in range(1, 10):
                                        if src_idx + offset < len(src_lp.data):
                                            row = src_lp.data[src_idx + offset]
                                            if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                               and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                                if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                    _row[12] = ambig_code = 6
                                                    break
                                        if src_idx - offset >= 0:
                                            row = src_lp.data[src_idx - offset]
                                            if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                               and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                                if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                    _row[12] = ambig_code = 6
                                                    break

                            elif ambig_code == 6:
                                if len([item for item in entity_assembly
                                        if item['entity_type'] not in ('non-polymer', 'water')]) == 1\
                                   and len(entity_assembly[0]['label_asym_id'].split(',')) == 1:
                                    _row[12] = ambig_code = 5

                            if ambig_code in (1, 2, 3):
                                if _row[13] is not None:
                                    _row[13] = None

                            if len_atom_ids > 1:
                                if _row[12] == 1 or _row[12] in emptyValue:
                                    if _row[6] not in methyl_atoms\
                                       or (_row[6] in methyl_atoms
                                           and ((_row[7][0] == 'H' and len_atom_ids == 6)
                                                or (_row[7][0] == 'C' and len_atom_ids == 2))):
                                        _row[12] = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _row[6], None)
                                __row = copy.copy(_row)
                                if fill_auth_atom_id:
                                    __row[19] = __row[6]

                                lp.add_data(__row)

                                for _atom_id in atom_ids[1:]:
                                    __row = copy.copy(_row)

                                    index += 1

                                    __row[0] = index
                                    __row[6] = _atom_id
                                    if fill_auth_atom_id:
                                        __row[19] = __row[6]
                                    if fill_orig_atom_id and len(missing_ch3) > 0 and __row[23] in emptyValue:
                                        if _atom_id in methyl_atoms:
                                            if ch3_name_in_xplor and _atom_id[0] in protonBeginCode:
                                                __row[23] = __row[6][-1] + __row[6][:-1]
                                            else:
                                                __row[23] = copy.copy(__row[6])

                                    lp.add_data(__row)

                                index += 1

                                _row[0] = index
                                _row[6] = atom_ids[-1]

                            if fill_auth_atom_id:
                                _row[19] = _row[6]
                            if fill_orig_atom_id and len(missing_ch3) > 0 and _row[23] in emptyValue:
                                if _row[6] in methyl_atoms:
                                    if ch3_name_in_xplor and _row[6][0] in protonBeginCode:
                                        _row[23] = _row[6][-1] + _row[6][:-1]
                                    else:
                                        _row[23] = copy.copy(_row[6])

                else:

                    _row[5] = comp_id
                    valid = True
                    missing_ch3 = []
                    if atom_id in self.__csStat.getRepMethylProtons(comp_id):
                        missing_ch3 = self.__csStat.getProtonsInSameGroup(comp_id, atom_id, True)
                        valid = self.__sail_flag
                        for offset in range(1, 10):
                            row_src = src_lp.data[src_idx]
                            if src_idx + offset < len(src_lp.data):
                                row = src_lp.data[src_idx + offset]
                                if (row[seq_id_col] == str(_row[3]) or (_row[3] != row_src[3] and row[seq_id_col] == row_src[seq_id_col])
                                    or (_row[24] == 'UNMAPPED' and row[seq_id_col] == str(_row[17])))\
                                   and row[comp_id_col].upper() == comp_id\
                                   and row[atom_id_col] in missing_ch3:
                                    valid = True
                                    missing_ch3.remove(row[atom_id_col])
                                    if len(missing_ch3) == 0:
                                        break
                            if src_idx - offset >= 0:
                                row = src_lp.data[src_idx - offset]
                                if (row[seq_id_col] == str(_row[3]) or (_row[3] != row_src[3] and row[seq_id_col] == row_src[seq_id_col])
                                    or (_row[24] == 'UNMAPPED' and row[seq_id_col] == str(_row[17])))\
                                   and row[comp_id_col].upper() == comp_id\
                                   and row[atom_id_col] in missing_ch3:
                                    valid = True
                                    missing_ch3.remove(row[atom_id_col])
                                    if len(missing_ch3) == 0:
                                        break
                    if len(missing_ch3) > 0 and (_row[9] in emptyValue or float(_row[9]) >= 4.0):
                        heme = False
                        if _row[9] not in emptyValue:
                            if self.__ccU.updateChemCompDict(comp_id):
                                heme = comp_id == 'HEM' or 'HEME' in self.__ccU.lastChemCompDict['_chem_comp.name']
                        if not heme:
                            missing_ch3 = []
                    _atom_id = atom_id
                    if not valid and len(missing_ch3) > 0:
                        atom_id = atom_id[:-1]
                        if _atom_id in self.__csStat.getRepMethylProtons(comp_id):
                            atom_id = _atom_id
                    if (valid or prefer_auth_atom_name or _row[24] == 'UNMAPPED') and atom_id[0] not in ('Q', 'M'):
                        atom_ids = [atom_id]
                    else:
                        atom_ids = self.__getAtomIdListInXplor(comp_id, atom_id)
                        if len(atom_ids) == 0:
                            atom_ids = self.__getAtomIdListInXplor(comp_id, translateToStdAtomName(atom_id, comp_id, ccU=self.__ccU))
                    if valid and len(missing_ch3) > 0:
                        if not fill_orig_atom_id or not any(c in ('x', 'y', 'X', 'Y') for c in _row[23])\
                           and len(self.__getAtomIdListInXplor(comp_id, _row[23])) > 1 and _row[24] != 'UNMAPPED':
                            atom_ids = self.__getAtomIdListInXplor(comp_id, _row[23])
                        else:
                            missing_ch3.clear()
                    if not valid and len(missing_ch3) > 0:
                        atom_ids.extend(missing_ch3)
                    len_atom_ids = len(atom_ids)
                    if len_atom_ids == 0:
                        _row[6] = atom_id
                        if fill_auth_atom_id or _row[6] != _row[19]:
                            _row[19] = _row[6]
                        _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                        if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                            _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                    else:
                        methyl_atoms = self.__csStat.getMethylAtoms(comp_id)
                        _row[6] = atom_ids[0]
                        _row[19] = None
                        fill_auth_atom_id = _row[18] not in emptyValue
                        if self.__ccU.updateChemCompDict(comp_id):
                            cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == _row[6]), None)
                            if cca is not None:
                                _row[7] = cca[self.__ccU.ccaTypeSymbol]
                                if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                    _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                            else:
                                _row[7] = 'H' if _row[6][0] in protonBeginCode else atom_id[0]
                                if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                    _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                        else:
                            _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                            if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]

                        ambig_code = _row[12]
                        if ambig_code == 0:
                            _row[12] = None

                        elif ambig_code in (2, 3):
                            _ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _row[6])
                            if _ambig_code not in (0, ambig_code):
                                if _ambig_code != 1:
                                    _row[12] = _ambig_code
                                else:
                                    _row[12] = ambig_code = 4

                        elif ambig_code == 4:
                            if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                row_src = src_lp.data[_src_idx]
                                for offset in range(1, 10):
                                    if src_idx + offset < len(src_lp.data):
                                        row = src_lp.data[src_idx + offset]
                                        if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                           and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                            if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                _row[12] = ambig_code = 6
                                                break
                                            _seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
                                            if _seq_id != _row[3]:
                                                _row[12] = ambig_code = 5
                                    if src_idx - offset >= 0:
                                        row = src_lp.data[src_idx - offset]
                                        if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                           and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                            if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                _row[12] = ambig_code = 6
                                                break
                                            _seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
                                            if _seq_id != _row[3]:
                                                _row[12] = ambig_code = 5

                        elif ambig_code == 5:
                            if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                row_src = src_lp.data[_src_idx]
                                for offset in range(1, 10):
                                    if src_idx + offset < len(src_lp.data):
                                        row = src_lp.data[src_idx + offset]
                                        if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                           and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                            if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                _row[12] = ambig_code = 6
                                                break
                                    if src_idx - offset >= 0:
                                        row = src_lp.data[src_idx - offset]
                                        if row[comp_id_col] == row_src[comp_id_col] and row[atom_id_col] == row_src[atom_id_col]\
                                           and row[ambig_code_col] == str(_row[12]) or (_row[12] != row_src[12] and row[ambig_code_col] == row_src[ambig_code_col]):
                                            if not (row[chain_id_col] == str(_row[1]) or (_row[1] != row_src[1] and row[chain_id_col] == row_src[chain_id_col])):
                                                _row[12] = ambig_code = 6
                                                break

                        elif ambig_code == 6:
                            if len([item for item in entity_assembly
                                    if item['entity_type'] not in ('non-polymer', 'water')]) == 1\
                               and len(entity_assembly[0]['label_asym_id'].split(',')) == 1:
                                _row[12] = ambig_code = 5

                        if ambig_code in (1, 2, 3):
                            if _row[13] is not None:
                                _row[13] = None

                        if len_atom_ids > 1:
                            if _row[12] == 1 or _row[12] in emptyValue:
                                if _row[6] not in methyl_atoms\
                                   or (_row[6] in methyl_atoms
                                       and ((_row[7][0] == 'H' and len_atom_ids == 6)
                                            or (_row[7][0] == 'C' and len_atom_ids == 2))):
                                    _row[12] = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _row[6], None)
                            __row = copy.copy(_row)
                            if fill_auth_atom_id:
                                __row[19] = __row[6]
                            lp.add_data(__row)

                            for _atom_id in atom_ids[1:]:
                                __row = copy.copy(_row)

                                index += 1

                                __row[0] = index
                                __row[6] = _atom_id
                                if fill_auth_atom_id:
                                    __row[19] = __row[6]
                                if fill_orig_atom_id and len(missing_ch3) > 0\
                                   and __row[23] in emptyValue:
                                    if _atom_id in methyl_atoms:
                                        if ch3_name_in_xplor and _atom_id[0] in protonBeginCode:
                                            __row[23] = __row[6][-1] + __row[6][:-1]
                                        else:
                                            __row[23] = copy.copy(__row[6])

                                lp.add_data(__row)

                            index += 1

                            _row[0] = index
                            _row[6] = atom_ids[-1]

                        if fill_auth_atom_id:
                            _row[19] = _row[6]
                        if fill_orig_atom_id and len(missing_ch3) > 0\
                           and _row[23] in emptyValue:
                            if _row[6] in methyl_atoms:
                                if ch3_name_in_xplor and _row[6][0] in protonBeginCode:
                                    _row[23] = _row[6][-1] + _row[6][:-1]
                                else:
                                    _row[23] = copy.copy(_row[6])

                return index, _row

            copied_auth_chain_ids = set()
            copied_chain_ids = set()

            if has_auth_seq:
                auth_asym_ids = [row[0] for row in auth_dat]

                common_auth_asym_ids = collections.Counter(auth_asym_ids).most_common()

                if len(common_auth_asym_ids) > 1:
                    auth_cs_tags = ['Auth_asym_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID', 'Val']

                    _common_auth_asym_ids = dict(common_auth_asym_ids)

                    for _auth_chain_id_1, _auth_chain_id_2 in itertools.combinations(_common_auth_asym_ids.keys(), 2):

                        if _common_auth_asym_ids[_auth_chain_id_1] != _common_auth_asym_ids[_auth_chain_id_2]:
                            continue

                        try:
                            _auth_seq_id_1 = next(int(row[1]) for row in auth_dat if row[0] == _auth_chain_id_1)
                            _auth_seq_id_2 = next(int(row[1]) for row in auth_dat if row[0] == _auth_chain_id_2)
                        except (ValueError, TypeError):
                            continue

                        _seq_key_1 = (_auth_chain_id_1, _auth_seq_id_1, row[2])
                        _seq_key_2 = (_auth_chain_id_2, _auth_seq_id_2, row[2])

                        if _seq_key_1 not in auth_to_entity_type or _seq_key_2 not in auth_to_entity_type:
                            continue

                        if auth_to_entity_type[_seq_key_1] != auth_to_entity_type[_seq_key_2] or auth_to_entity_type[_seq_key_1] in ('non-polymer', 'water'):
                            continue

                        _auth_cs_1 = [row[1:] for row in get_lp_tag(loop, auth_cs_tags) if row[0] == _auth_chain_id_1]
                        _auth_cs_2 = [row[1:] for row in get_lp_tag(loop, auth_cs_tags) if row[0] == _auth_chain_id_2]

                        _auth_cs_1 = sorted(_auth_cs_1, key=itemgetter(0, 2))
                        _auth_cs_2 = sorted(_auth_cs_2, key=itemgetter(0, 2))

                        if _auth_cs_1 == _auth_cs_2:
                            copied_auth_chain_ids.add(_auth_chain_id_2)

            else:

                tags = ['Entity_assembly_ID', 'Comp_index_ID', 'Comp_ID', 'Atom_ID']
                dat = get_lp_tag(loop, tags)

                chain_ids = [row[0] for row in dat]

                common_chain_ids = collections.Counter(chain_ids).most_common()

                if len(common_chain_ids) > 1:
                    cs_tags = ['Entity_assembly_ID', 'Comp_index_ID', 'Comp_ID', 'Atom_ID', 'Val']

                    _common_chain_ids = dict(common_chain_ids)

                    for _chain_id_1, _chain_id_2 in itertools.combinations(_common_chain_ids, 2):

                        if _common_chain_ids[_chain_id_1] != _common_chain_ids[_chain_id_2]:
                            continue

                        _cs_1 = [row[1:] for row in get_lp_tag(loop, cs_tags) if row[0] == _chain_id_1]
                        _cs_2 = [row[1:] for row in get_lp_tag(loop, cs_tags) if row[0] == _chain_id_2]

                        _cs_1 = sorted(_cs_1, key=itemgetter(0, 2))
                        _cs_2 = sorted(_cs_2, key=itemgetter(0, 2))

                        if _cs_1 == _cs_2:
                            copied_chain_ids.add(_chain_id_2)

            if has_orig_seq:
                orig_asym_id_col = loop.tags.index('Original_PDB_strand_ID')
                orig_seq_id_col = loop.tags.index('Original_PDB_residue_no')
                orig_comp_id_col = loop.tags.index('Original_PDB_residue_name')
                orig_atom_id_col = loop.tags.index('Original_PDB_atom_name')

            lp = pynmrstar.Loop.from_scratch(lp_category)

            tags = [lp_category + '.' + item for item in items]

            for tag in tags:
                lp.add_tag(tag)

            prefer_auth_atom_name = False

            if (self.__annotation_mode or self.__native_combined) and len(auth_atom_name_to_id) > 0:

                count_auth_name = count_auth_id = 0

                for row in loop:

                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = row[auth_seq_id_col]
                    auth_comp_id = row[auth_comp_id_col]
                    auth_atom_id = row[auth_atom_id_col]

                    auth_seq_id_ = int(auth_seq_id)
                    seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                    try:
                        auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                    except KeyError:
                        auth_asym_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                             if _auth_seq_id == auth_seq_id_ and _auth_comp_id == auth_comp_id), auth_asym_id)
                        if (auth_asym_id, auth_seq_id_, auth_comp_id) not in auth_to_star_seq:
                            auth_comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                 if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id_), auth_comp_id)

                    if auth_comp_id in auth_atom_name_to_id:
                        if auth_atom_id in auth_atom_name_to_id[auth_comp_id]:
                            count_auth_name += 1
                        if auth_atom_id in auth_atom_name_to_id[auth_comp_id].values():
                            count_auth_id += 1

                if count_auth_name + count_auth_id == 0:

                    for row in loop:

                        auth_asym_id = row[auth_asym_id_col]
                        auth_seq_id = row[auth_seq_id_col]
                        auth_comp_id = row[auth_comp_id_col]
                        auth_atom_id = row[auth_atom_id_col]

                        auth_seq_id_ = int(auth_seq_id)
                        seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                        try:
                            auth_to_star_seq_ann[seq_key]  # pylint: disable=pointless-statement
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                auth_comp_id = coord_atom_site[_seq_key]['comp_id']
                        except KeyError:
                            continue

                        if auth_comp_id in auth_atom_name_to_id:
                            if auth_atom_id in auth_atom_name_to_id[auth_comp_id]:
                                count_auth_name += 1
                            if auth_atom_id in auth_atom_name_to_id[auth_comp_id].values():
                                count_auth_id += 1

                prefer_auth_atom_name = count_auth_name > count_auth_id

            has_genuine_ambig_code = False

            can_auth_asym_id_mapping = {}  # DAOTHER-8751
            seq_id_offset_for_unmapped = {}  # DAOTHER-9065
            label_seq_id_offset_for_extended = {}  # D_1300044764

            while True:

                regenerate_request = False  # DAOTHER-9065

                can_auth_asym_id_mapping_failed = False  # DAOTHER-9158

                lp.clear_data()

                index = 1

                for idx, row in enumerate(loop):

                    _row = [None] * len(tags)

                    _row[0] = index

                    comp_id = _orig_comp_id = row[comp_id_col].upper()
                    _orig_atom_id = row[atom_id_col]
                    atom_id = _orig_atom_id.upper()

                    _row[9] = row[val_col]

                    try:
                        float(_row[9])
                    except ValueError:
                        continue

                    if val_err_col != -1:
                        val_err = row[val_err_col]
                        _row[10] = val_err

                        if val_err not in emptyValue:
                            try:
                                _val_err = float(val_err)
                                if _val_err < 0.0:
                                    _row[10] = abs(_val_err)
                            except ValueError:
                                pass

                    if fig_of_merit_col != -1:
                        _row[11] = row[fig_of_merit_col]

                    if ambig_code_col != -1:
                        ambig_code = row[ambig_code_col]
                        if ambig_code not in emptyValue:
                            try:
                                ambig_code = int(ambig_code) if isinstance(ambig_code, str) else ambig_code
                                if ambig_code in ALLOWED_AMBIGUITY_CODES:
                                    _row[12] = ambig_code
                                else:
                                    _row[12] = None
                            except ValueError:
                                _row[12] = None

                    if ambig_set_id_col != -1:
                        ambig_set_id = row[ambig_set_id_col]
                        if ambig_set_id not in emptyValue:
                            try:
                                ambig_set_id = int(ambig_set_id)
                                if ambig_set_id > 0:
                                    _row[13] = ambig_set_id
                            except ValueError:
                                _row[13] = None

                    if occupancy_col != -1:
                        occupancy = row[occupancy_col]
                        if occupancy not in emptyValue:
                            try:
                                occupancy = float(occupancy)
                                if occupancy >= 0.0:
                                    _row[14] = occupancy
                            except ValueError:
                                pass

                    if reson_id_col != -1:
                        reson_id = row[reson_id_col]
                        if reson_id not in emptyValue:
                            try:
                                reson_id = int(reson_id)
                                if reson_id > 0:
                                    _row[15] = reson_id
                            except ValueError:
                                pass

                    if has_auth_seq:

                        if row[auth_asym_id_col] in copied_auth_chain_ids:
                            continue

                        _row[16], _row[17], _row[18], _row[19] =\
                            row[auth_asym_id_col], row[auth_seq_id_col], \
                            row[auth_comp_id_col], row[auth_atom_id_col]

                    if has_orig_seq:
                        _row[20], _row[21], _row[22], _row[23] =\
                            row[orig_asym_id_col], row[orig_seq_id_col], \
                            row[orig_comp_id_col], row[orig_atom_id_col]

                    if details_col != -1:
                        _row[24] = row[details_col]

                    _row[25], _row[26] = self.__entry_id, list_id

                    resolved = True

                    if has_auth_seq:
                        auth_asym_id = row[auth_asym_id_col]
                        auth_seq_id = row[auth_seq_id_col]
                        auth_comp_id = row[auth_comp_id_col]

                        if valid_auth_seq:
                            auth_seq_id_ = int(auth_seq_id)
                            seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                            _seq_key = (seq_key[0], seq_key[1])
                            try:
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                if atom_id != _row[19]:
                                    if _seq_key in coord_atom_site:
                                        _coord_atom_site = coord_atom_site[_seq_key]
                                        if atom_id in _coord_atom_site['atom_id']:
                                            _row[19] = atom_id
                            except KeyError:
                                if self.__annotation_mode or self.__native_combined:
                                    auth_asym_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                         if _auth_seq_id == auth_seq_id_ and _auth_comp_id == auth_comp_id), auth_asym_id)
                                    seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                    if seq_key in auth_to_star_seq:
                                        _row[16] = row[auth_asym_id_col] = auth_asym_id
                                        _row[20] = row[orig_asym_id_col] = auth_asym_id
                                        _seq_key = (seq_key[0], seq_key[1])
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                    else:
                                        auth_asym_id, auth_comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                           for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                           if _auth_seq_id == auth_seq_id_), (auth_asym_id, auth_comp_id))
                                        seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                        if seq_key in auth_to_star_seq:
                                            _row[16] = row[auth_asym_id_col] = auth_asym_id
                                            _row[20] = row[orig_asym_id_col] = auth_asym_id
                                            _row[5] = row[comp_id_col] = auth_comp_id
                                            _row[18] = row[auth_comp_id_col] = auth_comp_id
                                            comp_id = auth_comp_id
                                            _seq_key = (seq_key[0], seq_key[1])
                                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                if seq_key not in auth_to_star_seq:
                                    auth_comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                         if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id_), auth_comp_id)
                                    comp_id = _row[18] = auth_comp_id
                                    seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                    if seq_key in auth_to_star_seq:
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                    else:
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq_ann[seq_key]

                            self.__ent_asym_id_with_exptl_data.add(entity_assembly_id)
                            _row[1], _row[2] = entity_assembly_id, entity_id
                            _row[3] = _row[4] = seq_id

                            if prefer_auth_atom_name:
                                if has_orig_seq:
                                    orig_atom_id = _row[23]
                                _atom_id = atom_id
                                _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                if _seq_key in coord_atom_site:
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']:
                                        if _atom_id in auth_atom_name_to_id[comp_id]:
                                            if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                                _row[19] = atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                            elif 'split_comp_id' not in _coord_atom_site and has_orig_seq and orig_atom_id in auth_atom_name_to_id[comp_id]:
                                                _row[19] = atom_id = auth_atom_name_to_id[comp_id][orig_atom_id]
                                    if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                       and comp_id == _coord_atom_site['comp_id']:
                                        _row[19] = atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    # DAOTHER-8751, 8817 (D_1300043061)
                                    elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                         and _atom_id in _coord_atom_site['alt_atom_id']\
                                         and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                        _row[18] = comp_id
                                        # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                        cca_row = next((cca_row for cca_row in self.__cca_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                        if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                           and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                            _row[19] = atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                        else:
                                            _row[19] = atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    elif 'split_comp_id' in _coord_atom_site:
                                        for _comp_id in _coord_atom_site['split_comp_id']:
                                            if _comp_id == comp_id:
                                                continue
                                            __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                            __coord_atom_site = coord_atom_site[__seq_key]
                                            if __coord_atom_site is None:
                                                continue
                                            if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                               and _atom_id in __coord_atom_site['alt_atom_id']:
                                                comp_id = _comp_id
                                                _row[18] = comp_id
                                                # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                                row[19] = atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                                _seq_key = __seq_key
                                                break
                                            if _atom_id in __coord_atom_site['atom_id']:
                                                comp_id = _comp_id
                                                _row[18] = comp_id
                                                # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                                _seq_key = __seq_key
                                                break

                            if has_ins_code and seq_key in auth_to_ins_code:
                                _row[27] = auth_to_ins_code[seq_key]

                            if seq_key in auth_to_orig_seq:
                                if _row[20] not in emptyValue and seq_key not in _auth_to_orig_seq:
                                    orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                    _auth_to_orig_seq[seq_key] = (_row[20], orig_seq_id, orig_comp_id)
                                if not has_orig_seq:
                                    orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                    if orig_seq_id in emptyValue:
                                        orig_seq_id = auth_seq_id
                                    if orig_comp_id in emptyValue:
                                        orig_comp_id = comp_id
                                    _row[20], _row[21], _row[22], _row[23] =\
                                        auth_asym_id, orig_seq_id, orig_comp_id, _orig_atom_id
                                elif any(d in emptyValue for d in orig_dat[idx]):
                                    if seq_key in _auth_to_orig_seq:
                                        _row[20], _row[21], _row[22] = _auth_to_orig_seq[seq_key]
                                    elif comp_id != auth_comp_id and translateToStdResName(comp_id, ccU=self.__ccU) == auth_comp_id:
                                        _row[20], _row[21], _row[22] = auth_asym_id, auth_seq_id, comp_id
                                        _row[5] = comp_id = auth_comp_id
                                    if _row[23] in emptyValue:
                                        _row[23] = atom_id
                                    ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                    if ambig_code > 0:
                                        orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                        if orig_seq_id in emptyValue:
                                            orig_seq_id = auth_seq_id
                                        if orig_comp_id in emptyValue:
                                            orig_comp_id = comp_id
                                        _row[20], _row[21], _row[22] =\
                                            auth_asym_id, orig_seq_id, orig_comp_id
                                        if atom_id[0] not in protonBeginCode:
                                            _row[23] = atom_id
                                        else:
                                            len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                            if len_in_grp == 2:
                                                _row[23] = (atom_id[0:-1] + '1')\
                                                    if ambig_code == 2 and ch2_name_in_xplor and atom_id[-1] == '3' else atom_id
                                            elif len_in_grp == 3:
                                                _row[23] = (atom_id[-1] + atom_id[0:-1])\
                                                    if ch3_name_in_xplor and atom_id[0] == 'H' and atom_id[-1] in ('1', '2', '3') else atom_id
                                            elif _row[23] in emptyValue:
                                                _row[23] = atom_id

                            else:
                                seq_key = next((k for k, v in auth_to_star_seq.items()
                                                if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                if seq_key is not None:
                                    _seq_key = (seq_key[0], seq_key[1])
                                    _row[16], _row[17], _row[18], _row[19] =\
                                        seq_key[0], seq_key[1], seq_key[2], atom_id

                                    if has_ins_code and seq_key in auth_to_ins_code:
                                        _row[27] = auth_to_ins_code[seq_key]

                                _row[20], _row[21], _row[22], _row[23] =\
                                    row[auth_asym_id_col], row[auth_seq_id_col], \
                                    row[auth_comp_id_col], row[auth_atom_id_col]

                            index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name, coord_atom_site, _seq_key,
                                                      comp_id, atom_id, loop, idx)

                        elif auth_asym_id not in emptyValue and auth_seq_id not in emptyValue and auth_comp_id not in emptyValue:

                            try:
                                _auth_seq_id = int(auth_seq_id)
                                seq_key = (auth_asym_id, _auth_seq_id, auth_comp_id)
                                _seq_key = (seq_key[0], seq_key[1])
                                if seq_key in auth_to_star_seq:
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                    self.__ent_asym_id_with_exptl_data.add(entity_assembly_id)
                                    _row[1], _row[2] = entity_assembly_id, entity_id
                                    _row[3] = _row[4] = seq_id

                                    if has_ins_code and seq_key in auth_to_ins_code:
                                        _row[27] = auth_to_ins_code[seq_key]

                                    if seq_key in auth_to_orig_seq:
                                        if _row[20] not in emptyValue and seq_key not in _auth_to_orig_seq:
                                            orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                            _auth_to_orig_seq[seq_key] = (_row[20], orig_seq_id, orig_comp_id)
                                        if not has_orig_seq:
                                            orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                            if orig_seq_id in emptyValue:
                                                orig_seq_id = auth_seq_id
                                            if orig_comp_id in emptyValue:
                                                orig_comp_id = comp_id
                                            _row[20], _row[21], _row[22], _row[23] =\
                                                auth_asym_id, orig_seq_id, orig_comp_id, _orig_atom_id
                                        elif any(d in emptyValue for d in orig_dat[idx]):
                                            if seq_key in _auth_to_orig_seq:
                                                _row[20], _row[21], _row[22] = _auth_to_orig_seq[seq_key]
                                            if _row[23] in emptyValue:
                                                _row[23] = atom_id
                                            ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                            if ambig_code > 0:
                                                orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                                if orig_seq_id in emptyValue:
                                                    orig_seq_id = auth_seq_id
                                                if orig_comp_id in emptyValue:
                                                    orig_comp_id = comp_id
                                                _row[20], _row[21], _row[22] =\
                                                    auth_asym_id, orig_seq_id, orig_comp_id
                                                if atom_id[0] not in protonBeginCode:
                                                    _row[23] = atom_id
                                                else:
                                                    len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                                    if len_in_grp == 2:
                                                        _row[23] = (atom_id[0:-1] + '1')\
                                                            if ambig_code == 2 and ch2_name_in_xplor and atom_id[-1] == '3' else atom_id
                                                    elif len_in_grp == 3:
                                                        _row[23] = (atom_id[-1] + atom_id[0:-1])\
                                                            if ch3_name_in_xplor and atom_id[0] == 'H' and atom_id[-1] in ('1', '2', '3') else atom_id
                                                    elif _row[23] in emptyValue:
                                                        _row[23] = atom_id

                                    else:
                                        seq_key = next((k for k, v in auth_to_star_seq.items()
                                                        if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                        if seq_key is not None:
                                            _seq_key = (seq_key[0], seq_key[1])
                                            _row[16], _row[17], _row[18], _row[19] =\
                                                seq_key[0], seq_key[1], seq_key[2], atom_id

                                            if has_ins_code and seq_key in auth_to_ins_code:
                                                _row[27] = auth_to_ins_code[seq_key]

                                        _row[20], _row[21], _row[22], _row[23] =\
                                            row[auth_asym_id_col], row[auth_seq_id_col], \
                                            row[auth_comp_id_col], row[auth_atom_id_col]

                                    index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                              coord_atom_site, _seq_key,
                                                              comp_id, atom_id, loop, idx)

                                else:
                                    resolved = False

                            except ValueError:
                                resolved = False

                        else:
                            resolved = False

                    # DAOTHER-9281
                    elif has_auth_chain and (valid_auth_seq or row[auth_asym_id_col] in valid_auth_seq_per_chain):
                        auth_asym_id = row[auth_asym_id_col]
                        auth_seq_id = row[aux_auth_seq_id_col]
                        auth_comp_id = row[aux_auth_comp_id_col]

                        _row[17] = auth_seq_id

                        auth_seq_id_ = int(auth_seq_id)
                        seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                        _seq_key = (seq_key[0], seq_key[1])
                        try:
                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            if atom_id != _row[19]:
                                if _seq_key in coord_atom_site:
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if atom_id in _coord_atom_site['atom_id']:
                                        _row[19] = atom_id
                        except KeyError:
                            if self.__annotation_mode or self.__native_combined:
                                auth_asym_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                     if _auth_seq_id == auth_seq_id_ and _auth_comp_id == auth_comp_id), auth_asym_id)
                                seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                if seq_key in auth_to_star_seq:
                                    _row[16] = row[auth_asym_id_col] = auth_asym_id
                                    _row[20] = row[orig_asym_id_col] = auth_asym_id
                                    _seq_key = (seq_key[0], seq_key[1])
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                else:
                                    auth_asym_id, auth_comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                       for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                       if _auth_seq_id == auth_seq_id_), (auth_asym_id, auth_comp_id))
                                    seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                    if seq_key in auth_to_star_seq:
                                        _row[16] = row[auth_asym_id_col] = auth_asym_id
                                        _row[20] = row[orig_asym_id_col] = auth_asym_id
                                        _row[5] = row[comp_id_col] = auth_comp_id
                                        _row[18] = row[auth_comp_id_col] = auth_comp_id
                                        comp_id = auth_comp_id
                                        _seq_key = (seq_key[0], seq_key[1])
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            if seq_key not in auth_to_star_seq:
                                auth_comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                     if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id_), auth_comp_id)
                                comp_id = _row[18] = auth_comp_id
                                seq_key = (auth_asym_id, auth_seq_id_, auth_comp_id)
                                if seq_key in auth_to_star_seq:
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                else:
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq_ann[seq_key]

                        self.__ent_asym_id_with_exptl_data.add(entity_assembly_id)
                        _row[1], _row[2] = entity_assembly_id, entity_id
                        _row[3] = _row[4] = seq_id

                        if prefer_auth_atom_name:
                            if has_orig_seq:
                                orig_atom_id = _row[23]
                            _atom_id = atom_id
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']:
                                    if _atom_id in auth_atom_name_to_id[comp_id]:
                                        if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                            _row[19] = atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                        elif 'split_comp_id' not in _coord_atom_site and has_orig_seq and orig_atom_id in auth_atom_name_to_id[comp_id]:
                                            _row[19] = atom_id = auth_atom_name_to_id[comp_id][orig_atom_id]
                                if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                   and comp_id == _coord_atom_site['comp_id']:
                                    _row[19] = atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                # DAOTHER-8751, 8817 (D_1300043061)
                                elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                     and _atom_id in _coord_atom_site['alt_atom_id']\
                                     and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                    _row[18] = comp_id
                                    # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                    cca_row = next((cca_row for cca_row in self.__cca_dat
                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                    if cca_row is not None:
                                        _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                    if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                       and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                        _row[19] = atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                    else:
                                        _row[19] = atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                elif 'split_comp_id' in _coord_atom_site:
                                    for _comp_id in _coord_atom_site['split_comp_id']:
                                        if _comp_id == comp_id:
                                            continue
                                        __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                        __coord_atom_site = coord_atom_site[__seq_key]
                                        if __coord_atom_site is None:
                                            continue
                                        if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                           and _atom_id in __coord_atom_site['alt_atom_id']:
                                            comp_id = _comp_id
                                            _row[18] = comp_id
                                            # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                            cca_row = next((cca_row for cca_row in self.__cca_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                            row[19] = atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                            _seq_key = __seq_key
                                            break
                                        if _atom_id in __coord_atom_site['atom_id']:
                                            comp_id = _comp_id
                                            _row[18] = comp_id
                                            # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                            cca_row = next((cca_row for cca_row in self.__cca_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                _row[1], _row[2], _row[3], _row[4] = cca_row[0], cca_row[1], cca_row[2], cca_row[3]
                                            _seq_key = __seq_key
                                            break

                        if has_ins_code and seq_key in auth_to_ins_code:
                            _row[27] = auth_to_ins_code[seq_key]

                        if seq_key in auth_to_orig_seq:
                            if _row[20] not in emptyValue and seq_key not in _auth_to_orig_seq:
                                orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                _auth_to_orig_seq[seq_key] = (_row[20], orig_seq_id, orig_comp_id)
                            if not has_orig_seq:
                                orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                if orig_seq_id in emptyValue:
                                    orig_seq_id = auth_seq_id
                                if orig_comp_id in emptyValue:
                                    orig_comp_id = comp_id
                                _row[20], _row[21], _row[22], _row[23] =\
                                    auth_asym_id, orig_seq_id, orig_comp_id, _orig_atom_id
                            elif any(d in emptyValue for d in orig_dat[idx]):
                                if seq_key in _auth_to_orig_seq:
                                    _row[20], _row[21], _row[22] = _auth_to_orig_seq[seq_key]
                                elif comp_id != auth_comp_id and translateToStdResName(comp_id, ccU=self.__ccU) == auth_comp_id:
                                    _row[20], _row[21], _row[22] = auth_asym_id, auth_seq_id, comp_id
                                    _row[5] = comp_id = auth_comp_id
                                if _row[23] in emptyValue:
                                    _row[23] = atom_id
                                ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                if ambig_code > 0:
                                    orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                    if orig_seq_id in emptyValue:
                                        orig_seq_id = auth_seq_id
                                    if orig_comp_id in emptyValue:
                                        orig_comp_id = comp_id
                                    _row[20], _row[21], _row[22] =\
                                        auth_asym_id, orig_seq_id, orig_comp_id
                                    if atom_id[0] not in protonBeginCode:
                                        _row[23] = atom_id
                                    else:
                                        len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                        if len_in_grp == 2:
                                            _row[23] = (atom_id[0:-1] + '1')\
                                                if ambig_code == 2 and ch2_name_in_xplor and atom_id[-1] == '3' else atom_id
                                        elif len_in_grp == 3:
                                            _row[23] = (atom_id[-1] + atom_id[0:-1])\
                                                if ch3_name_in_xplor and atom_id[0] == 'H' and atom_id[-1] in ('1', '2', '3') else atom_id
                                        elif _row[23] in emptyValue:
                                            _row[23] = atom_id

                        else:
                            seq_key = next((k for k, v in auth_to_star_seq.items()
                                            if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                            if seq_key is not None:
                                _seq_key = (seq_key[0], seq_key[1])
                                _row[16], _row[17], _row[18], _row[19] =\
                                    seq_key[0], seq_key[1], seq_key[2], atom_id

                                if has_ins_code and seq_key in auth_to_ins_code:
                                    _row[27] = auth_to_ins_code[seq_key]

                            _row[20], _row[21], _row[22], _row[23] =\
                                row[auth_asym_id_col], row[aux_auth_seq_id_col], \
                                row[aux_auth_comp_id_col], row[aux_auth_atom_id_col]

                        index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name, coord_atom_site, _seq_key,
                                                  comp_id, atom_id, loop, idx)

                    else:
                        resolved = False

                    if not resolved:

                        chain_id = row[chain_id_col]
                        if chain_id in emptyValue:
                            chain_id = 'A'

                        if chain_id in copied_chain_ids:
                            continue

                        try:
                            seq_id = int(row[seq_id_col])
                        except (ValueError, TypeError):
                            seq_id = None

                        if auth_asym_id_col != -1 and row[auth_asym_id_col] == 'UNMAPPED':
                            _row[24] = 'UNMAPPED'

                        auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                        resolved = True

                        if auth_asym_id is not None and auth_seq_id is not None:
                            seq_key = (auth_asym_id, auth_seq_id, _orig_comp_id)
                            _seq_key = (seq_key[0], seq_key[1])
                            if seq_key in auth_to_star_seq:
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), _orig_comp_id)
                                self.__ent_asym_id_with_exptl_data.add(entity_assembly_id)
                                _row[1], _row[2] = entity_assembly_id, entity_id
                                _row[3] = _row[4] = seq_id

                                _row[16], _row[17], _row[18], _row[19] =\
                                    auth_asym_id, auth_seq_id, comp_id, atom_id
                                if has_ins_code and seq_key in auth_to_ins_code:
                                    _row[27] = auth_to_ins_code[seq_key]

                                if seq_key in auth_to_orig_seq:
                                    if _row[20] not in emptyValue and seq_key not in _auth_to_orig_seq:
                                        orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                        _auth_to_orig_seq[seq_key] = (_row[20], orig_seq_id, orig_comp_id)
                                    if not has_orig_seq:
                                        orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                        if orig_seq_id in emptyValue:
                                            orig_seq_id = auth_seq_id
                                        if orig_comp_id in emptyValue:
                                            orig_comp_id = comp_id
                                        _row[20], _row[21], _row[22], _row[23] =\
                                            auth_asym_id, orig_seq_id, orig_comp_id, _orig_atom_id
                                    elif any(d in emptyValue for d in orig_dat[idx]):
                                        if seq_key in _auth_to_orig_seq:
                                            _row[20], _row[21], _row[22] = _auth_to_orig_seq[seq_key]
                                        if _row[23] in emptyValue:
                                            _row[23] = atom_id
                                        ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                        if ambig_code > 0:
                                            orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                            if orig_seq_id in emptyValue:
                                                orig_seq_id = auth_seq_id
                                            if orig_comp_id in emptyValue:
                                                orig_comp_id = comp_id
                                            _row[20], _row[21], _row[22] =\
                                                auth_asym_id, orig_seq_id, orig_comp_id
                                            if atom_id[0] not in protonBeginCode:
                                                _row[23] = atom_id
                                            else:
                                                len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                                if len_in_grp == 2:
                                                    _row[23] = (atom_id[0:-1] + '1')\
                                                        if ambig_code == 2 and ch2_name_in_xplor and atom_id[-1] == '3' else atom_id
                                                elif len_in_grp == 3:
                                                    _row[23] = (atom_id[-1] + atom_id[0:-1])\
                                                        if ch3_name_in_xplor and atom_id[0] == 'H' and atom_id[-1] in ('1', '2', '3') else atom_id
                                                elif _row[23] in emptyValue:
                                                    _row[23] = atom_id

                                else:
                                    seq_key = next((k for k, v in auth_to_star_seq.items()
                                                    if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                    if seq_key is not None:
                                        _seq_key = (seq_key[0], seq_key[1])
                                        _row[16], _row[17], _row[18], _row[19] =\
                                            seq_key[0], seq_key[1], seq_key[2], atom_id
                                        if has_ins_code and seq_key in auth_to_ins_code:
                                            _row[27] = auth_to_ins_code[seq_key]

                                    if has_auth_seq:
                                        _row[20], _row[21], _row[22], _row[23] =\
                                            row[auth_asym_id_col], row[auth_seq_id_col], \
                                            row[auth_comp_id_col], row[auth_atom_id_col]

                                index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                          coord_atom_site, _seq_key,
                                                          comp_id, atom_id, loop, idx)

                                if chain_id not in can_auth_asym_id_mapping:
                                    can_auth_asym_id_mapping[chain_id] = {'auth_asym_id': auth_asym_id,
                                                                          'ref_auth_seq_id': auth_seq_id
                                                                          }

                            else:

                                item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                if item is not None and ps is not None and any(_ps for _ps in ps if _ps['chain_id'] == auth_asym_id and auth_seq_id in _ps['seq_id']):
                                    entity_assembly_id = item['entity_assembly_id']
                                    entity_id = item['entity_id']

                                    _row[1], _row[2] = entity_assembly_id, entity_id
                                    _row[3] = _row[4] = seq_id

                                    seq_key = next((k for k, v in auth_to_star_seq.items()
                                                    if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                    if seq_key is not None:
                                        _seq_key = (seq_key[0], seq_key[1])
                                        _row[16], _row[17], _row[18], _row[19] =\
                                            seq_key[0], seq_key[1], seq_key[2], atom_id
                                        if has_ins_code and seq_key in auth_to_ins_code:
                                            _row[27] = auth_to_ins_code[seq_key]

                                    if has_auth_seq:
                                        _row[20], _row[21], _row[22], _row[23] =\
                                            row[auth_asym_id_col], row[auth_seq_id_col], \
                                            row[auth_comp_id_col], row[auth_atom_id_col]

                                    index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                              coord_atom_site, _seq_key,
                                                              comp_id, atom_id, loop, idx)

                                    if chain_id not in can_auth_asym_id_mapping:
                                        can_auth_asym_id_mapping[chain_id] = {'auth_asym_id': auth_asym_id,
                                                                              'ref_auth_seq_id': auth_seq_id
                                                                              }

                                else:
                                    resolved = False

                        else:

                            if has_auth_seq:

                                try:

                                    auth_asym_id = row[auth_asym_id_col]
                                    auth_seq_id = int(row[auth_seq_id_col])

                                    item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                    if item is not None and ps is not None and any(_ps for _ps in ps if _ps['chain_id'] == auth_asym_id and auth_seq_id in _ps['seq_id']):
                                        entity_assembly_id = item['entity_assembly_id']
                                        entity_id = item['entity_id']

                                        _row[1], _row[2] = entity_assembly_id, entity_id
                                        _row[3] = _row[4] = seq_id

                                        seq_key = next((k for k, v in auth_to_star_seq.items()
                                                        if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                        if seq_key is not None:

                                            if comp_id != seq_key[2] and comp_id in monDict3 and seq_key[2] in monDict3:
                                                resolved = False

                                            else:
                                                _seq_key = (seq_key[0], seq_key[1])
                                                _row[16], _row[17], _row[18], _row[19] =\
                                                    seq_key[0], seq_key[1], seq_key[2], atom_id
                                                if has_ins_code and seq_key in auth_to_ins_code:
                                                    _row[27] = auth_to_ins_code[seq_key]

                                        if resolved:
                                            _row[20], _row[21], _row[22], _row[23] =\
                                                row[auth_asym_id_col], row[auth_seq_id_col], \
                                                row[auth_comp_id_col], row[auth_atom_id_col]

                                            index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                                      coord_atom_site, _seq_key,
                                                                      comp_id, atom_id, loop, idx)

                                    else:
                                        resolved = False

                                except (ValueError, TypeError):
                                    resolved = False

                            else:

                                def retrieve_label_comp_id(can_seq_id, can_comp_id):
                                    for _seq_key in auth_to_star_seq.keys():
                                        if _seq_key[1] == can_seq_id and _seq_key in auth_to_orig_seq:
                                            if can_comp_id == auth_to_orig_seq[_seq_key][1]:
                                                return _seq_key[2]
                                    return can_comp_id

                                can_auth_asym_id = [_auth_asym_id for _auth_asym_id, _auth_seq_id, _comp_id in auth_to_star_seq
                                                    if _auth_seq_id == seq_id and _comp_id in (_orig_comp_id, '.')]

                                if len(can_auth_asym_id) == 0:
                                    can_auth_asym_id = [_auth_asym_id for _auth_asym_id, _auth_seq_id, _comp_id in auth_to_star_seq
                                                        if _auth_seq_id == seq_id and _comp_id == retrieve_label_comp_id(seq_id, _orig_comp_id)]
                                    if len(can_auth_asym_id) > 0:
                                        _orig_comp_id = retrieve_label_comp_id(seq_id, _orig_comp_id)

                                if len(can_auth_asym_id) != 1:
                                    resolved = False

                                else:
                                    auth_asym_id, auth_seq_id = can_auth_asym_id[0], seq_id

                                    seq_key = (auth_asym_id, auth_seq_id, _orig_comp_id)
                                    dummy_key = (auth_asym_id, auth_seq_id, '.')
                                    _seq_key = (seq_key[0], seq_key[1])
                                    if seq_key in auth_to_star_seq or (dummy_key in auth_to_star_seq and _orig_comp_id in monDict3):
                                        try:
                                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                        except KeyError:  # DAOTHER-9644: map residue on truncated loop
                                            if _seq_key not in truncated_loop_sequence:
                                                truncated_loop_sequence.append(_seq_key)
                                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[dummy_key]
                                            auth_to_star_seq[seq_key] = auth_to_star_seq[dummy_key]
                                            del auth_to_star_seq[dummy_key]
                                            auth_to_orig_seq[seq_key] = (auth_to_orig_seq[dummy_key][0], _orig_comp_id)
                                            del auth_to_orig_seq[dummy_key]
                                            if has_ins_code:
                                                auth_to_ins_code[seq_key] = auth_to_ins_code[dummy_key]
                                                del auth_to_ins_code[dummy_key]
                                            cif_ps = next((cif_ps for cif_ps in self.__caC['polymer_sequence']
                                                           if cif_ps['auth_chain_id'] == auth_asym_id
                                                           and auth_seq_id in cif_ps['auth_seq_id']), None)
                                            if cif_ps is not None:
                                                _idx_ = cif_ps['auth_seq_id'].index(auth_seq_id)
                                                if cif_ps['comp_id'][_idx_] in emptyValue:
                                                    cif_ps['comp_id'][_idx_] = cif_ps['auth_comp_id'][_idx_] = _orig_comp_id
                                                    if self.__asmChkCachePath is not None:
                                                        write_as_pickle(self.__caC, self.__asmChkCachePath)
                                        comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), _orig_comp_id)
                                        self.__ent_asym_id_with_exptl_data.add(entity_assembly_id)
                                        _row[1], _row[2] = entity_assembly_id, entity_id
                                        _row[3] = _row[4] = seq_id

                                        _row[16], _row[17], _row[18], _row[19] =\
                                            auth_asym_id, auth_seq_id, comp_id, atom_id
                                        if has_ins_code and seq_key in auth_to_ins_code:
                                            _row[27] = auth_to_ins_code[seq_key]

                                        if seq_key in auth_to_orig_seq:
                                            if _row[20] not in emptyValue and seq_key not in _auth_to_orig_seq:
                                                orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                                _auth_to_orig_seq[seq_key] = (_row[20], orig_seq_id, orig_comp_id)
                                            if not has_orig_seq:
                                                orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                                if orig_seq_id in emptyValue:
                                                    orig_seq_id = auth_seq_id
                                                if orig_comp_id in emptyValue:
                                                    orig_comp_id = comp_id
                                                _row[20], _row[21], _row[22], _row[23] =\
                                                    auth_asym_id, orig_seq_id, orig_comp_id, _orig_atom_id
                                            elif any(d in emptyValue for d in orig_dat[idx]):
                                                if seq_key in _auth_to_orig_seq:
                                                    _row[20], _row[21], _row[22] = _auth_to_orig_seq[seq_key]
                                                if _row[23] in emptyValue:
                                                    _row[23] = atom_id
                                                ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                                                if ambig_code > 0:
                                                    orig_seq_id, orig_comp_id = auth_to_orig_seq[seq_key]
                                                    if orig_seq_id in emptyValue:
                                                        orig_seq_id = auth_seq_id
                                                    if orig_comp_id in emptyValue:
                                                        orig_comp_id = comp_id
                                                    _row[20], _row[21], _row[22] =\
                                                        auth_asym_id, orig_seq_id, orig_comp_id
                                                    if atom_id[0] not in protonBeginCode:
                                                        _row[23] = atom_id
                                                    else:
                                                        len_in_grp = len(self.__csStat.getProtonsInSameGroup(comp_id, atom_id))
                                                        if len_in_grp == 2:
                                                            _row[23] = (atom_id[0:-1] + '1')\
                                                                if ambig_code == 2 and ch2_name_in_xplor and atom_id[-1] == '3' else atom_id
                                                        elif len_in_grp == 3:
                                                            _row[23] = (atom_id[-1] + atom_id[0:-1])\
                                                                if ch3_name_in_xplor and atom_id[0] == 'H' and atom_id[-1] in ('1', '2', '3') else atom_id
                                                        elif _row[23] in emptyValue:
                                                            _row[23] = atom_id

                                        else:
                                            seq_key = next((k for k, v in auth_to_star_seq.items()
                                                            if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                            if seq_key is not None:
                                                _seq_key = (seq_key[0], seq_key[1])
                                                _row[16], _row[17], _row[18], _row[19] =\
                                                    seq_key[0], seq_key[1], seq_key[2], atom_id
                                                if has_ins_code and seq_key in auth_to_ins_code:
                                                    _row[27] = auth_to_ins_code[seq_key]

                                            if has_auth_seq:
                                                _row[20], _row[21], _row[22], _row[23] =\
                                                    row[auth_asym_id_col], row[auth_seq_id_col], \
                                                    row[auth_comp_id_col], row[auth_atom_id_col]

                                        index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                                  coord_atom_site, _seq_key,
                                                                  comp_id, atom_id, loop, idx)

                                        if chain_id not in can_auth_asym_id_mapping:
                                            can_auth_asym_id_mapping[chain_id] = {'auth_asym_id': auth_asym_id,
                                                                                  'ref_auth_seq_id': auth_seq_id
                                                                                  }

                                    else:

                                        item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                        if item is not None and ps is not None and any(_ps for _ps in ps if _ps['chain_id'] == auth_asym_id and auth_seq_id in _ps['seq_id']):
                                            entity_assembly_id = item['entity_assembly_id']
                                            entity_id = item['entity_id']

                                            _row[1], _row[2] = entity_assembly_id, entity_id
                                            _row[3] = _row[4] = seq_id

                                            seq_key = next((k for k, v in auth_to_star_seq.items()
                                                            if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                            if seq_key is not None:
                                                _seq_key = (seq_key[0], seq_key[1])
                                                _row[16], _row[17], _row[18], _row[19] =\
                                                    seq_key[0], seq_key[1], seq_key[2], atom_id
                                                if has_ins_code and seq_key in auth_to_ins_code:
                                                    _row[27] = auth_to_ins_code[seq_key]

                                            if has_auth_seq:
                                                _row[20], _row[21], _row[22], _row[23] =\
                                                    row[auth_asym_id_col], row[auth_seq_id_col], \
                                                    row[auth_comp_id_col], row[auth_atom_id_col]

                                            index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                                      coord_atom_site, _seq_key,
                                                                      comp_id, atom_id, loop, idx)

                                            if chain_id not in can_auth_asym_id_mapping:
                                                can_auth_asym_id_mapping[chain_id] = {'auth_asym_id': auth_asym_id,
                                                                                      'ref_auth_seq_id': seq_key[1]
                                                                                      }

                                        else:
                                            resolved = False

                        is_valid, cc_name, _ = self.__getChemCompNameAndStatusOf(comp_id)
                        comp_id_bmrb_only = not is_valid and cc_name is not None and 'processing site' in cc_name

                        if not resolved and has_auth_seq and not comp_id_bmrb_only:
                            try:
                                seq_id = int(row[auth_seq_id_col])
                            except (ValueError, TypeError):
                                seq_id = None

                        if not resolved and seq_id is not None and has_coordinate:

                            def test_seq_id_offset(lp, index, row, _row, _idx, chain_id, seq_id, comp_id, offset):
                                _found = _resolved = False
                                _index = index

                                auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id + offset)
                                if auth_asym_id is not None and auth_seq_id is not None:
                                    _found = _resolved = True

                                    item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                    if item is not None and ps is not None and any(_ps for _ps in ps_common
                                                                                   if _ps['chain_id'] == auth_asym_id
                                                                                   and auth_seq_id in _ps['seq_id']):
                                        entity_assembly_id = item['entity_assembly_id']
                                        entity_id = item['entity_id']

                                        _row[1], _row[2] = entity_assembly_id, entity_id
                                        _row[3] = _row[4] = seq_id

                                        seq_key = next((k for k, v in auth_to_star_seq.items()
                                                        if v[0] == entity_assembly_id and v[1] == seq_id + offset and v[2] == entity_id), None)
                                        _seq_key = None
                                        if seq_key is not None and comp_id == seq_key[2]:
                                            _seq_key = (seq_key[0], seq_key[1])
                                            _row[16], _row[17], _row[18], _row[19] =\
                                                seq_key[0], seq_key[1] - offset, comp_id, atom_id
                                            if has_ins_code and seq_key in auth_to_ins_code:
                                                _row[27] = auth_to_ins_code[seq_key]
                                        else:
                                            if has_orig_seq:  # DAOTHER-8758
                                                try:
                                                    orig_asym_id = row[orig_asym_id_col]
                                                    orig_seq_id = int(row[orig_seq_id_col])
                                                    _item = next((item for item in entity_assembly if item['auth_asym_id'] == orig_asym_id), None)
                                                    if _item is not None:
                                                        _entity_assembly_id = _item['entity_assembly_id']
                                                        _entity_id = _item['entity_id']
                                                        __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                                          if v[0] == _entity_assembly_id and v[1] in (seq_id, orig_seq_id) and v[2] == _entity_id), None)
                                                        if __seq_key is not None:
                                                            comp_id = __seq_key[2]
                                                            _row[1], _row[2], _row[3], _row[4] = _entity_assembly_id, _entity_id, __seq_key[1], __seq_key[1]
                                                            _seq_key = (__seq_key[0], __seq_key[1])
                                                            _row[16], _row[17], _row[18], _row[19] =\
                                                                __seq_key[0], __seq_key[1], comp_id, atom_id
                                                            if has_ins_code and __seq_key in auth_to_ins_code:
                                                                _row[27] = auth_to_ins_code[__seq_key]
                                                        else:
                                                            _seq_key = (auth_asym_id, auth_seq_id + offset)
                                                    else:
                                                        _seq_key = (auth_asym_id, auth_seq_id + offset)
                                                except ValueError:
                                                    _seq_key = (auth_asym_id, auth_seq_id + offset)
                                            else:
                                                _item = next((item for item in entity_assembly if item['auth_asym_id'] == chain_id), None)
                                                if _item is not None:
                                                    _entity_assembly_id = _item['entity_assembly_id']
                                                    _entity_id = _item['entity_id']
                                                    __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                                      if v[0] == _entity_assembly_id and v[1] == seq_id + offset and v[2] == _entity_id), None)
                                                    if __seq_key is not None:
                                                        _offset = __seq_key[1] - (seq_id + offset)
                                                        _seq_id = seq_id - _offset
                                                        __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                                          if v[0] == _entity_assembly_id and v[1] == _seq_id and v[2] == _entity_id), None)
                                                        if __seq_key is not None and comp_id == __seq_key[2]:
                                                            comp_id = __seq_key[2]
                                                            _row[1], _row[2], _row[3], _row[4] = _entity_assembly_id, _entity_id, _seq_id, _seq_id
                                                            _seq_key = (__seq_key[0], __seq_key[1])
                                                            _row[16], _row[17], _row[18], _row[19] =\
                                                                __seq_key[0], __seq_key[1], comp_id, atom_id
                                                            if has_ins_code and __seq_key in auth_to_ins_code:
                                                                _row[27] = auth_to_ins_code[__seq_key]
                                                        else:
                                                            _resolved = False
                                                    else:
                                                        _resolved = False
                                                else:
                                                    _resolved = False

                                        if has_auth_seq:
                                            _row[20], _row[21], _row[22], _row[23] =\
                                                row[auth_asym_id_col], row[auth_seq_id_col], \
                                                row[auth_comp_id_col], row[auth_atom_id_col]
                                        else:
                                            _row[20], _row[21], _row[22], _row[23] =\
                                                _row[16], _row[17], _row[18], _row[19]

                                        if _resolved:
                                            _index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                                       coord_atom_site, _seq_key,
                                                                       comp_id, atom_id, loop, _idx)

                                    else:
                                        _resolved = False

                                return _found, _resolved, _index, _row

                            found = False
                            for offset in range(1, MAX_OFFSET_ATTEMPT):
                                found, resolved, _index, __row = test_seq_id_offset(lp, index, row, _row, idx, chain_id, seq_id, comp_id, offset)

                                if found:
                                    if resolved:
                                        index, _row = _index, __row
                                    break

                                found, resolved, _index, __row = test_seq_id_offset(lp, index, row, _row, idx, chain_id, seq_id, comp_id, -offset)

                                if found:
                                    if resolved:
                                        index, _row = _index, __row
                                    break

                            if not resolved and chain_id in can_auth_asym_id_mapping:  # DAOTHER-8751, 8755

                                if can_auth_asym_id_mapping_failed and trial == 0:  # DAOTHER-9158
                                    regenerate_request = True

                                mapping = can_auth_asym_id_mapping[chain_id]

                                auth_asym_id = mapping['auth_asym_id']
                                ref_auth_seq_id = mapping['ref_auth_seq_id']

                                item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                if item is not None and ps is not None and any(_ps for _ps in ps_common
                                                                               if _ps['chain_id'] in (auth_asym_id, str(letterToDigit(auth_asym_id)))
                                                                               and ref_auth_seq_id in _ps['seq_id']):
                                    resolved = True
                                    found = False

                                    entity_assembly_id = item['entity_assembly_id']
                                    entity_id = item['entity_id']

                                    _row[1], _row[2] = entity_assembly_id, entity_id
                                    _row[3] = _row[4] = seq_id

                                    _row[16], _row[17], _row[18], _row[19] =\
                                        auth_asym_id, seq_id, comp_id, atom_id

                                    if has_auth_seq:
                                        _row[20], _row[21], _row[22], _row[23] =\
                                            row[auth_asym_id_col], row[auth_seq_id_col], \
                                            row[auth_comp_id_col], row[auth_atom_id_col]
                                    else:
                                        _row[20], _row[21], _row[22], _row[23] =\
                                            _row[16], _row[17], _row[18], _row[19]

                                    # DAOTHER-9281
                                    if isinstance(_row[1], int) and str(_row[1]) in seq_id_offset_for_unmapped:
                                        __offset = seq_id_offset_for_unmapped[str(_row[1])]
                                    elif isinstance(_row[1], str) and _row[1] in seq_id_offset_for_unmapped:
                                        __offset = seq_id_offset_for_unmapped[_row[1]]
                                    else:
                                        __offset = 0

                                    if comp_id not in monDict3:
                                        for item in entity_assembly:
                                            if 'comp_id' in item and comp_id == item['comp_id']:
                                                _entity_assembly_id = item['entity_assembly_id']
                                                _entity_id = item['entity_id']

                                                __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                                  if v[0] == _entity_assembly_id and v[1] == seq_id and v[2] == _entity_id), None)
                                                if __seq_key is not None:
                                                    found = True
                                                    comp_id = __seq_key[2]
                                                    _row[1], _row[2], _row[3], _row[4] = _entity_assembly_id, _entity_id, __seq_key[1], __seq_key[1]
                                                    _seq_key = (__seq_key[0], __seq_key[1])
                                                    _row[16], _row[17], _row[18], _row[19] =\
                                                        __seq_key[0], __seq_key[1], comp_id, atom_id
                                                    if has_ins_code and __seq_key in auth_to_ins_code:
                                                        _row[27] = auth_to_ins_code[__seq_key]
                                                    break

                                                if self.__caC['non_polymer'] is not None:
                                                    ligands = 0
                                                    for np in self.__caC['non_polymer']:
                                                        if comp_id == np['comp_id'][0]:
                                                            ligands += len(np['seq_id'])
                                                    if ligands == 1:  # DAOTHER-9063, 2nd case
                                                        __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                                          if v[0] == _entity_assembly_id and v[2] == _entity_id), None)
                                                        if __seq_key is not None:
                                                            seq_id = auth_to_star_seq[__seq_key][1]
                                                            found = True
                                                            comp_id = __seq_key[2]
                                                            _row[1], _row[2] = _entity_assembly_id, _entity_id
                                                            _row[3] = _row[4] = seq_id
                                                            _seq_key = (__seq_key[0], __seq_key[1])
                                                            _row[16], _row[17], _row[18], _row[19] =\
                                                                __seq_key[0], __seq_key[1], comp_id, atom_id
                                                            if has_ins_code and __seq_key in auth_to_ins_code:
                                                                _row[27] = auth_to_ins_code[__seq_key]
                                                            break

                                    else:

                                        __seq_key = next((k for k, v in auth_to_star_seq.items()
                                                          if v[0] == entity_assembly_id
                                                          and v[1] == seq_id + __offset
                                                          and v[2] == entity_id), None)
                                        if __seq_key is not None:
                                            __comp_id = __seq_key[2]
                                            if self.__ccU.updateChemCompDict(comp_id):
                                                cc_type = self.__ccU.lastChemCompDict['_chem_comp.type']
                                                if self.__ccU.updateChemCompDict(__comp_id):
                                                    __cc_type = self.__ccU.lastChemCompDict['_chem_comp.type']
                                                    if cc_type == __cc_type:  # DAOTHER-9198
                                                        found = True
                                                        comp_id = __seq_key[2]
                                                        _row[1], _row[2], _row[3], _row[4] = entity_assembly_id, entity_id, __seq_key[1], __seq_key[1]
                                                        _seq_key = (__seq_key[0], __seq_key[1])
                                                        _row[16], _row[17], _row[18], _row[19] =\
                                                            __seq_key[0], __seq_key[1], comp_id, atom_id
                                                        if has_ins_code and __seq_key in auth_to_ins_code:
                                                            _row[27] = auth_to_ins_code[__seq_key]

                                    if not found:
                                        _row[24] = 'UNMAPPED'
                                        # DAOTHER-9065
                                        if __offset != 0:
                                            _row[3] += __offset
                                            _row[4] = _row[3]
                                        elif trial == 0:
                                            regenerate_request = True

                                        _seq_key = (auth_asym_id, seq_id)

                                    _index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                               coord_atom_site, _seq_key,
                                                               comp_id, atom_id, loop, index)

                            if not resolved and seq_id is not None and has_coordinate:

                                can_auth_asym_id_mapping_failed = True  # DAOTHER-9158

                                def test_seq_id_offset_as_is(lp, index, _row, _idx, chain_id, seq_id, comp_id, offset):
                                    _resolved = False
                                    _index = index

                                    auth_asym_id, auth_seq_id, label_seq_id = get_label_seq_scheme(chain_id, seq_id + offset)
                                    if auth_asym_id is not None and auth_seq_id is not None:
                                        _resolved = True

                                        item = next((item for item in entity_assembly if item['auth_asym_id'] == auth_asym_id), None)

                                        if item is not None and ps is not None and any(_ps for _ps in ps_common
                                                                                       if _ps['chain_id'] == chain_id
                                                                                       and label_seq_id in _ps['seq_id']):
                                            entity_assembly_id = item['entity_assembly_id']
                                            entity_id = item['entity_id']

                                            seq_key = next((k for k, v in auth_to_star_seq.items()
                                                            if k[0] == auth_asym_id and k[1] == auth_seq_id
                                                            and v[0] == entity_assembly_id and v[2] == entity_id), None)

                                            if seq_key is not None:
                                                _, _label_seq_id, _, _ = auth_to_star_seq[seq_key]

                                                if entity_id not in label_seq_id_offset_for_extended or _label_seq_id - label_seq_id == label_seq_id_offset_for_extended[entity_id]:
                                                    seq_id += (label_seq_id - auth_seq_id)
                                                    seq_id += (_label_seq_id - label_seq_id)

                                                    if entity_id not in label_seq_id_offset_for_extended:
                                                        label_seq_id_offset_for_extended[entity_id] = _label_seq_id - label_seq_id

                                                    _row[1], _row[2] = entity_assembly_id, entity_id
                                                    _row[3] = _row[4] = seq_id

                                                    _row[16] = _row[20] = auth_asym_id
                                                    if _row[21] in emptyValue:
                                                        _row[21] = _row[17]
                                                    if _row[22] in emptyValue:
                                                        _row[22] = _row[18]
                                                    if _row[23] in emptyValue:
                                                        _row[23] = _row[19]
                                                    if _row[24] in emptyValue:
                                                        _row[24] = 'UNMAPPED'

                                                    _index, _row = fill_cs_row(lp, index, _row, prefer_auth_atom_name,
                                                                               coord_atom_site, None,
                                                                               comp_id, atom_id, loop, _idx)

                                                else:
                                                    _resolved = False

                                            else:
                                                _resolved = False

                                        else:
                                            _resolved = False

                                    return _resolved, _index, _row

                                found = False
                                for offset in range(1, MAX_OFFSET_ATTEMPT):
                                    resolved, _index, __row = test_seq_id_offset_as_is(lp, index, _row, idx, chain_id, seq_id, comp_id, offset)

                                    if resolved:
                                        index, _row = _index, __row
                                        break

                                    resolved, _index, __row = test_seq_id_offset_as_is(lp, index, _row, idx, chain_id, seq_id, comp_id, -offset)

                                    if resolved:
                                        index, _row = _index, __row
                                        break

                        if not resolved:

                            entity_id = None
                            if (self.__combined_mode or (self.__bmrb_only and self.__internal_mode)) and entity_id_col != -1:
                                try:
                                    entity_id = int(row[entity_id_col])
                                except (ValueError, TypeError):
                                    entity_id = None

                            if not has_coordinate:
                                seq_id = int(row[seq_id_col])

                            _row[1], _row[2], _row[5] = chain_id, entity_id, comp_id
                            _row[3] = _row[4] = seq_id

                            # DAOTHER-9065
                            if details_col != -1 and row[details_col] == 'UNMAPPED':
                                if isinstance(_row[1], int) and str(_row[1]) in seq_id_offset_for_unmapped:
                                    __offset = seq_id_offset_for_unmapped[str(_row[1])]
                                elif isinstance(_row[1], str) and _row[1] in seq_id_offset_for_unmapped:
                                    __offset = seq_id_offset_for_unmapped[_row[1]]
                                else:
                                    __offset = None

                                if __offset is not None:
                                    offset = None
                                    if isinstance(_row[17], int):
                                        offset = _row[3] - _row[17]
                                    elif isinstance(_row[17], str) and _row[17].isdigit():
                                        offset = _row[3] - int(_row[17])
                                    if offset is not None and offset != __offset:
                                        if isinstance(_row[17], int):
                                            _row[3] = _row[17] + __offset
                                            _row[4] = _row[3]
                                        else:
                                            _row[3] = int(_row[17]) + __offset
                                            _row[4] = _row[3]
                                elif trial == 0:
                                    regenerate_request = True

                            atom_ids = self.__getAtomIdListInXplor(comp_id, atom_id)
                            if len(atom_ids) == 0 or atom_ids[0] not in self.__csStat.getAllAtoms(comp_id):
                                atom_ids = self.__getAtomIdListInXplor(comp_id, translateToStdAtomName(atom_id, comp_id, ccU=self.__ccU))
                            len_atom_ids = len(atom_ids)
                            if len_atom_ids == 0 or comp_id_bmrb_only or _row[24] == 'UNMAPPED':
                                _row[6] = atom_id
                                _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                                if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                    _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                            else:
                                _row[6] = atom_ids[0]
                                _row[19] = None
                                fill_auth_atom_id = _row[18] not in emptyValue
                                if self.__ccU.updateChemCompDict(comp_id):
                                    cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == _row[6]), None)
                                    if cca is not None:
                                        _row[7] = cca[self.__ccU.ccaTypeSymbol]
                                        if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                            _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                                    else:
                                        _row[7] = 'H' if _row[6][0] in protonBeginCode else atom_id[0]
                                        if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                            _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]
                                else:
                                    _row[7] = 'H' if atom_id[0] in pseProBeginCode else atom_id[0]
                                    if _row[7] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                        _row[8] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[_row[7]][0]

                                if len_atom_ids > 1:
                                    __row = copy.copy(_row)
                                    lp.add_data(__row)

                                    for _atom_id in atom_ids[1:]:
                                        __row = copy.copy(_row)

                                        index += 1

                                        __row[0] = index
                                        __row[6] = _atom_id

                                        lp.add_data(__row)

                                    index += 1

                                    _row[0] = index
                                    _row[6] = atom_ids[-1]

                                if fill_auth_atom_id:
                                    _row[19] = _row[6]

                    # DAOTHER-9065
                    if isinstance(_row[1], int) and str(_row[1]) not in seq_id_offset_for_unmapped and _row[24] in emptyValue:
                        if isinstance(_row[3], int):
                            if isinstance(_row[17], int):
                                seq_id_offset_for_unmapped[str(_row[1])] = _row[3] - _row[17]
                            elif isinstance(_row[17], str) and _row[17].isdigit():
                                seq_id_offset_for_unmapped[str(_row[1])] = _row[3] - int(_row[17])
                    elif isinstance(_row[1], str) and _row[1] not in seq_id_offset_for_unmapped and _row[24] in emptyValue:
                        if isinstance(_row[3], int):
                            if isinstance(_row[17], int):
                                seq_id_offset_for_unmapped[_row[1]] = _row[3] - _row[17]
                            elif isinstance(_row[17], str) and _row[17].isdigit():
                                seq_id_offset_for_unmapped[_row[1]] = _row[3] - int(_row[17])

                    if isinstance(_row[12], int):
                        comp_id = _row[5]
                        atom_id = _row[6]
                        ambig_code = _row[12]

                        if ambig_code == 0:
                            _row[12] = None

                        elif ambig_code in (2, 3):
                            _ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)
                            if _ambig_code not in (0, ambig_code):
                                if _ambig_code != 1:
                                    _row[12] = _ambig_code
                                else:
                                    _row[12] = ambig_code = 4

                        elif ambig_code == 4:
                            if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                _idx = idx
                                for offset in range(1, 10):
                                    if _idx + offset < len(loop.data):
                                        row_ = loop.data[_idx + offset]
                                        if row_[comp_id_col] == row[comp_id_col] and row_[atom_id_col] == row[atom_id_col]\
                                           and row_[ambig_code_col] == str(ambig_code):
                                            if row_[chain_id_col] != row[chain_id_col]:
                                                _row[12] = ambig_code = 6
                                                break
                                            if row_[seq_id_col] != row[seq_id_col]:
                                                _row[12] = ambig_code = 5
                                    if _idx - offset >= 0:
                                        row_ = loop.data[_idx - offset]
                                        if row_[comp_id_col] == row[comp_id_col] and row_[atom_id_col] == row[atom_id_col]\
                                           and row_[ambig_code_col] == str(ambig_code):
                                            if row_[chain_id_col] != row[chain_id_col]:
                                                _row[12] = ambig_code = 6
                                                break
                                            if row_[seq_id_col] != row[seq_id_col]:
                                                _row[12] = ambig_code = 5

                        elif ambig_code == 5:
                            if not self.__annotation_mode and _row[24] != 'UNMAPPED':
                                _idx = idx
                                for offset in range(1, 10):
                                    if _idx + offset < len(loop.data):
                                        row_ = loop.data[_idx + offset]
                                        if row_[comp_id_col] == row[comp_id_col] and row_[atom_id_col] == row[atom_id_col]\
                                           and row_[ambig_code_col] == str(ambig_code):
                                            if row_[chain_id_col] != row[chain_id_col]:
                                                _row[12] = ambig_code = 6
                                                break
                                    if _idx - offset >= 0:
                                        row_ = loop.data[_idx - offset]
                                        if row_[comp_id_col] == row[comp_id_col] and row_[atom_id_col] == row[atom_id_col]\
                                           and row_[ambig_code_col] == str(ambig_code):
                                            if row_[chain_id_col] != row[chain_id_col]:
                                                _row[12] = ambig_code = 6
                                                break

                        elif ambig_code == 6:
                            if len([item for item in entity_assembly
                                    if item['entity_type'] not in ('non-polymer', 'water')]) == 1\
                               and len(entity_assembly[0]['label_asym_id'].split(',')) == 1:
                                _row[12] = ambig_code = 5

                        if ambig_code in (1, 2, 3):
                            if _row[13] is not None:
                                _row[13] = None

                        elif ambig_code in (4, 5, 6, 9):
                            has_genuine_ambig_code = True

                    if _row[8] not in emptyValue:  # DAOTHER-9520: Atom_isotppe_number is mandatory
                        lp.add_data(_row)

                        index += 1

                if trial == 0 and len(incomplete_comp_id_annotation) > 0:  # DAOTHER-9286
                    regenerate_request = True

                if not regenerate_request:
                    break

                trial += 1

            key_items = self.key_items[file_type][content_subtype]

            conflict_id = self.__nefT.get_conflict_id(lp, lp_category, key_items)[0]

            if len(conflict_id) > 0:
                conflict_id_set = self.__nefT.get_conflict_id_set(lp, lp_category, key_items)[0]
                orig_atom_id_col = lp.tags.index('Original_PDB_atom_name') if 'Original_PDB_atom_name' in lp.tags else -1

                for _id in conflict_id:
                    _id_set = next(id_set for id_set in conflict_id_set if _id in id_set)

                    if len(set(str(lp.data[_id_]) for _id_ in _id_set)) == 1:
                        continue

                    # DAOTHER-9520: Suppress known warnings
                    if orig_atom_id_col != -1\
                       and any(lp.data[_id1_][orig_atom_id_col] != lp.data[_id2_][orig_atom_id_col]
                               for (_id1_, _id2_) in itertools.combinations(_id_set, 2)):

                        msg = ' vs '.join([str(lp.data[_id_]).replace('None', '.').replace(',', '').replace("'", '') for _id_ in _id_set])

                        warn = f"Resolved redundancy of assigned chemical shifts ({msg}) by deletion of the latter one."

                        self.report.warning.appendDescription('redundant_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__remediateCsLoop() ++ Warning  - {warn}\n")

                for _id in conflict_id:
                    del lp.data[_id]

                id_col = lp.tags.index('ID')

                for _id, _row in enumerate(lp, start=1):
                    _row[id_col] = _id

            if has_genuine_ambig_code:

                for _row in lp:

                    if _row[12] not in (4, 5):
                        continue

                    ambig_code = _row[12]

                    if _row[13] not in emptyValue:
                        ambig_code = copy.copy(_row[12])
                        ambig_set_id = copy.copy(_row[13])
                        chain_id = _row[1]
                        seq_id = _row[3]
                        comp_id = _row[5]
                        atom_id = _row[6]
                        atom_type = _row[7]

                        if atom_type == 'H':
                            atom_in_same_group = self.__csStat.getProtonsInSameGroup(comp_id, atom_id)

                            if not any(((ambig_code == 5 and (__row[1] != chain_id or __row[3] != seq_id))
                                        or (ambig_code == 4 and __row[6] not in atom_in_same_group))
                                       for __row in lp if __row[12] == ambig_code and __row[13] == ambig_set_id):

                                for __row in lp:
                                    if __row[12] == ambig_code and __row[13] == ambig_set_id:
                                        __row[12] = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id, None)
                                        __row[13] = None

                                if not isinstance(sf, pynmrstar.Loop) and any(aux_loop for aux_loop in sf if aux_loop.category == aux_lp_category):

                                    if __pynmrstar_v3_2__:
                                        aux_loop = sf.get_loop(aux_lp_category)
                                    else:
                                        aux_loop = sf.get_loop_by_category(aux_lp_category)

                                    if 'Ambiguous_shift_set_ID' in aux_loop.tags:
                                        ambig_set_id_col = aux_loop.tags.index('Ambiguous_shift_set_ID')

                                        del_row_idx = []

                                        for idx, __row in enumerate(aux_loop.data):
                                            if __row[ambig_set_id_col] == ambig_set_id:
                                                del_row_idx.append(idx)

                                        if len(del_row_idx) > 0:
                                            for idx in reversed(del_row_idx):
                                                del aux_loop.data[idx]

                                            if len(aux_loop.data) == 0:
                                                delete_aux_loop()

                has_genuine_ambig_code = False

                for _row in lp:

                    if _row[12] not in (4, 5, 6, 9):
                        continue

                    ambig_code = _row[12]
                    _ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id)

                    chain_id = _row[1]
                    seq_id = _row[3]
                    comp_id = _row[5]
                    atom_id = _row[6]
                    atom_type = _row[7]

                    if ambig_code == 4:
                        _atom_id_set_w_same_ambig_code = set(_row_[6] for _row_ in lp
                                                             if _row != _row_ and _row_[1] == chain_id and _row_[3] == seq_id
                                                             and _row_[7] == atom_type and _row_[12] == ambig_code)

                        if atom_type == 'H':
                            atom_in_same_group = self.__csStat.getProtonsInSameGroup(comp_id, atom_id, True)

                            if len(_atom_id_set_w_same_ambig_code - set(atom_in_same_group)) == 0:
                                if _ambig_code > 1:
                                    _row[12] = _ambig_code
                                    _row[13] = None

                        else:
                            geminal_atom = self.__csStat.getGeminalAtom(comp_id, atom_id)

                            if geminal_atom is not None and len(_atom_id_set_w_same_ambig_code - set([geminal_atom])) == 0:
                                if _ambig_code > 1:
                                    _row[12] = _ambig_code
                                    _row[13] = None

                    elif ambig_code == 5:
                        _atom_id_set_w_same_ambig_code = set(_row_[6] for _row_ in lp
                                                             if _row != _row_ and _row_[1] == chain_id and _row_[3] != seq_id
                                                             and _row_[7] == atom_type and _row_[12] == ambig_code)

                        if len(_atom_id_set_w_same_ambig_code) == 0 and _ambig_code != 0:
                            _row[12] = _ambig_code
                            _row[13] = None

                    else:
                        _row[13] = None

                    if _row[12] in (4, 5):
                        has_genuine_ambig_code = True

                if has_genuine_ambig_code:

                    aux_lp = pynmrstar.Loop.from_scratch(aux_lp_category)

                    aux_items = ['Ambiguous_shift_set_ID', 'Atom_chem_shift_ID', 'Entry_ID', 'Assigned_chem_shift_list_ID']

                    aux_tags = [aux_lp_category + '.' + item for item in aux_items]

                    for tag in aux_tags:
                        aux_lp.add_tag(tag)

                    inter_residue_seq_id = {}

                    for _row in lp:

                        if _row[12] != 5:
                            continue

                        chain_id = _row[1]
                        seq_id = _row[3]

                        if chain_id not in inter_residue_seq_id:
                            inter_residue_seq_id[chain_id] = set()

                        inter_residue_seq_id[chain_id].add(seq_id)

                    if len(inter_residue_seq_id) > 0:

                        for k, v in inter_residue_seq_id.items():
                            if len(v) == 1:
                                chain_id = k
                                seq_id = list(v)[0]

                                for _row in lp:

                                    if _row[12] != 5:
                                        continue

                                    if _row[1] == chain_id and _row[3] == seq_id:
                                        _row[12] = 4

                    aux_index_id = 0
                    ambig_shift_set_id = {}

                    for _idx, _row in enumerate(lp):

                        if _row[12] not in (4, 5):
                            continue

                        ambig_code = _row[12]

                        chain_id = _row[1]
                        seq_id = _row[3]
                        atom_type = _row[7]

                        if ambig_code == 4:
                            key = (chain_id, str(seq_id), atom_type, ambig_code)
                        else:
                            key = (chain_id, str(inter_residue_seq_id[chain_id]), atom_type, ambig_code)

                        if key not in ambig_shift_set_id:
                            aux_index_id += 1
                            ambig_shift_set_id[key] = aux_index_id

                        lp.data[_idx][13] = ambig_shift_set_id[key]

                        _aux_row = [None] * 4
                        _aux_row[0], _aux_row[1], _aux_row[2], _aux_row[3] =\
                            ambig_shift_set_id[key], _row[0], self.__entry_id, list_id

                        aux_lp.add_data(_aux_row)

                else:
                    delete_aux_loop()

            else:
                delete_aux_loop()

        del sf[loop]

        sf.add_loop(lp)

        if aux_lp is not None and len(aux_lp) > 0:
            delete_aux_loop()

            sf.add_loop(aux_lp)

        if file_type == 'nmr-star':
            val = get_first_sf_tag(sf, 'ID')
            if (isinstance(val, int) and val == list_id) or (isinstance(val, str) and val.isdigit() and int(val) == list_id):
                pass
            else:
                set_sf_tag(sf, 'ID', list_id)

        if not self.__native_combined:
            self.__testDataConsistencyInLoop__(file_list_id, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, list_id)

        return True

    def __removeUnusedPdbInsCode(self):
        """ Remove unused PDB_ind_code tags from loops.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']

            if file_type != 'nmr-star':
                continue

            if input_source_dic['content_subtype'] is None:
                continue

            modified = False

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype not in ('chem_shift', 'dist_restraint', 'dihed_restraint', 'rdc_restraint'):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]

                    modified |= self.__removeUnusedPdbInsCode__(fileListId, content_subtype, sf, lp_category)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]

                    modified |= self.__removeUnusedPdbInsCode__(fileListId, content_subtype, sf, lp_category)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        modified |= self.__removeUnusedPdbInsCode__(fileListId, content_subtype, sf, lp_category)

            if modified:
                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __removeUnusedPdbInsCode__(self, file_list_id, content_subtype, sf, lp_category):
        """ Remove unused PDB_ind_code tags from loops.
        """

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if loop is None:
            return False

        if content_subtype == 'chem_shift':
            tags = ['PDB_ins_code']
        elif content_subtype in ('dist_restraint', 'rdc_restraint'):
            tags = ['PDB_ins_code_1', 'PDB_ins_code_2']
        elif content_subtype == 'dihed_restraint':
            tags = ['PDB_ins_code_1', 'PDB_ins_code_2', 'PDB_ins_code_3', 'PDB_ins_code_4']
        else:
            return False

        if set(tags) & set(loop.tags) != set(tags):
            return False

        try:

            dat = get_lp_tag(loop, tags)

            for row in dat:
                if row is not None:
                    if len(row) > 0:
                        for col in row:
                            if col is not None and col not in emptyValue:
                                return False

            loop.remove_tag(tags)

            return True

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeUnusedPdbInsCode() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__removeUnusedPdbInsCode() ++ Error  - {str(e)}\n")

        return False

    def __syncMrLoop(self):
        """ Synchonize sequence scheme of restraint loop based on coordinates.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('entry_info', 'poly_seq', 'entity', 'chem_shift', 'chem_shift_ref'):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if file_type == 'nmr-star' and content_subtype == 'spectral_peak_alt':
                    lp_category = '_Assigned_peak_chem_shift'

                modified = False

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]

                    modified |= self.__syncMrLoop__(fileListId, file_type, content_subtype, sf, lp_category)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]

                    modified |= self.__syncMrLoop__(fileListId, file_type, content_subtype, sf, lp_category)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        modified |= self.__syncMrLoop__(fileListId, file_type, content_subtype, sf, lp_category)

                if modified:
                    self.__depositNmrData()

            return self.report.getTotalErrors() == __errors

    def __syncMrLoop__(self, file_list_id, file_type, content_subtype, sf, lp_category):
        """ Synchronize sequence scheme of restraint loop based on coordinates.
        """

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        if file_type == 'nef':

            chain_id_name = 'chain_code'
            seq_id_name = 'sequence_code'

            if chain_id_name in loop.tags:
                tags = [chain_id_name, seq_id_name]
                dat = get_lp_tag(loop, tags)
                for row in dat:
                    try:
                        seq_key = (row[0], int(row[1]))
                        if seq_key in self.__seq_id_map_for_remediation:
                            row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                    except (ValueError, TypeError):
                        if row[0] in self.__chain_id_map_for_remediation:
                            row[0] = self.__chain_id_map_for_remediation[row[0]]

            else:
                for j in range(1, MAX_DIM_NUM_OF_SPECTRA):
                    chain_id_name = f'chain_code_{j}'
                    seq_id_name = f'sequence_code_{j}'
                    if chain_id_name not in loop.tags:
                        break
                    tags = [chain_id_name, seq_id_name]
                    dat = get_lp_tag(loop, tags)
                    for row in dat:
                        try:
                            seq_key = (row[0], int(row[1]))
                            if seq_key in self.__seq_id_map_for_remediation:
                                row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                        except (ValueError, TypeError):
                            if row[0] in self.__chain_id_map_for_remediation:
                                row[0] = self.__chain_id_map_for_remediation[row[0]]

        else:

            if content_subtype == 'ccr_d_csa_restraint':
                for interaction in ['Dipole', 'CSA']:
                    for j in range(1, 3):
                        chain_id_name = f'{interaction}_entity_assembly_ID_{j}'
                        seq_id_name = f'{interaction}_comp_index_ID_{j}'
                        alt_seq_id_name = f'{interaction}_seq_ID_{j}'
                        if alt_seq_id_name in loop.tags:
                            tags = [chain_id_name, seq_id_name, alt_seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                        row[2] = row[1]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]

                        else:
                            tags = [chain_id_name, seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]

            elif content_subtype == 'ccr_dd_restraint':
                for interaction in ['Dipole_1', 'Dipole_2']:
                    for j in range(1, 3):
                        chain_id_name = f'{interaction}_entity_assembly_ID_{j}'
                        seq_id_name = f'{interaction}_comp_index_ID_{j}'
                        alt_seq_id_name = f'{interaction}_seq_ID_{j}'
                        if alt_seq_id_name in loop.tags:
                            tags = [chain_id_name, seq_id_name, alt_seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                        row[2] = row[1]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]

                        else:
                            tags = [chain_id_name, seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]

            else:
                chain_id_name = 'Entity_assembly_ID'
                seq_id_name = 'Comp_index_ID'
                alt_seq_id_name = 'Seq_ID'

                if chain_id_name in loop.tags:
                    if alt_seq_id_name in loop.tags:
                        tags = [chain_id_name, seq_id_name, alt_seq_id_name]
                        dat = get_lp_tag(loop, tags)
                        for row in dat:
                            try:
                                seq_key = (row[0], int(row[1]))
                                if seq_key in self.__seq_id_map_for_remediation:
                                    row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                    row[2] = row[1]
                            except (ValueError, TypeError):
                                if row[0] in self.__chain_id_map_for_remediation:
                                    row[0] = self.__chain_id_map_for_remediation[row[0]]

                    else:
                        tags = [chain_id_name, seq_id_name]
                        dat = get_lp_tag(loop, tags)
                        for row in dat:
                            try:
                                seq_key = (row[0], int(row[1]))
                                if seq_key in self.__seq_id_map_for_remediation:
                                    row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                            except (ValueError, TypeError):
                                if row[0] in self.__chain_id_map_for_remediation:
                                    row[0] = self.__chain_id_map_for_remediation[row[0]]

                else:
                    for j in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        chain_id_name = f'Entity_assembly_ID_{j}'
                        seq_id_name = f'Comp_index_ID_{j}'
                        alt_seq_id_name = f'Seq_ID_{j}'
                        if chain_id_name not in loop.tags:
                            break
                        if alt_seq_id_name in loop.tags:
                            tags = [chain_id_name, seq_id_name, alt_seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                        row[2] = row[1]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]
                        else:
                            tags = [chain_id_name, seq_id_name]
                            dat = get_lp_tag(loop, tags)
                            for row in dat:
                                try:
                                    seq_key = (row[0], int(row[1]))
                                    if seq_key in self.__seq_id_map_for_remediation:
                                        row[0], row[1] = self.__seq_id_map_for_remediation[seq_key]
                                except (ValueError, TypeError):
                                    if row[0] in self.__chain_id_map_for_remediation:
                                        row[0] = self.__chain_id_map_for_remediation[row[0]]

        return True

    def __testCsPseudoAtomNameConsistencyInMrLoop(self):
        """ Perform consistency test on pseudo atom names between assigned chemical shifts and NMR restraints. (DAOTHER-7681, issue #1)
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            if fileListId >= len(self.__star_data_type) or self.__star_data_type[fileListId] != 'Entry':
                continue

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            # DAOTHER-9405
            if file_type == 'nef':
                continue

            if input_source_dic['content_subtype'] is None\
               or 'chem_shift' not in input_source_dic['content_subtype']:
                continue

            rescue_mode = self.__cmpl_missing_data and input_source_dic['content_subtype']['chem_shift'] == 1

            cs_item_names = self.item_names_in_cs_loop[file_type]
            cs_chain_id_name = cs_item_names['chain_id']
            cs_seq_id_name = cs_item_names['seq_id']
            cs_comp_id_name = cs_item_names['comp_id']
            cs_atom_id_name = cs_item_names['atom_id']
            cs_value_name = cs_item_names['value']

            missing_cs_atoms = []

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        try:
                            cs_data, cs_list = next((lp['data'], lp['sf_framecode']) for lp in self.__lp_data['chem_shift']
                                                    if lp['file_name'] == file_name)
                        except StopIteration:
                            continue

                        max_dim = 3 if content_subtype in ('dist_restraint', 'rdc_restraint') else 5

                        item_names = []
                        for j in range(1, max_dim):
                            _item_names = {}
                            for k, v in self.item_names_in_pk_loop[file_type].items():
                                if '%s' in v:
                                    v = v % j
                                _item_names[k] = v
                            item_names.append(_item_names)

                        num_dim = max_dim - 1

                        chain_id_names = []
                        seq_id_names = []
                        comp_id_names = []
                        atom_id_names = []

                        for d in range(num_dim):

                            chain_id_names.append(item_names[d]['chain_id'])
                            seq_id_names.append(item_names[d]['seq_id'])
                            comp_id_names.append(item_names[d]['comp_id'])
                            atom_id_names.append(item_names[d]['atom_id'])

                        index_tag = self.index_tags[file_type][content_subtype]

                        try:

                            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                            if lp_data is not None:

                                for row in lp_data:
                                    for d in range(num_dim):
                                        chain_id = row.get(chain_id_names[d])
                                        seq_id = row.get(seq_id_names[d])
                                        comp_id = row.get(comp_id_names[d])
                                        atom_id = row.get(atom_id_names[d])

                                        if chain_id in emptyValue or seq_id in emptyValue or comp_id in emptyValue or atom_id in emptyValue:
                                            continue

                                        _atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        len_atom_id = len(_atom_ids)

                                        if len_atom_id == 0:
                                            atom_id_ = atom_id

                                        elif len_atom_id == 1 and atom_id == _atom_ids[0]:
                                            atom_id_ = atom_id

                                        else:  # representative atom id
                                            atom_id_ = _atom_ids[0]

                                        if self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id_) < 2:
                                            continue

                                        _atom_id_ = atom_id_

                                        if file_type == 'nmr-star' and self.__isNmrAtomName(comp_id, atom_id):
                                            pass
                                        else:
                                            atom_id_ = atom_id

                                        atom_ids_w_cs = [_row[cs_atom_id_name] for _row in cs_data
                                                         if _row[cs_chain_id_name] == chain_id
                                                         and _row[cs_seq_id_name] == seq_id
                                                         and _row[cs_comp_id_name] == comp_id]

                                        if atom_id_ in atom_ids_w_cs:
                                            continue

                                        has_chem_shift = False

                                        for atom_id_w_cs in atom_ids_w_cs:
                                            _atom_id_w_cs = self.__getAtomIdList(comp_id, atom_id_w_cs)
                                            if any(_atom_id for _atom_id in _atom_ids if _atom_id in _atom_id_w_cs):
                                                has_chem_shift = True
                                                break

                                        if has_chem_shift:
                                            continue

                                        gem_atom_id = self.__csStat.getGeminalAtom(comp_id, _atom_id_)

                                        if gem_atom_id is None:
                                            continue

                                        gem_atom_id_w_cs = None

                                        atom_ids_w_cs = [_row[cs_atom_id_name] for _row in cs_data
                                                         if _row[cs_chain_id_name] == chain_id
                                                         and _row[cs_seq_id_name] == seq_id
                                                         and _row[cs_comp_id_name] == comp_id]

                                        for atom_id_w_cs in atom_ids_w_cs:
                                            _atom_id_w_cs = self.__getAtomIdList(comp_id, atom_id_w_cs)
                                            if gem_atom_id in _atom_id_w_cs:
                                                gem_atom_id_w_cs = atom_id_w_cs
                                                break

                                        if gem_atom_id_w_cs is None:
                                            continue

                                        if content_subtype == 'dist_restraint':
                                            subtype_name = "distance restraint"
                                        elif content_subtype == 'dihed_restraint':
                                            subtype_name = "dihedral angle restraint"
                                        else:
                                            subtype_name = "RDC restraint"

                                        if _atom_id_ in self.__csStat.getMethylAtoms(comp_id)\
                                           and content_subtype == 'dist_restraint'\
                                           and not self.__remediation_mode:

                                            cs_atom_id_map = {'chain_id': chain_id, 'seq_id': seq_id, 'comp_id': comp_id,
                                                              'src_atom_id': gem_atom_id_w_cs, 'dst_atom_id': atom_id,
                                                              'content_subtype_name': subtype_name + 's'}

                                            if cs_atom_id_map not in missing_cs_atoms:
                                                missing_cs_atoms.append(cs_atom_id_map)

                                            if rescue_mode:
                                                continue

                                            err = f"[Check row of {index_tag} {row[index_tag]}] Assignment of {subtype_name} "\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                + " was not found in assigned chemical shifts. In contrast, "\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], gem_atom_id_w_cs)\
                                                + f" is in the assgined chemical shifts of {cs_list!r} saveframe."

                                            self.report.error.appendDescription('invalid_data',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ ValueError  - {err}\n")

                                        else:

                                            warn = f"[Check row of {index_tag} {row[index_tag]}] Assignment of {subtype_name} "\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                + " was not found in assigned chemical shifts. In contrast, "\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], gem_atom_id_w_cs)\
                                                + f" is in the assgined chemical shifts of {cs_list!r} saveframe."

                                            self.report.warning.appendDescription('missing_data',
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                   'description': warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Warning  - {warn}\n")

                        except Exception as e:

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Error  - " + str(e))
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Error  - {str(e)}\n")

            if rescue_mode and len(missing_cs_atoms) > 0:

                content_subtype = 'chem_shift'

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                star_data = copy.copy(self.__star_data[fileListId])

                for sf in star_data.get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if __pynmrstar_v3_2__:
                        loop = sf.get_loop(lp_category)
                    else:
                        loop = sf.get_loop_by_category(lp_category)

                    lp = pynmrstar.Loop.from_scratch(lp_category)

                    for tag in loop.tags:
                        lp.add_tag(lp_category + '.' + tag)

                    chain_id_col = loop.tags.index(cs_chain_id_name)
                    seq_id_col = loop.tags.index(cs_seq_id_name)
                    comp_id_col = loop.tags.index(cs_comp_id_name)
                    atom_id_col = loop.tags.index(cs_atom_id_name)
                    value_col = loop.tags.index(cs_value_name)

                    for row in loop:
                        lp.add_data(row)

                        chain_id = row[chain_id_col]
                        try:
                            seq_id = int(row[seq_id_col])
                        except (ValueError, TypeError):
                            continue
                        comp_id = row[comp_id_col]
                        atom_id = row[atom_id_col]
                        value = row[value_col]

                        _missing_cs_atoms = [missing_cs_atom for missing_cs_atom in missing_cs_atoms
                                             if missing_cs_atom['chain_id'] == chain_id
                                             and missing_cs_atom['seq_id'] == seq_id
                                             and missing_cs_atom['comp_id'] == comp_id
                                             and missing_cs_atom['src_atom_id'] == atom_id]

                        if len(_missing_cs_atoms) == 0:
                            continue

                        _subtype_name = ' and '.join([missing_cs_atom['content_subtype_name'] for missing_cs_atom in _missing_cs_atoms])

                        missing_cs_atom = _missing_cs_atoms[0]

                        _row = copy.copy(row)
                        _row[atom_id_col] = missing_cs_atom['dst_atom_id']
                        lp.data.append(_row)

                        warn = "The unbound resonance assignment "\
                            + self.__getReducedAtomNotation(cs_chain_id_name, chain_id, cs_seq_id_name, seq_id,
                                                            cs_comp_id_name, comp_id, cs_atom_id_name, missing_cs_atom['dst_atom_id'])\
                            + f" in {_subtype_name} has been added to the assigned chemical shifts by referring to "\
                            + self.__getReducedAtomNotation(cs_chain_id_name, chain_id, cs_seq_id_name, seq_id,
                                                            cs_comp_id_name, comp_id, cs_atom_id_name, missing_cs_atom['src_atom_id'])\
                            + f", {value} ppm."

                        self.report.warning.appendDescription('complemented_chemical_shift',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Warning  - {warn}\n")

                    del sf[loop]

                    sf.add_loop(lp)

                    parent_pointer = 1
                    for idx, lp_data in enumerate(self.__lp_data[content_subtype]):
                        if lp_data['file_name'] == file_name and lp_data['sf_framecode'] == sf_framecode:
                            del self.__lp_data[content_subtype][idx]
                            parent_pointer = idx + 1
                            break

                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]
                    allowed_tags = self.allowed_tags[file_type][content_subtype]
                    disallowed_tags = None

                    try:

                        lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                                         allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                                         test_on_index=True, enforce_non_zero=True, enforce_sign=True, enforce_range=True, enforce_enum=True,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__excl_missing_data)[0]

                        self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testCsPseudoAtomNameConsistencyInMrLoop() ++ Error  - {str(e)}\n")

                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __testCsValueConsistencyInPkLoop(self):
        """ Perform consistency test on peak position and assignment of spectral peaks.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            if fileListId >= len(self.__star_data_type) or self.__star_data_type[fileListId] != 'Entry':
                continue

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'spectral_peak'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            cs_item_names = self.item_names_in_cs_loop[file_type]
            cs_chain_id_name = cs_item_names['chain_id']
            cs_seq_id_name = cs_item_names['seq_id']
            cs_comp_id_name = cs_item_names['comp_id']
            cs_atom_id_name = cs_item_names['atom_id']
            cs_value_name = cs_item_names['value']
            cs_error_name = cs_item_names['error']
            cs_atom_type = cs_item_names['atom_type']
            cs_iso_number = cs_item_names['isotope_number']

            for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                try:

                    cs_list = get_first_sf_tag(sf, self.cs_list_sf_tag_name[file_type])

                except Exception:
                    continue

                try:

                    cs_data = next(lp['data'] for lp in self.__lp_data['chem_shift']
                                   if lp['file_name'] == file_name and lp['sf_framecode'] == cs_list)

                except StopIteration:

                    if cs_list not in emptyValue:

                        if fileListId == 0:

                            err = "Assigned chemical shifts are required to verify the consistensy of assigned peak list. "\
                                f"Referred {cs_list!r} saveframe containing the assigned chemical shift does not exist."

                            self.report.error.appendDescription('missing_mandatory_content',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ Error  - {err}\n")

                            continue

                        cs_input_source = self.report.input_sources[0]
                        cs_input_source_dic = cs_input_source.get()
                        cs_file_name = cs_input_source_dic['file_name']

                        try:
                            cs_data = next(lp['data'] for lp in self.__lp_data['chem_shift'] if lp['file_name'] == cs_file_name)
                        except StopIteration:
                            continue

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return False

                max_dim = num_dim + 1

                aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                 if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                 and lp['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)

                axis_codes = []
                abs_pk_pos = []
                sp_widths = []

                if aux_data is not None and len(aux_data) > 0:
                    for i in range(1, max_dim):
                        for sp_dim in aux_data:
                            if file_type == 'nef':
                                if sp_dim['dimension_id'] != i:
                                    continue
                                axis_codes.append(sp_dim['axis_code'])
                                abs_pk_pos.append(False if 'absolute_peak_poistions' not in sp_dim else sp_dim['absolute_peak_positions'])
                                sp_width = None if 'axis_unit' not in sp_dim else sp_dim.get('spectral_width')
                                if 'axis_unit' in sp_dim and sp_dim['axis_unit'] == 'Hz'\
                                   and 'spectrometer_frequency' in sp_dim and sp_width is not None:
                                    sp_freq = sp_dim['spectrometer_frequency']
                                    sp_width /= sp_freq
                                sp_widths.append(sp_width)
                            else:
                                if sp_dim['ID'] != i:
                                    continue
                                axis_codes.append(sp_dim['Axis_code'])
                                abs_pk_pos.append(False if 'Absolute_peak_positions' not in sp_dim else sp_dim['Absolute_peak_positions'])
                                sp_width = None if 'Sweep_width_units' not in sp_dim else sp_dim.get('Sweep_width')
                                if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                                   and 'Spectrometer_frequency' in sp_dim and sp_width is not None:
                                    sp_freq = sp_dim['Spectrometer_frequency']
                                    sp_width /= sp_freq
                                sp_widths.append(sp_width)
                            break
                else:
                    for i in range(num_dim):
                        axis_codes.append(None)
                        abs_pk_pos.append(False)
                        sp_widths.append(None)

                aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                 if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                 and lp['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)

                onebond = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if file_type == 'nef':
                            if sp_dim_trans['transfer_type'] == 'onebond':
                                dim_1 = sp_dim_trans['dimension_1']
                                dim_2 = sp_dim_trans['dimension_2']
                                onebond[dim_1 - 1][dim_2 - 1] = True
                                onebond[dim_2 - 1][dim_1 - 1] = True
                        else:
                            if sp_dim_trans['Type'] == 'onebond':
                                dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                                dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                                onebond[dim_1 - 1][dim_2 - 1] = True
                                onebond[dim_2 - 1][dim_1 - 1] = True

                jcoupling = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if file_type == 'nef':
                            if sp_dim_trans['transfer_type'].startswith('j'):
                                dim_1 = sp_dim_trans['dimension_1']
                                dim_2 = sp_dim_trans['dimension_2']
                                jcoupling[dim_1 - 1][dim_2 - 1] = True
                                jcoupling[dim_2 - 1][dim_1 - 1] = True
                        else:
                            if sp_dim_trans['Type'].startswith('j'):
                                dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                                dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                                jcoupling[dim_1 - 1][dim_2 - 1] = True
                                jcoupling[dim_2 - 1][dim_1 - 1] = True

                relayed = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if file_type == 'nef':
                            if sp_dim_trans['transfer_type'].startswith('relayed'):
                                dim_1 = sp_dim_trans['dimension_1']
                                dim_2 = sp_dim_trans['dimension_2']
                                relayed[dim_1 - 1][dim_2 - 1] = True
                                relayed[dim_2 - 1][dim_1 - 1] = True
                        else:
                            if sp_dim_trans['Type'].startswith('relayed'):
                                dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                                dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                                relayed[dim_1 - 1][dim_2 - 1] = True
                                relayed[dim_2 - 1][dim_1 - 1] = True

                item_names = []
                for dim in range(1, max_dim):
                    _d = {}
                    for k, v in self.item_names_in_pk_loop[file_type].items():
                        if '%s' in v:
                            v = v % dim
                        _d[k] = v
                    item_names.append(_d)

                chain_id_names = []
                seq_id_names = []
                comp_id_names = []
                atom_id_names = []
                position_names = []

                for d in range(num_dim):
                    chain_id_names.append(item_names[d]['chain_id'])
                    seq_id_names.append(item_names[d]['seq_id'])
                    comp_id_names.append(item_names[d]['comp_id'])
                    atom_id_names.append(item_names[d]['atom_id'])
                    position_names.append(item_names[d]['position'])

                index_tag = self.index_tags[file_type][content_subtype]

                try:

                    lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                    if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                    if lp_data is not None:

                        for row in lp_data:
                            for d in range(num_dim):

                                if __pynmrstar_v3__\
                                   and not (chain_id_names[d] in row and seq_id_names[d] in row
                                            and comp_id_names[d] in row and atom_id_names[d] in row):
                                    continue

                                chain_id = row.get(chain_id_names[d])
                                seq_id = row.get(seq_id_names[d])
                                comp_id = row.get(comp_id_names[d])
                                atom_id = _atom_id = row.get(atom_id_names[d])

                                if chain_id in emptyValue or seq_id in emptyValue or comp_id in emptyValue or atom_id in emptyValue:
                                    continue

                                position = row[position_names[d]]

                                _atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                len_atom_id = len(_atom_ids)

                                if len_atom_id == 0:
                                    atom_id_ = atom_id

                                elif len_atom_id == 1 and atom_id == _atom_ids[0]:
                                    atom_id_ = atom_id

                                else:  # representative atom id
                                    atom_id_ = _atom_ids[0]

                                cs_idx = -1

                                if file_type == 'nmr-star' and self.__isNmrAtomName(comp_id, atom_id):
                                    pass
                                else:
                                    atom_id_ = atom_id

                                atom_ids_w_cs = [_row[cs_atom_id_name] for _row in cs_data
                                                 if _row[cs_chain_id_name] == chain_id
                                                 and _row[cs_seq_id_name] == seq_id
                                                 and _row[cs_comp_id_name] == comp_id]

                                if atom_id_ in atom_ids_w_cs:
                                    cs_idx = atom_ids_w_cs.index(atom_id_)

                                else:
                                    for atom_id_w_cs in atom_ids_w_cs:
                                        _atom_id_w_cs = self.__getAtomIdList(comp_id, atom_id_w_cs)
                                        if any(_atom_id_ for _atom_id_ in _atom_ids if _atom_id_ in _atom_id_w_cs):
                                            cs_idx = atom_ids_w_cs.index(atom_id_w_cs)
                                            break

                                if cs_idx == -1:

                                    err = f"[Check row of {index_tag} {row[index_tag]}] Assignment of spectral peak "\
                                        + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                        comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                        + f" was not found in assigned chemical shifts of {cs_list!r} saveframe."

                                    self.report.warning.appendDescription('insufficient_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': err})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ Warning  - {err}\n")

                                else:

                                    cs_intra = [_row for _row in cs_data
                                                if _row[cs_chain_id_name] == chain_id
                                                and _row[cs_seq_id_name] == seq_id
                                                and _row[cs_comp_id_name] == comp_id]

                                    cs = cs_intra[cs_idx]

                                    value = cs[cs_value_name]
                                    error = cs[cs_error_name]

                                    if value in emptyValue:
                                        continue

                                    if error is None or error < 1.0e-3 or error * self.cs_diff_error_scaled_by_sigma > CS_UNCERT_MAX:
                                        error = CS_UNCERT_MAX
                                    else:
                                        error *= self.cs_diff_error_scaled_by_sigma

                                    if abs(position - value) > error:

                                        if not abs_pk_pos[d] and sp_widths[d] is not None:
                                            if position < value:
                                                while position < value:
                                                    position += sp_widths[d]
                                            elif position > value:
                                                while position > value:
                                                    position -= sp_widths[d]

                                        if abs(position - value) > error and sp_widths[d] is not None:

                                            if CS_RANGE_MIN < sp_widths[d] < CS_RANGE_MAX:

                                                err = f"[Check row of {index_tag} {row[index_tag]}] "\
                                                    f"Peak position of spectral peak {position_names[d]} {position} ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                    comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                    + f") in {sf_framecode!r} saveframe is inconsistent with the assigned chemical shift value "\
                                                    f"{value} (difference {position - value:.3f}, tolerance {error}) in {cs_list!r} saveframe."

                                                if error >= CS_UNCERT_MAX and not self.__remediation_mode:

                                                    self.report.error.appendDescription('invalid_data',
                                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                         'description': err})
                                                    self.report.setError()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                                                else:

                                                    self.report.warning.appendDescription('unusual_chemical_shift',
                                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                           'description': err})
                                                    self.report.setWarning()

                                                    if self.__verbose:
                                                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ Warning  - {err}\n")

                                    axis_code = str(cs[cs_iso_number]) + cs[cs_atom_type]

                                    if axis_codes[d] is not None and axis_code != axis_codes[d]:

                                        err = f"[Check row of {index_tag} {row[index_tag]}] Assignment of spectral peak "\
                                            + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                            comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                            + f" is inconsistent with axis code {axis_code} vs {axis_codes[d]}."

                                        self.report.error.appendDescription('invalid_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                             'description': err})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                                if True in onebond[d]:
                                    for d2 in range(num_dim):
                                        if onebond[d][d2]:
                                            chain_id2 = row[chain_id_names[d2]]
                                            seq_id2 = row[seq_id_names[d2]]
                                            comp_id2 = row[comp_id_names[d2]]
                                            atom_id2 = _atom_id2 = row[atom_id_names[d2]]

                                            if atom_id2 is not None:
                                                diff = len(atom_id) != len(atom_id2)
                                                _atom_id = '_' + (atom_id[1:-1] if atom_id.startswith('H') and diff else atom_id[1:])
                                                _atom_id2 = '_' + (atom_id2[1:-1] if atom_id2.startswith('H') and diff else atom_id2[1:])

                                            if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                               (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id or _atom_id2 != _atom_id)):

                                                # DAOTHER-7681, issue #2
                                                if d < d2 and chain_id2 == chain_id and seq_id2 == seq_id and comp_id2 == comp_id and _atom_id2 != _atom_id and\
                                                   self.__ccU.updateChemCompDict(comp_id):
                                                    _atom_id = self.__getAtomIdList(comp_id, atom_id)
                                                    _atom_id2 = self.__getAtomIdList(comp_id, atom_id2)
                                                    if any(b for b in self.__ccU.lastBonds
                                                           if ((b[self.__ccU.ccbAtomId1] in _atom_id and b[self.__ccU.ccbAtomId2] in _atom_id2)
                                                               or (b[self.__ccU.ccbAtomId1] in _atom_id2 and b[self.__ccU.ccbAtomId2] in _atom_id))):
                                                        continue

                                                err = f"[Check row of {index_tag} {row[index_tag]}] Coherence transfer type is onebond. "\
                                                    "However, assignment of spectral peak is inconsistent with the type, ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                    comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                    + ") vs ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                    comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                    + ")."

                                                self.report.error.appendDescription('invalid_data',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                                if True in jcoupling[d]:
                                    for d2 in range(num_dim):
                                        if jcoupling[d][d2]:
                                            chain_id2 = row[chain_id_names[d2]]
                                            seq_id2 = row[seq_id_names[d2]]
                                            comp_id2 = row[comp_id_names[d2]]
                                            atom_id2 = row[atom_id_names[d2]]

                                            if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                               (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id)):  # DAOTHER-7389, issue #2

                                                err = f"[Check row of {index_tag} {row[index_tag]}] Coherence transfer type is jcoupling. "\
                                                    "However, assignment of spectral peak is inconsistent with the type, ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                    comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                    + ") vs ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                    comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                    + ")."

                                                self.report.error.appendDescription('invalid_data',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                                if True in relayed[d]:
                                    for d2 in range(num_dim):
                                        if relayed[d][d2]:
                                            chain_id2 = row[chain_id_names[d2]]
                                            seq_id2 = row[seq_id_names[d2]]
                                            comp_id2 = row[comp_id_names[d2]]
                                            atom_id2 = row[atom_id_names[d2]]

                                            if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                               (d < d2 and (chain_id2 != chain_id or abs(seq_id2 - seq_id) > 1)):  # DAOTHER-7389, issue #2

                                                err = f"[Check row of {index_tag} {row[index_tag]}] Coherence transfer type is relayed. "\
                                                    "However, assignment of spectral peak is inconsistent with the type, ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                    comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                    + ") vs ("\
                                                    + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                    comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                    + ")."

                                                self.report.error.appendDescription('invalid_data',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkLoop() ++ Error  - {str(e)}\n")

        return self.report.getTotalErrors() == __errors

    def __testCsValueConsistencyInPkAltLoop(self):
        """ Perform consistency test on peak position and assignment of spectral peaks.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            if fileListId >= len(self.__star_data_type) or self.__star_data_type[fileListId] != 'Entry':
                continue

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if file_type == 'nef' or input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'spectral_peak_alt'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = '_Assigned_peak_chem_shift'

            cs_item_names = self.item_names_in_cs_loop[file_type]
            cs_chain_id_name = cs_item_names['chain_id']
            cs_seq_id_name = cs_item_names['seq_id']
            cs_comp_id_name = cs_item_names['comp_id']
            cs_atom_id_name = cs_item_names['atom_id']
            cs_value_name = cs_item_names['value']
            cs_error_name = cs_item_names['error']
            cs_atom_type = cs_item_names['atom_type']
            cs_iso_number = cs_item_names['isotope_number']

            for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                cs_data = None

                cs_file_name = file_name
                csFileListId = fileListId

                try:

                    cs_list = get_first_sf_tag(sf, self.cs_list_sf_tag_name[file_type])
                    _cs_list_id = get_first_sf_tag(sf, 'ID')

                    try:

                        cs_data = next(lp['data'] for lp in self.__lp_data['chem_shift']
                                       if lp['file_name'] == file_name and lp['sf_framecode'] == cs_list)

                    except StopIteration:

                        if cs_list not in emptyValue:

                            if fileListId == 0:

                                err = "Assigned chemical shifts are required to verify the consistensy of assigned peak lists. "\
                                    f"Referred {cs_list!r} saveframe containing the assigned chemical shift does not exist."

                                self.report.error.appendDescription('missing_mandatory_content',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ Error  - {err}\n")

                                continue

                            cs_input_source = self.report.input_sources[0]
                            cs_input_source_dic = cs_input_source.get()
                            cs_file_name = cs_input_source_dic['file_name']
                            csFileListId = 0

                except Exception:
                    pass

                if cs_data is None:

                    try:

                        _cs_data = next(lp for lp in self.__lp_data['chem_shift'] if lp['file_name'] == cs_file_name)

                    except StopIteration:
                        continue

                    cs_data = _cs_data['data']
                    cs_list = _cs_data['sf_framecode']

                    cs_sf = self.__getSaveframeByName(csFileListId, cs_list)

                    if cs_sf is None:
                        continue

                    _cs_list_id = get_first_sf_tag(sf, 'ID')

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return False

                max_dim = num_dim + 1

                item_names = []
                for dim in range(1, max_dim):
                    _d = {}
                    for k, v in self.item_names_in_pk_loop[file_type].items():
                        if '%s' in v:
                            v = v % dim
                        _d[k] = v
                    item_names.append(_d)

                chain_id_names = []
                seq_id_names = []
                comp_id_names = []
                atom_id_names = []

                for i in range(num_dim):
                    chain_id_names.append(item_names[i]['chain_id'])
                    seq_id_names.append(item_names[i]['seq_id'])
                    comp_id_names.append(item_names[i]['comp_id'])
                    atom_id_names.append(item_names[i]['atom_id'])

                aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                 if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                 and lp['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)

                axis_codes = []
                abs_pk_pos = []
                sp_widths = []

                if aux_data is not None and len(aux_data) > 0:
                    for i in range(1, max_dim):
                        for sp_dim in aux_data:
                            if sp_dim['ID'] != i:
                                continue
                            axis_codes.append(sp_dim['Axis_code'])
                            abs_pk_pos.append(False if 'Absolute_peak_positions' not in sp_dim else sp_dim['Absolute_peak_positions'])
                            sp_width = None if 'Sweep_width_units' not in sp_dim else sp_dim.get('Sweep_width')
                            if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz' and 'Spectrometer_frequency' in sp_dim and sp_width is not None:
                                sp_freq = sp_dim['Spectrometer_frequency']
                                sp_width /= sp_freq
                            sp_widths.append(sp_width)
                            break
                else:
                    for i in range(num_dim):
                        axis_codes.append(None)
                        abs_pk_pos.append(False)
                        sp_widths.append(None)

                aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                 if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                 and lp['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)

                onebond = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if sp_dim_trans['Type'] == 'onebond':
                            dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                            dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                            onebond[dim_1 - 1][dim_2 - 1] = True
                            onebond[dim_2 - 1][dim_1 - 1] = True

                jcoupling = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if sp_dim_trans['Type'].startswith('j'):
                            dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                            dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                            jcoupling[dim_1 - 1][dim_2 - 1] = True
                            jcoupling[dim_2 - 1][dim_1 - 1] = True

                relayed = [[False] * num_dim for i in range(num_dim)]
                if aux_data is not None:
                    for sp_dim_trans in aux_data:
                        if sp_dim_trans['Type'].startswith('relayed'):
                            dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                            dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                            relayed[dim_1 - 1][dim_2 - 1] = True
                            relayed[dim_2 - 1][dim_1 - 1] = True

                pk_id_name = 'Peak_ID'
                dim_id_name = 'Spectral_dim_ID'
                set_id_name = 'Set_ID'

                try:

                    lp_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                    if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                    and lp['category'] == lp_category), None)

                    if lp_data is not None:

                        for row in lp_data:

                            if __pynmrstar_v3__\
                               and not (cs_chain_id_name in row and cs_seq_id_name in row
                                        and cs_comp_id_name in row and cs_atom_id_name in row):
                                continue

                            chain_id = row[cs_chain_id_name]
                            if chain_id in emptyValue:
                                continue

                            seq_id = row[cs_seq_id_name]
                            if seq_id in emptyValue:
                                continue

                            comp_id = row[cs_comp_id_name]
                            if comp_id in emptyValue:
                                continue

                            atom_id = _atom_id = row[cs_atom_id_name]
                            if atom_id in emptyValue:
                                continue

                            cs_list_id = row['Assigned_chem_shift_list_ID']

                            if cs_list_id != _cs_list_id:

                                for _cs_data in self.__lp_data['chem_shift']:

                                    if _cs_data['file_name'] == file_name:

                                        cs_data = _cs_data['data']
                                        cs_list = _cs_data['sf_framecode']

                                        cs_sf = self.__getSaveframeByName(csFileListId, cs_list)

                                        if cs_sf is None:
                                            continue

                                        _cs_list_id = get_first_sf_tag(sf, 'ID')

                                        if cs_list_id == _cs_list_id:
                                            break

                            pk_id = row[pk_id_name]
                            d = row[dim_id_name] - 1
                            set_id = row[set_id_name]

                            position = row[cs_value_name]

                            _atom_ids = self.__getAtomIdList(comp_id, atom_id)

                            len_atom_id = len(_atom_ids)

                            if len_atom_id == 0:
                                atom_id_ = atom_id

                            elif len_atom_id == 1 and atom_id == _atom_ids[0]:
                                atom_id_ = atom_id

                            else:  # representative atom id
                                atom_id_ = _atom_ids[0]

                            cs_idx = -1

                            if file_type == 'nmr-star' and self.__isNmrAtomName(comp_id, atom_id):
                                pass
                            else:
                                atom_id_ = atom_id

                            atom_ids_w_cs = [_row[cs_atom_id_name] for _row in cs_data
                                             if _row[cs_chain_id_name] == chain_id
                                             and _row[cs_seq_id_name] == seq_id
                                             and _row[cs_comp_id_name] == comp_id]

                            if atom_id_ in atom_ids_w_cs:
                                cs_idx = atom_ids_w_cs.index(atom_id_)

                            else:
                                for atom_id_w_cs in atom_ids_w_cs:
                                    _atom_id_w_cs = self.__getAtomIdList(comp_id, atom_id_w_cs)
                                    if any(_atom_id_ for _atom_id_ in _atom_ids if _atom_id_ in _atom_id_w_cs):
                                        cs_idx = atom_ids_w_cs.index(atom_id_w_cs)
                                        break

                            if cs_idx == -1:

                                err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Assignment of spectral peak "\
                                    + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                    comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                    + f" was not found in assigned chemical shifts of {cs_list!r} saveframe."

                                self.report.warning.appendDescription('insufficient_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': err})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ Warning  - {err}\n")

                            else:

                                cs_intra = [_row for _row in cs_data
                                            if _row[cs_chain_id_name] == chain_id
                                            and _row[cs_seq_id_name] == seq_id
                                            and _row[cs_comp_id_name] == comp_id]

                                cs = cs_intra[cs_idx]

                                value = cs[cs_value_name]
                                error = cs[cs_error_name]

                                if value in emptyValue:
                                    continue

                                if error is None or error < 1.0e-3 or error * self.cs_diff_error_scaled_by_sigma > CS_UNCERT_MAX:
                                    error = CS_UNCERT_MAX
                                else:
                                    error *= self.cs_diff_error_scaled_by_sigma

                                if abs(position - value) > error:

                                    if d < num_dim and not abs_pk_pos[d] and sp_widths[d] is not None:
                                        if position < value:
                                            while position < value:
                                                position += sp_widths[d]
                                        elif position > value:
                                            while position > value:
                                                position -= sp_widths[d]

                                    if abs(position - value) > error and sp_widths[d] is not None:

                                        if CS_RANGE_MIN < sp_widths[d] < CS_RANGE_MAX:

                                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Peak position of spectral peak {cs_value_name} {position} ("\
                                                + self.__getReducedAtomNotation(cs_chain_id_name, chain_id, cs_seq_id_name, seq_id,
                                                                                cs_comp_id_name, comp_id, cs_atom_id_name, atom_id)\
                                                + f") in {sf_framecode!r} saveframe is inconsistent with the assigned chemical shift value "\
                                                f"{value} (difference {position - value:.3f}, tolerance {error}) in {cs_list!r} saveframe."

                                            if error >= CS_UNCERT_MAX and not self.__remediation_mode:

                                                self.report.error.appendDescription('invalid_data',
                                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                     'description': err})
                                                self.report.setError()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                                            else:

                                                self.report.warning.appendDescription('unusual_chemical_shift',
                                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                       'description': err})
                                                self.report.setWarning()

                                                if self.__verbose:
                                                    self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ Warning  - {err}\n")

                                axis_code = str(cs[cs_iso_number]) + cs[cs_atom_type]

                                if aux_data is not None and d < num_dim and axis_code != axis_codes[d]:

                                    err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Assignment of spectral peak "\
                                        + self.__getReducedAtomNotation(cs_chain_id_name, chain_id, cs_seq_id_name, seq_id,
                                                                        cs_comp_id_name, comp_id, cs_atom_id_name, atom_id)\
                                        + f" is inconsistent with axis code {axis_code} vs {axis_codes[d]}."

                                    self.report.error.appendDescription('invalid_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                            if d < num_dim and True in onebond[d]:
                                for d2 in range(num_dim):
                                    if onebond[d][d2]:

                                        try:
                                            _row = next(_row for _row in lp_data
                                                        if _row[pk_id_name] == pk_id
                                                        and _row[dim_id_name] - 1 == d2
                                                        and _row[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = _row[cs_chain_id_name]
                                        seq_id2 = _row[cs_seq_id_name]
                                        comp_id2 = _row[cs_comp_id_name]
                                        atom_id2 = _atom_id2 = _row[cs_atom_id_name]

                                        if atom_id2 is not None:
                                            diff = len(atom_id) != len(atom_id2)
                                            _atom_id = '_' + (atom_id[1:-1] if atom_id.startswith('H') and diff else atom_id[1:])
                                            _atom_id2 = '_' + (atom_id2[1:-1] if atom_id2.startswith('H') and diff else atom_id2[1:])

                                        if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                           (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id or _atom_id2 != _atom_id)):

                                            # DAOTHER-7681, issue #2
                                            if d < d2 and chain_id2 == chain_id and seq_id2 == seq_id and comp_id2 == comp_id and _atom_id2 != _atom_id and\
                                               self.__ccU.updateChemCompDict(comp_id):
                                                _atom_id = self.__getAtomIdList(comp_id, atom_id)
                                                _atom_id2 = self.__getAtomIdList(comp_id, atom_id2)
                                                if any(b for b in self.__ccU.lastBonds
                                                       if ((b[self.__ccU.ccbAtomId1] in _atom_id and b[self.__ccU.ccbAtomId2] in _atom_id2)
                                                           or (b[self.__ccU.ccbAtomId1] in _atom_id2 and b[self.__ccU.ccbAtomId2] in _atom_id))):
                                                    continue

                                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Coherence transfer type is onebond. "\
                                                "However, assignment of spectral peak is inconsistent with the type, ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                + ") vs ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                + ")."

                                            self.report.error.appendDescription('invalid_data',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                            if d < num_dim and True in jcoupling[d]:
                                for d2 in range(num_dim):
                                    if jcoupling[d][d2]:

                                        try:
                                            _row = next(_row for _row in lp_data
                                                        if _row[pk_id_name] == pk_id
                                                        and _row[dim_id_name] - 1 == d2
                                                        and _row[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = _row[cs_chain_id_name]
                                        seq_id2 = _row[cs_seq_id_name]
                                        comp_id2 = _row[cs_comp_id_name]
                                        atom_id2 = _row[cs_atom_id_name]

                                        if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                           (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id)):  # DAOTHER-7389, issue #2

                                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Coherence transfer type is jcoupling. "\
                                                "However, assignment of spectral peak is inconsistent with the type, ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                + ") vs ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                + ")."

                                            self.report.error.appendDescription('invalid_data',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                            if d < num_dim and True in relayed[d]:
                                for d2 in range(num_dim):
                                    if relayed[d][d2]:

                                        try:
                                            _row = next(_row for _row in lp_data
                                                        if _row[pk_id_name] == pk_id
                                                        and _row[dim_id_name] - 1 == d2
                                                        and _row[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = _row[cs_chain_id_name]
                                        seq_id2 = _row[cs_seq_id_name]
                                        comp_id2 = _row[cs_comp_id_name]
                                        atom_id2 = _row[cs_atom_id_name]

                                        if chain_id2 in emptyValue or seq_id2 in emptyValue or comp_id2 in emptyValue or atom_id2 in emptyValue or\
                                           (d < d2 and (chain_id2 != chain_id or abs(seq_id2 - seq_id) > 1)):  # DAOTHER-7389, issue #2

                                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] Coherence transfer type is relayed. "\
                                                "However, assignment of spectral peak is inconsistent with the type, ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d], chain_id, seq_id_names[d], seq_id,
                                                                                comp_id_names[d], comp_id, atom_id_names[d], atom_id)\
                                                + ") vs ("\
                                                + self.__getReducedAtomNotation(chain_id_names[d2], chain_id2, seq_id_names[d2], seq_id2,
                                                                                comp_id_names[d2], comp_id2, atom_id_names[d2], atom_id2)\
                                                + ")."

                                            self.report.error.appendDescription('invalid_data',
                                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                                 'description': err})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testCsValueConsistencyInPkAltLoop() ++ Error  - {str(e)}\n")

        return self.report.getTotalErrors() == __errors

    def __testRdcVector(self):
        """ Perform consistency test on RDC bond vectors.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'rdc_restraint'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                self.__testRdcVector__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__testRdcVector__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__testRdcVector__(file_name, file_type, content_subtype, sf_framecode, lp_category)

        return self.report.getTotalErrors() == __errors

    def __testRdcVector__(self, file_name, file_type, content_subtype, sf_framecode, lp_category):
        """ Perform consistency test on RDC bond vectors.
        """

        item_names = self.item_names_in_rdc_loop[file_type]
        index_tag = self.index_tags[file_type][content_subtype]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']

        try:

            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is not None:

                for row in lp_data:
                    chain_id_1 = row[chain_id_1_name]
                    seq_id_1 = row[seq_id_1_name]
                    comp_id_1 = row[comp_id_1_name]
                    atom_id_1 = row[atom_id_1_name]
                    chain_id_2 = row[chain_id_2_name]
                    seq_id_2 = row[seq_id_2_name]
                    comp_id_2 = row[comp_id_2_name]
                    atom_id_2 = row[atom_id_2_name]

                    if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                        continue

                    if (atom_id_1[0] not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS) or (atom_id_2[0] not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS):

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = idx_msg + "Non-magnetic susceptible spin appears in RDC vector; "\
                            f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, "\
                            f"{chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2})."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {err}\n")

                    if chain_id_1 != chain_id_2:

                        if self.__exptl_method == 'SOLID-STATE NMR' and self.__symmetric is None:

                            src_id = self.report.getInputSourceIdOfCoord()

                            if src_id >= 0:

                                cif_input_source = self.report.input_sources[src_id]
                                cif_input_source_dic = cif_input_source.get()

                                has_cif_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

                                if has_cif_poly_seq:

                                    cif_polymer_sequence = cif_input_source_dic['polymer_sequence']

                                    self.__symmetric = 'no'

                                    for ps in cif_polymer_sequence:

                                        if 'identical_auth_chain_id' in ps:

                                            if len(ps['identical_auth_chain_id']) + 1 > 2:
                                                self.__symmetric = 'yes'

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        if self.__symmetric == 'no':

                            err = idx_msg + "Found inter-chain RDC vector; "\
                                f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) in a loop {lp_category}."

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {err}\n")

                        else:

                            err = idx_msg + "Found inter-chain RDC vector; "\
                                f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) in a loop {lp_category}. "\
                                "However, it might be an artificial RDC constraint on solid-state NMR applied to symmetric samples such as fibrils.\n"

                            self.report.warning.appendDescription('anomalous_rdc_vector',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': err})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Warning  - {err}\n")

                    elif abs(seq_id_1 - seq_id_2) > 1:

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = idx_msg + "Found inter-residue RDC vector; "\
                            f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) in a loop {lp_category}."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {err}\n")

                    elif abs(seq_id_1 - seq_id_2) == 1:

                        if self.__csStat.peptideLike(comp_id_1) and self.__csStat.peptideLike(comp_id_2) and\
                                ((seq_id_1 < seq_id_2 and atom_id_1 == 'C' and atom_id_2 in rdcBbPairCode)
                                 or (seq_id_1 > seq_id_2 and atom_id_1 in rdcBbPairCode and atom_id_2 == 'C')
                                 or (seq_id_1 < seq_id_2 and atom_id_1.startswith('HA') and atom_id_2 == 'H')
                                 or (seq_id_1 > seq_id_2 and atom_id_1 == 'H' and atom_id_2.startswith('HA'))):
                            pass

                        else:

                            idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                            err = idx_msg + "Found inter-residue RDC vector; "\
                                f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) in a loop {lp_category}."

                            self.report.error.appendDescription('invalid_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {err}\n")

                    elif atom_id_1 == atom_id_2:

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = idx_msg + "Found zero RDC vector; "\
                            f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2})."

                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {err}\n")

                    else:

                        if self.__ccU.updateChemCompDict(comp_id_1):  # matches with comp_id in CCD

                            if not self.__ccU.hasBond(comp_id_1, atom_id_1, atom_id_2):

                                if self.__nefT.validate_comp_atom(comp_id_1, atom_id_1) and self.__nefT.validate_comp_atom(comp_id_2, atom_id_2):

                                    idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                                    warn = idx_msg + "Found an RDC vector over multiple covalent bonds; "\
                                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2})."

                                    self.report.warning.appendDescription('unusual/rare_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Warning  - {warn}\n")

                                else:  # raised error already somewhere because of invalid atom nomenclature
                                    pass

                        else:  # raised warning already somewhere because of unknown comp_id
                            pass

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRdcVector() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRdcVector() ++ Error  - {str(e)}\n")

    def __testCoordCovalentBond(self):
        """ Perform consistency test on covalent bonds.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'poly_seq'

            if content_subtype not in input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.aux_lp_categories[file_type][content_subtype][0]

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                self.__testCoordCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__testCoordCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__testCoordCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

        return self.report.getTotalErrors() == __errors

    def __testCoordCovalentBond__(self, file_name, file_type, content_subtype, sf_framecode, lp_category):
        """ Perform consistency test on covalent bonds.
        """

        item_names = self.item_names_in_rdc_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']

        try:

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == lp_category), None)

            if aux_data is not None:

                for row in aux_data:
                    chain_id_1 = row[chain_id_1_name]
                    seq_id_1 = row[seq_id_1_name]
                    comp_id_1 = row[comp_id_1_name]
                    atom_id_1 = row[atom_id_1_name]
                    chain_id_2 = row[chain_id_2_name]
                    seq_id_2 = row[seq_id_2_name]
                    comp_id_2 = row[comp_id_2_name]
                    atom_id_2 = row[atom_id_2_name]

                    bond = self.__getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)

                    if bond is None:
                        continue

                    broken_bond = [b for b in bond if b['distance'] > self.cutoff_bond_length]

                    if len(broken_bond) == 0:
                        continue

                    length_list = ''
                    for bb in broken_bond:
                        length_list += f"{bb['distance']} (model_id {bb['model_id']}), "

                    warn = "Covalent bond ("\
                        + self.__getReducedAtomNotation(chain_id_1_name, chain_id_1, seq_id_1_name, seq_id_1, comp_id_1_name, comp_id_1, atom_id_1_name, atom_id_1)\
                        + " - "\
                        + self.__getReducedAtomNotation(chain_id_2_name, chain_id_2, seq_id_2_name, seq_id_2, comp_id_2_name, comp_id_2, atom_id_2_name, atom_id_2)\
                        + f") is out of acceptable range, {length_list[:-2]}Å."

                    self.report.warning.appendDescription('anomalous_bond_length',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testCoordCovalentBond() ++ Warning  - {warn}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCoordCovalentBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testCoordCovalentBond() ++ Error  - {str(e)}\n")

    def __getNmrBondLength(self, nmr_chain_id_1, nmr_seq_id_1, nmr_atom_id_1, nmr_chain_id_2, nmr_seq_id_2, nmr_atom_id_2):
        """ Return the bond length of given two NMR atoms.
            @return: the bond length
        """

        intra_chain = nmr_chain_id_1 == nmr_chain_id_2

        s_1 = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id_1)

        if s_1 is None:
            return None

        s_2 = s_1 if intra_chain else self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id_2)

        if s_2 is None:
            return None

        cif_chain_id_1 = s_1['chain_id']
        cif_chain_id_2 = cif_chain_id_1 if intra_chain else s_2['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return None

        seq_key = (nmr_chain_id_1, nmr_seq_id_1, nmr_atom_id_1, nmr_chain_id_2, nmr_seq_id_2, nmr_atom_id_2)

        if seq_key in self.__coord_bond_length:
            return self.__coord_bond_length[seq_key]

        result_1 = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                         if seq_align['ref_chain_id'] == nmr_chain_id_1 and seq_align['test_chain_id'] == cif_chain_id_1), None)
        result_2 = result_1 if intra_chain else next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                                                      if seq_align['ref_chain_id'] == nmr_chain_id_2 and seq_align['test_chain_id'] == cif_chain_id_2), None)

        if result_1 is not None and result_2 is not None:

            cif_seq_id_1 = next((test_seq_id for ref_seq_id, test_seq_id
                                 in zip(result_1['ref_seq_id'], result_1['test_seq_id']) if ref_seq_id == nmr_seq_id_1), None)

            if cif_seq_id_1 is None:
                self.__coord_bond_length[seq_key] = None
                return None

            cif_seq_id_2 = next((test_seq_id for ref_seq_id, test_seq_id
                                 in zip(result_2['ref_seq_id'], result_2['test_seq_id']) if ref_seq_id == nmr_seq_id_2), None)

            if cif_seq_id_2 is None:
                self.__coord_bond_length[seq_key] = None
                return None

            bond = self.__getCoordBondLength(cif_chain_id_1, cif_seq_id_1, nmr_atom_id_1, cif_chain_id_2, cif_seq_id_2, nmr_atom_id_2)

            if bond is not None:
                self.__coord_bond_length[seq_key] = bond

                return bond

        self.__coord_bond_length[seq_key] = None

        return None

    def __getCoordBondLength(self, cif_chain_id_1, cif_seq_id_1, cif_atom_id_1, cif_chain_id_2, cif_seq_id_2, cif_atom_id_2,
                             label_scheme=True):
        """ Return the bond length of given two CIF atoms.
            @return: the bond length
        """

        try:

            model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

            data_items = [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                          {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                          {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                          {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                          ]

            atom_site_1 = self.__cR.getDictListWithFilter('atom_site',
                                                          data_items,
                                                          [{'name': 'label_asym_id' if label_scheme else 'auth_asym_id', 'type': 'str', 'value': cif_chain_id_1},
                                                           {'name': 'label_seq_id' if label_scheme else 'auth_seq_id', 'type': 'int', 'value': cif_seq_id_1},
                                                           {'name': 'label_atom_id' if label_scheme else 'auth_atom_id', 'type': 'str', 'value': cif_atom_id_1},
                                                           {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                           ])

            atom_site_2 = self.__cR.getDictListWithFilter('atom_site',
                                                          data_items,
                                                          [{'name': 'label_asym_id' if label_scheme else 'auth_asym_id', 'type': 'str', 'value': cif_chain_id_2},
                                                           {'name': 'label_seq_id' if label_scheme else 'auth_seq_id', 'type': 'int', 'value': cif_seq_id_2},
                                                           {'name': 'label_atom_id' if label_scheme else 'auth_atom_id', 'type': 'str', 'value': cif_atom_id_2},
                                                           {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                           ])

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getCoordBondLength() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__getCoordBondLength() ++ Error  - {str(e)}\n")

            return None

        model_ids = set(a['model_id'] for a in atom_site_1) | set(a['model_id'] for a in atom_site_2)

        bond = []

        for model_id in model_ids:
            a_1 = next((a for a in atom_site_1 if a['model_id'] == model_id), None)
            a_2 = next((a for a in atom_site_2 if a['model_id'] == model_id), None)

            if a_1 is None or a_2 is None:
                continue

            bond.append({'model_id': model_id, 'distance': float(f"{distance(to_np_array(a_1), to_np_array(a_2)):.3f}")})

        if len(bond) > 0:
            return bond

        return None

    def __testResidueVariant(self):
        """ Perform consistency test on residue variants.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        cif_polymer_sequence = cif_input_source_dic['polymer_sequence']

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            nmr_input_source = self.report.input_sources[fileListId]
            nmr_input_source_dic = nmr_input_source.get()

            file_name = nmr_input_source_dic['file_name']
            file_type = nmr_input_source_dic['file_type']

            if nmr_input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'poly_seq'

            if content_subtype not in nmr_input_source_dic['content_subtype']:
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.aux_lp_categories[file_type][content_subtype][1]  # nef: _nef_sequence, nmr-star: _Entity_deleted_atom

            if lp_category not in self.__lp_category_list[fileListId]:
                continue

            seq_align_dic = self.report.sequence_alignment.get()
            chain_assign_dic = self.report.chain_assignment.get()

            if 'nmr_poly_seq_vs_model_poly_seq' not in chain_assign_dic:

                err = "Chain assignment does not exist, __assignCoordPolymerSequence() should be invoked."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCoordResidueVariant() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testCoordResidueVariant() ++ Error  - {err}\n")

                continue

            if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
                continue

            if not has_key_value(chain_assign_dic, 'nmr_poly_seq_vs_model_poly_seq'):
                continue

            nmr2ca = {}

            for ca in chain_assign_dic['nmr_poly_seq_vs_model_poly_seq']:

                ref_chain_id = ca['ref_chain_id']
                test_chain_id = ca['test_chain_id']

                result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                               if seq_align['ref_chain_id'] == ref_chain_id and seq_align['test_chain_id'] == test_chain_id), None)

                if ref_chain_id not in nmr2ca:
                    nmr2ca[ref_chain_id] = []

                sa = {'seq_align': result}  # DAOTHER-7465

                if 'unmapped_sequence' in ca:
                    sa['seq_unmap'] = [unmapped['ref_seq_id'] for unmapped in ca['unmapped_sequence']]

                nmr2ca[ref_chain_id].append(sa)

            if self.__star_data_type[fileListId] == 'Loop':
                sf = self.__star_data[fileListId]
                sf_framecode = ''

                self.__testResidueVariant__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

            elif self.__star_data_type[fileListId] == 'Saveframe':
                sf = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                self.__testResidueVariant__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

            else:

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    if not any(loop for loop in sf.loops if loop.category == lp_category):
                        continue

                    self.__testResidueVariant__(file_name, file_type, content_subtype, sf, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

        return self.report.getTotalErrors() == __errors

    def __testResidueVariant__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca):
        """ Perform consistency test on residue variants.
        """

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        variant_name = 'residue_variant' if file_type == 'nef' else item_names['atom_id']

        key_items = self.aux_key_items[file_type][content_subtype][lp_category]
        data_items = self.aux_data_items[file_type][content_subtype][lp_category]
        allowed_tags = self.aux_allowed_tags[file_type][content_subtype][lp_category]

        try:

            aux_data = self.__nefT.check_data(sf, lp_category, key_items, data_items,
                                              allowed_tags, None, None,
                                              enforce_allowed_tags=(file_type == 'nmr-star'),
                                              excl_missing_data=self.__excl_missing_data)[0]

            if aux_data is not None:

                for row in aux_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    variant = row[variant_name]

                    if chain_id not in nmr2ca:
                        continue

                    ca = next((ca['seq_align'] for ca in nmr2ca[chain_id] if ('seq_unmap' not in ca or (seq_id not in ca['seq_unmap']))), None)  # DAOTHER-7465

                    if ca is None:
                        continue

                    cif_chain_id = ca['test_chain_id']

                    cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                       in zip(ca['ref_seq_id'], ca['test_seq_id']) if ref_seq_id == seq_id), None)

                    if cif_seq_id is None:
                        continue

                    cif_ps = next(ps for ps in cif_polymer_sequence if ps['chain_id'] == cif_chain_id)

                    cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                        in zip(cif_ps['seq_id'], cif_ps['comp_id']) if _seq_id == cif_seq_id), None)

                    if cif_comp_id is None:
                        continue

                    seq_key = (cif_chain_id, cif_seq_id)

                    if seq_key in self.__coord_unobs_res:  # DAOTHER-7665
                        continue

                    coord_atom_site_ = self.__coord_atom_site.get(seq_key)

                    self.__ccU.updateChemCompDict(comp_id)

                    if file_type == 'nef':

                        if variant in emptyValue:
                            continue

                        for _variant in variant.split(','):
                            _variant_ = _variant.strip(' ')

                            if _variant_[0] not in ('-', '+'):

                                warn = f"Residue variant {_variant_!r} should start with either '-' or '+' symbol according to the NEF sepcification."

                                self.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                       'description': warn})
                                self.report.setWarning()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__textResidueVariant() ++ Warning  - {warn}\n")

                                continue

                            atom_id = _variant_[1:]

                            if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                                len_atom_id = len(_atom_id)

                                if len_atom_id == 0:
                                    continue

                                if len_atom_id == 1 and atom_id == _atom_id[0]:
                                    atom_id_ = atom_id
                                    atom_name = atom_id

                                    if details is not None:
                                        atom_name += f", where {details.rstrip('.')}"

                                else:
                                    atom_name = f'{atom_id} (e.g. '

                                    for atom_id_ in _atom_id:
                                        atom_name += f'{atom_id_} '

                                    atom_name = f'{atom_name.rstrip()})'

                                    # representative atom id
                                    atom_id_ = _atom_id[0]

                            else:
                                atom_id_ = atom_id
                                atom_name = atom_id

                            if _variant_[0] == '-':

                                if self.__ccU.lastStatus:  # matches with comp_id in CCD

                                    if not self.__nefT.validate_comp_atom(comp_id, atom_id_):

                                        warn = "Atom ("\
                                            + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                            + f", {variant_name} {_variant_!r}) did not match with chemical component dictionary (CCD)."

                                        self.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__textResidueVariant() ++ Warning  - {warn}\n")

                                if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                                   and (atom_id_ in coord_atom_site_['atom_id']
                                        or ('auth_atom_id' in coord_atom_site_ and atom_id_ in coord_atom_site_['auth_atom_id'])):

                                    err = "Atom ("\
                                        + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + f", {variant_name} {_variant_!r}) is unexpectedly incorporated in the coordinates."

                                    self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ Error  - {err}\n")

                            else:

                                if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                                   and (atom_id_ not in coord_atom_site_['atom_id']
                                        and (('auth_atom_id' in coord_atom_site_ and atom_id_ not in coord_atom_site_['auth_atom_id'])
                                             or 'auth_atom_id' not in coord_atom_site_)):

                                    err = "Atom ("\
                                        + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + f") which is a {variant_name} {_variant_!r} is not present in the coordinates."

                                    checked = False
                                    if atom_id_[0] in protonBeginCode:
                                        cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id_), None)
                                        bonded_to = self.__ccU.getBondedAtoms(comp_id, atom_id_)
                                        peptide_like = self.__csStat.peptideLike(comp_id)
                                        if cca is not None and len(bonded_to) > 0:
                                            if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                               and (cca[self.__ccU.ccaLeavingAtomFlag] != 'Y'
                                                    or (peptide_like
                                                        and cca[self.__ccU.ccaNTerminalAtomFlag] == 'N'
                                                        and cca[self.__ccU.ccaCTerminalAtomFlag] == 'N')):
                                                checked = True
                                                err = "Atom ("\
                                                    + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                                    + f") which is a {variant_name} {_variant_!r} is not properly instantiated in the coordinates. Please re-upload the model file."

                                    if self.__remediation_mode and checked:
                                        continue

                                    self.report.error.appendDescription('hydrogen_not_instantiated' if checked else 'atom_not_found',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                         'description': err})
                                    self.report.setError()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ Error  - {err}\n")

                    else:

                        atom_id = variant

                        if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                            _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                            len_atom_id = len(_atom_id)

                            if len_atom_id == 0:
                                continue

                            if len_atom_id == 1 and atom_id == _atom_id[0]:
                                atom_id_ = atom_id
                                atom_name = atom_id

                                if details is not None:
                                    atom_name += f", where {details.rstrip('.')}"

                            else:
                                atom_name = f'{atom_id} (e.g. '

                                for atom_id_ in _atom_id:
                                    atom_name += f'{atom_id_} '

                                atom_name = f'{atom_name.rstrip()})'

                                # representative atom id
                                atom_id_ = _atom_id[0]

                        else:
                            atom_id_ = atom_id
                            atom_name = atom_id

                            if self.__ccU.lastStatus:  # matches with comp_id in CCD

                                if not self.__nefT.validate_comp_atom(comp_id, atom_id_):

                                    warn = "Atom ("\
                                        + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + ") did not match with chemical component dictionary (CCD)."

                                    self.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__textResidueVariant() ++ Warning  - {warn}\n")

                            if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                               and (atom_id_ in coord_atom_site_['atom_id']
                                    and (('auth_atom_id' in coord_atom_site_ and atom_id_ in coord_atom_site_['auth_atom_id'])
                                         or 'auth_atom_id' not in coord_atom_site_)):

                                err = "Atom ("\
                                    + self.__getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_name)\
                                    + ") is unexpectedly incorporated in the coordinates."

                                self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ Error  - {err}\n")

        except LookupError as e:

            item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

            self.report.error.appendDescription(item,
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            self.__lfh.write("+NmrDpUtility.__testResidueVariant() ++ LookupError  - "
                             f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ ValueError  - {str(e)}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testResidueVariant() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ Error  - {str(e)}\n")

    def __getReducedAtomNotation(self, chain_id_name, chain_id, seq_id_name, seq_id, comp_id_name, comp_id, atom_id_name, atom_id):
        """ Return reduced form of atom notation.
        """

        if self.__reduced_atom_notation:
            return f"{chain_id}:{seq_id}:{comp_id}:{atom_id}"

        return f"{chain_id_name} {chain_id}, {seq_id_name} {seq_id}, {comp_id_name} {comp_id}, {atom_id_name} {atom_id}"

    def __getReducedAtomNotations(self, key_items, row_data):
        """ Return reduced from of series of atom notations.
        """

        msg = ''

        if self.__reduced_atom_notation:
            j = 0
            for k in key_items:
                msg += f"{row_data[k['name']]}:"
                j += 1
                if j % 4 == 0:
                    msg = msg[:-1] + ' - '
            return msg[:-3]

        for k in key_items:
            msg += k['name'] + f" {row_data[k['name']]}, "

        return msg[:-2]

    def __retrieveCoordAssemblyChecker(self):
        """ Wrapper function for ParserListenerUtil.coordAssemblyChecker.
        """

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')

        nmrPolySeq = input_source_dic['polymer_sequence'] if has_poly_seq and self.__bmrb_only and self.__internal_mode else None

        hash_code_ext = ''
        if nmrPolySeq is not None:
            hash_code_ext = f'_{hashlib.md5(str(nmrPolySeq).encode()).hexdigest()[:4]}'

        self.__asmChkCachePath = None

        if self.__cifHashCode is not None:

            self.__asmChkCachePath = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}{hash_code_ext}_asm_chk.pkl")
            self.__caC = load_from_pickle(self.__asmChkCachePath)

            # DAOTHER-8817
            if self.__caC is not None and 'chem_comp_atom' in self.__caC\
               and 'auth_atom_name_to_id' in self.__caC\
               and 'auth_atom_name_to_id_ext' in self.__caC\
               and 'auth_to_star_seq_ann' in self.__caC\
               and 'mod_residue' in self.__caC\
               and 'split_ligand' in self.__caC:
                self.__nefT.set_chem_comp_dict(self.__caC['chem_comp_atom'],
                                               self.__caC['chem_comp_bond'],
                                               self.__caC['chem_comp_topo'])
                return

        self.__parseCoordinate()  # need to set representative_model/alt_id values

        self.__caC = coordAssemblyChecker(self.__verbose, self.__lfh,
                                          self.__representative_model_id,
                                          self.__representative_alt_id,
                                          self.__cR, None, nmrPolySeq)

        if self.__caC is not None and self.__asmChkCachePath is not None:
            write_as_pickle(self.__caC, self.__asmChkCachePath)

        # DAOTHER-8817
        self.__nefT.set_chem_comp_dict(self.__caC['chem_comp_atom'],
                                       self.__caC['chem_comp_bond'],
                                       self.__caC['chem_comp_topo'])

    def __validateStrMr(self):
        """ Validate restraints of NMR-STAR restraint files.
        """

        if self.__release_mode:
            return True

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        if self.__list_id_counter is None:
            self.__list_id_counter = {}
        if self.__mr_sf_dict_holder is None:
            self.__mr_sf_dict_holder = {}

        if self.__combined_mode and (not self.__remediation_mode or self.__annotation_mode or self.__native_combined):  # DAOTHER-8751, 8817 (D_1300043061)

            if len(self.__star_data) == 0:
                return True

            master_entry = self.__star_data[0]

            fileListId = 0

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type != 'nmr-star':
                return True

            file_name = input_source_dic['file_name']

            if input_source_dic['content_subtype'] is None:
                return True

            for content_subtype in self.mr_content_subtypes:

                if content_subtype not in input_source_dic['content_subtype']:
                    continue

                if content_subtype not in self.__mr_sf_dict_holder:
                    self.__mr_sf_dict_holder[content_subtype] = []

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                    original_file_name = get_first_sf_tag(sf, 'Data_file_name')
                    if len(original_file_name) == 0:
                        original_file_name = file_name.replace('-corrected', '')
                        if 'original_file_name' in input_source_dic:
                            if input_source_dic['original_file_name'] is not None:
                                original_file_name = os.path.basename(input_source_dic['original_file_name'])

                    if self.__validateStrMr__(fileListId, file_type, original_file_name, content_subtype, sf, sf_framecode, lp_category):
                        del master_entry[sf]

                        _sf = self.__mr_sf_dict_holder[content_subtype][-1]['saveframe']

                        master_entry.add_saveframe(_sf)

            if self.__dstPath is not None:

                # __validateStrPk() will do the same task in later
                if not any(content_subtype in self.pk_content_subtypes for content_type in input_source_dic['content_subtype']):

                    if not self.__annotation_mode:

                        if __pynmrstar_v3__:
                            master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
                        else:
                            master_entry.write_to_file(self.__dstPath)

                        if 'nmr_cif_file_path' in self.__outputParamDict:

                            try:

                                myIo = IoAdapterPy(False, sys.stderr)
                                containerList = myIo.readFile(self.__dstPath)

                                if containerList is not None and len(containerList) > 1:

                                    if self.__verbose:
                                        self.__lfh.write(f"Input container list is {[(c.getName(), c.getType()) for c in containerList]!r}\n")

                                    for c in containerList:
                                        c.setType('data')

                                    myIo.writeFile(self.__outputParamDict['nmr_cif_file_path'], containerList=containerList[1:])

                            except Exception as e:
                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {str(e)}\n")

            return True

        mr_file_path_list = 'restraint_file_path_list'

        if mr_file_path_list not in self.__inputParamDict:
            return True

        for fileListId in range(self.__cs_file_path_list_len, self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type != 'nmr-star':
                continue

            file_name = input_source_dic['file_name']

            original_file_name = file_name.replace('-corrected', '')
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in self.mr_content_subtypes:

                if content_subtype not in input_source_dic['content_subtype']:
                    continue

                if content_subtype not in self.__mr_sf_dict_holder:
                    self.__mr_sf_dict_holder[content_subtype] = []

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__validateStrMr__(fileListId, file_type, original_file_name, content_subtype, sf, sf_framecode, lp_category)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__validateStrMr__(fileListId, file_type, original_file_name, content_subtype, sf, sf_framecode, lp_category)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        self.__validateStrMr__(fileListId, file_type, original_file_name, content_subtype, sf, sf_framecode, lp_category)

        return True

    def __validateStrMr__(self, file_list_id, file_type, original_file_name, content_subtype, _sf, sf_framecode, lp_category):
        """ Validate data content of NMR-STAR restraint files.
        """

        self.__list_id_counter = incListIdCounter(content_subtype, self.__list_id_counter, reduced=False)

        list_id = self.__list_id_counter[content_subtype]

        restraint_name = getRestraintName(content_subtype)

        _sf_framecode = sf_framecode

        is_sf = True
        if len(sf_framecode) == 0:
            sf_framecode = restraint_name.replace(' ', '_').lower() + f'_{list_id}'
            is_sf = False

        # refresh saveframe

        sf = getSaveframe(content_subtype, sf_framecode, list_id, self.__entry_id, original_file_name,
                          reduced=False)

        # merge saveframe tags of the source saveframe

        if is_sf:

            origTagNames = [t[0] for t in _sf.tags]
            tagNames = [t[0] for t in sf.tags]

            for idx, origTagName in enumerate(origTagNames):
                if origTagName in self.sf_allowed_tags[file_type][content_subtype]:
                    set_sf_tag(sf, origTagName, _sf.tags[idx][1])

        try:

            if __pynmrstar_v3_2__:
                loop = _sf if self.__star_data_type[file_list_id] == 'Loop' else _sf.get_loop(lp_category)
            else:
                loop = _sf if self.__star_data_type[file_list_id] == 'Loop' else _sf.get_loop_by_category(lp_category)

            if not isinstance(loop, pynmrstar.Loop):
                loop = None

        except KeyError:
            loop = None

        _restraint_name = restraint_name.split()

        sf_item = {'file_type': file_type, 'saveframe': sf, 'list_id': list_id,
                   'id': 0, 'index_id': 0,
                   'constraint_type': ' '.join(_restraint_name[:-1])}

        if content_subtype == 'dist_restraint':
            sf_item['constraint_subsubtype'] = 'simple'

        if loop is not None:

            input_source = self.report.input_sources[file_list_id]
            input_source_dic = input_source.get()

            has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if has_poly_seq_in_loop:

                # if self.__caC is None:
                #     self.__retrieveCoordAssemblyChecker()

                polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

                ps = None

                seq_align = chain_assign = None
                br_seq_align = br_chain_assign = None
                np_seq_align = np_chain_assign = None

                if content_subtype in polymer_sequence_in_loop:
                    ps_in_loop = next((ps for ps in polymer_sequence_in_loop[content_subtype] if ps['sf_framecode'] == _sf_framecode), None)

                    if ps_in_loop is not None:
                        list_id = ps_in_loop['list_id']
                        ps = ps_in_loop['polymer_sequence']

                        seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['polymer_sequence'], ps, conservative=False)
                        chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['polymer_sequence'], ps, seq_align)

                        if self.__caC['branched'] is not None:
                            br_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['branched'], ps, conservative=False)
                            br_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['branched'], ps, br_seq_align)

                        if self.__caC['non_polymer'] is not None:
                            np_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['non_polymer'], ps, conservative=False)
                            np_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['non_polymer'], ps, np_seq_align)

                def get_auth_seq_scheme(chain_id, seq_id):
                    auth_asym_id = auth_seq_id = None

                    if seq_id is not None:

                        if chain_assign is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                           and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                        if test_seq_id == seq_id), None)

                        if (auth_asym_id is None or auth_seq_id is None) and br_seq_align is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in br_seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                           and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                        if test_seq_id == seq_id), None)

                        if (auth_asym_id is None or auth_seq_id is None) and np_seq_align is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in np_seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                           and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                        if test_seq_id == seq_id), None)

                    return auth_asym_id, auth_seq_id

                has_ins_code = False

                if ps is not None:

                    for s in ps:

                        if has_ins_code:
                            break

                        auth_asym_id, _ = get_auth_seq_scheme(s['chain_id'], s['seq_id'][0])

                        if self.__caC['polymer_sequence'] is not None\
                           and any(cif_ps for cif_ps in self.__caC['polymer_sequence']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                        if self.__caC['branched'] is not None\
                           and any(cif_ps for cif_ps in self.__caC['branched']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                        if self.__caC['non_polymer'] is not None\
                           and any(cif_ps for cif_ps in self.__caC['non_polymer']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                lp = getLoop(content_subtype, reduced=False, hasInsCode=has_ins_code)

                sf.add_loop(lp)
                sf_item['loop'] = lp

                index_tag = self.index_tags[file_type][content_subtype]
                id_col = loop.tags.index('ID') if 'ID' in loop.tags else -1
                combination_id_col = member_id_col = member_logic_code_col = upper_limit_col = -1
                auth_comp_id_1_col = auth_comp_id_2_col = torsion_angle_name_col = -1
                if content_subtype == 'dist_restraint':
                    if 'Combination_ID' in loop.tags:
                        combination_id_col = loop.tags.index('Combination_ID')
                    if 'Member_ID' in loop.tags:
                        member_id_col = loop.tags.index('Member_ID')
                    if 'Member_logic_code' in loop.tags:
                        member_logic_code_col = loop.tags.index('Member_logic_code')
                    if 'Distance_upper_bound_val' in loop.tags:
                        upper_limit_col = loop.tags.index('Distance_upper_bound_val')
                    if 'Auth_comp_ID_1' in loop.tags:
                        auth_comp_id_1_col = loop.tags.index('Auth_comp_ID_1')
                    if 'Auth_comp_ID_2' in loop.tags:
                        auth_comp_id_2_col = loop.tags.index('Auth_comp_ID_2')
                elif content_subtype == 'dihed_restraint':
                    if 'Torsion_angle_name' in loop.tags:
                        torsion_angle_name_col = loop.tags.index('Torsion_angle_name')

                key_items = [item['name'] for item in NMR_STAR_LP_KEY_ITEMS[content_subtype]]

                if content_subtype == 'ccr_dd_restraint' and 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                    key_items = copy.copy(key_items)
                    key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                    if key_item is not None:
                        key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

                len_key_items = len(key_items)

                atom_dim_num = (len_key_items - 1) // 5  # 5 for entity_assembly_id, entity_id, comp_index_id, comp_id, atom_id tags

                if atom_dim_num == 0:
                    err = f"Unexpected key items {key_items} set for processing {lp_category} loop in {sf_framecode} saveframe of {original_file_name} file."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ KeyError  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ KeyError  - {err}\n")

                    return False

                key_chain_id_names = [key_items[idx] for idx in range(1, len_key_items, 5)]
                key_entity_id_names = [key_items[idx] for idx in range(2, len_key_items, 5)]
                key_seq_id_names = [key_items[idx] for idx in range(3, len_key_items, 5)]
                key_comp_id_names = [key_items[idx] for idx in range(4, len_key_items, 5)]
                key_atom_id_names = [key_items[idx] for idx in range(5, len_key_items, 5)]

                key_tags = key_chain_id_names
                key_tags.extend(key_seq_id_names)
                key_tags.extend(key_comp_id_names)
                key_tags.extend(key_atom_id_names)

                auth_items = [auth_item['name'] for auth_item in NMR_STAR_LP_DATA_ITEMS[content_subtype]
                              if auth_item['name'].startswith('Auth') or 'auth' in auth_item['name']]

                auth_chain_id_names = [auth_item for auth_item in auth_items if 'asym' in auth_item or 'entity_assembly' in auth_item]
                auth_seq_id_names = [auth_item for auth_item in auth_items if 'seq' in auth_item]
                auth_comp_id_names = [auth_item for auth_item in auth_items if 'comp' in auth_item]
                auth_atom_id_names = [auth_item for auth_item in auth_items if 'atom' in auth_item and 'atom_name' not in auth_item]
                auth_atom_name_names = [auth_item for auth_item in auth_items if 'atom_name' in auth_item]

                auth_pdb_tags = auth_chain_id_names
                auth_pdb_tags.extend(auth_seq_id_names)
                auth_pdb_tags.extend(auth_comp_id_names)
                auth_pdb_tags.extend(auth_atom_id_names)

                coord_atom_site = self.__caC['coord_atom_site']
                auth_to_star_seq = self.__caC['auth_to_star_seq']
                auth_to_orig_seq = self.__caC['auth_to_orig_seq']
                auth_to_ins_code = self.__caC['auth_to_ins_code'] if has_ins_code else None
                auth_to_star_seq_ann = self.__caC['auth_to_star_seq_ann']
                auth_atom_name_to_id = self.__caC['auth_atom_name_to_id']

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                offset_holder = {}

                has_key_seq = False

                if set(key_tags) & set(loop.tags) == set(key_tags):
                    dat = get_lp_tag(loop, key_seq_id_names)
                    if len(dat) > 0:
                        has_key_seq = True
                        for row in dat:
                            try:
                                for d in range(atom_dim_num):
                                    int(row[d])
                            except (ValueError, TypeError):
                                has_key_seq = False
                                break

                has_auth_seq = valid_auth_seq = False

                if set(auth_pdb_tags) & set(loop.tags) == set(auth_pdb_tags):
                    auth_dat = get_lp_tag(loop, auth_pdb_tags)
                    if len(auth_dat) > 0:
                        has_auth_seq = valid_auth_seq = True
                        if not self.__annotation_mode:
                            for row in auth_dat:
                                try:
                                    for d in range(atom_dim_num):
                                        seq_key = (row[d], int(row[atom_dim_num + d]), row[atom_dim_num * 2 + d])
                                        if seq_key not in auth_to_star_seq_ann:
                                            valid_auth_seq = False
                                            break
                                    if not valid_auth_seq:
                                        break
                                except (ValueError, TypeError):
                                    has_auth_seq = valid_auth_seq = False
                                    break

                if has_key_seq or has_auth_seq:

                    has_auth_atom_name = len(auth_atom_name_names) > 0 and set(auth_atom_name_names) & set(loop.tags) == set(auth_atom_name_names)

                    if valid_auth_seq:

                        if has_auth_atom_name:
                            auth_pdb_tags.extend(auth_atom_name_names)

                        dat = get_lp_tag(loop, auth_pdb_tags)

                        prefer_auth_atom_name = False

                        if (self.__annotation_mode or self.__native_combined) and len(auth_atom_name_to_id) > 0:

                            count_auth_name = count_auth_id = 0

                            for row_ in dat:

                                for d in range(atom_dim_num):
                                    chain_id = row_[d]
                                    seq_id = int(row_[atom_dim_num + d])
                                    comp_id = row_[atom_dim_num * 2 + d]
                                    atom_id = row_[atom_dim_num * 3 + d]

                                    seq_key = (chain_id, seq_id, comp_id)

                                    try:
                                        auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                                    except KeyError:
                                        comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                        if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            if count_auth_name + count_auth_id == 0:

                                for row_ in dat:

                                    for d in range(atom_dim_num):
                                        chain_id = row_[d]
                                        seq_id = int(row_[atom_dim_num + d])
                                        comp_id = row_[atom_dim_num * 2 + d]
                                        atom_id = row_[atom_dim_num * 3 + d]

                                        seq_key = (chain_id, seq_id, comp_id)

                                        try:
                                            auth_to_star_seq_ann[seq_key]  # pylint: disable=pointless-statement
                                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                                comp_id = coord_atom_site[_seq_key]['comp_id']
                                        except KeyError:
                                            continue

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                            prefer_auth_atom_name = count_auth_name > count_auth_id

                        for idx, row_ in enumerate(dat):
                            atom_sels = [None] * atom_dim_num

                            for d in range(atom_dim_num):
                                chain_id = auth_chain_id = row_[d]
                                seq_id = int(row_[atom_dim_num + d])
                                comp_id = row_[atom_dim_num * 2 + d]
                                atom_id = row_[atom_dim_num * 3 + d]

                                seq_key = (chain_id, seq_id, comp_id)

                                try:
                                    auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                                except KeyError:
                                    if self.__annotation_mode or self.__native_combined:
                                        chain_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                         if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                        seq_key = (chain_id, seq_id, comp_id)
                                        if seq_key in auth_to_star_seq:
                                            row_[d] = chain_id
                                        else:
                                            chain_id, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                      for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                      if _auth_seq_id == seq_id), (chain_id, comp_id))
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                                row_[atom_dim_num * 2 + d] = comp_id
                                    if seq_key not in auth_to_star_seq:
                                        comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                        if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)

                                if has_auth_atom_name:
                                    auth_atom_id = row_[atom_dim_num * 4 + d]
                                    if auth_atom_id in emptyValue:
                                        auth_atom_id = atom_id
                                else:
                                    auth_atom_id = atom_id

                                _assign, warn = assignCoordPolymerSequenceWithChainId(self.__caC, self.__nefT, chain_id, seq_id, comp_id, atom_id)

                                rescued = False

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__remediation_mode or 'Macromolecules page' not in warn:
                                            self.report.error.appendDescription('atom_not_found',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    if content_subtype != 'dihed_restraint' or not self.__remediation_mode:
                                        continue

                                    if d not in (0, 3) or not warn.startswith('[Atom not found]'):
                                        _d = 1 if d == 0 else 2
                                        _chain_id = row_[_d]
                                        _seq_id = int(row_[atom_dim_num + _d])
                                        _comp_id = row_[atom_dim_num * 2 + _d]
                                        _atom_id = row_[atom_dim_num * 3 + _d]

                                        if chain_id != _chain_id or abs(seq_id - _seq_id) != 1:
                                            continue

                                        if not self.__ccU.updateChemCompDict(comp_id.upper()):
                                            continue

                                        cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id.upper()), None)

                                        if cca is None:
                                            continue

                                        __assign, _warn = assignCoordPolymerSequenceWithChainId(self.__caC, self.__nefT, _chain_id, _seq_id, _comp_id, _atom_id)

                                        if len(__assign) != 1 or _warn is not None:
                                            continue

                                        chainId, cifSeqId, _, _ = __assign[0]
                                        cifSeqId -= _seq_id - seq_id

                                        atom_sels[d] = [{'chain_id': chainId,
                                                         'seq_id': cifSeqId,
                                                         'comp_id': comp_id.upper(),
                                                         'atom_id': atom_id.upper(),
                                                         'auth_atom_id': auth_atom_id}]
                                        warn = None

                                        rescued = True

                                if not rescued:
                                    enableWarning = True
                                    if content_subtype == 'dist_restraint':
                                        if (auth_comp_id_1_col != -1 and loop.data[idx][auth_comp_id_1_col] == 'HOH')\
                                           or (auth_comp_id_2_col != -1 and loop.data[idx][auth_comp_id_2_col] == 'HOH'):
                                            enableWarning = False
                                    elif content_subtype == 'dihed_restraint':
                                        if torsion_angle_name_col != -1 and loop.data[idx][torsion_angle_name_col] == 'PPA':
                                            enableWarning = False

                                    atom_sels[d], warn = selectCoordAtoms(self.__cR, self.__caC, self.__nefT, _assign, auth_chain_id, seq_id, comp_id, atom_id, auth_atom_id,
                                                                          allowAmbig=content_subtype in ('dist_restraint', 'noepk_restraint'),
                                                                          enableWarning=enableWarning,
                                                                          preferAuthAtomName=prefer_auth_atom_name,
                                                                          representativeModelId=self.__representative_model_id, representativeAltId=self.__representative_alt_id,
                                                                          modelNumName=model_num_name)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__remediation_mode or 'Macromolecules page' not in warn:
                                            self.report.error.appendDescription('atom_not_found',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Hydrogen not instantiated]'):
                                        if self.__remediation_mode:
                                            self.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Warning  - {idx_msg + warn}\n")
                                        else:
                                            self.report.error.appendDescription('hydrogen_not_instantiated',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom nomenclature]'):
                                        self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                            {'file_name': original_file_name,
                                                                             'sf_framecode': sf_framecode,
                                                                             'category': lp_category,
                                                                             'description': idx_msg + warn})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                                        self.report.error.appendDescription('invalid_data',
                                                                            {'file_name': original_file_name,
                                                                             'sf_framecode': sf_framecode,
                                                                             'category': lp_category,
                                                                             'description': idx_msg + warn})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ ValueError  - {idx_msg + warn}\n")

                                    continue

                            if any(d for d in range(atom_dim_num) if atom_sels[d] is None or len(atom_sels[d]) == 0):
                                continue

                            sf_item['id'] += 1

                            if content_subtype == 'dist_restraint':
                                Id = '.'
                                if id_col != -1:
                                    Id = loop.data[idx][id_col]
                                    try:
                                        _Id = int(Id)
                                    except ValueError:
                                        Id = '.'
                                Id = sf_item['id'] if isinstance(Id, str) and Id == '.' else _Id
                                combinationId = '.'
                                if combination_id_col != -1:
                                    combinationId = loop.data[idx][combination_id_col]
                                    try:
                                        int(combinationId)
                                    except ValueError:
                                        combinationId = '.'
                                memberId = '.'
                                if member_id_col != -1:
                                    memberId = loop.data[idx][member_id_col]
                                    try:
                                        int(memberId)
                                    except ValueError:
                                        memberId = '.'
                                valid_atom_sels = atom_sels[0] is not None and atom_sels[1] is not None
                                if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                   and (isAmbigAtomSelection(atom_sels[0], self.__csStat)
                                        or isAmbigAtomSelection(atom_sels[1], self.__csStat)):
                                    memberId = 0
                                memberLogicCode = '.'
                                if member_logic_code_col != -1:
                                    memberLogicCode = loop.data[idx][member_logic_code_col]
                                    if memberLogicCode in emptyValue:
                                        memberLogicCode = '.'
                                memberLogicCode = 'OR' if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1 else memberLogicCode

                                if isinstance(memberId, int):
                                    _atom1 = _atom2 = None

                                if valid_atom_sels:
                                    for atom1, atom2 in itertools.product(atom_sels[0], atom_sels[1]):
                                        if isIdenticalRestraint([atom1, atom2]):
                                            continue
                                        if isinstance(memberId, int):
                                            if _atom1 is None or isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                                               or isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                                                memberId += 1
                                                _atom1, _atom2 = atom1, atom2
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[0] is not None:
                                    atom2 = None
                                    for atom1 in atom_sels[0]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[1] is not None:
                                    atom1 = None
                                    for atom2 in atom_sels[1]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                else:
                                    atom1 = atom2 = None
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__annotation_mode)
                                    lp.add_data(_row)

                            else:

                                sf_item['index_id'] += 1
                                _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                                      None, None, list_id, self.__entry_id,
                                                      loop.tags, loop.data[idx],
                                                      auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                      atom_sels, self.__annotation_mode)
                                lp.add_data(_row)

                    else:

                        if has_auth_atom_name:
                            key_tags.extend(auth_atom_name_names)

                        dat = get_lp_tag(loop, key_tags)

                        prefer_auth_atom_name = False

                        if (self.__annotation_mode or self.__native_combined) and len(auth_atom_name_to_id) > 0:

                            count_auth_name = count_auth_id = 0

                            for row_ in dat:

                                for d in range(atom_dim_num):
                                    chain_id = row_[d]
                                    seq_id = int(row_[atom_dim_num + d])
                                    comp_id = row_[atom_dim_num * 2 + d]
                                    atom_id = row_[atom_dim_num * 3 + d]

                                    seq_key = (chain_id, seq_id, comp_id)

                                    try:
                                        auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                                    except KeyError:
                                        chain_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                         if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                        seq_key = (chain_id, seq_id, comp_id)
                                        if seq_key in auth_to_star_seq:
                                            row_[d] = chain_id
                                        else:
                                            chain_id, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                      for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                      if _auth_seq_id == seq_id), (chain_id, comp_id))
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                                row_[atom_dim_num * 2 + d] = comp_id
                                        if seq_key not in auth_to_star_seq:
                                            comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                            if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            if count_auth_name + count_auth_id == 0:

                                for row_ in dat:

                                    for d in range(atom_dim_num):
                                        chain_id = row_[d]
                                        seq_id = int(row_[atom_dim_num + d])
                                        comp_id = row_[atom_dim_num * 2 + d]
                                        atom_id = row_[atom_dim_num * 3 + d]

                                        seq_key = (chain_id, seq_id, comp_id)

                                        try:
                                            auth_to_star_seq_ann[seq_key]  # pylint: disable=pointless-statement
                                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                                comp_id = coord_atom_site[_seq_key]['comp_id']
                                        except KeyError:
                                            continue

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                            prefer_auth_atom_name = count_auth_name > count_auth_id

                        for idx, row_ in enumerate(dat):
                            atom_sels = [None] * atom_dim_num

                            for d in range(atom_dim_num):
                                chain_id = auth_chain_id = row_[d]
                                seq_id = int(row_[atom_dim_num + d])
                                comp_id = row_[atom_dim_num * 2 + d]
                                atom_id = row_[atom_dim_num * 3 + d]

                                seq_key = (chain_id, seq_id, comp_id)

                                try:
                                    auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                                except KeyError:
                                    if self.__annotation_mode or self.__native_combined:
                                        chain_id = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                         if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                        seq_key = (chain_id, seq_id, comp_id)
                                        if seq_key in auth_to_star_seq:
                                            row_[d] = chain_id
                                        else:
                                            chain_id, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                      for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                      if _auth_seq_id == seq_id), (chain_id, comp_id))
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                                row_[atom_dim_num * 2 + d] = comp_id
                                    if seq_key not in auth_to_star_seq:
                                        comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                        if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)

                                if has_auth_atom_name:
                                    auth_atom_id = row_[atom_dim_num * 4 + d]
                                    if auth_atom_id in emptyValue:
                                        auth_atom_id = atom_id
                                else:
                                    auth_atom_id = atom_id

                                auth_asym_id = auth_seq_id = None

                                if chain_assign is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)
                                            if auth_seq_id is None:
                                                for offset in range(1, 10):
                                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                        if test_seq_id == seq_id + offset), None)
                                                    if auth_seq_id is not None:
                                                        auth_seq_id -= offset
                                                        break
                                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                        if test_seq_id == seq_id - offset), None)
                                                    if auth_seq_id is not None:
                                                        auth_seq_id += offset
                                                        break

                                if (auth_asym_id is None or auth_seq_id is None) and br_seq_align is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in br_seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)

                                if (auth_asym_id is None or auth_seq_id is None) and np_seq_align is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in np_seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)

                                if auth_asym_id is None or auth_seq_id is None:
                                    entity_id_name = key_entity_id_names[d]
                                    if entity_id_name not in loop.tags:
                                        continue
                                    try:
                                        entity_assembly_id = int(chain_id)
                                        entity_id = int(loop.data[idx][loop.tags.index(entity_id_name)])
                                    except ValueError:
                                        continue
                                    k = next((k for k, v in auth_to_star_seq.items() if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                    if k is None:
                                        continue
                                    auth_asym_id, auth_seq_id, _ = k

                                chain_id, seq_id = auth_asym_id, auth_seq_id

                                _assign, warn = assignCoordPolymerSequenceWithChainId(self.__caC, self.__nefT, chain_id, seq_id, comp_id, atom_id)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__remediation_mode or 'Macromolecules page' not in warn:
                                            self.report.error.appendDescription('atom_not_found',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    continue

                                enableWarning = True
                                if content_subtype == 'dist_restraint':
                                    if (auth_comp_id_1_col != -1 and loop.data[idx][auth_comp_id_1_col] == 'HOH')\
                                       or (auth_comp_id_2_col != -1 and loop.data[idx][auth_comp_id_2_col] == 'HOH'):
                                        enableWarning = False
                                elif content_subtype == 'dihed_restraint':
                                    if torsion_angle_name_col != -1 and loop.data[idx][torsion_angle_name_col] == 'PPA':
                                        enableWarning = False

                                atom_sels[d], warn = selectCoordAtoms(self.__cR, self.__caC, self.__nefT, _assign, auth_chain_id, seq_id, comp_id, atom_id, auth_atom_id,
                                                                      allowAmbig=content_subtype in ('dist_restraint', 'noepk_restraint'),
                                                                      enableWarning=enableWarning,
                                                                      preferAuthAtomName=prefer_auth_atom_name,
                                                                      representativeModelId=self.__representative_model_id, representativeAltId=self.__representative_alt_id,
                                                                      modelNumName=model_num_name)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__remediation_mode or 'Macromolecules page' not in warn:
                                            self.report.error.appendDescription('atom_not_found',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Hydrogen not instantiated]'):
                                        if self.__remediation_mode:
                                            self.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Warning  - {idx_msg + warn}\n")
                                        else:
                                            self.report.error.appendDescription('hydrogen_not_instantiated',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})
                                            self.report.setError()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom nomenclature]'):
                                        self.report.error.appendDescription('invalid_atom_nomenclature',
                                                                            {'file_name': original_file_name,
                                                                             'sf_framecode': sf_framecode,
                                                                             'category': lp_category,
                                                                             'description': idx_msg + warn})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                                        self.report.error.appendDescription('invalid_data',
                                                                            {'file_name': original_file_name,
                                                                             'sf_framecode': sf_framecode,
                                                                             'category': lp_category,
                                                                             'description': idx_msg + warn})
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__validateStrMr() ++ ValueError  - {idx_msg + warn}\n")

                                    continue

                            if any(d for d in range(atom_dim_num) if atom_sels[d] is None or len(atom_sels[d]) == 0):
                                continue

                            sf_item['id'] += 1

                            if content_subtype == 'dist_restraint':
                                Id = '.'
                                if id_col != -1:
                                    Id = loop.data[idx][id_col]
                                    try:
                                        _Id = int(Id)
                                    except ValueError:
                                        Id = '.'
                                Id = sf_item['id'] if isinstance(Id, str) and Id == '.' else _Id
                                combinationId = '.'
                                if combination_id_col != -1:
                                    combinationId = loop.data[idx][combination_id_col]
                                    try:
                                        int(combinationId)
                                    except ValueError:
                                        combinationId = '.'
                                memberId = '.'
                                if member_id_col != -1:
                                    memberId = loop.data[idx][member_id_col]
                                    try:
                                        int(memberId)
                                    except ValueError:
                                        memberId = '.'
                                valid_atom_sels = atom_sels[0] is not None and atom_sels[1] is not None
                                if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                   and (isAmbigAtomSelection(atom_sels[0], self.__csStat)
                                        or isAmbigAtomSelection(atom_sels[1], self.__csStat)):
                                    memberId = 0
                                memberLogicCode = '.'
                                if member_logic_code_col != -1:
                                    memberLogicCode = loop.data[idx][member_logic_code_col]
                                    if memberLogicCode in emptyValue:
                                        memberLogicCode = '.'
                                memberLogicCode = 'OR' if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1 else memberLogicCode

                                if isinstance(memberId, int):
                                    _atom1 = _atom2 = None

                                if valid_atom_sels:
                                    for atom1, atom2 in itertools.product(atom_sels[0], atom_sels[1]):
                                        if isIdenticalRestraint([atom1, atom2]):
                                            continue
                                        if isinstance(memberId, int):
                                            if _atom1 is None or isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                                               or isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                                                memberId += 1
                                                _atom1, _atom2 = atom1, atom2
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[0] is not None:
                                    atom2 = None
                                    for atom1 in atom_sels[0]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[1] is not None:
                                    atom1 = None
                                    for atom2 in atom_sels[1]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__annotation_mode)
                                        lp.add_data(_row)

                                else:
                                    atom1 = atom2 = None
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__annotation_mode)
                                    lp.add_data(_row)

                            else:

                                sf_item['index_id'] += 1
                                _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                                      None, None, list_id, self.__entry_id,
                                                      loop.tags, loop.data[idx],
                                                      auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                      atom_sels, self.__annotation_mode)
                                lp.add_data(_row)

                else:  # nothing to do because of insufficient sequence tags

                    lp = loop

                    sf_item['loop'] = lp

            else:  # nothing to do because of missing polymer sequence for this loop

                lp = loop

                sf_item['loop'] = lp

            if content_subtype == 'dist_restraint':

                sf_item['loop'] = lp

                # MR parser for XPLOR-NIH/CNS/CHARMM already fills _Gen_dist_constraint.ID with genuine IDs
                if sf_item['file_type'] not in ('nm-res-xpl', 'nm-res-cns', 'nm-res-cha'):
                    if not self.__updateGenDistConstIdInMrStr(sf_item):
                        err = 'Atoms in distance restraints can not be properly identified. Please re-upload the NMR-STAR file.'
                        self.report.error.appendDescription('missing_mandatory_content',
                                                            {'file_name': original_file_name,
                                                             'sf_framecode': sf_framecode,
                                                             'category': lp_category,
                                                             'description': err})

                sf_item['constraint_type'] = 'distance'
                sf_item['constraint_subsubtype'] = 'simple'
                constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                if len(constraint_type) > 0 and constraint_type not in emptyValue:
                    sf_item['constraint_subtype'] = constraint_type

                item_names = self.item_names_in_ds_loop[file_type]
                id_col = lp.tags.index('ID')
                member_logic_code_col = lp.tags.index('Member_logic_code') if 'Member_logic_code' in lp.tags else -1
                auth_asym_id_1_col = lp.tags.index('Auth_asym_ID_1')
                auth_seq_id_1_col = lp.tags.index('Auth_seq_ID_1')
                auth_asym_id_2_col = lp.tags.index('Auth_asym_ID_2')
                auth_seq_id_2_col = lp.tags.index('Auth_seq_ID_2')
                comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                has_or_code = False

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                            has_or_code = True
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in emptyValue:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'dist', dst_func)
                        else:
                            if getPotentialType(file_type, 'dist', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

                if has_or_code:

                    prev_id = -1
                    for row in lp:
                        if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                            _id = int(row[id_col])
                            if _id != prev_id:
                                _atom1 = {'chain_id': row[auth_asym_id_1_col],
                                          'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                          'comp_id': row[comp_id_1_col],
                                          'atom_id': row[atom_id_1_col]}
                                _atom2 = {'chain_id': row[auth_asym_id_2_col],
                                          'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                          'comp_id': row[comp_id_2_col],
                                          'atom_id': row[atom_id_2_col]}
                                prev_id = _id
                                continue
                            atom1 = {'chain_id': row[auth_asym_id_1_col],
                                     'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                     'comp_id': row[comp_id_1_col],
                                     'atom_id': row[atom_id_1_col]}
                            atom2 = {'chain_id': row[auth_asym_id_2_col],
                                     'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                     'comp_id': row[comp_id_2_col],
                                     'atom_id': row[atom_id_2_col]}
                            if isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                               or isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                                sf_item['constraint_subsubtype'] = 'ambi'
                                break
                            _atom1, _atom2 = atom1, atom2

                    if sf_item['constraint_subsubtype'] == 'ambi':

                        if 'pre' in sf_framecode or 'paramag' in sf_framecode:
                            sf_item['constraint_subtype'] = 'paramagnetic relaxation'
                        if 'cidnp' in sf_framecode:
                            sf_item['constraint_subtype'] = 'photo cidnp'
                        if 'csp' in sf_framecode or 'perturb' in sf_framecode:
                            sf_item['constraint_subtype'] = 'chemical shift perturbation'
                        if 'mutat' in sf_framecode:
                            sf_item['constraint_subtype'] = 'mutation'
                        if 'protect' in sf_framecode:
                            sf_item['constraint_subtype'] = 'hydrogen exchange protection'
                        if 'symm' in sf_framecode:
                            sf_item['constraint_subtype'] = 'symmetry'

                        if 'pre' in original_file_name or 'paramag' in original_file_name:
                            sf_item['constraint_subtype'] = 'paramagnetic relaxation'
                        if 'cidnp' in original_file_name:
                            sf_item['constraint_subtype'] = 'photo cidnp'
                        if 'csp' in original_file_name or 'perturb' in original_file_name:
                            sf_item['constraint_subtype'] = 'chemical shift perturbation'
                        if 'mutat' in original_file_name:
                            sf_item['constraint_subtype'] = 'mutation'
                        if 'protect' in original_file_name:
                            sf_item['constraint_subtype'] = 'hydrogen exchange protection'
                        if 'symm' in original_file_name:
                            sf_item['constraint_subtype'] = 'symmetry'

                if sf_item['constraint_subsubtype'] == 'simple':

                    metal_coord = False
                    disele_bond = False
                    disulf_bond = False
                    hydrog_bond = False

                    for row in lp:
                        comp_id_1 = row[comp_id_1_col]
                        comp_id_2 = row[comp_id_2_col]
                        atom_id_1 = row[atom_id_1_col]
                        atom_id_2 = row[atom_id_2_col]

                        if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                            continue

                        atom_id_1_ = atom_id_1[0]
                        atom_id_2_ = atom_id_2[0]
                        if comp_id_1 == atom_id_1 or comp_id_2 == atom_id_2:
                            metal_coord = True
                        elif 'SE' in (atom_id_1, atom_id_2):
                            disele_bond = True
                        elif 'SG' in (atom_id_1, atom_id_2):
                            disulf_bond = True
                        elif (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):
                            hydrog_bond = True

                    if not metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                        if 'build' in sf_framecode and 'up' in sf_framecode:
                            if 'roe' in sf_framecode:
                                sf_item['constraint_subtype'] = 'ROE build-up'
                            else:
                                sf_item['constraint_subtype'] = 'NOE build-up'

                        elif 'not' in sf_framecode and 'seen' in sf_framecode:
                            sf_item['constraint_subtype'] = 'NOE not seen'

                        elif 'roe' in sf_framecode:
                            sf_item['constraint_subtype'] = 'ROE'

                        elif 'build' in original_file_name and 'up' in original_file_name:
                            if 'roe' in original_file_name:
                                sf_item['constraint_subtype'] = 'ROE build-up'
                            else:
                                sf_item['constraint_subtype'] = 'NOE build-up'

                        elif 'not' in original_file_name and 'seen' in original_file_name:
                            sf_item['constraint_subtype'] = 'NOE not seen'

                        elif 'roe' in original_file_name:
                            sf_item['constraint_subtype'] = 'ROE'

                        sf_item['constraint_subtype'] = 'NOE'

                    elif metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'metal coordination'

                    elif not metal_coord and disele_bond and not disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'diselenide bond'

                    elif not metal_coord and not disele_bond and disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'disulfide bond'

                    elif not metal_coord and not disele_bond and not disulf_bond and hydrog_bond:
                        sf_item['constraint_subtype'] = 'hydrogen bond'

            elif content_subtype == 'dihed_restraint':
                self.__updateTorsionAngleConstIdInMrStr(sf_item)

                auth_to_entity_type = self.__caC['auth_to_entity_type']

                sf_item['constraint_type'] = 'dihedral angle'

                item_names = self.item_names_in_dh_loop[file_type]
                id_col = lp.tags.index('ID')
                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in emptyValue:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'dihed', dst_func)
                        else:
                            if getPotentialType(file_type, 'dihed', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

                id_col = lp.tags.index('ID')
                auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')

                _protein_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'peptide' in entity_type:
                            _protein_angles += 1
                        else:
                            _other_angles += 1

                if _protein_angles > _other_angles:
                    sf_item['constraint_type'] = 'protein dihedral angle'

                    tagNames = [t[0] for t in sf.tags]

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'backbone chemical shifts'
                        sf.add_tag('Constraint_subtype', 'backbone chemical shifts')

                _na_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'nucleotide' in entity_type:
                            _na_angles += 1
                        else:
                            _other_angles += 1

                if _na_angles > _other_angles:
                    sf_item['constraint_type'] = 'nucleic acid dihedral angle'

                    tagNames = [t[0] for t in sf.tags]

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'unknown'
                        sf.add_tag('Constraint_type', 'unknown')

                _br_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'saccharide' in entity_type:
                            _br_angles += 1
                        else:
                            _other_angles += 1

                if _br_angles > _other_angles:
                    sf_item['constraint_type'] = 'carbohydrate dihedral angle'  # DAOTHER-9471

                    tagNames = [t[0] for t in sf.tags]

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'unknown'
                        sf.add_tag('Constraint_type', 'unknown')

            elif content_subtype == 'rdc_restraint':

                sf_item['constraint_type'] = 'dipolar coupling'  # DAOTHER-9471
                sf_item['constraint_subtype'] = 'RDC'

                item_names = self.item_names_in_rdc_loop[file_type]
                id_col = lp.tags.index('ID')
                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in emptyValue:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'rdc', dst_func)
                        else:
                            if getPotentialType(file_type, 'rdc', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

            else:

                sf_item['id'] = len(lp)

            # merge other loops of the source saveframe

            if is_sf:

                for loop in _sf.loops:

                    if loop.category == lp_category:
                        continue

                    if loop.category in self.linked_lp_categories[file_type][content_subtype]:
                        sf.add_loop(loop)

        self.__mr_sf_dict_holder[content_subtype].append(sf_item)

        return True

    def __updateGenDistConstIdInMrStr(self, sf_item):
        """ Update _Gen_dist_constraint.ID in NMR-STAR restraint file.
        """

        loop = sf_item['loop']

        lp = pynmrstar.Loop.from_scratch(loop.category)

        for tag in loop.tags:
            lp.add_tag(loop.category + '.' + tag)

        id_col = loop.tags.index('ID')
        if 'Index_ID' not in loop.tags:
            tag = loop.category + '.Index_ID'
            loop.add_tag(tag)
            lp.add_tag(tag)
            for idx, row in enumerate(loop, start=1):
                row.append(str(idx))
            for idx, row in enumerate(lp, start=1):
                row.append(str(idx))
        index_id_col = loop.tags.index('Index_ID')
        if 'Member_ID' not in loop.tags:
            tag = loop.category + '.Member_ID'
            loop.add_tag(tag)
            lp.add_tag(tag)
            for row in loop:
                row.append('.')
            for row in lp:
                row.append('.')
        member_id_col = loop.tags.index('Member_ID')
        if 'Member_logic_code' not in loop.tags:
            tag = loop.category + '.Member_logic_code'
            loop.add_tag(tag)
            lp.add_tag(tag)
            for row in loop:
                row.append('.')
            for row in lp:
                row.append('.')
        member_logic_code_col = loop.tags.index('Member_logic_code')

        combination_id_col = loop.tags.index('Combination_ID') if 'Combination_ID' in loop.tags else -1

        chain_id_1_col = loop.tags.index('Auth_asym_ID_1')
        seq_id_1_col = loop.tags.index('Auth_seq_ID_1')
        comp_id_1_col = loop.tags.index('Auth_comp_ID_1')
        atom_id_1_col = loop.tags.index('Auth_atom_ID_1')

        ref_chain_id_1_col = loop.tags.index('Entity_assembly_ID_1')

        chain_id_2_col = loop.tags.index('Auth_asym_ID_2')
        seq_id_2_col = loop.tags.index('Auth_seq_ID_2')
        comp_id_2_col = loop.tags.index('Auth_comp_ID_2')
        atom_id_2_col = loop.tags.index('Auth_atom_ID_2')

        ref_chain_id_2_col = loop.tags.index('Entity_assembly_ID_2')

        target_val_col = loop.tags.index('Target_val') if 'Target_val' in loop.tags else -1
        target_val_err_col = loop.tags.index('Target_val_uncertainty') if 'Target_val_uncertainty' in loop.tags else -1
        lower_linear_limit_col = loop.tags.index('Lower_linear_limit') if 'Lower_linear_limit' in loop.tags else -1
        upper_linear_limit_col = loop.tags.index('Upper_linear_limit') if 'Upper_linear_limit' in loop.tags else -1
        lower_limit_col = loop.tags.index('Distance_lower_bound_val') if 'Distance_lower_bound_val' in loop.tags else -1
        upper_limit_col = loop.tags.index('Distance_upper_bound_val') if 'Distance_upper_bound_val' in loop.tags else -1
        weight_col = loop.tags.index('Weight') if 'Weight' in loop.tags else -1

        def concat_target_val(row):
            return (str(row[target_val_col]) if target_val_col != -1 else '')\
                + (str(row[target_val_err_col]) if target_val_err_col != -1 else '')\
                + (str(row[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                + (str(row[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                + (str(row[lower_limit_col]) if lower_limit_col != -1 else '')\
                + (str(row[upper_limit_col]) if upper_limit_col != -1 else '')\
                + (str(row[weight_col]) if weight_col != -1 else '')

        _rest_id = None
        _member_logic_code = None
        _atom1 = _atom2 = {}
        _values = ''

        modified = False
        has_member_id = False

        sf_item['id'] = 0

        for row in loop:
            _row = row

            sf_item['id'] += 1

            try:

                rest_id = row[id_col]
                member_id = row[member_id_col]
                member_logic_code = row[member_logic_code_col]
                values = concat_target_val(row)

                try:
                    atom1 = {'chain_id': row[chain_id_1_col],
                             'seq_id': int(row[seq_id_1_col]),
                             'comp_id': row[comp_id_1_col],
                             'atom_id': row[atom_id_1_col],
                             'ref_chain_id': row[ref_chain_id_1_col]}
                except (ValueError, TypeError):
                    atom1 = {}

                try:
                    atom2 = {'chain_id': row[chain_id_2_col],
                             'seq_id': int(row[seq_id_2_col]),
                             'comp_id': row[comp_id_2_col],
                             'atom_id': row[atom_id_2_col],
                             'ref_chain_id': row[ref_chain_id_2_col]}
                except (ValueError, TypeError):
                    atom2 = {}

                if member_id not in emptyValue:
                    has_member_id = True

                if _rest_id is None:
                    pass

                elif rest_id != _rest_id and len(atom1) > 0 and len(atom2) > 0:

                    if member_id in emptyValue or member_logic_code == 'OR':

                        if (values == _values and not isAmbigAtomSelection([atom1, _atom1], self.__csStat)
                            and not isAmbigAtomSelection([atom2, _atom2], self.__csStat))\
                           or (values == _values and atom1['ref_chain_id'] != atom2['ref_chain_id']
                               and ((not isAmbigAtomSelection([atom1, _atom1], self.__csStat)
                                     and atom1['ref_chain_id'] != _atom2['ref_chain_id'] and atom2['comp_id'] == _atom2['comp_id'])
                                    or (not isAmbigAtomSelection([atom2, _atom2], self.__csStat)
                                        and atom2['ref_chain_id'] != _atom1['ref_chain_id'] and atom1['comp_id'] == _atom1['comp_id']))):
                            _row[member_logic_code_col] = 'OR'

                            if _member_logic_code in emptyValue:
                                lp.data[-1][member_logic_code_col] = 'OR'

                            sf_item['id'] -= 1

                            modified = True

                elif member_logic_code != 'AND':

                    if not isAmbigAtomSelection([atom1, _atom1], self.__csStat)\
                       and not isAmbigAtomSelection([atom2, _atom2], self.__csStat):

                        if member_logic_code in emptyValue:
                            modified = True

                        _row[member_logic_code_col] = 'OR'

                        if _member_logic_code in emptyValue:
                            lp.data[-1][member_logic_code_col] = 'OR'

                            modified = True

                    sf_item['id'] -= 1

                _rest_id, _member_logic_code, _atom1, _atom2, _values = rest_id, member_logic_code, atom1, atom2, values

            except ValueError:
                _atom1 = _atom2 = {}

            if not self.__native_combined:  # DAOTHER-8855
                _row[id_col] = sf_item['id']
            if combination_id_col == -1 or (combination_id_col != -1 and _row[combination_id_col] in emptyValue):
                _row[member_id_col] = None
            lp.add_data(_row)

        if not modified and not has_member_id:
            return True

        member_id_dict = {}

        def update_member_id_dict(rows):
            if len(rows) < 2:
                return

            atom_sel1 = []
            atom_sel2 = []

            for row in rows:

                try:
                    atom1 = {'chain_id': row[chain_id_1_col],
                             'seq_id': int(row[seq_id_1_col]),
                             'comp_id': row[comp_id_1_col],
                             'atom_id': row[atom_id_1_col]}
                except (ValueError, TypeError):
                    atom1 = {}

                try:
                    atom2 = {'chain_id': row[chain_id_2_col],
                             'seq_id': int(row[seq_id_2_col]),
                             'comp_id': row[comp_id_2_col],
                             'atom_id': row[atom_id_2_col]}
                except (ValueError, TypeError):
                    atom2 = {}

                atom_sel1.append(atom1)
                atom_sel2.append(atom2)

            if isAmbigAtomSelection(atom_sel1, self.__csStat)\
               or isAmbigAtomSelection(atom_sel2, self.__csStat):
                for member_id, row in enumerate(rows, start=1):
                    index_id = row[index_id_col]
                    member_id_dict[index_id] = member_id

        _row = None
        _rest_id = None
        _union_rows = []

        for row in lp:
            rest_id = row[id_col]

            if _rest_id is not None and rest_id == _rest_id:
                if len(_union_rows) == 0:
                    _union_rows.append(_row)

                _union_rows.append(row)

            else:

                if len(_union_rows) > 0:
                    update_member_id_dict(_union_rows)

                _union_rows = []

            _row = row
            _rest_id = rest_id

        if len(_union_rows) > 0:
            update_member_id_dict(_union_rows)

        if len(member_id_dict) > 0:
            for row in lp:
                index_id = row[index_id_col]
                member_logic_code = row[member_logic_code_col]
                if member_logic_code == 'AND':
                    continue

                if index_id in member_id_dict:
                    row[member_id_col] = member_id_dict[index_id]

        def concat_all_val(row):
            return str(row[chain_id_1_col]) + str(row[seq_id_1_col]) + str(row[comp_id_1_col]) + str(row[atom_id_1_col])\
                + str(row[chain_id_2_col]) + str(row[seq_id_2_col]) + str(row[comp_id_2_col]) + str(row[atom_id_2_col])\
                + concat_target_val(row)

        len_data = len(lp.data)

        for idx, row in enumerate(lp, start=1):
            if row[member_logic_code_col] != 'OR':
                continue
            if idx - 2 > 0:
                _row = lp.data[idx - 2]
                if concat_all_val(row) == concat_all_val(_row):
                    row[member_logic_code_col] = '.'
            if idx < len_data:
                _row = lp.data[idx]
                if concat_all_val(row) == concat_all_val(_row):
                    row[member_logic_code_col] = '.'

        try:

            del sf_item['saveframe'][loop]

            sf_item['saveframe'].add_loop(lp)
            sf_item['loop'] = lp

            return True

        except ValueError:
            return False

    def __updateTorsionAngleConstIdInMrStr(self, sf_item):  # pylint: disable=no-self-use
        """ Update _Torsion_angle_constraint.ID in NMR-STAR restraint file.
        """

        loop = sf_item['loop']

        lp = pynmrstar.Loop.from_scratch(loop.category)

        for tag in loop.tags:
            lp.add_tag(loop.category + '.' + tag)

        id_col = loop.tags.index('ID')
        if 'Index_ID' not in loop.tags:
            tag = loop.category + '.Index_ID'
            loop.add_tag(tag)
            lp.add_tag(tag)
            for idx, row in enumerate(loop, start=1):
                row.append(str(idx))
            for idx, row in enumerate(lp, start=1):
                row.append(str(idx))
        if 'Combination_ID' not in loop.tags:
            tag = loop.category + '.Combination_ID'
            loop.add_tag(tag)
            lp.add_tag(tag)
            for row in loop:
                row.append('.')
            for row in lp:
                row.append('.')
        index_id_col = loop.tags.index('Index_ID')
        combination_id_col = loop.tags.index('Combination_ID')

        chain_id_1_col = loop.tags.index('Auth_asym_ID_1')
        seq_id_1_col = loop.tags.index('Auth_seq_ID_1')
        atom_id_1_col = loop.tags.index('Auth_atom_ID_1')

        chain_id_2_col = loop.tags.index('Auth_asym_ID_2')
        seq_id_2_col = loop.tags.index('Auth_seq_ID_2')
        atom_id_2_col = loop.tags.index('Auth_atom_ID_2')

        chain_id_3_col = loop.tags.index('Auth_asym_ID_3')
        seq_id_3_col = loop.tags.index('Auth_seq_ID_3')
        atom_id_3_col = loop.tags.index('Auth_atom_ID_3')

        chain_id_4_col = loop.tags.index('Auth_asym_ID_4')
        seq_id_4_col = loop.tags.index('Auth_seq_ID_4')
        atom_id_4_col = loop.tags.index('Auth_atom_ID_4')

        target_val_col = loop.tags.index('Angle_target_val') if 'Angle_target_val' in loop.tags else -1
        target_val_err_col = loop.tags.index('Angle_target_val_err') if 'Angle_target_val_err' in loop.tags else -1
        lower_linear_limit_col = loop.tags.index('Angle_lower_linear_limit') if 'Angle_lower_linear_limit' in loop.tags else -1
        upper_linear_limit_col = loop.tags.index('Angle_upper_linear_limit') if 'Angle_upper_linear_limit' in loop.tags else -1
        lower_limit_col = loop.tags.index('Angle_lower_bound_val') if 'Angle_lower_bound_val' in loop.tags else -1
        upper_limit_col = loop.tags.index('Angle_upper_bound_val') if 'Angle_upper_bound_val' in loop.tags else -1
        weight_col = loop.tags.index('Weight') if 'Weight' in loop.tags else -1

        modified = False

        sf_item['id'] = 0
        sf_item['index_id'] = 0

        len_loop = len(loop)

        proc_row = [False] * len_loop

        for idx, row in enumerate(loop):

            if proc_row[idx]:
                continue

            _row = row

            sf_item['id'] += 1
            sf_item['index_id'] += 1

            combination_id = row[combination_id_col]

            if combination_id not in emptyValue and str(combination_id) != '1':
                sf_item['id'] -= 1

            _row[id_col] = sf_item['id']
            _row[index_id_col] = sf_item['index_id']

            try:
                key = _row[chain_id_1_col] + str(_row[seq_id_1_col]) + _row[atom_id_1_col]\
                    + _row[chain_id_2_col] + str(_row[seq_id_2_col]) + _row[atom_id_2_col]\
                    + _row[chain_id_3_col] + str(_row[seq_id_3_col]) + _row[atom_id_3_col]\
                    + _row[chain_id_4_col] + str(_row[seq_id_4_col]) + _row[atom_id_4_col]
                values = (str(_row[target_val_col]) if target_val_col != -1 else '')\
                    + (str(_row[target_val_err_col]) if target_val_err_col != -1 else '')\
                    + (str(_row[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                    + (str(_row[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                    + (str(_row[lower_limit_col]) if lower_limit_col != -1 else '')\
                    + (str(_row[upper_limit_col]) if upper_limit_col != -1 else '')\
                    + (str(_row[weight_col]) if weight_col != -1 else '')
            except TypeError:
                return False

            if combination_id in emptyValue and idx + 1 < len_loop:
                combination_id = 1

                for idx2 in range(idx + 1, len_loop):

                    if proc_row[idx2]:
                        continue

                    _row_ = loop.data[idx2]

                    try:
                        _key = _row_[chain_id_1_col] + str(_row_[seq_id_1_col]) + _row_[atom_id_1_col]\
                            + _row_[chain_id_2_col] + str(_row_[seq_id_2_col]) + _row_[atom_id_2_col]\
                            + _row_[chain_id_3_col] + str(_row_[seq_id_3_col]) + _row_[atom_id_3_col]\
                            + _row_[chain_id_4_col] + str(_row_[seq_id_4_col]) + _row_[atom_id_4_col]
                        _values = (str(_row_[target_val_col]) if target_val_col != -1 else '')\
                            + (str(_row_[target_val_err_col]) if target_val_err_col != -1 else '')\
                            + (str(_row_[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                            + (str(_row_[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                            + (str(_row_[lower_limit_col]) if lower_limit_col != -1 else '')\
                            + (str(_row_[upper_limit_col]) if upper_limit_col != -1 else '')\
                            + (str(_row_[weight_col]) if weight_col != -1 else '')
                    except TypeError:
                        return False

                    if key == _key:
                        modified = True

                        if values == _values:
                            proc_row[idx2] = True
                            continue

                        if combination_id == 1:
                            _row[combination_id_col] = combination_id
                            lp.add_data(_row)

                        sf_item['index_id'] += 1
                        combination_id += 1

                        _row_[id_col] = sf_item['id']
                        _row_[index_id_col] = sf_item['index_id']
                        _row_[combination_id_col] = combination_id

                        lp.add_data(_row_)

                        proc_row[idx2] = True

                if combination_id == 1:
                    lp.add_data(_row)

            else:
                lp.add_data(_row)

        if not modified:
            return True

        try:

            del sf_item['saveframe'][loop]

            sf_item['saveframe'].add_loop(lp)
            sf_item['loop'] = lp

            return True

        except ValueError:
            return False

    def __mergeStrPk(self):
        """ Merge spectral peak lists in NMR-STAR restraint files.
        """

        if self.__combined_mode:
            return True

        mr_file_path_list = 'restraint_file_path_list'

        if mr_file_path_list not in self.__inputParamDict:
            return True

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        if self.__pk_sf_holder is None:
            self.__pk_sf_holder = []

        list_id = len(self.__pk_sf_holder) + 1

        master_entry = self.__star_data[0]

        for fileListId in range(self.__cs_file_path_list_len, self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type != 'nmr-star':
                continue

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in self.pk_content_subtypes:

                if content_subtype not in input_source_dic['content_subtype']:
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]

                if self.__bmrb_only and self.__internal_mode and self.__nmr_cif_sf_category_list is not None:
                    if sf_category in self.__nmr_cif_sf_category_list:
                        continue

                if self.__star_data_type[fileListId] == 'Loop':
                    pass

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]

                    self.__c2S.set_entry_id(sf, self.__entry_id)
                    self.__c2S.set_local_sf_id(sf, list_id)

                    master_entry.add_saveframe(sf)

                    self.__pk_sf_holder.append({'file_type': 'nmr-star', 'saveframe': sf})

                    list_id += 1

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        self.__c2S.set_entry_id(sf, self.__entry_id)
                        self.__c2S.set_local_sf_id(sf, list_id)

                        master_entry.add_saveframe(sf)

                        self.__pk_sf_holder.append({'file_type': 'nmr-star', 'saveframe': sf})

                        list_id += 1

        return True

    def __mergeAnyPkAsIs(self):
        """ Merge spectral peak list file(s) in any plain text format (file type: nm-pea-any) into a single NMR-STAR file as is.
        """

        if self.__combined_mode:
            return True

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        if self.__pk_sf_holder is None:
            self.__pk_sf_holder = []

        fileListId = self.__file_path_list_len

        list_id = len(self.__pk_sf_holder) + 1

        master_entry = self.__star_data[0]

        for ar in self.__inputParamDict[ar_file_path_list]:

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            fileListId += 1

            if file_type != 'nm-pea-any':
                continue

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])

            file_path = ar['file_name']

            try:

                with open(file_path, 'r', encoding='utf-8') as ifh:
                    ifh.read()

            except UnicodeDecodeError:  # catch exception due to binary format (DAOTHER-9425)
                continue

            content_subtype = 'spectral_peak'

            sf_category = self.sf_categories['nmr-star'][content_subtype]
            sf_framecode = f'spectral_peak_list_{list_id}'

            if self.__bmrb_only and self.__internal_mode and self.__nmr_cif_sf_category_list is not None:
                if sf_category in self.__nmr_cif_sf_category_list:
                    continue

            try:

                sf = master_entry.get_saveframe_by_name(sf_framecode)
                text_data = get_first_sf_tag(sf, 'Text_data')

                if any(loop for loop in sf.loops if loop.category in ('_Peak_raw_format', '_Peak'))\
                        or text_data not in emptyValue:

                    list_id += 1

                    continue

                file_format = None

                with open(file_path, 'r', encoding='utf-8') as ifh:
                    has_header = False
                    for idx, line in enumerate(ifh):
                        if line.isspace() or comment_pattern.match(line):
                            if line.startswith('#INAME'):
                                has_header = True
                            continue
                        file_format = get_peak_list_format(line, has_header)
                        if file_format is not None or idx >= self.mr_max_spacer_lines:
                            break

                dimensions = None

                if file_format is not None:
                    with open(file_path, 'r', encoding='utf-8') as ifh:
                        has_header = False
                        for line in ifh:
                            if file_format == 'NMRView' and not has_header:
                                if line.startswith('label'):
                                    has_header = True
                                continue
                            dimensions = get_number_of_dimensions_of_peak_list(file_format, line)
                            if dimensions is not None and 0 < dimensions <= MAX_DIM_NUM_OF_SPECTRA:
                                break

                set_sf_tag(sf, 'Number_of_spectral_dimensions', dimensions)

                _sf_id = _sf_framecode = None
                _sf_category = self.sf_categories['nmr-star']['chem_shift']
                if len(master_entry.get_saveframes_by_category(_sf_category)) == 1:
                    _sf = master_entry.get_saveframes_by_category(_sf_category)[0]
                    _sf_id = get_first_sf_tag(_sf, 'ID')
                    _sf_framecode = f"${get_first_sf_tag(_sf, 'sf_framecode')}"

                set_sf_tag(sf, 'Assigned_chem_shift_list_ID', _sf_id)
                set_sf_tag(sf, 'Assigned_chem_shift_list_label', _sf_framecode)

                set_sf_tag(sf, 'Text_data_format', file_format if file_format is not None else 'unknown')

                with open(file_path, 'r', encoding='ascii', errors='ignore') as ifh:
                    set_sf_tag(sf, 'Text_data', ifh.read())

                list_id += 1

            except KeyError:

                sf = pynmrstar.Saveframe.from_scratch(sf_framecode, self.sf_tag_prefixes['nmr-star'][content_subtype])
                sf.add_tag('Sf_category', sf_category)
                sf.add_tag('Sf_framecode', sf_framecode)
                sf.add_tag('Entry_ID', self.__entry_id)
                sf.add_tag('ID', list_id)
                sf.add_tag('Data_file_name', original_file_name if original_file_name is not None else file_name)

                _sf_id = _sf_framecode = None
                _sf_category = 'sample'
                if len(master_entry.get_saveframes_by_category(_sf_category)) == 1:
                    _sf = master_entry.get_saveframes_by_category(_sf_category)[0]
                    _sf_id = get_first_sf_tag(_sf, 'ID')
                    _sf_framecode = f"${get_first_sf_tag(_sf, 'sf_framecode')}"

                sf.add_tag('Sample_ID', _sf_id)
                sf.add_tag('Sample_label', _sf_framecode)

                _sf_id = _sf_framecode = None
                _sf_category = 'sample_conditions'
                if len(master_entry.get_saveframes_by_category(_sf_category)) == 1:
                    _sf = master_entry.get_saveframes_by_category(_sf_category)[0]
                    _sf_id = get_first_sf_tag(_sf, 'ID')
                    _sf_framecode = f"${get_first_sf_tag(_sf, 'sf_framecode')}"

                sf.add_tag('Sample_condition_list_ID', _sf_id)
                sf.add_tag('Sample_condition_list_label', _sf_framecode)

                sf.add_tag('Experiment_ID', None)
                sf.add_tag('Experiment_name', None)
                sf.add_tag('Experiment_class', None)
                sf.add_tag('Experiment_type', None)

                file_format = None

                with open(file_path, 'r', encoding='utf-8') as ifh:
                    has_header = False
                    for idx, line in enumerate(ifh):
                        if line.isspace() or comment_pattern.match(line):
                            if line.startswith('#INAME'):
                                has_header = True
                            continue
                        file_format = get_peak_list_format(line, has_header)
                        if file_format is not None or idx >= self.mr_max_spacer_lines:
                            break

                dimensions = None

                if file_format is not None:
                    with open(file_path, 'r', encoding='utf-8') as ifh:
                        has_header = False
                        for line in ifh:
                            if file_format == 'NMRView' and not has_header:
                                if line.startswith('label'):
                                    has_header = True
                                continue
                            dimensions = get_number_of_dimensions_of_peak_list(file_format, line)
                            if dimensions is not None and 0 < dimensions <= MAX_DIM_NUM_OF_SPECTRA:
                                break

                sf.add_tag('Number_of_spectral_dimensions', dimensions)

                sf.add_tag('Chemical_shift_list', None)

                _sf_id = _sf_framecode = None
                _sf_category = self.sf_categories['nmr-star']['chem_shift']
                if len(master_entry.get_saveframes_by_category(_sf_category)) == 1:
                    _sf = master_entry.get_saveframes_by_category(_sf_category)[0]
                    _sf_id = get_first_sf_tag(_sf, 'ID')
                    _sf_framecode = f"${get_first_sf_tag(_sf, 'sf_framecode')}"

                sf.add_tag('Assigned_chem_shift_list_ID', _sf_id)
                sf.add_tag('Assigned_chem_shift_list_label', _sf_framecode)

                sf.add_tag('Details', None)
                sf.add_tag('Text_data_format', file_format if file_format is not None else 'unknown')

                with open(file_path, 'r', encoding='ascii', errors='ignore') as ifh:
                    sf.add_tag('Text_data', ifh.read())

                master_entry.add_saveframe(sf)

                self.__pk_sf_holder.append({'file_type': file_type, 'saveframe': sf})

                list_id += 1

        return True

    def __validateLegacyMr(self):
        """ Validate data content of legacy NMR restraint files.
        """

        if self.__combined_mode:
            return True

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        amberAtomNumberDict = None
        gromacsAtomNumberDict = None
        _amberAtomNumberDict = {}

        has_nm_aux_gro_file = False

        cyanaUplDistRest = 0
        cyanaLolDistRest = 0

        fileListId = self.__file_path_list_len

        def deal_aux_warn_message(listener):

            if listener.warningMessage is not None:

                for warn in listener.warningMessage:

                    if warn.startswith('[Concatenated sequence]'):
                        self.report.warning.appendDescription('concatenated_sequence',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Sequence mismatch]'):
                        self.report.error.appendDescription('sequence_mismatch',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")

                    elif warn.startswith('[Unknown atom name]'):
                        self.report.warning.appendDescription('inconsistent_mr_data',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Unknown residue name]'):
                        self.report.warning.appendDescription('inconsistent_mr_data',
                                                              {'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()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ KeyError  - {warn}\n")

        for ar in self.__inputParamDict[ar_file_path_list]:

            file_path = ar['file_name']

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            fileListId += 1

            if file_type == 'nm-aux-gro':
                has_nm_aux_gro_file = True

            if file_type == 'nm-aux-amb' and content_subtype is not None and 'topology' in content_subtype:

                if 'is_valid' in ar and ar['is_valid']:

                    file_name = input_source_dic['file_name']

                    original_file_name = None
                    if 'original_file_name' in input_source_dic:
                        if input_source_dic['original_file_name'] is not None:
                            original_file_name = os.path.basename(input_source_dic['original_file_name'])
                        if file_name != original_file_name and original_file_name is not None:
                            file_name = f"{original_file_name} ({file_name})"

                    self.__cur_original_ar_file_name = original_file_name

                    reader = AmberPTReader(self.__verbose, self.__lfh,
                                           self.__representative_model_id,
                                           self.__representative_alt_id,
                                           self.__mr_atom_name_mapping,
                                           self.__cR, self.__caC,
                                           self.__ccU, self.__csStat, self.__nefT)

                    listener, _, _ = reader.parse(file_path, self.__cifPath)

                    if listener is not None:

                        deal_aux_warn_message(listener)

                        amberAtomNumberDict = listener.getAtomNumberDict()

                        poly_seq = listener.getPolymerSequence()
                        if poly_seq is not None:
                            input_source.setItemValue('polymer_sequence', poly_seq)

                        seq_align = listener.getSequenceAlignment()
                        if seq_align is not None:
                            self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_topology', seq_align)

            elif file_type == 'nm-aux-gro' and content_subtype is not None and 'topology' in content_subtype:

                if 'is_valid' in ar and ar['is_valid']:

                    file_name = input_source_dic['file_name']

                    original_file_name = None
                    if 'original_file_name' in input_source_dic:
                        if input_source_dic['original_file_name'] is not None:
                            original_file_name = os.path.basename(input_source_dic['original_file_name'])
                        if file_name != original_file_name and original_file_name is not None:
                            file_name = f"{original_file_name} ({file_name})"

                    self.__cur_original_ar_file_name = original_file_name

                    reader = GromacsPTReader(self.__verbose, self.__lfh,
                                             self.__representative_model_id,
                                             self.__representative_alt_id,
                                             self.__mr_atom_name_mapping,
                                             self.__cR, self.__caC,
                                             self.__ccU, self.__csStat, self.__nefT)

                    listener, _, _ = reader.parse(file_path, self.__cifPath)

                    if listener is not None:

                        deal_aux_warn_message(listener)

                        gromacsAtomNumberDict = listener.getAtomNumberDict()

                        poly_seq = listener.getPolymerSequence()
                        if poly_seq is not None:
                            input_source.setItemValue('polymer_sequence', poly_seq)

                        seq_align = listener.getSequenceAlignment()
                        if seq_align is not None:
                            self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_topology', seq_align)

                elif file_type == 'nm-res-cya' and content_subtype is not None and 'dist_restraint' in content_subtype:
                    if ar['dist_type'] in ('upl', 'both'):
                        cyanaUplDistRest += 1
                    if ar['dist_type'] in ('lol', 'both'):
                        cyanaLolDistRest += 1

        fileListId = self.__file_path_list_len

        ar_file_order = []
        ar_file_any_dist = []  # 6gbm, NOE restraint files must take precedence over other distance constraints such as hydrogen bonds
        ar_file_wo_dist = []

        derived_from_public_mr = False

        hint_for_noe_dist = ['noe', 'roe', 'dist']
        hint_for_any_dist = ['bond', 'disul', 'not', 'seen', 'pre', 'paramag', 'cidnp', 'csp', 'perturb', 'mutat', 'protect', 'symm']

        for ar in self.__inputParamDict[ar_file_path_list]:

            file_path = ar['file_name']
            file_size = os.path.getsize(file_path)

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if fileListId == self.__file_path_list_len and file_type == 'nm-res-mr':
                derived_from_public_mr = True

            fileListId += 1

            if file_type in ('nm-aux-amb', 'nm-aux-gro', 'nm-res-oth', 'nm-res-mr', 'nm-res-sax', 'nm-pea-any'):
                continue

            if self.__remediation_mode and os.path.exists(file_path + '-ignored'):
                continue

            if content_subtype is None or len(content_subtype) == 0:
                continue

            if 'is_valid' not in ar or not ar['is_valid']:
                continue

            file_name = input_source_dic['file_name']

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])
                if file_name != original_file_name and original_file_name is not None:
                    file_name = original_file_name

            file_name = file_name.lower()

            if 'dist_restraint' in content_subtype.keys():
                if any(k in file_name for k in hint_for_noe_dist) and not any(k in file_name for k in hint_for_any_dist):
                    ar_file_order.append((input_source, ar, file_size))
                else:
                    ar_file_any_dist.append((input_source, ar, file_size))
            else:
                ar_file_wo_dist.append((input_source, ar, file_size))

        ar_file_order = sorted(ar_file_order, key=itemgetter(2), reverse=True)
        ar_file_order.extend(sorted(ar_file_any_dist, key=itemgetter(2), reverse=True))
        ar_file_order.extend(sorted(ar_file_wo_dist, key=itemgetter(2), reverse=True))

        poly_seq_set = []

        create_sf_dict = self.__remediation_mode

        if self.__list_id_counter is None:
            self.__list_id_counter = {}
        if self.__mr_sf_dict_holder is None:
            self.__mr_sf_dict_holder = {}

        if self.__nmr_ext_poly_seq is None:
            self.__nmr_ext_poly_seq = []

        if not self.__bmrb_only or not self.__internal_mode:  # nmrPolySeq is None in __retrieveCoordAssemblyChecker()

            input_source = self.report.input_sources[0]
            input_source_dic = input_source.get()

            has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')

            nmr_poly_seq = input_source_dic['polymer_sequence']
            cif_poly_seq = self.__caC['polymer_sequence']

            seq_align, _ = alignPolymerSequence(self.__pA, cif_poly_seq, nmr_poly_seq)
            chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, 'nmr-star', cif_poly_seq, nmr_poly_seq, seq_align)

            if chain_assign is not None:

                for ca in chain_assign:
                    ref_chain_id = ca['ref_chain_id']
                    test_chain_id = ca['test_chain_id']

                    sa = next(sa for sa in seq_align
                              if sa['ref_chain_id'] == ref_chain_id
                              and sa['test_chain_id'] == test_chain_id)

                    if sa['conflict'] > 0 or sa['unmapped'] == 0:
                        continue

                    s1 = next(s for s in nmr_poly_seq if s['chain_id'] == test_chain_id)
                    s2 = next(s for s in cif_poly_seq if s['auth_chain_id'] == ref_chain_id)

                    self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + test_chain_id)
                    self.__pA.addTestSequence(s2['comp_id'], test_chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(test_chain_id)

                    length = len(myAlign)

                    _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    if conflict == 0 and unmapped > 0:

                        nmr_seq_ids = []
                        cif_auth_seq_ids = []

                        for i in range(length):
                            if str(myAlign[i][0]) != '.' and i < len(s1['seq_id']):
                                nmr_seq_ids.append(s1['seq_id'][i])
                            else:
                                nmr_seq_ids.append(None)

                        for i in range(length):
                            if str(myAlign[i][1]) != '.' and i < len(s2['seq_id']):
                                cif_auth_seq_ids.append(s2['auth_seq_id'][i])
                            else:
                                cif_auth_seq_ids.append(None)

                        for i in range(length):
                            myPr = myAlign[i]
                            if myPr[0] == myPr[1]:
                                continue

                            nmr_comp_id = str(myPr[0])
                            cif_comp_id = str(myPr[1])

                            if cif_comp_id == '.' and nmr_comp_id != '.':
                                nmr_seq_id = nmr_seq_ids[i] - offset_1 if nmr_seq_ids[i] is not None else None
                                if nmr_seq_id is not None:
                                    offset = None
                                    for _offset in range(1, 20):
                                        if i + _offset < length:
                                            _myPr = myAlign[i + _offset]
                                            if _myPr[0] == _myPr[1]:
                                                offset = _offset
                                                break
                                        if i - _offset >= 0:
                                            _myPr = myAlign[i - _offset]
                                            if _myPr[0] == _myPr[1]:
                                                offset = -_offset
                                                break

                                    if offset is not None and cif_auth_seq_ids[i + offset] is not None:
                                        cif_auth_seq_id = cif_auth_seq_ids[i + offset] - offset - offset_2
                                        self.__nmr_ext_poly_seq.append({'auth_chain_id': s2['auth_chain_id'],
                                                                        'auth_seq_id': cif_auth_seq_id,
                                                                        'auth_comp_id': nmr_comp_id})

        reasons_dict = {}

        def deal_res_warn_message(listener):

            if listener.warningMessage is not None:

                for warn in listener.warningMessage:

                    if warn.startswith('[Concatenated sequence]'):
                        self.report.warning.appendDescription('concatenated_sequence',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Sequence mismatch]'):
                        self.report.error.appendDescription('sequence_mismatch',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")

                    elif warn.startswith('[Atom not found]'):
                        if not self.__remediation_mode or 'Macromolecules page' not in warn:
                            self.report.error.appendDescription('atom_not_found',
                                                                {'file_name': file_name, 'description': warn})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")
                        else:
                            self.report.warning.appendDescription('sequence_mismatch',
                                                                  {'file_name': file_name, 'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Hydrogen not instantiated]'):
                        if self.__remediation_mode:
                            self.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                  {'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('hydrogen_not_instantiated',
                                                                {'file_name': file_name, 'description': warn})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")

                    elif warn.startswith('[Coordinate issue]'):
                        self.report.error.appendDescription('coordinate_issue',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")

                    elif warn.startswith('[Invalid atom nomenclature]'):
                        self.report.error.appendDescription('invalid_atom_nomenclature',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {warn}\n")

                    elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                        self.report.error.appendDescription('invalid_data',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ ValueError  - {warn}\n")

                    elif warn.startswith('[Sequence mismatch warning]'):
                        self.report.warning.appendDescription('sequence_mismatch',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                        if seq_mismatch_warning_pattern.match(warn):
                            g = seq_mismatch_warning_pattern.search(warn).groups()
                            d = {'auth_chain_id': g[2],
                                 'auth_seq_id': int(g[0]),
                                 'auth_comp_id': g[1]}
                            if d not in self.__nmr_ext_poly_seq:
                                self.__nmr_ext_poly_seq.append(d)

                    elif warn.startswith('[Missing data]'):
                        self.report.error.appendDescription('missing_data',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ ValueError  - {warn}\n")

                    elif warn.startswith('[Enum mismatch]'):
                        self.report.warning.appendDescription('enum_mismatch',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Enum mismatch ignorable]'):
                        self.report.warning.appendDescription('enum_mismatch_ignorable',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Unmatched atom type]'):
                        self.report.warning.appendDescription('inconsistent_mr_data',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Range value error]') and not self.__remediation_mode:
                        self.report.error.appendDescription('anomalous_data',
                                                            {'file_name': file_name, 'description': warn})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ ValueError  - {warn}\n")

                    elif warn.startswith('[Range value warning]') or (warn.startswith('[Range value error]') and self.__remediation_mode):
                        self.report.warning.appendDescription('inconsistent_mr_data',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Insufficient atom selection]') or warn.startswith('[Insufficient angle selection]'):
                        self.report.warning.appendDescription('insufficient_mr_data',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Redundant data]'):
                        self.report.warning.appendDescription('redundant_mr_data',
                                                              {'file_name': file_name, 'description': warn})
                        self.report.setWarning()

                        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})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Warning  - {warn}\n")

                    elif warn.startswith('[Anomalous data]'):
                        self.report.warning.appendDescription('anomalous_data',
                                                              {'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})
                        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()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ KeyError  - {warn}\n")

        for input_source, ar, _ in ar_file_order:

            file_path = ar['file_name']

            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type in ('nm-aux-amb', 'nm-aux-gro', 'nm-res-oth', 'nm-res-mr', 'nm-res-sax', 'nm-pea-any'):
                continue

            if self.__remediation_mode and os.path.exists(file_path + '-ignored'):
                continue

            file_name = input_source_dic['file_name']

            original_file_name = None
            if 'original_file_name' in input_source_dic:
                if input_source_dic['original_file_name'] is not None:
                    original_file_name = os.path.basename(input_source_dic['original_file_name'])
                if file_name != original_file_name and original_file_name is not None:
                    file_name = f"{original_file_name} ({file_name})"

            if file_type == 'nm-res-amb' and amberAtomNumberDict is None and 'has_comments' in ar and not ar['has_comments']:

                err = f"To verify AMBER restraint file {file_name!r}, AMBER topology file must be uploaded "\
                    "or Sander comments should be included in the AMBER restraint file."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                continue

            if file_type == 'nm-res-gro' and not has_nm_aux_gro_file:

                err = f"GROMACS topology file must be uploaded to verify GROMACS restraint file {file_name!r}."

                self.report.error.appendDescription('missing_mandatory_content',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                continue

            if content_subtype is None or len(content_subtype) == 0:
                continue

            if 'is_valid' not in ar or not ar['is_valid']:
                continue

            self.__cur_original_ar_file_name = original_file_name

            _reasons = reasons_dict.get(file_type)
            if _reasons is not None\
               and 'label_seq_scheme' not in _reasons\
               and 'global_auth_sequence_offset' not in _reasons\
               and 'chain_id_remap' not in _reasons\
               and file_type != 'nm-res-amb'\
               and 'dist_restraint' not in content_subtype:
                _reasons = None

            reasons = _reasons

            if file_type == 'nm-res-xpl':
                reader = XplorMRReader(self.__verbose, self.__lfh,
                                       self.__representative_model_id,
                                       self.__representative_alt_id,
                                       self.__mr_atom_name_mapping,
                                       self.__cR, self.__caC,
                                       self.__ccU, self.__csStat, self.__nefT,
                                       reasons)
                reader.setRemediateMode(self.__remediation_mode and derived_from_public_mr)

                _list_id_counter = copy.copy(self.__list_id_counter)
                __list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None and _reasons is not None:

                        reader = XplorMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               None)
                        reader.setRemediateMode(self.__remediation_mode and derived_from_public_mr)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = XplorMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               reasons)
                        reader.setRemediateMode(self.__remediation_mode and derived_from_public_mr)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=__list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    # support content subtype change during MR validation with the coordinates
                    input_source.setItemValue('content_subtype', listener.getContentSubtype())

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (XPLOR-NIH) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-cns':
                reader = CnsMRReader(self.__verbose, self.__lfh,
                                     self.__representative_model_id,
                                     self.__representative_alt_id,
                                     self.__mr_atom_name_mapping,
                                     self.__cR, self.__caC,
                                     self.__ccU, self.__csStat, self.__nefT,
                                     reasons)

                _list_id_counter = copy.copy(self.__list_id_counter)
                __list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None and _reasons is not None:

                        reader = CnsMRReader(self.__verbose, self.__lfh,
                                             self.__representative_model_id,
                                             self.__representative_alt_id,
                                             self.__mr_atom_name_mapping,
                                             self.__cR, self.__caC,
                                             self.__ccU, self.__csStat, self.__nefT,
                                             None)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = CnsMRReader(self.__verbose, self.__lfh,
                                             self.__representative_model_id,
                                             self.__representative_alt_id,
                                             self.__mr_atom_name_mapping,
                                             self.__cR, self.__caC,
                                             self.__ccU, self.__csStat, self.__nefT,
                                             reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=__list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (CNS) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-amb':
                reader = AmberMRReader(self.__verbose, self.__lfh,
                                       self.__representative_model_id,
                                       self.__representative_alt_id,
                                       self.__mr_atom_name_mapping,
                                       self.__cR, self.__caC,
                                       self.__ccU, self.__csStat, self.__nefT,
                                       amberAtomNumberDict, _amberAtomNumberDict,
                                       reasons)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = reader.getReasons()

                    if reasons is not None and _reasons is not None and listener.warningMessage is not None and len(listener.warningMessage) > 0:

                        reader = AmberMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               amberAtomNumberDict, _amberAtomNumberDict,
                                               None)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = reader.getReasons()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                    deal_res_warn_message(listener)

                    cur_dict = listener.getAtomNumberDict()
                    if cur_dict is not None:
                        if len(_amberAtomNumberDict) == 0:
                            _amberAtomNumberDict = cur_dict
                        else:
                            for k, v in cur_dict.items():
                                if k not in _amberAtomNumberDict:
                                    _amberAtomNumberDict[k] = v

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (AMBER) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-cya':
                has_dist_restraint = 'dist_restraint' in content_subtype

                upl_or_lol = None
                if has_dist_restraint:
                    dist_type = ar['dist_type']
                    if cyanaLolDistRest == 0 and dist_type == 'upl':
                        upl_or_lol = 'upl_only'
                    elif cyanaUplDistRest == 0 and dist_type == 'lol':
                        upl_or_lol = 'lol_only'
                    elif dist_type == 'upl':
                        upl_or_lol = 'upl_w_lol'
                    elif dist_type == 'lol':
                        upl_or_lol = 'lol_w_upl'
                    else:
                        upl_or_lol = None

                cya_file_ext = self.__retrieveOriginalFileExtensionOfCyanaMrFile()

                reader = CyanaMRReader(self.__verbose, self.__lfh,
                                       self.__representative_model_id,
                                       self.__representative_alt_id,
                                       self.__mr_atom_name_mapping,
                                       self.__cR, self.__caC,
                                       self.__ccU, self.__csStat, self.__nefT,
                                       reasons, upl_or_lol, cya_file_ext)
                reader.setRemediateMode(self.__remediation_mode)

                _list_id_counter = copy.copy(self.__list_id_counter)
                __list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None and _reasons is not None:

                        reader = CyanaMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               None, upl_or_lol, cya_file_ext)
                        reader.setRemediateMode(self.__remediation_mode)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = CyanaMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               reasons, upl_or_lol, cya_file_ext)
                        reader.setRemediateMode(self.__remediation_mode)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=__list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    # support content subtype change during MR validation with the coordinates
                    input_source.setItemValue('content_subtype', listener.getContentSubtype())

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (CYANA) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-ros':
                reader = RosettaMRReader(self.__verbose, self.__lfh,
                                         self.__representative_model_id,
                                         self.__representative_alt_id,
                                         self.__mr_atom_name_mapping,
                                         self.__cR, self.__caC,
                                         self.__ccU, self.__csStat, self.__nefT,
                                         reasons)
                reader.setRemediateMode(self.__remediation_mode)

                _list_id_counter = copy.copy(self.__list_id_counter)
                __list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None and _reasons is not None:

                        reader = RosettaMRReader(self.__verbose, self.__lfh,
                                                 self.__representative_model_id,
                                                 self.__representative_alt_id,
                                                 self.__mr_atom_name_mapping,
                                                 self.__cR, self.__caC,
                                                 self.__ccU, self.__csStat, self.__nefT,
                                                 None)
                        reader.setRemediateMode(self.__remediation_mode)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = RosettaMRReader(self.__verbose, self.__lfh,
                                                 self.__representative_model_id,
                                                 self.__representative_alt_id,
                                                 self.__mr_atom_name_mapping,
                                                 self.__cR, self.__caC,
                                                 self.__ccU, self.__csStat, self.__nefT,
                                                 reasons)
                        reader.setRemediateMode(self.__remediation_mode)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=__list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (ROSETTA) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-bio':
                reader = BiosymMRReader(self.__verbose, self.__lfh,
                                        self.__representative_model_id,
                                        self.__representative_alt_id,
                                        self.__mr_atom_name_mapping,
                                        self.__cR, self.__caC,
                                        self.__ccU, self.__csStat, self.__nefT)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = BiosymMRReader(self.__verbose, self.__lfh,
                                                self.__representative_model_id,
                                                self.__representative_alt_id,
                                                self.__mr_atom_name_mapping,
                                                self.__cR, self.__caC,
                                                self.__ccU, self.__csStat, self.__nefT,
                                                reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (BIOSYM) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-gro':
                reader = GromacsMRReader(self.__verbose, self.__lfh,
                                         self.__representative_model_id,
                                         self.__representative_alt_id,
                                         self.__mr_atom_name_mapping,
                                         self.__cR, self.__caC,
                                         self.__ccU, self.__csStat, self.__nefT,
                                         gromacsAtomNumberDict)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (GROMACS) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-dyn':
                reader = DynamoMRReader(self.__verbose, self.__lfh,
                                        self.__representative_model_id,
                                        self.__representative_alt_id,
                                        self.__mr_atom_name_mapping,
                                        self.__cR, self.__caC,
                                        self.__ccU, self.__csStat, self.__nefT)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = DynamoMRReader(self.__verbose, self.__lfh,
                                                self.__representative_model_id,
                                                self.__representative_alt_id,
                                                self.__mr_atom_name_mapping,
                                                self.__cR, self.__caC,
                                                self.__ccU, self.__csStat, self.__nefT,
                                                reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (DYNAMO/PALES/TALOS) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-syb':
                reader = SybylMRReader(self.__verbose, self.__lfh,
                                       self.__representative_model_id,
                                       self.__representative_alt_id,
                                       self.__mr_atom_name_mapping,
                                       self.__cR, self.__caC,
                                       self.__ccU, self.__csStat, self.__nefT)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = SybylMRReader(self.__verbose, self.__lfh,
                                               self.__representative_model_id,
                                               self.__representative_alt_id,
                                               self.__mr_atom_name_mapping,
                                               self.__cR, self.__caC,
                                               self.__ccU, self.__csStat, self.__nefT,
                                               reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (SYBYL) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-isd':
                reader = IsdMRReader(self.__verbose, self.__lfh,
                                     self.__representative_model_id,
                                     self.__representative_alt_id,
                                     self.__mr_atom_name_mapping,
                                     self.__cR, self.__caC,
                                     self.__ccU, self.__csStat, self.__nefT)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = IsdMRReader(self.__verbose, self.__lfh,
                                             self.__representative_model_id,
                                             self.__representative_alt_id,
                                             self.__mr_atom_name_mapping,
                                             self.__cR, self.__caC,
                                             self.__ccU, self.__csStat, self.__nefT,
                                             reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (ISD) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-cha':
                reader = CharmmMRReader(self.__verbose, self.__lfh,
                                        self.__representative_model_id,
                                        self.__representative_alt_id,
                                        self.__mr_atom_name_mapping,
                                        self.__cR, self.__caC,
                                        self.__ccU, self.__csStat, self.__nefT,
                                        reasons)

                _list_id_counter = copy.copy(self.__list_id_counter)
                __list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None and _reasons is not None:

                        reader = CharmmMRReader(self.__verbose, self.__lfh,
                                                self.__representative_model_id,
                                                self.__representative_alt_id,
                                                self.__mr_atom_name_mapping,
                                                self.__cR, self.__caC,
                                                self.__ccU, self.__csStat, self.__nefT,
                                                None)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                        if listener is not None:
                            reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'dist_restraint' in content_subtype.keys():
                            reasons_dict[file_type] = reasons

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = CharmmMRReader(self.__verbose, self.__lfh,
                                                self.__representative_model_id,
                                                self.__representative_alt_id,
                                                self.__mr_atom_name_mapping,
                                                self.__cR, self.__caC,
                                                self.__ccU, self.__csStat, self.__nefT,
                                                reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=__list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (CHARMM) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

            elif file_type == 'nm-res-ari':
                reader = AriaMRReader(self.__verbose, self.__lfh,
                                      self.__representative_model_id,
                                      self.__representative_alt_id,
                                      self.__mr_atom_name_mapping,
                                      self.__cR, self.__caC,
                                      self.__ccU, self.__csStat, self.__nefT)

                _list_id_counter = copy.copy(self.__list_id_counter)

                listener, _, _ = reader.parse(file_path, self.__cifPath,
                                              createSfDict=create_sf_dict, originalFileName=original_file_name,
                                              listIdCounter=self.__list_id_counter, entryId=self.__entry_id)

                if listener is not None:
                    reasons = listener.getReasonsForReparsing()

                    if reasons is not None:

                        if 'model_chain_id_ext' in reasons:
                            self.__auth_asym_ids_with_chem_exch.update(reasons['model_chain_id_ext'])
                        if 'chain_id_clone' in reasons:
                            self.__auth_seq_ids_with_chem_exch.update(reasons['chain_id_clone'])

                        reader = AriaMRReader(self.__verbose, self.__lfh,
                                              self.__representative_model_id,
                                              self.__representative_alt_id,
                                              self.__mr_atom_name_mapping,
                                              self.__cR, self.__caC,
                                              self.__ccU, self.__csStat, self.__nefT,
                                              reasons)

                        listener, _, _ = reader.parse(file_path, self.__cifPath,
                                                      createSfDict=create_sf_dict, originalFileName=original_file_name,
                                                      listIdCounter=_list_id_counter, entryId=self.__entry_id)

                    deal_res_warn_message(listener)

                    poly_seq = listener.getPolymerSequence()
                    if poly_seq is not None:
                        input_source.setItemValue('polymer_sequence', poly_seq)
                        poly_seq_set.append(poly_seq)

                    seq_align = listener.getSequenceAlignment()
                    if seq_align is not None:
                        self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

                    if create_sf_dict:
                        if len(listener.getContentSubtype()) == 0:
                            err = f"Failed to validate NMR restraint file (ARIA) {file_name!r}."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__validateLegacyMr() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__validateLegacyMr() ++ Error  - {err}\n")

                        self.__list_id_counter, sf_dict = listener.getSfDict()
                        if sf_dict is not None:
                            for k, v in sf_dict.items():
                                content_subtype = contentSubtypeOf(k[0])
                                if content_subtype not in self.__mr_sf_dict_holder:
                                    self.__mr_sf_dict_holder[content_subtype] = []
                                for sf in v:
                                    if sf not in self.__mr_sf_dict_holder[content_subtype]:
                                        self.__mr_sf_dict_holder[content_subtype].append(sf)

        if len(poly_seq_set) > 1:

            poly_seq_rst = None
            for idx, poly_seq in enumerate(poly_seq_set):
                if idx == 0:
                    poly_seq_rst = poly_seq
                    continue
                for ps in poly_seq:
                    chain_id = ps['chain_id']
                    for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):
                        updatePolySeqRst(poly_seq_rst, chain_id, seq_id, comp_id)

            poly_seq_model = self.__caC['polymer_sequence']

            sortPolySeqRst(poly_seq_rst)

            file_type = 'nm-res-mr'

            seq_align, _ = alignPolymerSequence(self.__pA, poly_seq_model, poly_seq_rst, conservative=False)
            chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, poly_seq_model, poly_seq_rst, seq_align)

            if chain_assign is not None:

                if len(poly_seq_model) == len(poly_seq_rst):

                    chain_mapping = {}

                    for ca in chain_assign:
                        ref_chain_id = ca['ref_chain_id']
                        test_chain_id = ca['test_chain_id']

                        if ref_chain_id != test_chain_id:
                            chain_mapping[test_chain_id] = ref_chain_id

                    if len(chain_mapping) == len(poly_seq_model):

                        for ps in poly_seq_rst:
                            if ps['chain_id'] in chain_mapping:
                                ps['chain_id'] = chain_mapping[ps['chain_id']]

                        seq_align, _ = alignPolymerSequence(self.__pA, poly_seq_model, poly_seq_rst, conservative=False)
                        chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, poly_seq_model, poly_seq_rst, seq_align)

                    trimSequenceAlignment(seq_align, chain_assign)

            input_source.setItemValue('polymer_sequence', poly_seq_rst)

            self.report.sequence_alignment.setItemValue('model_poly_seq_vs_mr_restraint', seq_align)

        if len(self.__nmr_ext_poly_seq) > 0:
            entity_assembly = self.__caC['entity_assembly'] if self.__caC is not None else []
            auth_chain_ids = list(set(d['auth_chain_id'] for d in self.__nmr_ext_poly_seq))
            for auth_chain_id in auth_chain_ids:
                item = next(item for item in entity_assembly if auth_chain_id in item['auth_asym_id'].split(','))
                if item['entity_type'] == 'polymer':
                    poly_type = item['entity_poly_type']
                    if poly_type.startswith('polypeptide'):
                        unknown_residue = 'UNK'
                    elif any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('DA', 'DC', 'DG', 'DT'))\
                            and any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('A', 'C', 'G', 'U')):
                        unknown_residue = 'DN'
                    elif poly_type == 'polydeoxyribonucleotide':
                        unknown_residue = 'DN'
                    elif poly_type == 'polyribonucleotide':
                        unknown_residue = 'N'
                    else:
                        continue
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['auth_chain_id'] == auth_chain_id)
                    auth_seq_ids = [d['auth_seq_id'] for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == auth_chain_id]
                    auth_seq_ids.extend(list(filter(None, ps['auth_seq_id'])))
                    min_auth_seq_id = min(auth_seq_ids)
                    max_auth_seq_id = max(auth_seq_ids)
                    for auth_seq_id in range(min_auth_seq_id, max_auth_seq_id + 1):
                        if auth_seq_id not in ps['auth_seq_id']\
                           and not any(d for d in self.__nmr_ext_poly_seq
                                       if d['auth_chain_id'] == auth_chain_id and d['auth_seq_id'] == auth_seq_id):
                            self.__nmr_ext_poly_seq.append({'auth_chain_id': auth_chain_id,
                                                            'auth_seq_id': auth_seq_id,
                                                            'auth_comp_id': unknown_residue})

            self.__nmr_ext_poly_seq = sorted(self.__nmr_ext_poly_seq, key=itemgetter('auth_chain_id', 'auth_seq_id'))

        return not self.report.isError()

    def __validateSaxsMr(self):
        """ Validate SAXS restraint files.
        """

        if self.__combined_mode:
            return True

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list not in self.__inputParamDict:
            return True

        content_subtype = 'saxs_restraint'

        if self.__list_id_counter is None:
            self.__list_id_counter = {}
        if self.__mr_sf_dict_holder is None:
            self.__mr_sf_dict_holder = {}

        if content_subtype not in self.__mr_sf_dict_holder:
            self.__mr_sf_dict_holder[content_subtype] = []

        fileListId = self.__file_path_list_len

        for ar in self.__inputParamDict[ar_file_path_list]:

            file_path = ar['file_name']

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']

            fileListId += 1

            if file_type != 'nm-res-sax':
                continue

            file_name = input_source_dic['file_name']

            original_file_name = os.path.basename(file_name).replace('-corrected', '').replace('-selected-as-res-sax', '')
            if '-div_' in original_file_name:
                original_file_name = None
                # if 'original_file_name' in input_source_dic:
                #     if input_source_dic['original_file_name'] is not None:
                #         original_file_name = os.path.basename(input_source_dic['original_file_name'])

            sf_item = {}

            title = None

            _q_value = 0.0
            _row = None

            lp_count = 0

            with open(file_path, 'r') as ifh:
                for line in ifh:

                    line = ' '.join(line.split())

                    _line = line.split()

                    len_line = len(_line)

                    if len_line == 0:
                        continue

                    if line.startswith('#') or line.startswith('!'):

                        if len(line) == 1:
                            continue

                        __line = line[1:].split(' ')

                        if len(__line) == 0 or line[1].startswith('#') or line[1].startswith('!'):
                            continue

                        title_can = __line[0]
                        if len(title_can) == 0 and len(__line) > 1:
                            title_can = __line[1]

                        try:
                            float(title_can)
                            continue
                        except ValueError:
                            if len(title_can) > 1\
                               and '(' not in title_can and ')' not in title_can\
                               and '[' not in title_can and ']' not in title_can:
                                if len(title_can) > 0:
                                    title = title_can
                            _row = None

                        continue

                    if len_line != 3:
                        continue

                    try:

                        q_value = float(_line[0])
                        float(_line[1])
                        float(_line[2])

                        dstFunc = {'weight': '1.0',
                                   'target_value': _line[1].replace('E', 'e'),
                                   'target_value_uncertainty': _line[2].replace('E', 'e')}

                        if _q_value == 0.0:

                            if len(sf_item) > 0 and sf_item['id'] > 0:
                                self.__mr_sf_dict_holder[content_subtype].append(sf_item)
                                lp_count += 1

                            self.__list_id_counter = incListIdCounter(content_subtype, self.__list_id_counter, reduced=False)

                            list_id = self.__list_id_counter[content_subtype]

                            restraint_name = getRestraintName(content_subtype)

                            sf_framecode = restraint_name.replace(' ', '_').lower() + f'_{list_id}'

                            if original_file_name is not None:
                                title = original_file_name

                            sf = getSaveframe(content_subtype, sf_framecode, list_id, self.__entry_id, title, reduced=False)

                            _restraint_name = restraint_name.split()

                            sf_item = {'file_type': file_type, 'saveframe': sf, 'list_id': list_id,
                                       'id': 0, 'index_id': 0,
                                       'constraint_type': ' '.join(_restraint_name[:-1])}

                            lp = getLoop(content_subtype, reduced=False)

                            sf.add_loop(lp)
                            sf_item['loop'] = lp

                            if _row is not None:

                                sf_item['loop'].add_data(_row)

                                sf_item['id'] = sf_item['index_id'] = 1

                                _row = None

                        if q_value > _q_value:

                            sf_item['id'] += 1
                            sf_item['index_id'] += 1

                            row = getRow('saxs', sf_item['id'], sf_item['index_id'], None, None, _line[0].replace('E', 'e'),
                                         sf_item['list_id'], self.__entry_id, dstFunc, None, None, None, None, None)
                            sf_item['loop'].add_data(row)

                            _q_value = q_value

                        else:

                            _row = getRow('saxs', 1, 1, None, None, _line[0].replace('E', 'e'),
                                          sf_item['list_id'] + 1, self.__entry_id, dstFunc, None, None, None, None, None)

                            _q_value = 0.0

                    except ValueError:
                        continue

            if len(sf_item) > 0 and sf_item['id'] > 0:
                self.__mr_sf_dict_holder[content_subtype].append(sf_item)

                lp_count += 1
                input_source.setItemValue('content_subtype', {'saxs_restraint': lp_count})

        if len(self.__mr_sf_dict_holder[content_subtype]) == 0:
            del self.__mr_sf_dict_holder[content_subtype]

        return True

    def __validateStrPk(self):
        """ Validate spectral peak lists in NMR-STAR restraint files.
        """

        if self.__release_mode:
            return True

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        list_id = 1

        if self.__combined_mode and (not self.__remediation_mode or self.__annotation_mode or self.__native_combined):  # DAOTHER-8751, 8817 (D_1300043061)

            if len(self.__star_data) == 0:
                return True

            master_entry = self.__star_data[0]

            fileListId = 0

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type != 'nmr-star':
                return True

            if input_source_dic['content_subtype'] is None:
                return True

            for content_subtype in self.pk_content_subtypes:

                if content_subtype not in input_source_dic['content_subtype']:
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__validateStrPk__(fileListId, file_type, content_subtype, list_id, sf, sf_framecode, lp_category)

                    list_id += 1

            if list_id > 1 and self.__dstPath is not None:

                if not self.__annotation_mode:

                    if __pynmrstar_v3__:
                        master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
                    else:
                        master_entry.write_to_file(self.__dstPath)

                    if 'nmr_cif_file_path' in self.__outputParamDict:

                        try:

                            myIo = IoAdapterPy(False, sys.stderr)
                            containerList = myIo.readFile(self.__dstPath)

                            if containerList is not None and len(containerList) > 1:

                                if self.__verbose:
                                    self.__lfh.write(f"Input container list is {[(c.getName(), c.getType()) for c in containerList]!r}\n")

                                for c in containerList:
                                    c.setType('data')

                                myIo.writeFile(self.__outputParamDict['nmr_cif_file_path'], containerList=containerList[1:])

                        except Exception as e:
                            self.__lfh.write(f"+NmrDpUtility.__validateStrPk() ++ Error  - {str(e)}\n")

            return True

        mr_file_path_list = 'restraint_file_path_list'

        if mr_file_path_list not in self.__inputParamDict:
            return True

        for fileListId in range(self.__cs_file_path_list_len, self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_type = input_source_dic['file_type']
            content_subtype = input_source_dic['content_subtype']

            if file_type != 'nmr-star':
                continue

            file_name = input_source_dic['file_name']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in self.pk_content_subtypes:

                if content_subtype not in input_source_dic['content_subtype']:
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__star_data_type[fileListId] == 'Loop':

                    err = f"Mandatory loops with categories {self.aux_lp_categories[file_type][content_subtype]} are missing. "\
                        f"Please re-upload the {file_type.upper()} file."

                    self.report.error.appendDescription('missing_data',
                                                        {'file_name': file_name, 'category': lp_category,
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__validateStrPk() ++ Error  - {err}\n")

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__validateStrPk__(fileListId, file_type, content_subtype, list_id, sf, sf_framecode, lp_category)

                    list_id += 1

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        self.__validateStrPk__(fileListId, file_type, content_subtype, list_id, sf, sf_framecode, lp_category)

                        list_id += 1

        return True

    def __validateStrPk__(self, file_list_id, file_type, content_subtype, list_id, sf, sf_framecode, lp_category):
        """ Validate spectral peak lists in NMR-STAR restraint files.
        """

        _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
        num_dim = int(_num_dim)

        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
            return False

        max_dim = num_dim + 1

        lp_category = '_Peak_row_format' if content_subtype == 'spectral_peak' else '_Assigned_peak_chem_shift'

        try:

            if __pynmrstar_v3_2__:
                loop = sf.get_loop(lp_category)
            else:
                loop = sf.get_loop_by_category(lp_category)

        except KeyError:
            return False

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if not has_poly_seq_in_loop:
            return False

        coord_atom_site = self.__caC['coord_atom_site']
        auth_to_star_seq = self.__caC['auth_to_star_seq']
        auth_to_star_seq_ann = self.__caC['auth_to_star_seq_ann']
        auth_atom_name_to_id = self.__caC['auth_atom_name_to_id']
        auth_atom_name_to_id_ext = self.__caC['auth_atom_name_to_id_ext']

        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        seq_align = chain_assign = None
        br_seq_align = br_chain_assign = None
        np_seq_align = np_chain_assign = None

        if content_subtype in polymer_sequence_in_loop:
            ps_in_loop = next((ps for ps in polymer_sequence_in_loop[content_subtype] if ps['sf_framecode'] == sf_framecode), None)

            if ps_in_loop is not None:
                ps = ps_in_loop['polymer_sequence']

                seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['polymer_sequence'], ps, conservative=False)
                chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['polymer_sequence'], ps, seq_align)

                if self.__caC['branched'] is not None:
                    br_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['branched'], ps, conservative=False)
                    br_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['branched'], ps, br_seq_align)

                if self.__caC['non_polymer'] is not None:
                    np_seq_align, _ = alignPolymerSequence(self.__pA, self.__caC['non_polymer'], ps, conservative=False)
                    np_chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, self.__caC['non_polymer'], ps, np_seq_align)

        def get_auth_seq_scheme(chain_id, seq_id):
            auth_asym_id = auth_seq_id = None

            if seq_id is not None:

                if chain_assign is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if (auth_asym_id is None or auth_seq_id is None) and br_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in br_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if (auth_asym_id is None or auth_seq_id is None) and np_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in np_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']
                                   and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

            return auth_asym_id, auth_seq_id

        list_items = ['Details', 'Entry_ID', 'Spectral_peak_list_ID']

        if content_subtype == 'spectral_peak':

            core_items = ['Index_ID', 'ID', 'Volume', 'Volume_uncertainty', 'Height', 'Height_uncertainty']
            aux_items = [item for item in ['Figure_of_merit', 'Restraint'] if item in loop.tags]

            position_item_temps = ['Position_%s', 'Position_uncertainty_%s', 'Line_width_%s', 'Line_width_uncertainty_%s']

            position_items = []

            for dim in range(1, max_dim):
                for idx, position_item_temp in enumerate(position_item_temps):
                    position_item = position_item_temp % dim
                    if idx == 0:
                        position_items.append(position_item)
                    elif position_item in loop.tags:
                        position_items.append(position_item)

            assign_item_temps = ['Entity_assembly_ID_%s', 'Entity_ID_%s', 'Comp_index_ID_%s', 'Seq_ID_%s', 'Comp_ID_%s', 'Atom_ID_%s']
            ambigutity_item_temps = ['Ambiguity_code_%s', 'Ambiguity_set_ID_%s']

            assign_items = []

            for dim in range(1, max_dim):
                for assign_item_temp in assign_item_temps:
                    assign_items.append(assign_item_temp % dim)
                for ambigutity_item_temp in ambigutity_item_temps:
                    ambigutity_item = ambigutity_item_temp % dim
                    if ambigutity_item in loop.tags:
                        assign_items.append(ambigutity_item)

            auth_assign_item_temps = ['Auth_asym_ID_%s', 'Auth_seq_ID_%s', 'Auth_comp_ID_%s', 'Auth_atom_ID_%s']

            auth_assign_items = []

            for dim in range(1, max_dim):
                for auth_assign_item_temp in auth_assign_item_temps:
                    auth_assign_items.append(auth_assign_item_temp % dim)

        else:

            core_items = ['Peak_ID', 'Spectral_dim_ID', 'Set_ID', 'Magnetization_linkage_ID', 'Val']
            aux_items = [item for item in ['Contribution_fractional_val', 'Figure_of_merit', 'Assigned_chem_shift_list_ID', 'Atom_chem_shift_ID']
                         if item in loop.tags]

            assign_items = ['Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Comp_ID', 'Atom_ID']
            ambigutity_items = ['Ambiguity_code', 'Ambiguity_set_ID']
            for ambiguity_item in ambigutity_items:
                if ambiguity_item in loop.tags:
                    assign_items.append(ambiguity_item)

            auth_assign_items = ['Auth_entity_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID']

        items = core_items
        if len(aux_items) > 0:
            items.extend(aux_items)
        if content_subtype == 'spectral_peak':
            items.extend(position_items)
        items.extend(assign_items)
        items.extend(auth_assign_items)
        items.extend(list_items)

        lp = pynmrstar.Loop.from_scratch(lp_category)

        tags = [lp_category + '.' + item for item in items]

        for tag in tags:
            lp.add_tag(tag)

        prefer_auth_atom_name = False

        if (self.__annotation_mode or self.__native_combined) and len(auth_atom_name_to_id) > 0:

            count_auth_name = count_auth_id = 0

            for row in loop:

                if content_subtype == 'spectral_peak':

                    for dim in range(1, max_dim):
                        has_auth_seq = valid_auth_seq = True
                        for auth_assign_item_temp in auth_assign_item_temps:
                            auth_assign_item = auth_assign_item_temp % dim
                            if auth_assign_item not in loop.tags:
                                has_auth_seq = valid_auth_seq = False
                                break
                        if has_auth_seq:
                            try:
                                auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                                auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                                comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                                atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq:
                                        valid_auth_seq = False
                            except (ValueError, TypeError):
                                has_auth_seq = valid_auth_seq = False

                        if valid_auth_seq:

                            if atom_id not in emptyValue:

                                if comp_id in auth_atom_name_to_id:
                                    if atom_id in auth_atom_name_to_id[comp_id]:
                                        count_auth_name += 1
                                    if atom_id in auth_atom_name_to_id[comp_id].values():
                                        count_auth_id += 1

                        else:

                            chain_id = seq_id = comp_id = atom_id = None

                            for col, assign_item_temp in enumerate(assign_item_temps):
                                assign_item = assign_item_temp % dim
                                if col == 0:
                                    chain_id = row[loop.tags.index(assign_item)]
                                elif col == 1:
                                    continue
                                elif col == 2:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                                elif col == 3:
                                    if seq_id is None:
                                        try:
                                            seq_id = int(row[loop.tags.index(assign_item)])
                                        except (ValueError, TypeError):
                                            pass
                                elif col == 4:
                                    comp_id = row[loop.tags.index(assign_item)]
                                    if comp_id not in emptyValue:
                                        comp_id = comp_id.upper()
                                else:
                                    atom_id = row[loop.tags.index(assign_item)]

                            auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                            if auth_asym_id is not None and auth_seq_id is not None:
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                    seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key in auth_to_star_seq:

                                    if atom_id not in emptyValue:

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                else:

                    has_auth_seq = valid_auth_seq = True
                    for auth_assign_item in auth_assign_items:
                        if auth_assign_item not in loop.tags:
                            has_auth_seq = valid_auth_seq = False
                            break
                    if has_auth_seq:
                        try:
                            auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                            auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                            comp_id = row[loop.tags.index(auth_assign_items[2])]
                            atom_id = row[loop.tags.index(auth_assign_items[3])]
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                        except (ValueError, TypeError):
                            has_auth_seq = valid_auth_seq = False

                    if valid_auth_seq:

                        if atom_id not in emptyValue:

                            if comp_id in auth_atom_name_to_id:
                                if atom_id in auth_atom_name_to_id[comp_id]:
                                    count_auth_name += 1
                                if atom_id in auth_atom_name_to_id[comp_id].values():
                                    count_auth_id += 1

                    else:

                        chain_id = seq_id = comp_id = atom_id = None

                        for col, assign_item in enumerate(assign_items):
                            if col == 0:
                                chain_id = row[loop.tags.index(assign_item)]
                            elif col == 1:
                                continue
                            elif col == 2:
                                try:
                                    seq_id = int(row[loop.tags.index(assign_item)])
                                except (ValueError, TypeError):
                                    pass
                            elif col == 3:
                                comp_id = row[loop.tags.index(assign_item)]
                                if comp_id not in emptyValue:
                                    comp_id = comp_id.upper()
                            else:
                                atom_id = row[loop.tags.index(assign_item)]

                        auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                        if auth_asym_id is not None and auth_seq_id is not None:
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key in auth_to_star_seq:

                                if atom_id not in emptyValue:

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

            if count_auth_name + count_auth_id == 0:

                for row in loop:

                    if content_subtype == 'spectral_peak':

                        for dim in range(1, max_dim):
                            has_auth_seq = valid_auth_seq = True
                            for auth_assign_item_temp in auth_assign_item_temps:
                                auth_assign_item = auth_assign_item_temp % dim
                                if auth_assign_item not in loop.tags:
                                    has_auth_seq = valid_auth_seq = False
                                    break
                            if has_auth_seq:
                                try:
                                    auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                                    auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                                    comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                                    atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq_ann:
                                        valid_auth_seq = False
                                    else:
                                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                                            comp_id = coord_atom_site[_seq_key]['comp_id']
                                except (ValueError, TypeError):
                                    has_auth_seq = valid_auth_seq = False

                            if valid_auth_seq:

                                if atom_id not in emptyValue:

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            else:

                                chain_id = seq_id = comp_id = atom_id = None

                                for col, assign_item_temp in enumerate(assign_item_temps):
                                    assign_item = assign_item_temp % dim
                                    if col == 0:
                                        chain_id = row[loop.tags.index(assign_item)]
                                    elif col == 1:
                                        continue
                                    elif col == 2:
                                        try:
                                            seq_id = int(row[loop.tags.index(assign_item)])
                                        except (ValueError, TypeError):
                                            pass
                                    elif col == 3:
                                        if seq_id is None:
                                            try:
                                                seq_id = int(row[loop.tags.index(assign_item)])
                                            except (ValueError, TypeError):
                                                pass
                                    elif col == 4:
                                        comp_id = row[loop.tags.index(assign_item)]
                                        if comp_id not in emptyValue:
                                            comp_id = comp_id.upper()
                                    else:
                                        atom_id = row[loop.tags.index(assign_item)]

                                auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                                if auth_asym_id is not None and auth_seq_id is not None:
                                    seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                    if seq_key in auth_to_star_seq_ann:
                                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                                            comp_id = coord_atom_site[_seq_key]['comp_id']

                                        if atom_id not in emptyValue:

                                            if comp_id in auth_atom_name_to_id:
                                                if atom_id in auth_atom_name_to_id[comp_id]:
                                                    count_auth_name += 1
                                                if atom_id in auth_atom_name_to_id[comp_id].values():
                                                    count_auth_id += 1

                    else:

                        has_auth_seq = valid_auth_seq = True
                        for auth_assign_item in auth_assign_items:
                            if auth_assign_item not in loop.tags:
                                has_auth_seq = valid_auth_seq = False
                                break
                        if has_auth_seq:
                            try:
                                auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                                auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                                comp_id = row[loop.tags.index(auth_assign_items[2])]
                                atom_id = row[loop.tags.index(auth_assign_items[3])]
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                                else:
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:  # DAOTHER-8817
                                        comp_id = coord_atom_site[_seq_key]['comp_id']
                            except (ValueError, TypeError):
                                has_auth_seq = valid_auth_seq = False

                        if valid_auth_seq:

                            if atom_id not in emptyValue:

                                if comp_id in auth_atom_name_to_id:
                                    if atom_id in auth_atom_name_to_id[comp_id]:
                                        count_auth_name += 1
                                    if atom_id in auth_atom_name_to_id[comp_id].values():
                                        count_auth_id += 1

                        else:

                            chain_id = seq_id = comp_id = atom_id = None

                            for col, assign_item in enumerate(assign_items):
                                if col == 0:
                                    chain_id = row[loop.tags.index(assign_item)]
                                elif col == 1:
                                    continue
                                elif col == 2:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                                elif col == 3:
                                    comp_id = row[loop.tags.index(assign_item)]
                                    if comp_id not in emptyValue:
                                        comp_id = comp_id.upper()
                                else:
                                    atom_id = row[loop.tags.index(assign_item)]

                            auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                            if auth_asym_id is not None and auth_seq_id is not None:
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key in auth_to_star_seq_ann:
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:  # DAOTHER-8817
                                        comp_id = coord_atom_site[_seq_key]['comp_id']

                                    if atom_id not in emptyValue:

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

            prefer_auth_atom_name = count_auth_name > count_auth_id

        index = 1

        for idx, row in enumerate(loop):

            _row = [None] * len(tags)

            for col, item in enumerate(loop.tags):
                if item in items:
                    _row[items.index(item)] = row[col]

            if content_subtype == 'spectral_peak':

                _row[0] = index

                for dim in range(1, max_dim):
                    has_auth_seq = valid_auth_seq = True
                    for auth_assign_item_temp in auth_assign_item_temps:
                        auth_assign_item = auth_assign_item_temp % dim
                        if auth_assign_item not in loop.tags:
                            has_auth_seq = valid_auth_seq = False
                            break
                    if has_auth_seq:
                        try:
                            auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                            auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                            comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                            atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key not in auth_to_star_seq:
                                if self.__annotation_mode:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq:
                                        valid_auth_seq = False
                                elif seq_key not in auth_to_star_seq_ann:
                                    valid_auth_seq = False
                        except (ValueError, TypeError):
                            has_auth_seq = valid_auth_seq = False

                    if valid_auth_seq:
                        try:
                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if 'chain_id' in _coord_atom_site:
                                    auth_asym_id = _coord_atom_site['chain_id']
                                comp_id = _coord_atom_site['comp_id']
                        except KeyError:
                            entity_assembly_id = seq_id = entity_id = None
                            if self.__annotation_mode:
                                auth_asym_id_ = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                      if _auth_seq_id == auth_seq_id_ and _auth_comp_id == comp_id), auth_asym_id_)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key in auth_to_star_seq:
                                    row[loop.tags.index(auth_assign_item_temps[0] % dim)] = auth_asym_id
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                else:
                                    auth_asym_id_, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                                   for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                   if _auth_seq_id == auth_seq_id_), (auth_asym_id_, comp_id))
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key in auth_to_star_seq:
                                        row[loop.tags.index(auth_assign_item_temps[0] % dim)] = auth_asym_id
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]

                        if prefer_auth_atom_name:
                            _atom_id = atom_id
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                   and _atom_id in auth_atom_name_to_id[comp_id]:
                                    if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                        atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                   and comp_id == _coord_atom_site['comp_id']:
                                    atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                # DAOTHER-8751, 8817 (D_1300043061)
                                elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                     and _atom_id in _coord_atom_site['alt_atom_id']\
                                     and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                    # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                    cca_row = next((cca_row for cca_row in self.__cca_dat
                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                    if cca_row is not None:
                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                    if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                       and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                        atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                    else:
                                        atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                elif 'split_comp_id' in _coord_atom_site:
                                    for _comp_id in _coord_atom_site['split_comp_id']:
                                        if _comp_id == comp_id:
                                            continue
                                        __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                        if __seq_key not in coord_atom_site:
                                            continue
                                        __coord_atom_site = coord_atom_site[__seq_key]
                                        if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                           and _atom_id in __coord_atom_site['alt_atom_id']:
                                            comp_id = _comp_id
                                            # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                            cca_row = next((cca_row for cca_row in self.__cca_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                            break
                                        if _atom_id in __coord_atom_site['atom_id']:
                                            comp_id = _comp_id
                                            # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                            cca_row = next((cca_row for cca_row in self.__cca_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            break

                        for col, assign_item_temp in enumerate(assign_item_temps):
                            assign_item = assign_item_temp % dim
                            if col == 0:
                                _row[items.index(assign_item)] = entity_assembly_id
                            elif col == 1:
                                _row[items.index(assign_item)] = entity_id
                            elif col in (2, 3):
                                _row[items.index(assign_item)] = seq_id
                            elif col == 4:
                                _row[items.index(assign_item)] = comp_id
                            else:
                                _row[items.index(assign_item)] = atom_id

                    else:

                        chain_id = seq_id = comp_id = atom_id = None

                        for col, assign_item_temp in enumerate(assign_item_temps):
                            assign_item = assign_item_temp % dim
                            if col == 0:
                                chain_id = row[loop.tags.index(assign_item)]
                            elif col == 1:
                                continue
                            elif col == 2:
                                try:
                                    seq_id = int(row[loop.tags.index(assign_item)])
                                except (ValueError, TypeError):
                                    pass
                            elif col == 3:
                                if seq_id is None:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                            elif col == 4:
                                comp_id = row[loop.tags.index(assign_item)]
                                if comp_id not in emptyValue:
                                    comp_id = comp_id.upper()
                            else:
                                atom_id = row[loop.tags.index(assign_item)]

                        auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                        if auth_asym_id is not None and auth_seq_id is not None:
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if self.__annotation_mode and seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key in auth_to_star_seq:
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                if _seq_key in coord_atom_site:  # DAOTHER-8817
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if 'chain_id' in _coord_atom_site:
                                        auth_asym_id = _coord_atom_site['chain_id']
                                    comp_id = _coord_atom_site['comp_id']

                                if prefer_auth_atom_name:
                                    _atom_id = atom_id
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:
                                        _coord_atom_site = coord_atom_site[_seq_key]
                                        if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                           and _atom_id in auth_atom_name_to_id[comp_id]:
                                            if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                                atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                        if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                           and comp_id == _coord_atom_site['comp_id']:
                                            atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                        # DAOTHER-8751, 8817 (D_1300043061)
                                        elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                             and _atom_id in _coord_atom_site['alt_atom_id']\
                                             and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                            # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                            cca_row = next((cca_row for cca_row in self.__cca_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                               and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                                atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                            else:
                                                atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                        elif 'split_comp_id' in _coord_atom_site:
                                            for _comp_id in _coord_atom_site['split_comp_id']:
                                                if _comp_id == comp_id:
                                                    continue
                                                __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                                if __seq_key not in coord_atom_site:
                                                    continue
                                                __coord_atom_site = coord_atom_site[__seq_key]
                                                if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                                   and _atom_id in __coord_atom_site['alt_atom_id']:
                                                    comp_id = _comp_id
                                                    # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                    cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                    if cca_row is not None:
                                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                    atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                                    break
                                                if _atom_id in __coord_atom_site['atom_id']:
                                                    comp_id = _comp_id
                                                    # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                    cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                    if cca_row is not None:
                                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                    break

                                for col, assign_item_temp in enumerate(assign_item_temps):
                                    assign_item = assign_item_temp % dim
                                    if col == 0:
                                        _row[items.index(assign_item)] = entity_assembly_id
                                    elif col == 1:
                                        _row[items.index(assign_item)] = entity_id
                                    elif col in (2, 3):
                                        _row[items.index(assign_item)] = seq_id
                                    elif col == 4:
                                        _row[items.index(assign_item)] = comp_id
                                    else:
                                        _row[items.index(assign_item)] = atom_id

                                for col, auth_assign_item_temp in enumerate(auth_assign_item_temps):
                                    auth_assign_item = auth_assign_item_temp % dim
                                    if col == 0:
                                        _row[items.index(auth_assign_item)] = auth_asym_id
                                    elif col == 1:
                                        _row[items.index(auth_assign_item)] = auth_seq_id
                                    elif col == 2:
                                        _row[items.index(auth_assign_item)] = comp_id
                                    else:
                                        _row[items.index(auth_assign_item)] = atom_id

            else:

                has_auth_seq = valid_auth_seq = True
                for auth_assign_item in auth_assign_items:
                    if auth_assign_item not in loop.tags:
                        has_auth_seq = valid_auth_seq = False
                        break
                if has_auth_seq:
                    try:
                        auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                        auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                        comp_id = row[loop.tags.index(auth_assign_items[2])]
                        atom_id = row[loop.tags.index(auth_assign_items[3])]
                        seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                        if seq_key not in auth_to_star_seq:
                            if self.__annotation_mode:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                            elif seq_key not in auth_to_star_seq_ann:
                                valid_auth_seq = False
                    except (ValueError, TypeError):
                        has_auth_seq = valid_auth_seq = False

                if valid_auth_seq:
                    try:
                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                            _coord_atom_site = coord_atom_site[_seq_key]
                            if 'chain_id' in _coord_atom_site:
                                auth_asym_id = _coord_atom_site['chain_id']
                            comp_id = _coord_atom_site['comp_id']
                    except KeyError:
                        entity_assembly_id = seq_id = entity_id = None
                        if self.__annotation_mode:
                            auth_asym_id_ = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                  if _auth_seq_id == auth_seq_id_ and _auth_comp_id == comp_id), auth_asym_id_)
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key in auth_to_star_seq:
                                row[loop.tags.index(auth_assign_items[0])] = auth_asym_id_
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            else:
                                auth_asym_id_, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                               for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                               if _auth_seq_id == auth_seq_id_), (auth_asym_id_, comp_id))
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key in auth_to_star_seq:
                                    row[loop.tags.index(auth_assign_items[0])] = auth_asym_id_
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]

                    if prefer_auth_atom_name:
                        _atom_id = atom_id
                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                        if _seq_key in coord_atom_site:
                            _coord_atom_site = coord_atom_site[_seq_key]
                            if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                               and _atom_id in auth_atom_name_to_id[comp_id]:
                                if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                    atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                            if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                               and comp_id == _coord_atom_site['comp_id']:
                                atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                            # DAOTHER-8751, 8817 (D_1300043061)
                            elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                 and _atom_id in _coord_atom_site['alt_atom_id']\
                                 and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                cca_row = next((cca_row for cca_row in self.__cca_dat
                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                if cca_row is not None:
                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                   and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                    atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                else:
                                    atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                            elif 'split_comp_id' in _coord_atom_site:
                                for _comp_id in _coord_atom_site['split_comp_id']:
                                    if _comp_id == comp_id:
                                        continue
                                    __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                    if __seq_key not in coord_atom_site:
                                        continue
                                    __coord_atom_site = coord_atom_site[__seq_key]
                                    if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                       and _atom_id in __coord_atom_site['alt_atom_id']:
                                        comp_id = _comp_id
                                        # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                        cca_row = next((cca_row for cca_row in self.__cca_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                        break
                                    if _atom_id in __coord_atom_site['atom_id']:
                                        comp_id = _comp_id
                                        # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                        cca_row = next((cca_row for cca_row in self.__cca_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        break

                    for col, assign_item in enumerate(assign_items):
                        if col == 0:
                            _row[items.index(assign_item)] = entity_assembly_id
                        elif col == 1:
                            _row[items.index(assign_item)] = entity_id
                        elif col == 2:
                            _row[items.index(assign_item)] = seq_id
                        elif col == 3:
                            _row[items.index(assign_item)] = comp_id
                        else:
                            _row[items.index(assign_item)] = atom_id

                else:

                    chain_id = seq_id = comp_id = atom_id = None

                    for col, assign_item in enumerate(assign_items):
                        if col == 0:
                            chain_id = row[loop.tags.index(assign_item)]
                        elif col == 1:
                            continue
                        elif col == 2:
                            try:
                                seq_id = int(row[loop.tags.index(assign_item)])
                            except (ValueError, TypeError):
                                pass
                        elif col == 3:
                            comp_id = row[loop.tags.index(assign_item)]
                            if comp_id not in emptyValue:
                                comp_id = comp_id.upper()
                        else:
                            atom_id = row[loop.tags.index(assign_item)]

                    auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                    if auth_asym_id is not None and auth_seq_id is not None:
                        seq_key = (auth_asym_id, auth_seq_id, comp_id)
                        if self.__annotation_mode and seq_key not in auth_to_star_seq:
                            comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                            if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                        if seq_key in auth_to_star_seq:
                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if 'chain_id' in _coord_atom_site:
                                    auth_asym_id = _coord_atom_site['chain_id']
                                comp_id = _coord_atom_site['comp_id']

                            if prefer_auth_atom_name:
                                _atom_id = atom_id
                                _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                if _seq_key in coord_atom_site:
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                       and _atom_id in auth_atom_name_to_id[comp_id]:
                                        if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                            atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                    if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                       and comp_id == _coord_atom_site['comp_id']:
                                        atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    # DAOTHER-8751, 8817 (D_1300043061)
                                    elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                         and _atom_id in _coord_atom_site['alt_atom_id']\
                                         and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                        # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                        cca_row = next((cca_row for cca_row in self.__cca_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                           and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                            atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                        else:
                                            atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    elif 'split_comp_id' in _coord_atom_site:
                                        for _comp_id in _coord_atom_site['split_comp_id']:
                                            if _comp_id == comp_id:
                                                continue
                                            __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                            if __seq_key not in coord_atom_site:
                                                continue
                                            __coord_atom_site = coord_atom_site[__seq_key]
                                            if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                               and _atom_id in __coord_atom_site['alt_atom_id']:
                                                comp_id = _comp_id
                                                # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                                break
                                            if _atom_id in __coord_atom_site['atom_id']:
                                                comp_id = _comp_id
                                                # 'Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'
                                                cca_row = next((cca_row for cca_row in self.__cca_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0] and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                break

                            for col, assign_item in enumerate(assign_items):
                                if col == 0:
                                    _row[items.index(assign_item)] = entity_assembly_id
                                elif col == 1:
                                    _row[items.index(assign_item)] = entity_id
                                elif col == 2:
                                    _row[items.index(assign_item)] = seq_id
                                elif col == 3:
                                    _row[items.index(assign_item)] = comp_id
                                else:
                                    _row[items.index(assign_item)] = atom_id

                            for col, auth_assign_item in enumerate(auth_assign_items):
                                if col == 0:
                                    _row[items.index(auth_assign_item)] = auth_asym_id
                                elif col == 1:
                                    _row[items.index(auth_assign_item)] = auth_seq_id
                                elif col == 2:
                                    _row[items.index(auth_assign_item)] = comp_id
                                else:
                                    _row[items.index(auth_assign_item)] = atom_id

            _row[-2] = self.__entry_id
            _row[-1] = list_id

            lp.add_data(_row)

            index += 1

        del sf[loop]

        sf.add_loop(lp)

        self.__c2S.set_entry_id(sf, self.__entry_id)
        self.__c2S.set_local_sf_id(sf, list_id)

        return True

    def __calculateStatsOfExptlData(self):
        """ Calculate statistics of experimental data.
        """

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            seq_align_dic = self.report.sequence_alignment.get()

            stats = {}

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('entry_info', 'entity'):
                    continue

                if self.report_prev is not None:
                    prev_stats = self.report_prev.getNmrLegacyStatsOfExptlData(fileListId, content_subtype)
                    if prev_stats is not None:
                        stats[content_subtype] = prev_stats
                        continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if content_subtype == 'poly_seq':
                    lp_category = self.aux_lp_categories[file_type][content_subtype][0]

                asm = []

                list_id = 1

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf, list_id, sf_framecode, lp_category, seq_align_dic, asm)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf, list_id, sf_framecode, lp_category, seq_align_dic, asm)

                else:

                    for list_id, sf in enumerate(self.__star_data[fileListId].get_saveframes_by_category(sf_category), start=1):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf, list_id, sf_framecode, lp_category, seq_align_dic, asm)

                if len(asm) > 0:
                    stats[content_subtype] = asm

            input_source.setItemValue('stats_of_exptl_data', stats)

        return self.report.getTotalErrors() == __errors

    def __calculateStatsOfExptlData__(self, file_list_id, file_name, file_type, content_subtype, sf, list_id, sf_framecode, lp_category, seq_align_dic, asm):
        """ Calculate statistics of experimental data.
        """

        index_tag = self.index_tags[file_type][content_subtype]

        _list_id = list_id
        if file_type == 'nmr-star' and self.__combined_mode:
            val = get_first_sf_tag(sf, 'ID')
            if isinstance(val, int):
                _list_id = val
            elif len(val) > 0:
                try:
                    _list_id = int(val)
                except ValueError:
                    return

        if content_subtype != 'poly_seq':
            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)
        else:
            lp_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                           if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode and lp['category'] == lp_category), None)

        if lp_data is None or len(lp_data) == 0:
            return

        ambig = False

        if file_type == 'nmr-star' and self.__star_data_type[0] == 'Entry':

            _sf_category = 'constraint_statistics'
            _lp_category = '_Constraint_file'

            try:

                tagNames = [t[0] for t in sf.tags]

                if 'Block_ID' in tagNames:
                    block_id = get_first_sf_tag(sf, 'Block_ID')

                    _sf = self.__star_data[0].get_saveframes_by_category(_sf_category)

                    if __pynmrstar_v3_2__:
                        _loop = _sf[0].get_loop(_lp_category)
                    else:
                        _loop = _sf[0].get_loop_by_category(_lp_category)

                    _block_id_col = _loop.tags.index('Block_ID')
                    _constraint_type_col = _loop.tags.index('Constraint_type')
                    _constraint_subtype_col = _loop.tags.index('Constraint_subtype')
                    _constraint_subsubtype_col = _loop.tags.index('Constraint_subsubtype')

                    _row = next((_row for _row in _loop if _row[_block_id_col] == block_id), None)

                    if _row is not None:
                        _constraint_type = _row[_constraint_type_col]
                        _constraint_subtype = _row[_constraint_subtype_col]
                        _constraint_subsubtype = _row[_constraint_subsubtype_col]

                        if (_constraint_type == 'distance' and _constraint_subtype not in ('NOE', 'ROE'))\
                           or ('dihedral angle' in _constraint_type and _constraint_subtype == 'unknown'):
                            ambig = True

                        if _constraint_subsubtype not in emptyValue and _constraint_subsubtype == 'ambi':
                            ambig = True

            except (IndexError, ValueError):
                pass

        sf_tag_data = next((t['data'] for t in self.__sf_tag_data[content_subtype] if t['file_name'] == file_name and t['sf_framecode'] == sf_framecode), None)

        ent = {'list_id': _list_id, 'sf_framecode': sf_framecode, 'number_of_rows': len(lp_data)}

        if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

            if len(sf_framecode) == 0:
                ent['exp_type'] = 'Unknown'
            else:
                ctype = get_first_sf_tag(sf, 'restraint_origin' if file_type == 'nef' else 'Constraint_type')
                if len(ctype) > 0:
                    ent['exp_type'] = ctype
                else:
                    ent['exp_type'] = 'Unknown'

        elif content_subtype.startswith('spectral_peak'):

            if len(sf_framecode) == 0:
                ent['exp_type'] = 'Unknown'
            else:
                ctype = get_first_sf_tag(sf, 'experiment_type' if file_type == 'nef' else 'Experiment_type')
                if len(ctype) > 0:
                    ent['exp_type'] = ctype
                else:
                    ent['exp_type'] = 'Unknown'

        if content_subtype in ('chem_shift', 'dist_restraint', 'dihed_restraint', 'rdc_restraint', 'spectral_peak', 'spectral_peak_alt'):

            sa_name = 'nmr_poly_seq_vs_' + content_subtype

            if has_key_value(seq_align_dic, sa_name):

                low_seq_coverage = ''

                seq_coverage = []

                for seq_align in seq_align_dic[sa_name]:

                    if seq_align['list_id'] == list_id:

                        sc = {}
                        sc['chain_id'] = seq_align['chain_id']
                        sc['length'] = seq_align['length']
                        sc['sequence_coverage'] = seq_align['sequence_coverage']

                        if seq_align['sequence_coverage'] < LOW_SEQ_COVERAGE and seq_align['length'] > 1 and not ambig:
                            if ('exp_type' not in ent)\
                               or (ent['exp_type'] not in ('disulfide bound', 'disulfide_bond', 'paramagnetic relaxation', 'pre', 'symmetry', 'J-couplings', 'jcoupling')):
                                low_seq_coverage += f"coverage {seq_align['sequence_coverage']} for chain_id {seq_align['chain_id']}, length {seq_align['length']}, "

                        seq_coverage.append(sc)

                if len(seq_coverage) > 0:

                    ent['sequence_coverage'] = seq_coverage

                    if len(low_seq_coverage) > 0 and not ambig:

                        warn = 'Sequence coverage of NMR experimental data is relatively low ('\
                            + low_seq_coverage[:-2] + f") in {sf_framecode!r} saveframe."

                        self.report.warning.appendDescription('insufficient_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfExptlData() ++ Warning  - {warn}\n")

                if content_subtype == 'chem_shift':

                    try:

                        item_names = self.item_names_in_cs_loop[file_type]

                        anomalous_errs = self.report.error.getValueListWithSf('anomalous_data', file_name, sf_framecode, key='Z_score')
                        anomalous_warns = self.report.warning.getValueListWithSf('anomalous_data', file_name, sf_framecode, key='Z_score')
                        unusual_warns = self.report.warning.getValueListWithSf('unusual_data', file_name, sf_framecode, key='Z_score')

                        cs_ann = []

                        if anomalous_errs is not None:

                            for a_err in anomalous_errs:
                                ann = {}
                                ann['level'] = 'anomalous'
                                ann['chain_id'] = a_err['row_location'][item_names['chain_id']]
                                ann['seq_id'] = int(a_err['row_location'][item_names['seq_id']])
                                ann['comp_id'] = a_err['row_location'][item_names['comp_id']]
                                ann['atom_id'] = a_err['row_location'][item_names['atom_id']]
                                ann['value'] = a_err['value']
                                ann['z_score'] = a_err['z_score']

                                comp_id = ann['comp_id']
                                atom_id = ann['atom_id'].split(' ')[0]

                                polypeptide_like = self.__csStat.peptideLike(comp_id)

                                if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                        if anomalous_warns is not None:

                            for a_warn in anomalous_warns:
                                ann = {}
                                ann['level'] = 'anomalous'
                                ann['chain_id'] = a_warn['row_location'][item_names['chain_id']]
                                ann['seq_id'] = int(a_warn['row_location'][item_names['seq_id']])
                                ann['comp_id'] = a_warn['row_location'][item_names['comp_id']]
                                ann['atom_id'] = a_warn['row_location'][item_names['atom_id']]
                                ann['value'] = a_warn['value']
                                ann['z_score'] = a_warn['z_score']

                                comp_id = ann['comp_id']
                                atom_id = ann['atom_id'].split(' ')[0]

                                polypeptide_like = self.__csStat.peptideLike(comp_id)

                                if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                        if unusual_warns is not None:

                            for u_warn in unusual_warns:
                                ann = {}
                                ann['level'] = 'unusual'
                                ann['chain_id'] = u_warn['row_location'][item_names['chain_id']]
                                ann['seq_id'] = int(u_warn['row_location'][item_names['seq_id']])
                                ann['comp_id'] = u_warn['row_location'][item_names['comp_id']]
                                ann['atom_id'] = u_warn['row_location'][item_names['atom_id']]
                                ann['value'] = u_warn['value']
                                ann['z_score'] = u_warn['z_score']

                                comp_id = ann['comp_id']
                                atom_id = ann['atom_id'].split(' ')[0]

                                polypeptide_like = self.__csStat.peptideLike(comp_id)

                                if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfExptlData() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfExptlData() ++ Error  - {str(e)}\n")

                    self.__calculateStatsOfAssignedChemShift(file_list_id, sf_framecode, lp_data, cs_ann, ent)

                elif content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint') and len(lp_data) <= MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK:

                    conflict_id_set = self.__nefT.get_conflict_id_set(sf, lp_category, self.consist_key_items[file_type][content_subtype])[0]

                    conflict_warns = self.report.warning.getValueListWithSf('conflicted_data', file_name, sf_framecode)
                    inconsist_warns = self.report.warning.getValueListWithSf('inconsistent_data', file_name, sf_framecode)
                    redundant_warns = self.report.warning.getValueListWithSf('redundant_data', file_name, sf_framecode)

                    inconsistent = set()
                    redundant = set()

                    if conflict_warns is not None:

                        for item in conflict_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    inconsistent.add(int(index))

                    if inconsist_warns is not None:

                        for item in inconsist_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    inconsistent.add(int(index))

                    if redundant_warns is not None:

                        for item in redundant_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    redundant.add(int(index))

                    if content_subtype == 'dist_restraint':
                        self.__calculateStatsOfDistanceRestraint(file_list_id, sf_framecode, lp_data, conflict_id_set, inconsistent, redundant, ent)

                    elif content_subtype == 'dihed_restraint':
                        self.__calculateStatsOfDihedralRestraint(file_list_id, lp_data, conflict_id_set, inconsistent, redundant, ent)

                    elif content_subtype == 'rdc_restraint':
                        self.__calculateStatsOfRdcRestraint(file_list_id, lp_data, conflict_id_set, inconsistent, redundant, ent)

            if content_subtype.startswith('spectral_peak'):

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return

                if content_subtype == 'spectral_peak':
                    self.__calculateStatsOfSpectralPeak(file_list_id, sf_framecode, num_dim, lp_data, ent)
                elif content_subtype == 'spectral_peak_alt':
                    self.__calculateStatsOfSpectralPeakAlt(file_list_id, sf_framecode, num_dim, lp_data, ent)

        elif content_subtype == 'poly_seq':
            self.__calculateStatsOfCovalentBond(file_list_id, sf_framecode, lp_category, lp_data, ent)

        elif content_subtype == 'chem_shift_ref':
            ent['loop'] = lp_data
            ent['saveframe_tag'] = sf_tag_data

        has_err = self.report.error.exists(file_name, sf_framecode)
        has_warn = self.report.warning.exists(file_name, sf_framecode)

        if has_err:
            status = 'Error'
            ent['error_descriptions'] = self.report.error.getCombinedDescriptions(file_name, sf_framecode)
            if has_warn:
                ent['warning_descriptions'] = self.report.warning.getCombinedDescriptions(file_name, sf_framecode)
        elif has_warn:
            status = 'Warning'
            ent['warning_descriptions'] = self.report.warning.getCombinedDescriptions(file_name, sf_framecode)
        else:
            status = 'OK'

        ent['status'] = status

        asm.append(ent)

    def __calculateStatsOfAssignedChemShift(self, file_list_id, sf_framecode, lp_data, cs_ann, ent):
        """ Calculate statistics of assigned chemical shifts.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        value_name = item_names['value']
        atom_type = item_names['atom_type']
        iso_number = item_names['isotope_number']

        try:

            count = {}

            for row in lp_data:

                if row[atom_type] in emptyValue or row[iso_number] in emptyValue or row[value_name] in emptyValue:
                    continue

                data_type = str(row[iso_number]) + row[atom_type].lower() + '_chemical_shifts'

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

            if len(count) > 0:
                ent['number_of_assignments'] = count

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is None:
                return

            if 'sequence_coverage' in ent:

                completeness = []

                for sc in ent['sequence_coverage']:

                    cc = {}

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    cc['chain_id'] = chain_id

                    # all atoms

                    all_c = []

                    excluded_comp_id = []
                    excluded_atom_id = []

                    h1_col = -1
                    c13_col = -1
                    n15_col = -1
                    p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = 'all_' + data_type
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        all_c.append(atom_group)

                        col += 1

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.peptideLike(comp_id)

                            if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):

                                all_atoms = self.__csStat.getAllAtoms(comp_id, excl_minor_atom=True, primary=polypeptide_like)
                                non_excl_atoms = self.__csStat.getAllAtoms(comp_id, excl_minor_atom=False)
                                non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                for a in all_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                        all_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        all_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        all_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        all_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in emptyValue:
                                        continue

                                    atom_id = row[atom_id_name]
                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in atom_set:
                                                continue

                                            atom_set.add(a)

                                            if a in all_atoms:

                                                if data_type == '1H' and h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                                    all_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    all_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    all_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    all_c[p31_col]['number_of_assigned_shifts'] += 1

                                            elif a in non_excl_atoms:
                                                excluded_atom_id.append({'seq_id': seq_id, 'comp_id': comp_id, 'atom_id': a, 'value': row[value_name]})

                                    else:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if atom_id in all_atoms:

                                            if data_type == '1H' and h1_col != -1 and atom_id not in non_rep_methyl_pros and atom_id[0] in protonBeginCode:
                                                all_c[h1_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '13C' and c13_col != -1:
                                                all_c[c13_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '15N' and n15_col != -1:
                                                all_c[n15_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '31P' and p31_col != -1:
                                                all_c[p31_col]['number_of_assigned_shifts'] += 1

                                        elif atom_id in non_excl_atoms:
                                            excluded_atom_id.append({'seq_id': seq_id, 'comp_id': comp_id, 'atom_id': atom_id, 'value': row[value_name]})

                            else:
                                excluded_comp_id.append({'seq_id': seq_id, 'comp_id': comp_id})

                        for c in all_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] = float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    cc['completeness_of_all_assignments'] = all_c

                    cc['excluded_comp_id_in_statistics'] = excluded_comp_id if len(excluded_comp_id) > 0 else None
                    cc['excluded_atom_id_in_statistics'] = excluded_atom_id if len(excluded_atom_id) > 0 else None

                    # backbone atoms (bb)

                    bb_c = []

                    h1_col = -1
                    c13_col = -1
                    n15_col = -1
                    p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = 'backbone_' + data_type
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        bb_c.append(atom_group)

                        col += 1

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.peptideLike(comp_id)

                            if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):

                                bb_atoms = self.__csStat.getBackBoneAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                for a in bb_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                        bb_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        bb_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        bb_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        bb_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in emptyValue:
                                        continue

                                    atom_id = row[atom_id_name]
                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in bb_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                                    bb_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    bb_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    bb_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    bb_c[p31_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in bb_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1 and atom_id not in non_rep_methyl_pros and atom_id[0] in protonBeginCode:
                                            bb_c[h1_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '13C' and c13_col != -1:
                                            bb_c[c13_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '15N' and n15_col != -1:
                                            bb_c[n15_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '31P' and p31_col != -1:
                                            bb_c[p31_col]['number_of_assigned_shifts'] += 1

                        for c in bb_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] = float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    if len(bb_c) > 0:
                        cc['completeness_of_backbone_assignments'] = bb_c

                    # sidechain atoms (sc)

                    sc_c = []

                    h1_col = -1
                    c13_col = -1
                    n15_col = -1
                    p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = 'sidechain_' + data_type
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        sc_c.append(atom_group)

                        col += 1

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.peptideLike(comp_id)

                            if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):

                                sc_atoms = self.__csStat.getSideChainAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                for a in sc_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                        sc_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        sc_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        sc_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        sc_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in emptyValue:
                                        continue

                                    atom_id = row[atom_id_name]
                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in sc_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                                    sc_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    sc_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    sc_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    sc_c[p31_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in sc_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1 and atom_id not in non_rep_methyl_pros and atom_id[0] in protonBeginCode:
                                            sc_c[h1_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '13C' and c13_col != -1:
                                            sc_c[c13_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '15N' and n15_col != -1:
                                            sc_c[n15_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '31P' and p31_col != -1:
                                            sc_c[p31_col]['number_of_assigned_shifts'] += 1

                        for c in sc_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] = float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    if len(sc_c) > 0:
                        cc['completeness_of_sidechain_assignments'] = sc_c

                    # methyl group atoms (ch3)

                    ch3_c = []

                    h1_col = -1
                    c13_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = 'methyl_' + data_type
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        else:
                            continue

                        ch3_c.append(atom_group)

                        col += 1

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.peptideLike(comp_id)

                            if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):

                                ch3_atoms = self.__csStat.getMethylAtoms(comp_id)
                                non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                for a in ch3_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                        ch3_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        ch3_c[c13_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in emptyValue:
                                        continue

                                    atom_id = row[atom_id_name]
                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in ch3_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                                    ch3_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    ch3_c[c13_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in ch3_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1 and atom_id not in non_rep_methyl_pros and atom_id[0] in protonBeginCode:
                                            ch3_c[h1_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '13C' and c13_col != -1:
                                            ch3_c[c13_col]['number_of_assigned_shifts'] += 1

                        for c in ch3_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] = float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    if len(ch3_c) > 0:
                        cc['completeness_of_methyl_assignments'] = ch3_c

                    # aromatic atoms (aro)

                    aro_c = []

                    h1_col = -1
                    c13_col = -1
                    n15_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = 'aromatic_' + data_type
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        aro_c.append(atom_group)

                        col += 1

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.peptideLike(comp_id)

                            if self.__csStat.hasSufficientStat(comp_id, polypeptide_like):

                                aro_atoms = self.__csStat.getAromaticAtoms(comp_id, excl_minor_atom=True, primary=polypeptide_like)
                                non_rep_methyl_pros = self.__csStat.getNonRepMethylProtons(comp_id)

                                for a in aro_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                        aro_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        aro_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        aro_c[n15_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in emptyValue:
                                        continue

                                    atom_id = row[atom_id_name]
                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in aro_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1 and a not in non_rep_methyl_pros and a[0] in protonBeginCode:
                                                    aro_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    aro_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    aro_c[n15_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in aro_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1 and atom_id not in non_rep_methyl_pros and atom_id[0] in protonBeginCode:
                                            aro_c[h1_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '13C' and c13_col != -1:
                                            aro_c[c13_col]['number_of_assigned_shifts'] += 1

                                        elif data_type == '15N' and n15_col != -1:
                                            aro_c[n15_col]['number_of_assigned_shifts'] += 1

                        for c in aro_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] = float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    if len(aro_c) > 0:
                        cc['completeness_of_aromatic_assignments'] = aro_c

                    completeness.append(cc)

                if len(completeness) > 0:
                    ent['completeness'] = completeness

            z_scores = {}

            for k in count:
                z_scores[k] = []

            max_val = 0.0
            min_val = 0.0

            for row in lp_data:

                if row[atom_type] in emptyValue or row[iso_number] in emptyValue or row[value_name] in emptyValue:
                    continue

                data_type = str(row[iso_number]) + row[atom_type].lower() + '_chemical_shifts'

                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]
                comp_id = row[comp_id_name]
                atom_id = row[atom_id_name]
                value = row[value_name]

                _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                if value in emptyValue:
                    continue

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id = self.__getAtomIdList(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id

                    else:  # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id

                has_cs_stat = False

                # non-standard residue
                if comp_id not in monDict3:

                    neighbor_comp_ids = set(_row[comp_id_name] for _row in lp_data if _row[chain_id_name] == _chain_id
                                            and abs(_row[seq_id_name] - seq_id) < 4 and _row[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__csStat.peptideLike(comp_id2)

                    for cs_stat in self.__csStat.get(comp_id):

                        if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0:
                            avg_value = cs_stat['avg']
                            std_value = cs_stat['std']

                            has_cs_stat = True

                            break

                # standard residue
                else:

                    for cs_stat in self.__csStat.get(comp_id, self.report.isDiamagnetic()):

                        if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0:
                            avg_value = cs_stat['avg']
                            std_value = cs_stat['std']

                            has_cs_stat = True

                            break

                if (not has_cs_stat) or std_value is None or std_value <= 0.0 or avg_value is None:
                    continue

                z_score = (value - avg_value) / std_value

                if z_score > max_val:
                    max_val = z_score

                elif z_score < min_val:
                    min_val = z_score

                z_scores[data_type].append(z_score)

            target_scale = (max_val - min_val) / 20.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals = []
            count_of_vals = []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = len([z for z in z_scores[k] if v <= z < v + scale])

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed, 'annotations': cs_ann}

            if 'sequence_coverage' in ent:

                # prediction of redox state of CYS

                cys_redox_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            if comp_id not in ('CYS', 'DCY'):
                                continue

                            cys = {'chain_id': chain_id, 'seq_id': seq_id}

                            ca_chem_shift = None
                            cb_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CA':
                                        ca_chem_shift = row[value_name]
                                    elif atom_id == 'CB':
                                        cb_chem_shift = row[value_name]

                                if ca_chem_shift is None or cb_chem_shift is None:
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            cys['ca_chem_shift'] = ca_chem_shift
                            cys['cb_chem_shift'] = cb_chem_shift

                            if cb_chem_shift is not None:
                                if cb_chem_shift < 32.0:
                                    cys['redox_state_pred'] = 'reduced'
                                elif cb_chem_shift > 35.0:
                                    cys['redox_state_pred'] = 'oxidized'
                                else:
                                    cys['redox_state_pred'] = 'ambiguous'
                            elif ca_chem_shift is not None:
                                cys['redox_state_pred'] = 'ambiguous'
                            else:
                                cys['redox_state_pred'] = 'unknown'

                            if cys['redox_state_pred'] == 'ambiguous':
                                oxi, red = predict_redox_state_of_cystein(ca_chem_shift, cb_chem_shift)
                                if oxi < 0.001:
                                    cys['redox_state_pred'] = 'reduced'
                                elif red < 0.001:
                                    cys['redox_state_pred'] = 'oxidized'
                                else:
                                    cys['redox_state_pred'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                            if self.__hasCoordSeq(chain_id, seq_id):
                                cys['in_disulfide_bond'] = False
                                if has_key_value(input_source_dic, 'disulfide_bond'):
                                    if any(b for b in input_source_dic['disulfide_bond']
                                           if (b['chain_id_1'] == chain_id and b['seq_id_1'] == seq_id)
                                           or (b['chain_id_2'] == chain_id and b['seq_id_2'] == seq_id)):
                                        cys['in_disulfide_bond'] = True

                                cys['in_other_bond'] = False
                                if has_key_value(input_source_dic, 'other_bond'):
                                    if any(b for b in input_source_dic['other_bond']
                                           if (b['chain_id_1'] == chain_id and b['seq_id_1'] == seq_id)
                                           or (b['chain_id_2'] == chain_id and b['seq_id_2'] == seq_id)):
                                        cys['in_other_bond'] = True

                            cys_redox_state.append(cys)

                    if len(cys_redox_state) > 0:
                        ent['cys_redox_state'] = cys_redox_state

                # prediction of cis-trans peptide of PRO

                pro_cis_trans = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            if comp_id != 'PRO':
                                continue

                            pro = {'chain_id': chain_id, 'seq_id': seq_id}

                            cb_chem_shift = None
                            cg_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CB':
                                        cb_chem_shift = row[value_name]
                                    elif atom_id == 'CG':
                                        cg_chem_shift = row[value_name]

                                if cb_chem_shift is None or cg_chem_shift is None:
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            pro['cb_chem_shift'] = cb_chem_shift
                            pro['cg_chem_shift'] = cg_chem_shift

                            if (cb_chem_shift is not None) and (cg_chem_shift is not None):
                                delta = cb_chem_shift - cg_chem_shift
                                if delta < 4.8:
                                    pro['cis_trans_pred'] = 'trans'
                                elif delta > 9.15:
                                    pro['cis_trans_pred'] = 'cis'
                                else:
                                    pro['cis_trans_pred'] = 'ambiguous'
                            elif (cb_chem_shift is not None) or (cg_chem_shift is not None):
                                pro['cis_trans_pred'] = 'ambiguous'
                            else:
                                pro['cis_trans_pred'] = 'unknown'

                            if pro['cis_trans_pred'] == 'ambiguous':
                                cis, trs = predict_cis_trans_peptide_of_proline(cb_chem_shift, cg_chem_shift)
                                if cis < 0.001:
                                    pro['cis_trans_pred'] = 'trans'
                                elif trs < 0.001:
                                    pro['cis_trans_pred'] = 'cis'
                                else:
                                    pro['cis_trans_pred'] = f"cis {cis:.1%}, trans {trs:.1%}"

                            if self.__hasCoordSeq(chain_id, seq_id):
                                in_cis_peptide_bond = self.__isProtCis(chain_id, seq_id)

                                pro['in_cis_peptide_bond'] = in_cis_peptide_bond

                                if pro['cis_trans_pred'] != 'unknown':

                                    if (in_cis_peptide_bond and pro['cis_trans_pred'] != 'cis')\
                                       or (not in_cis_peptide_bond and pro['cis_trans_pred'] != 'trans'):
                                        item = None
                                        if ',' in pro['cis_trans_pred']:
                                            if (in_cis_peptide_bond and cis > trs) or\
                                               (not in_cis_peptide_bond and trs > cis):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                        if item is not None:

                                            shifts = ''
                                            if cb_chem_shift is not None:
                                                shifts += f"CB {cb_chem_shift} ppm, "
                                            if cg_chem_shift is not None:
                                                shifts += f"CG {cg_chem_shift} ppm, "

                                            warn = f"{'cis' if in_cis_peptide_bond else 'trans'}-peptide bond of "\
                                                f"{chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                                f"the assigned chemical shift values ({shifts}cis_trans_pred {pro['cis_trans_pred']})."

                                            self.report.warning.appendDescription(item,
                                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                                   'description': warn})
                                            self.report.setWarning()

                                            if self.__verbose:
                                                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Warning  - {warn}\n")

                            pro_cis_trans.append(pro)

                    if len(pro_cis_trans) > 0:
                        ent['pro_cis_trans'] = pro_cis_trans

                # prediction of tautomeric state of HIS

                his_tautomeric_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            if comp_id != 'HIS':
                                continue

                            his = {'chain_id': chain_id, 'seq_id': seq_id}

                            cg_chem_shift = None
                            cd2_chem_shift = None
                            nd1_chem_shift = None
                            ne2_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CG':
                                        cg_chem_shift = row[value_name]
                                    elif atom_id == 'CD2':
                                        cd2_chem_shift = row[value_name]
                                    elif atom_id == 'ND1':
                                        nd1_chem_shift = row[value_name]
                                    elif atom_id == 'NE2':
                                        ne2_chem_shift = row[value_name]

                                if cg_chem_shift is None or cd2_chem_shift is None or nd1_chem_shift is None or ne2_chem_shift is None:
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            his['cg_chem_shift'] = cg_chem_shift
                            his['cd2_chem_shift'] = cd2_chem_shift
                            his['nd1_chem_shift'] = nd1_chem_shift
                            his['ne2_chem_shift'] = ne2_chem_shift

                            if (cg_chem_shift is not None) or (cd2_chem_shift is not None)\
                               or (nd1_chem_shift is not None) or (ne2_chem_shift is not None):
                                bip, tau, pi = predict_tautomer_state_of_histidine(cg_chem_shift, cd2_chem_shift, nd1_chem_shift, ne2_chem_shift)
                                if tau < 0.001 and pi < 0.001:
                                    his['tautomeric_state_pred'] = 'biprotonated'
                                elif bip < 0.001 and pi < 0.001:
                                    his['tautomeric_state_pred'] = 'tau-tautomer'
                                elif bip < 0.001 and tau < 0.001:
                                    his['tautomeric_state_pred'] = 'pi-tautomer'
                                else:
                                    his['tautomeric_state_pred'] = f"biprotonated {bip:.1%}, tau-tautomer {tau:.1%}, pi-tautomer {pi:.1%}"
                            else:
                                his['tautomeric_state_pred'] = 'unknown'

                            his['tautomeric_state'] = self.__getTautomerOfHistidine(chain_id, seq_id)

                            if his['tautomeric_state_pred'] != 'unknown':
                                item = None
                                if his['tautomeric_state_pred'] != his['tautomeric_state'] and his['tautomeric_state'] != 'unknown':
                                    if ',' in his['tautomeric_state_pred']:
                                        if (his['tautomeric_state'] == 'biprotonated' and bip > tau and bip > pi) or\
                                           (his['tautomeric_state'] == 'tau-tautomer' and tau > bip and tau > pi) or\
                                           (his['tautomeric_state'] == 'pi-tautomer' and pi > bip and pi > tau):
                                            pass
                                        else:
                                            item = 'unusual_chemical_shift'
                                    else:
                                        item = 'anomalous_chemical_shift'

                                if item is not None:

                                    shifts = ''
                                    if cg_chem_shift is not None:
                                        shifts += f"CG {cg_chem_shift} ppm, "
                                    if cd2_chem_shift is not None:
                                        shifts += f"CD2 {cd2_chem_shift} ppm, "
                                    if nd1_chem_shift is not None:
                                        shifts += f"ND1 {nd1_chem_shift} ppm, "
                                    if ne2_chem_shift is not None:
                                        shifts += f"NE2 {ne2_chem_shift} ppm, "

                                    warn = f"Tautomeric state {his['tautomeric_state']} of {chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                        f"the assigned chemical shift values ({shifts}tautomeric_state_pred {his['tautomeric_state_pred']})."

                                    self.report.warning.appendDescription(item,
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Warning  - {warn}\n")

                            his_tautomeric_state.append(his)

                if len(his_tautomeric_state) > 0:
                    ent['his_tautomeric_state'] = his_tautomeric_state

                # prediction of rotameric state of VAL/LEU/ILE

                ilv_comp_ids = ('VAL', 'LEU', 'ILE')

                ilv_rotameric_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            if comp_id not in ilv_comp_ids:
                                continue

                            ilv = {'chain_id': chain_id, 'seq_id': seq_id, 'comp_id': comp_id}

                            if comp_id == 'VAL':

                                cg1_chem_shift = None
                                cg2_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id\
                                       and atom_id.startswith('CG'):

                                        _atom_id = atom_id

                                        if self.__isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.__getRepAtomId(comp_id, atom_id)

                                        if _atom_id == 'CG1':
                                            cg1_chem_shift = row[value_name]
                                        elif _atom_id == 'CG2':
                                            cg2_chem_shift = row[value_name]

                                    if cg1_chem_shift is None or cg2_chem_shift is None:
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cg1_chem_shift'] = cg1_chem_shift
                                ilv['cg2_chem_shift'] = cg2_chem_shift

                                if (cg1_chem_shift is not None) or (cg2_chem_shift is not None):
                                    gp, t, gm = predict_rotamer_state_of_valine(cg1_chem_shift, cg2_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfValine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi1')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm) or\
                                               (_rotameric_state == 'trans' and t > gm and t > gp) or\
                                               (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cg1_chem_shift is not None:
                                            shifts += f"CG1 {cg1_chem_shift} ppm, "
                                        if cg2_chem_shift is not None:
                                            shifts += f"CG2 {cg2_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                            f"the assigned chemical shift values ({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.report.warning.appendDescription(item,
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Warning  - {warn}\n")

                            elif comp_id == 'LEU':

                                cd1_chem_shift = None
                                cd2_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id\
                                       and atom_id.startswith('CD'):

                                        _atom_id = atom_id

                                        if self.__isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.__getRepAtomId(comp_id, atom_id)

                                        if _atom_id == 'CD1':
                                            cd1_chem_shift = row[value_name]
                                        elif _atom_id == 'CD2':
                                            cd2_chem_shift = row[value_name]

                                    if cd1_chem_shift is None or cd2_chem_shift is None:
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cd1_chem_shift'] = cd1_chem_shift
                                ilv['cd2_chem_shift'] = cd2_chem_shift

                                if (cd1_chem_shift is not None) or (cd2_chem_shift is not None):
                                    gp, t, gm = predict_rotamer_state_of_leucine(cd1_chem_shift, cd2_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfLeucine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi2')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm) or\
                                               (_rotameric_state == 'trans' and t > gm and t > gp) or\
                                               (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cd1_chem_shift is not None:
                                            shifts += f"CD1 {cd1_chem_shift} ppm, "
                                        if cd2_chem_shift is not None:
                                            shifts += f"CD2 {cd2_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                            f"the assigned chemical shift values ({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.report.warning.appendDescription(item,
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Warning  - {warn}\n")

                            else:

                                cd1_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                        if atom_id == 'CD1':
                                            cd1_chem_shift = row[value_name]

                                    if cd1_chem_shift is None:
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cd1_chem_shift'] = cd1_chem_shift

                                if cd1_chem_shift is not None:
                                    gp, t, gm = predict_rotamer_state_of_isoleucine(cd1_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfIsoleucine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi2')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm) or\
                                               (_rotameric_state == 'trans' and t > gm and t > gp) or\
                                               (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cd1_chem_shift is not None:
                                            shifts += f"CD1 {cd1_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                            f"the assigned chemical shift values ({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.report.warning.appendDescription(item,
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Warning  - {warn}\n")

                            ilv_rotameric_state.append(ilv)

                if len(ilv_rotameric_state) > 0:
                    ent['ilv_rotameric_state'] = ilv_rotameric_state

                # random coil index

                rci_atom_ids = ('HA', 'HA1', 'HA2', 'HA3', 'H', 'HN', 'NH', 'C', 'CO', 'N', 'CA', 'CB')

                rci = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__remediation_mode else str(letterToDigit(chain_id))

                    s = next((s for s in polymer_sequence if s['chain_id'] == chain_id), None)

                    if s is not None:

                        rci_residues = []
                        rci_assignments = []
                        seq_ids_wo_assign = []
                        oxidized_cys_seq_ids = []

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            if comp_id not in emptyValue:
                                if comp_id not in monDict3:
                                    continue
                                if not self.__csStat.peptideLike(comp_id):
                                    continue
                                rci_residues.append([comp_id, seq_id])
                            else:
                                _comp_id = self.__getCoordCompId(chain_id, seq_id)
                                if _comp_id is not None:
                                    if _comp_id not in monDict3:
                                        continue
                                    if not self.__csStat.peptideLike(_comp_id):
                                        continue
                                    rci_residues.append([_comp_id, seq_id])
                                else:
                                    continue

                            has_bb_atoms = False

                            for row in lp_data:

                                if row[chain_id_name] != _chain_id or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                   or row[value_name] in emptyValue:
                                    continue

                                atom_id = row[atom_id_name]

                                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                    _atom_id = self.__getAtomIdList(comp_id, atom_id)

                                    len_atom_id = len(_atom_id)

                                    if len_atom_id == 0:
                                        continue

                                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                                        atom_id_ = atom_id

                                    else:  # representative atom id
                                        atom_id_ = _atom_id[0]

                                else:
                                    atom_id_ = atom_id

                                if atom_id_ not in rci_atom_ids:
                                    continue

                                rci_assignments.append([comp_id, seq_id, atom_id, row[atom_type], row[value_name]])

                                has_bb_atoms = True

                            if has_bb_atoms:

                                if comp_id in ('CYS', 'DCY'):

                                    ca_chem_shift = None
                                    cb_chem_shift = None

                                    for row in lp_data:

                                        atom_id = row[atom_id_name]

                                        if row[chain_id_name] == _chain_id and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                            if atom_id == 'CA':
                                                ca_chem_shift = row[value_name]
                                            elif atom_id == 'CB':
                                                cb_chem_shift = row[value_name]

                                        if ca_chem_shift is None or cb_chem_shift is None:
                                            if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                                break
                                        else:
                                            break

                                    ambig_redox_state = False

                                    if cb_chem_shift is not None:
                                        if cb_chem_shift < 32.0:
                                            pass
                                        elif cb_chem_shift > 35.0:
                                            oxidized_cys_seq_ids.append(seq_id)
                                        else:
                                            ambig_redox_state = True
                                    elif ca_chem_shift is not None:
                                        ambig_redox_state = True

                                    if ambig_redox_state:
                                        oxi, red = predict_redox_state_of_cystein(ca_chem_shift, cb_chem_shift)
                                        if oxi < 0.001:
                                            pass
                                        elif red < 0.001 or oxi > 0.5:
                                            oxidized_cys_seq_ids.append(seq_id)

                            else:
                                seq_ids_wo_assign.append(seq_id)

                        if len(rci_assignments) > 0:
                            result = self.__rci.calculate(rci_residues, rci_assignments, oxidized_cys_seq_ids, seq_ids_wo_assign)

                            if 'rci' in result and len(result['rci']) > 0:
                                result['chain_id'] = chain_id
                                result['comp_id'] = [res[0] for res in rci_residues]
                                struct_conf = self.__extractCoordStructConf(chain_id, s['seq_id'])
                                len_struct_conf = len(struct_conf)
                                result['struct_conf'] = []
                                for seq_id in result['seq_id']:
                                    pos = s['seq_id'].index(seq_id)
                                    if pos < len_struct_conf:
                                        result['struct_conf'].append(struct_conf[pos])

                                cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(chain_id)

                                if cif_ps is not None and 'well_defined_region' in cif_ps:

                                    _score = 0.0
                                    dom_idx = -1

                                    for i, r in enumerate(cif_ps['well_defined_region']):
                                        try:
                                            score = r['percent_of_core'] / r['medoid_rmsd']
                                            if score > _score:
                                                _score = score
                                                dom_idx = i
                                        except Exception:
                                            continue

                                    if dom_idx != -1:
                                        result['rmsd_in_well_defined_region'] = cif_ps['well_defined_region'][dom_idx]['medoid_rmsd']

                                rci.append(result)

                if len(rci) > 0:
                    ent['random_coil_index'] = rci

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfAssignedChemShift() ++ Error  - {str(e)}\n")

    def __calculateStatsOfDistanceRestraint(self, file_list_id, sf_framecode, lp_data, conflict_id_set, inconsistent, redundant, ent):
        """ Calculate statistics of distance restraints.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        index_tag = self.index_tags[file_type]['dist_restraint']
        item_names = self.item_names_in_ds_loop[file_type]
        combination_id_name = item_names['combination_id']
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']
        member_id_name = item_names['member_id'] if file_type == 'nmr-star' else None
        member_logic_code_name = item_names['member_logic_code'] if file_type == 'nmr-star' else None
        target_value_name = item_names['target_value']
        if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
            target_value_name = item_names['target_value_alt']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']
        weight_name = self.weight_tags[file_type]['dist_restraint']
        id_tag = self.consist_id_tags[file_type]['dist_restraint']

        len_lp_data = len(lp_data)

        try:

            max_val = -100.0
            min_val = 100.0

            count = {}
            comb_count = {}
            inco_count = {}
            redu_count = {}
            weights = {}
            potential_types = {}
            set_id = set()

            count_per_residue = []
            count_on_map = []
            count_on_asym_map = []

            has_inter_chain_constraint = False

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is not None:

                for s in polymer_sequence:
                    struct_conf = self.__extractCoordStructConf(s['chain_id'], s['seq_id'])
                    count_per_residue.append({'chain_id': s['chain_id'], 'seq_id': s['seq_id'], 'comp_id': s['comp_id'],
                                              'struct_conf': struct_conf})
                    count_on_map.append({'chain_id': s['chain_id'], 'seq_id': s['seq_id'], 'comp_id': s['comp_id'],
                                         'struct_conf': struct_conf})

                if len(polymer_sequence) > 1:
                    for s, t in itertools.combinations(polymer_sequence, 2):
                        count_on_asym_map.append({'chain_id_1': s['chain_id'], 'chain_id_2': t['chain_id'],
                                                  'seq_id_1': s['seq_id'], 'seq_id_2': t['seq_id'],
                                                  'comp_id_1': s['comp_id'], 'comp_id_2': t['comp_id'],
                                                  'struct_conf_1': self.__extractCoordStructConf(s['chain_id'], s['seq_id']),
                                                  'struct_conf_2': self.__extractCoordStructConf(t['chain_id'], t['seq_id'])})

            _rest_id = -1
            _atom1 = _atom2 = None

            for idx, row in enumerate(lp_data):
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)
                member_id = row.get(member_id_name) if file_type == 'nmr-star' else None
                member_logic_code = row.get(member_logic_code_name) if file_type == 'nmr-star' else None

                comp_id_1 = row[comp_id_1_name]
                comp_id_2 = row[comp_id_2_name]

                if 'HOH' in (comp_id_1, comp_id_2):
                    continue

                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]
                atom_id_1 = row[atom_id_1_name]
                atom_id_2 = row[atom_id_2_name]
                weight = row.get(weight_name)

                rest_id = row[id_tag]
                set_id.add(id)

                if (member_logic_code is not None and member_logic_code == 'OR') or rest_id == _rest_id:
                    atom1 = {'chain_id': chain_id_1,
                             'seq_id': int(seq_id_1) if seq_id_1 not in emptyValue else None,
                             'comp_id': comp_id_1,
                             'atom_id': atom_id_1}
                    atom2 = {'chain_id': chain_id_2,
                             'seq_id': int(seq_id_2) if seq_id_2 not in emptyValue else None,
                             'comp_id': comp_id_2,
                             'atom_id': atom_id_2}
                    if _atom1 is not None and _atom2 is not None:
                        if not isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                           and not isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                            _rest_id, _atom1, _atom2 = rest_id, atom1, atom2
                            continue
                    _atom1, _atom2 = atom1, atom2

                _rest_id = rest_id

                target_value = row.get(target_value_name)

                upper_limit = None
                lower_limit = None

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0
                        upper_limit = row[lower_limit_name]
                        lower_limit = row[upper_limit_name]

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    elif has_key_value(row, upper_linear_limit_name):
                        target_value = row[upper_linear_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, upper_limit_name):
                        target_value = row[upper_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, lower_linear_limit_name):
                        target_value = row[lower_linear_limit_name]
                        lower_limit = target_value

                    elif has_key_value(row, lower_limit_name):
                        target_value = row[lower_limit_name]
                        lower_limit = target_value

                    else:
                        continue

                max_val = max(max_val, target_value)
                min_val = min(min_val, target_value)

                data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, idx, target_value, upper_limit, lower_limit,
                                                              member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                              chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                if 'hydrogen_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Hydrogen bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'disulfide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Disulfide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'diselenide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Diselenide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'other_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Other bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in emptyValue):
                    if data_type in comb_count:
                        comb_count[data_type] += 1
                    else:
                        comb_count[data_type] = 1

                if index is not None:

                    if index in inconsistent:
                        if data_type in inco_count:
                            inco_count[data_type] += 1
                        else:
                            inco_count[data_type] = 1

                    if index in redundant:
                        if data_type in redu_count:
                            redu_count[data_type] += 1
                        else:
                            redu_count[data_type] = 1

                # detect weight

                if weight is not None:
                    if data_type not in weights:
                        weights[data_type] = []
                    weights[data_type].append(weight)

                # detect potential type

                # targe_value = row.get(target_value_name)
                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'log-harmonic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if polymer_sequence is not None:

                    # count per residue

                    for c in count_per_residue:
                        if data_type not in c:
                            c[data_type] = [0] * len(c['seq_id'])
                        if c['chain_id'] == chain_id_1 and seq_id_1 in c['seq_id']:
                            c[data_type][c['seq_id'].index(seq_id_1)] += 1
                        if c['chain_id'] == chain_id_2 and seq_id_2 in c['seq_id']:
                            c[data_type][c['seq_id'].index(seq_id_2)] += 1

                    # count on map

                    if chain_id_1 == chain_id_2:

                        for c in count_on_map:
                            if data_type not in c:
                                c[data_type] = []
                            if c['chain_id'] == chain_id_1:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_1 and b['seq_id_2'] == seq_id_2)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id'] and seq_id_2 in c['seq_id']:
                                        c[data_type].append({'seq_id_1': seq_id_1, 'seq_id_2': seq_id_2, 'total': 1})

                    else:

                        for c in count_on_asym_map:
                            if data_type not in c:
                                c[data_type] = []
                            if c['chain_id_1'] == chain_id_1 and c['chain_id_2'] == chain_id_2:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_1 and b['seq_id_2'] == seq_id_2)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id_1'] and seq_id_2 in c['seq_id_2']:
                                        c[data_type].append({'seq_id_1': seq_id_1, 'seq_id_2': seq_id_2, 'total': 1})
                            elif c['chain_id_1'] == chain_id_2 and c['chain_id_2'] == chain_id_1:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_2 and b['seq_id_2'] == seq_id_1)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id_1'] and seq_id_2 in c['seq_id_2']:
                                        c[data_type].append({'seq_id_1': seq_id_2, 'seq_id_2': seq_id_1, 'total': 1})
                            if not has_inter_chain_constraint and len(c[data_type]) > 0:
                                has_inter_chain_constraint = True

            if len(count) == 0:
                return

            ent['number_of_constraints'] = count
            ent['number_of_constraint_sets'] = len(set_id)
            if len(comb_count) > 0:
                ent['number_of_combined_constraints'] = comb_count
            if len(inco_count) > 0:
                ent['number_of_inconsistent_constraints'] = inco_count
            if len(redu_count) > 0:
                ent['number_of_redundant_constraints'] = redu_count
            if polymer_sequence is not None:
                ent['constraints_per_residue'] = count_per_residue
                ent['constraints_on_contact_map'] = count_on_map
            if has_inter_chain_constraint:
                ent['constraints_on_asym_contact_map'] = count_on_asym_map
            ent['range'] = {'max_value': float(f'{max_val:.2f}'), 'min_value': float(f'{min_val:.2f}')}
            if len(weights) > 0:
                _weights = {}
                for k, v in weights.items():
                    _weights[k] = collections.Counter(v).most_common()
                ent['weight_of_constraints'] = _weights
            if len(potential_types) > 0:
                _potential_types = {}
                for k, v in potential_types.items():
                    _potential_types[k] = collections.Counter(v).most_common()
                ent['potential_type_of_constraints'] = _potential_types

            target_scale = (max_val - min_val) / 10.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals = []
            count_of_vals = []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = 0

                for idx, row in enumerate(lp_data):
                    member_id = row.get(member_id_name) if file_type == 'nmr-star' else None

                    chain_id_1 = row[chain_id_1_name]
                    chain_id_2 = row[chain_id_2_name]
                    seq_id_1 = row[seq_id_1_name]
                    seq_id_2 = row[seq_id_2_name]
                    comp_id_1 = row[comp_id_1_name]
                    comp_id_2 = row[comp_id_2_name]
                    atom_id_1 = row[atom_id_1_name]
                    atom_id_2 = row[atom_id_2_name]

                    target_value = row.get(target_value_name)

                    upper_limit = None
                    lower_limit = None

                    if target_value is None:

                        if has_key_value(row, lower_limit_name)\
                                and has_key_value(row, upper_limit_name):
                            target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0
                            upper_limit = row[lower_limit_name]
                            lower_limit = row[upper_limit_name]

                        elif has_key_value(row, lower_linear_limit_name)\
                                and has_key_value(row, upper_linear_limit_name):
                            target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                        elif has_key_value(row, upper_linear_limit_name):
                            target_value = row[upper_linear_limit_name]
                            upper_limit = target_value

                        elif has_key_value(row, upper_limit_name):
                            target_value = row[upper_limit_name]
                            upper_limit = target_value

                        elif has_key_value(row, lower_linear_limit_name):
                            target_value = row[lower_linear_limit_name]
                            lower_limit = target_value

                        elif has_key_value(row, lower_limit_name):
                            target_value = row[lower_limit_name]
                            lower_limit = target_value

                        else:
                            continue

                    if target_value < v or target_value >= v + scale:
                        continue

                    data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, idx, target_value, upper_limit, lower_limit,
                                                                  member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                  chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                    if data_type in _count:
                        _count[data_type] += 1
                    else:
                        _count[data_type] = 1

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                # max_inclusive = DIST_UNCERT_MAX

                max_val = 0.0
                min_val = 0.0

                dist_ann = []

                for id_set in conflict_id_set:
                    len_id_set = len(id_set)

                    if len_id_set < 2:
                        continue

                    for i in range(len_id_set - 1):

                        for j in range(i + 1, len_id_set):
                            if id_set[j] >= len_lp_data:
                                continue
                            row_1 = lp_data[id_set[i]]
                            row_2 = lp_data[id_set[j]]

                            target_value_1 = row_1.get(target_value_name)

                            if target_value_1 is None:

                                if has_key_value(row_1, lower_limit_name)\
                                        and has_key_value(row_1, upper_limit_name):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif has_key_value(row_1, lower_linear_limit_name)\
                                        and has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif has_key_value(row_1, upper_limit_name):
                                    target_value_1 = row_1[upper_limit_name]

                                elif has_key_value(row_1, lower_linear_limit_name):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif has_key_value(row_1, lower_limit_name):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2.get(target_value_name)

                            if target_value_2 is None:

                                if has_key_value(row_2, lower_limit_name)\
                                        and has_key_value(row_2, upper_limit_name):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif has_key_value(row_2, lower_linear_limit_name)\
                                        and has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif has_key_value(row_2, upper_limit_name):
                                    target_value_2 = row_2[upper_limit_name]

                                elif has_key_value(row_2, lower_linear_limit_name):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif has_key_value(row_2, lower_limit_name):
                                    target_value_2 = row_2[lower_limit_name]

                            if target_value_1 is None or target_value_2 is None:
                                continue

                            if target_value_1 == target_value_2:
                                continue

                            discrepancy = abs(target_value_1 - target_value_2) / abs(target_value_1 + target_value_2) * 100.0

                            max_val = max(max_val, discrepancy)

                            if discrepancy >= self.r_inconsistent_dist_restraint * 100.0:
                                ann = {}
                                ann['level'] = 'conflicted' if discrepancy >= self.r_conflicted_dist_restraint * 100.0 else 'inconsistent'
                                ann['chain_id_1'] = row_1[chain_id_1_name]
                                ann['seq_id_1'] = row_1[seq_id_1_name]
                                ann['comp_id_1'] = row_1[comp_id_1_name]
                                ann['atom_id_1'] = row_1[atom_id_1_name]
                                if row_1[chain_id_1_name] != row_2[chain_id_2_name]:
                                    ann['chain_id_2'] = row_2[chain_id_2_name]
                                    ann['seq_id_2'] = row_2[seq_id_2_name]
                                    ann['comp_id_2'] = row_2[comp_id_2_name]
                                elif row_1[seq_id_1_name] != row_2[seq_id_2_name]:
                                    ann['seq_id_2'] = row_2[seq_id_2_name]
                                    ann['comp_id_2'] = row_2[comp_id_2_name]
                                ann['atom_id_2'] = row_2[atom_id_2_name]
                                ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                dist_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals = []
                    count_of_vals = []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_id_1 = id_set[i]
                                    row_id_2 = id_set[j]
                                    if row_id_2 >= len_lp_data:
                                        continue
                                    row_1 = lp_data[row_id_1]
                                    row_2 = lp_data[row_id_2]

                                    target_value_1 = row_1.get(target_value_name)

                                    if target_value_1 is None:

                                        if has_key_value(row_1, lower_limit_name)\
                                                and has_key_value(row_1, upper_limit_name):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif has_key_value(row_1, lower_linear_limit_name)\
                                                and has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif has_key_value(row_1, upper_limit_name):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif has_key_value(row_1, lower_linear_limit_name):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif has_key_value(row_1, lower_limit_name):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2.get(target_value_name)

                                    if target_value_2 is None:

                                        if has_key_value(row_2, lower_limit_name)\
                                                and has_key_value(row_2, upper_limit_name):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif has_key_value(row_2, lower_linear_limit_name)\
                                                and has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif has_key_value(row_2, upper_limit_name):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif has_key_value(row_2, lower_linear_limit_name):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif has_key_value(row_2, lower_limit_name):
                                            target_value_2 = row_2[lower_limit_name]

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if target_value_1 is None or target_value_2 is None:
                                        redundant = False
                                        continue

                                    if target_value_1 == target_value_2:
                                        continue

                                    redundant = False

                                    discrepancy = abs(target_value_1 - target_value_2) / abs(target_value_1 + target_value_2) * 100.0

                                    if discrepancy < v or discrepancy >= v + scale:
                                        continue

                                    target_value = row_1.get(target_value_name)

                                    upper_limit = None
                                    lower_limit = None

                                    if target_value is None:

                                        if has_key_value(row_1, lower_limit_name)\
                                                and has_key_value(row_1, upper_limit_name):
                                            target_value = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0
                                            upper_limit = row_1[lower_limit_name]
                                            lower_limit = row_1[upper_limit_name]

                                        elif has_key_value(row_1, lower_linear_limit_name)\
                                                and has_key_value(row_1, upper_linear_limit_name):
                                            target_value = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_1, upper_linear_limit_name):
                                            target_value = row_1[upper_linear_limit_name]
                                            upper_limit = target_value

                                        elif has_key_value(row_1, upper_limit_name):
                                            target_value = row_1[upper_limit_name]
                                            upper_limit = target_value

                                        elif has_key_value(row_1, lower_linear_limit_name):
                                            target_value = row_1[lower_linear_limit_name]
                                            lower_limit = target_value

                                        elif has_key_value(row_1, lower_limit_name):
                                            target_value = row_1[lower_limit_name]
                                            lower_limit = target_value

                                        else:
                                            continue

                                    member_id = row_1.get(member_id_name) if file_type == 'nmr-star' else None

                                    chain_id_1 = row_1[chain_id_1_name]
                                    chain_id_2 = row_1[chain_id_2_name]
                                    seq_id_1 = row_1[seq_id_1_name]
                                    seq_id_2 = row_1[seq_id_2_name]
                                    comp_id_1 = row_1[comp_id_1_name]
                                    comp_id_2 = row_1[comp_id_2_name]
                                    atom_id_1 = row_1[atom_id_1_name]
                                    atom_id_2 = row_1[atom_id_2_name]

                                    data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, row_id_1, target_value, upper_limit, lower_limit,
                                                                                  member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                                  chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                                    if data_type in _count:
                                        _count[data_type] += 1
                                    else:
                                        _count[data_type] = 1

                            if 0.0 <= v < scale and redundant:

                                target_value = row_1.get(target_value_name)

                                upper_limit = None
                                lower_limit = None

                                if target_value is None:

                                    if has_key_value(row_1, lower_limit_name)\
                                            and has_key_value(row_1, upper_limit_name):
                                        target_value = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0
                                        upper_limit = row_1[lower_limit_name]
                                        lower_limit = row_1[upper_limit_name]

                                    elif has_key_value(row_1, lower_linear_limit_name)\
                                            and has_key_value(row_1, upper_linear_limit_name):
                                        target_value = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                    elif has_key_value(row_1, upper_linear_limit_name):
                                        target_value = row_1[upper_linear_limit_name]
                                        upper_limit = target_value

                                    elif has_key_value(row_1, upper_limit_name):
                                        target_value = row_1[upper_limit_name]
                                        upper_limit = target_value

                                    elif has_key_value(row_1, lower_linear_limit_name):
                                        target_value = row_1[lower_linear_limit_name]
                                        lower_limit = target_value

                                    elif has_key_value(row_1, lower_limit_name):
                                        target_value = row_1[lower_limit_name]
                                        lower_limit = target_value

                                    else:
                                        continue

                                member_id = row_1.get(member_id_name) if file_type == 'nmr-star' else None

                                chain_id_1 = row_1[chain_id_1_name]
                                chain_id_2 = row_1[chain_id_2_name]
                                seq_id_1 = row_1[seq_id_1_name]
                                seq_id_2 = row_1[seq_id_2_name]
                                comp_id_1 = row_1[comp_id_1_name]
                                comp_id_2 = row_1[comp_id_2_name]
                                atom_id_1 = row_1[atom_id_1_name]
                                atom_id_2 = row_1[atom_id_2_name]

                                data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, row_id_1, target_value, upper_limit, lower_limit,
                                                                              member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                              chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                                if data_type in _count:
                                    _count[data_type] += 1
                                else:
                                    _count[data_type] = 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals, 'number_of_values': transposed, 'annotations': dist_ann}

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDistanceRestraint() ++ Error  - {str(e)}\n")

    def __calculateStatsOfCovalentBond(self, file_list_id, sf_framecode, lp_category, lp_data, ent):
        """ Calculate statistics of covalent bonds.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']

        try:

            count = {}

            count_on_map = []
            count_on_asym_map = []

            has_inter_chain_constraint = False

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is not None:

                for s in polymer_sequence:
                    struct_conf = self.__extractCoordStructConf(s['chain_id'], s['seq_id'])
                    count_on_map.append({'chain_id': s['chain_id'], 'seq_id': s['seq_id'], 'comp_id': s['comp_id'],
                                         'struct_conf': struct_conf})

                if len(polymer_sequence) > 1:
                    for s, t in itertools.combinations(polymer_sequence, 2):
                        count_on_asym_map.append({'chain_id_1': s['chain_id'], 'chain_id_2': t['chain_id'],
                                                  'seq_id_1': s['seq_id'], 'seq_id_2': t['seq_id'],
                                                  'comp_id_1': s['comp_id'], 'comp_id_2': t['comp_id'],
                                                  'struct_conf_1': self.__extractCoordStructConf(s['chain_id'], s['seq_id']),
                                                  'struct_conf_2': self.__extractCoordStructConf(t['chain_id'], t['seq_id'])})

            for idx, row in enumerate(lp_data):
                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]
                comp_id_1 = row[comp_id_1_name]
                comp_id_2 = row[comp_id_2_name]
                atom_id_1 = row[atom_id_1_name]
                atom_id_2 = row[atom_id_2_name]

                bond = self.__getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)

                if bond is None:
                    continue

                dist = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)

                if dist is None:
                    dist = bond[0]['distance']

                data_type = self.__getTypeOfCovalentBond(file_type, lp_data, idx, dist,
                                                         chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                if 'hydrogen_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Hydrogen bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'disulfide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Disulfide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'diselenide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Diselenide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'other_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Other bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.report.warning.appendDescription('unusual_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if polymer_sequence is not None:

                    # count on map

                    if chain_id_1 == chain_id_2:

                        for c in count_on_map:
                            if data_type not in c:
                                c[data_type] = []
                            if c['chain_id'] == chain_id_1:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_1 and b['seq_id_2'] == seq_id_2)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id'] and seq_id_2 in c['seq_id']:
                                        c[data_type].append({'seq_id_1': seq_id_1, 'seq_id_2': seq_id_2, 'total': 1})

                    else:

                        for c in count_on_asym_map:
                            if data_type not in c:
                                c[data_type] = []
                            if c['chain_id_1'] == chain_id_1 and c['chain_id_2'] == chain_id_2:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_1 and b['seq_id_2'] == seq_id_2)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id_1'] and seq_id_2 in c['seq_id_2']:
                                        c[data_type].append({'seq_id_1': seq_id_1, 'seq_id_2': seq_id_2, 'total': 1})
                            elif c['chain_id_1'] == chain_id_2 and c['chain_id_2'] == chain_id_1:
                                try:
                                    b = next(b for b in c[data_type] if b['seq_id_1'] == seq_id_2 and b['seq_id_2'] == seq_id_1)
                                    b['total'] += 1
                                except StopIteration:
                                    if seq_id_1 in c['seq_id_1'] and seq_id_2 in c['seq_id_2']:
                                        c[data_type].append({'seq_id_1': seq_id_2, 'seq_id_2': seq_id_1, 'total': 1})
                            if not has_inter_chain_constraint and len(c[data_type]) > 0:
                                has_inter_chain_constraint = True

            if len(count) == 0:
                return

            ent['number_of_constraints'] = count
            if polymer_sequence is not None:
                ent['constraints_on_contact_map'] = count_on_map
            if has_inter_chain_constraint:
                ent['constraints_on_asym_contact_map'] = count_on_asym_map

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfCovalentBond() ++ Error  - {str(e)}\n")

    def __getTypeOfDistanceRestraint(self, file_type, lp_data, row_id, target_value, upper_limit, lower_limit,
                                     member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                     chain_id_2, seq_id_2, comp_id_2, atom_id_2):
        """ Return type of distance restraint.
        """

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']

        hydrogen_bond_type = None
        hydrogen_bond = False
        disulfide_bond_type = None
        disulfide_bond = False
        diselenide_bond_type = None
        diselenide_bond = False
        other_bond_type = None
        other_bond = False
        symmetry = False

        if chain_id_1 != chain_id_2 or seq_id_1 != seq_id_2:

            atom_id_1_ = atom_id_1[0]
            atom_id_2_ = atom_id_2[0]

            if upper_limit is not None:
                target_value -= 0.4

            if lower_limit is not None:
                target_value += 0.4

            balanced = (upper_limit is None and lower_limit is None)\
                or (upper_limit is not None and lower_limit is not None)\
                or (upper_limit is not None and upper_limit == 0.0)\
                or (lower_limit is not None and lower_limit == 0.0)

            delta_minus = 0.1 if upper_limit is not None and lower_limit is not None else 0.0

            ambig = member_id is not None or (upper_limit is not None and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP))

            if not ambig:

                if (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):

                    if 1.2 - delta_minus <= target_value <= 1.5:
                        hydrogen_bond_type = 'F...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.2 - delta_minus:
                        hydrogen_bond_type = 'F...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 2.0:
                        hydrogen_bond_type = 'F...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):

                    if 2.2 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'F...h-F'
                        hydrogen_bond = True
                    elif target_value < 2.2 - delta_minus:
                        hydrogen_bond_type = 'F...h-F (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 3.0:
                        hydrogen_bond_type = 'F...h-F (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):

                    if 1.5 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'O...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.5 - delta_minus:
                        hydrogen_bond_type = 'O...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 4.0:
                        hydrogen_bond_type = 'O...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'O...h-N'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'O...h-N (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'O...h-N (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'O...h-O'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'O...h-O (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'O...h-O (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):

                    if 1.5 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'N...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.5 - delta_minus:
                        hydrogen_bond_type = 'N...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 4.0:
                        hydrogen_bond_type = 'N...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'N...h_N'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'N...h_N (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'N...h_N (too far!)'
                        hydrogen_bond = True

                elif atom_id_1_ == 'S' and atom_id_2_ == 'S' and not atom_id_1.startswith('SE') and not atom_id_2.startswith('SE'):

                    if 1.9 - delta_minus <= target_value <= 2.3:
                        disulfide_bond_type = 'S...S'
                        disulfide_bond = True
                    elif target_value < 1.9 - delta_minus:
                        disulfide_bond_type = 'S...S (too close!)'
                        disulfide_bond = True
                    elif target_value <= 3.6:
                        disulfide_bond_type = 'S...S (too far!)'
                        disulfide_bond = True

                elif atom_id_1.startswith('SE') and atom_id_2.startswith('SE'):

                    if 2.1 - delta_minus <= target_value <= 2.6:
                        diselenide_bond_type = 'Se...Se'
                        diselenide_bond = True
                    elif target_value < 2.1 - delta_minus:
                        diselenide_bond_type = 'Se...Se (too close!)'
                        diselenide_bond = True
                    elif target_value <= 4.2:
                        diselenide_bond_type = 'Se...Se (too far!)'
                        diselenide_bond = True

                elif (atom_id_1_ == 'N' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'N' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 1.9 - delta_minus <= target_value <= 2.1 or not balanced:
                        other_bond_type = 'N...' + metal
                        other_bond = True
                    elif target_value < 1.9 - delta_minus:
                        other_bond_type = 'N...' + metal + ' (too close!)'
                        other_bond = True
                    elif target_value <= 3.2:
                        other_bond_type = 'N...' + metal + ' (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'O' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'O' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.0 - delta_minus <= target_value <= 2.2 or not balanced:
                        other_bond_type = 'O...' + metal
                        other_bond = True
                    elif target_value < 2.0 - delta_minus:
                        other_bond_type = 'O...' + metal + ' (too close!)'
                        other_bond = True
                    elif target_value <= 3.4:
                        other_bond_type = 'O...' + metal + ' (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'P' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'P' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.1 - delta_minus <= target_value <= 2.5 or not balanced:
                        other_bond_type = 'P...' + metal
                        other_bond = True
                    elif target_value < 2.1 - delta_minus:
                        other_bond_type = 'P...' + metal + ' (too close!)'
                        other_bond = True
                    elif target_value <= 4.0:
                        other_bond_type = 'P...' + metal + ' (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'S' and not atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2)) or\
                     (atom_id_2_ == 'S' and not atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.2 - delta_minus <= target_value <= 2.6 or not balanced:
                        other_bond_type = 'S...' + metal
                        other_bond = True
                    elif target_value < 2.2 - delta_minus:
                        other_bond_type = 'S...' + metal + ' (too close!)'
                        other_bond = True
                    elif target_value <= 4.2:
                        other_bond_type = 'S...' + metal + ' (too far!)'
                        other_bond = True

                elif (atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2)) or\
                     (atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.3 - delta_minus <= target_value <= 2.7 or not balanced:
                        other_bond_type = 'Se...' + metal
                        other_bond = True
                    elif target_value < 2.3 - delta_minus:
                        other_bond_type = 'Se...' + metal + ' (too close!)'
                        other_bond = True
                    elif target_value <= 4.4:
                        other_bond_type = 'Se...' + metal + ' (too far!)'
                        other_bond = True

                elif chain_id_1 != chain_id_2:

                    for idx, row in enumerate(lp_data):

                        if idx == row_id:
                            continue

                        _chain_id_1 = row[chain_id_1_name]
                        _chain_id_2 = row[chain_id_2_name]
                        _seq_id_1 = row[seq_id_1_name]
                        _seq_id_2 = row[seq_id_2_name]
                        _comp_id_1 = row[comp_id_1_name]
                        _comp_id_2 = row[comp_id_2_name]

                        if _chain_id_1 != _chain_id_2 and _chain_id_1 != chain_id_1 and _chain_id_2 != chain_id_2:

                            if seq_id_1 == _seq_id_1 and comp_id_1 == _comp_id_1 and\
                               seq_id_2 == _seq_id_2 and comp_id_2 == _comp_id_2:
                                symmetry = True
                                break

                            if seq_id_1 == _seq_id_2 and comp_id_1 == _comp_id_2 and\
                               seq_id_2 == _seq_id_1 and comp_id_2 == _comp_id_1:
                                symmetry = True
                                break

        range_of_seq = abs(seq_id_1 - seq_id_2)

        if hydrogen_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_hydrogen_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_hydrogen_bonds'
            else:
                data_type = 'hydrogen_bonds'
            data_type += f'_{hydrogen_bond_type}'
        elif disulfide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_disulfide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_disulfide_bonds'
            else:
                data_type = 'disulfide_bonds'
            data_type += f'_{disulfide_bond_type}'
        elif diselenide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_diselenide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_diselenide_bonds'
            else:
                data_type = 'diselenide_bonds'
            data_type += f'_{diselenide_bond_type}'
        elif other_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_other_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_other_bonds'
            else:
                data_type = 'other_bonds'
            data_type += f'_{other_bond_type}'
        elif symmetry:
            data_type = 'symmetric_constraints'
        elif chain_id_1 != chain_id_2:
            data_type = 'inter-chain_constraints'
        elif range_of_seq == 0:
            data_type = 'intra-residue_constraints'
        elif range_of_seq < 5:

            if file_type == 'nef' or (self.__isNmrAtomName(comp_id_1, atom_id_1) or self.__isNmrAtomName(comp_id_2, atom_id_2)):
                _atom_id_1 = self.__getAtomIdList(comp_id_1, atom_id_1)
                _atom_id_2 = self.__getAtomIdList(comp_id_2, atom_id_2)

                if len(_atom_id_1) > 0 and len(_atom_id_2) > 0:
                    is_sc_atom_1 = _atom_id_1[0] in self.__csStat.getSideChainAtoms(comp_id_1)
                    is_sc_atom_2 = _atom_id_2[0] in self.__csStat.getSideChainAtoms(comp_id_2)

                    if is_sc_atom_1:
                        is_bb_atom_1 = False
                    else:
                        is_bb_atom_1 = _atom_id_1[0] in self.__csStat.getBackBoneAtoms(comp_id_1)

                    if is_sc_atom_2:
                        is_bb_atom_2 = False
                    else:
                        is_bb_atom_2 = _atom_id_2[0] in self.__csStat.getBackBoneAtoms(comp_id_2)

                else:
                    is_bb_atom_1 = False
                    is_bb_atom_2 = False
                    is_sc_atom_1 = False
                    is_sc_atom_2 = False

            else:
                is_sc_atom_1 = atom_id_1 in self.__csStat.getSideChainAtoms(comp_id_1)
                is_sc_atom_2 = atom_id_2 in self.__csStat.getSideChainAtoms(comp_id_2)

                if is_sc_atom_1:
                    is_bb_atom_1 = False
                else:
                    is_bb_atom_1 = atom_id_1 in self.__csStat.getBackBoneAtoms(comp_id_1)

                if is_sc_atom_2:
                    is_bb_atom_2 = False
                else:
                    is_bb_atom_2 = atom_id_2 in self.__csStat.getBackBoneAtoms(comp_id_2)

            is_bb_bb = is_bb_atom_1 and is_bb_atom_2
            is_bb_sc = (is_bb_atom_1 and is_sc_atom_2) or (is_sc_atom_1 and is_bb_atom_2)
            is_sc_sc = is_sc_atom_1 and is_sc_atom_2

            if range_of_seq == 1:
                data_type = 'sequential_constraints'
            else:
                data_type = 'medium_range_constraints'

            if is_bb_bb:
                data_type += '_backbone-backbone'
            elif is_bb_sc:
                data_type += '_backbone-sidechain'
            elif is_sc_sc:
                data_type += '_sidechain-sidechain'
        else:
            data_type = 'long_range_constraints'

        return data_type

    def __getTypeOfCovalentBond(self, file_type, lp_data, row_id, target_value,
                                chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2):
        """ Return type of covalent bond.
        """

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']

        hydrogen_bond_type = None
        hydrogen_bond = False
        disulfide_bond_type = None
        disulfide_bond = False
        diselenide_bond_type = None
        diselenide_bond = False
        other_bond_type = None
        other_bond = False
        symmetry = False

        if chain_id_1 != chain_id_2 or seq_id_1 != seq_id_2:

            atom_id_1_ = atom_id_1[0]
            atom_id_2_ = atom_id_2[0]

            if (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):

                if 1.2 <= target_value <= 1.5:
                    hydrogen_bond_type = 'F...H-x'
                    hydrogen_bond = True
                elif target_value < 1.2:
                    hydrogen_bond_type = 'F...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 2.0:
                    hydrogen_bond_type = 'F...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):

                if 2.2 <= target_value <= 2.5:
                    hydrogen_bond_type = 'F...h-F'
                    hydrogen_bond = True
                elif target_value < 2.2:
                    hydrogen_bond_type = 'F...h-F (too close!)'
                    hydrogen_bond = True
                elif target_value <= 3.0:
                    hydrogen_bond_type = 'F...h-F (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):

                if 1.5 <= target_value <= 2.5:
                    hydrogen_bond_type = 'O...H-x'
                    hydrogen_bond = True
                elif target_value < 1.5:
                    hydrogen_bond_type = 'O...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 4.0:
                    hydrogen_bond_type = 'O...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'O...h-N'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'O...h-N (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'O...h-N (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'O...h-O'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'O...h-O (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'O...h-O (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):

                if 1.5 <= target_value <= 2.5:
                    hydrogen_bond_type = 'N...H-x'
                    hydrogen_bond = True
                elif target_value < 1.5:
                    hydrogen_bond_type = 'N...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 4.0:
                    hydrogen_bond_type = 'N...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'N...h_N'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'N...h_N (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'N...h_N (too far!)'
                    hydrogen_bond = True

            elif atom_id_1_ == 'S' and atom_id_2_ == 'S' and not atom_id_1.startswith('SE') and not atom_id_2.startswith('SE'):

                if 1.9 <= target_value <= 2.3:
                    disulfide_bond_type = 'S...S'
                    disulfide_bond = True
                elif target_value < 1.9:
                    disulfide_bond_type = 'S...S (too close!)'
                    disulfide_bond = True
                elif target_value <= 3.6:
                    disulfide_bond_type = 'S...S (too far!)'
                    disulfide_bond = True

            elif atom_id_1.startswith('SE') and atom_id_2.startswith('SE'):

                if 2.1 <= target_value <= 2.6:
                    diselenide_bond_type = 'Se...Se'
                    diselenide_bond = True
                elif target_value < 2.1:
                    diselenide_bond_type = 'Se...Se (too close!)'
                    diselenide_bond = True
                elif target_value <= 4.2:
                    diselenide_bond_type = 'Se...Se (too far!)'
                    diselenide_bond = True

            elif (atom_id_1_ == 'N' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'N' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 1.9 <= target_value <= 2.1:
                    other_bond_type = 'N...' + metal
                    other_bond = True
                elif target_value < 1.9:
                    other_bond_type = 'N...' + metal + ' (too close!)'
                    other_bond = True
                elif target_value <= 3.2:
                    other_bond_type = 'N...' + metal + ' (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'O' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'O' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.0 <= target_value <= 2.2:
                    other_bond_type = 'O...' + metal
                    other_bond = True
                elif target_value < 2.0:
                    other_bond_type = 'O...' + metal + ' (too close!)'
                    other_bond = True
                elif target_value <= 3.4:
                    other_bond_type = 'O...' + metal + ' (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'P' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'P' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.1 <= target_value <= 2.5:
                    other_bond_type = 'P...' + metal
                    other_bond = True
                elif target_value < 2.1:
                    other_bond_type = 'P...' + metal + ' (too close!)'
                    other_bond = True
                elif target_value <= 4.0:
                    other_bond_type = 'P...' + metal + ' (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'S' and not atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2)) or\
                 (atom_id_2_ == 'S' and not atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.2 <= target_value <= 2.6:
                    other_bond_type = 'S...' + metal
                    other_bond = True
                elif target_value < 2.2:
                    other_bond_type = 'S...' + metal + ' (too close!)'
                    other_bond = True
                elif target_value <= 4.2:
                    other_bond_type = 'S...' + metal + ' (too far!)'
                    other_bond = True

            elif (atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2)) or\
                 (atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.3 <= target_value <= 2.7:
                    other_bond_type = 'Se...' + metal
                    other_bond = True
                elif target_value < 2.3:
                    other_bond_type = 'Se...' + metal + ' (too close!)'
                    other_bond = True
                elif target_value <= 4.4:
                    other_bond_type = 'Se...' + metal + ' (too far!)'
                    other_bond = True

            elif chain_id_1 != chain_id_2:

                for idx, row in enumerate(lp_data):

                    if idx == row_id:
                        continue

                    _chain_id_1 = row[chain_id_1_name]
                    _chain_id_2 = row[chain_id_2_name]
                    _seq_id_1 = row[seq_id_1_name]
                    _seq_id_2 = row[seq_id_2_name]
                    _comp_id_1 = row[comp_id_1_name]
                    _comp_id_2 = row[comp_id_2_name]

                    if _chain_id_1 != _chain_id_2 and _chain_id_1 != chain_id_1 and _chain_id_2 != chain_id_2:

                        if seq_id_1 == _seq_id_1 and comp_id_1 == _comp_id_1 and\
                           seq_id_2 == _seq_id_2 and comp_id_2 == _comp_id_2:
                            symmetry = True
                            break

                        if seq_id_1 == _seq_id_2 and comp_id_1 == _comp_id_2 and\
                           seq_id_2 == _seq_id_1 and comp_id_2 == _comp_id_1:
                            symmetry = True
                            break

        range_of_seq = abs(seq_id_1 - seq_id_2)

        if hydrogen_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_hydrogen_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_hydrogen_bonds'
            else:
                data_type = 'hydrogen_bonds'
            data_type += f'_{hydrogen_bond_type}'
        elif disulfide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_disulfide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_disulfide_bonds'
            else:
                data_type = 'disulfide_bonds'
            data_type += f'_{disulfide_bond_type}'
        elif diselenide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_diselenide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_diselenide_bonds'
            else:
                data_type = 'diselenide_bonds'
            data_type += f'_{diselenide_bond_type}'
        elif other_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_other_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_other_bonds'
            else:
                data_type = 'other_bonds'
            data_type += f'_{other_bond_type}'
        elif symmetry:
            data_type = 'symmetric_constraints'
        elif chain_id_1 != chain_id_2:
            data_type = 'inter-chain_constraints'
        elif range_of_seq == 0:
            data_type = 'intra-residue_constraints'
        elif range_of_seq < 5:

            if file_type == 'nef' or (self.__isNmrAtomName(comp_id_1, atom_id_1) or self.__isNmrAtomName(comp_id_2, atom_id_2)):
                _atom_id_1 = self.__getAtomIdList(comp_id_1, atom_id_1)
                _atom_id_2 = self.__getAtomIdList(comp_id_2, atom_id_2)

                if len(_atom_id_1) > 0 and len(_atom_id_2) > 0:
                    is_sc_atom_1 = _atom_id_1[0] in self.__csStat.getSideChainAtoms(comp_id_1)
                    is_sc_atom_2 = _atom_id_2[0] in self.__csStat.getSideChainAtoms(comp_id_2)

                    if is_sc_atom_1:
                        is_bb_atom_1 = False
                    else:
                        is_bb_atom_1 = _atom_id_1[0] in self.__csStat.getBackBoneAtoms(comp_id_1)

                    if is_sc_atom_2:
                        is_bb_atom_2 = False
                    else:
                        is_bb_atom_2 = _atom_id_2[0] in self.__csStat.getBackBoneAtoms(comp_id_2)

                else:
                    is_bb_atom_1 = False
                    is_bb_atom_2 = False
                    is_sc_atom_1 = False
                    is_sc_atom_2 = False

            else:
                is_sc_atom_1 = atom_id_1 in self.__csStat.getSideChainAtoms(comp_id_1)
                is_sc_atom_2 = atom_id_2 in self.__csStat.getSideChainAtoms(comp_id_2)

                if is_sc_atom_1:
                    is_bb_atom_1 = False
                else:
                    is_bb_atom_1 = atom_id_1 in self.__csStat.getBackBoneAtoms(comp_id_1)

                if is_sc_atom_2:
                    is_bb_atom_2 = False
                else:
                    is_bb_atom_2 = atom_id_2 in self.__csStat.getBackBoneAtoms(comp_id_2)

            is_bb_bb = is_bb_atom_1 and is_bb_atom_2
            is_bb_sc = (is_bb_atom_1 and is_sc_atom_2) or (is_sc_atom_1 and is_bb_atom_2)
            is_sc_sc = is_sc_atom_1 and is_sc_atom_2

            if range_of_seq == 1:
                data_type = 'sequential_constraints'
            else:
                data_type = 'medium_range_constraints'

            if is_bb_bb:
                data_type += '_backbone-backbone'
            elif is_bb_sc:
                data_type += '_backbone-sidechain'
            elif is_sc_sc:
                data_type += '_sidechain-sidechain'
        else:
            data_type = 'long_range_constraints'

        return data_type

    def __calculateStatsOfDihedralRestraint(self, file_list_id, lp_data, conflict_id_set, inconsistent, redundant, ent):
        """ Calculate statistics of dihedral angle restraints.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        index_tag = self.index_tags[file_type]['dihed_restraint']
        item_names = self.potential_items[file_type]['dihed_restraint']
        target_value_name = item_names['target_value']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']

        dh_item_names = self.item_names_in_dh_loop[file_type]
        combination_id_name = dh_item_names['combination_id']
        chain_id_1_name = dh_item_names['chain_id_1']
        chain_id_2_name = dh_item_names['chain_id_2']
        chain_id_3_name = dh_item_names['chain_id_3']
        chain_id_4_name = dh_item_names['chain_id_4']
        seq_id_1_name = dh_item_names['seq_id_1']
        seq_id_2_name = dh_item_names['seq_id_2']
        seq_id_3_name = dh_item_names['seq_id_3']
        seq_id_4_name = dh_item_names['seq_id_4']
        comp_id_1_name = dh_item_names['comp_id_1']
        comp_id_2_name = dh_item_names['comp_id_2']
        comp_id_3_name = dh_item_names['comp_id_3']
        comp_id_4_name = dh_item_names['comp_id_4']
        atom_id_1_name = dh_item_names['atom_id_1']
        atom_id_2_name = dh_item_names['atom_id_2']
        atom_id_3_name = dh_item_names['atom_id_3']
        atom_id_4_name = dh_item_names['atom_id_4']
        angle_type_name = dh_item_names['angle_type']
        weight_name = self.weight_tags[file_type]['dihed_restraint']
        id_tag = self.consist_id_tags[file_type]['dihed_restraint']

        try:

            count = {}
            comb_count = {}
            inco_count = {}
            redu_count = {}
            polymer_types = {}
            weights = {}
            potential_types = {}
            set_id = set()

            phi_list = []
            psi_list = []
            chi1_list = []
            chi2_list = []
            value_per_residue = []

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is not None:

                for s in polymer_sequence:
                    struct_conf = self.__extractCoordStructConf(s['chain_id'], s['seq_id'])
                    value_per_residue.append({'chain_id': s['chain_id'], 'seq_id': s['seq_id'], 'comp_id': s['comp_id'],
                                              'struct_conf': struct_conf})

            for row in lp_data:
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)

                target_value = row.get(target_value_name)

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    else:
                        continue

                target_value = float(f"{target_value:.1f}")

                while target_value > 180.0:
                    target_value -= 360.0
                while target_value < -180.0:
                    target_value += 360.0

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    lower_limit = row[lower_limit_name]
                    upper_limit = row[upper_limit_name]

                    while lower_limit - target_value > 180.0:
                        lower_limit -= 360.0
                    while lower_limit - target_value < -180.0:
                        lower_limit += 360.0

                    while upper_limit - target_value > 180.0:
                        upper_limit -= 360.0
                    while upper_limit - target_value < -180.0:
                        upper_limit += 360.0

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    lower_limit = row[lower_linear_limit_name]
                    upper_limit = row[upper_linear_limit_name]

                    while lower_limit - target_value > 180.0:
                        lower_limit -= 360.0
                    while lower_limit - target_value < -180.0:
                        lower_limit += 360.0

                    while upper_limit - target_value > 180.0:
                        upper_limit -= 360.0
                    while upper_limit - target_value < -180.0:
                        upper_limit += 360.0

                else:
                    lower_limit = None
                    upper_limit = None

                data_type = row[angle_type_name]

                if data_type == 'PPA':
                    continue

                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                chain_id_3 = row[chain_id_3_name]
                chain_id_4 = row[chain_id_4_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]
                seq_id_3 = row[seq_id_3_name]
                seq_id_4 = row[seq_id_4_name]
                comp_id_1 = row[comp_id_1_name]
                comp_id_2 = row[comp_id_2_name]
                comp_id_3 = row[comp_id_3_name]
                comp_id_4 = row[comp_id_4_name]
                atom_id_1 = row[atom_id_1_name]
                atom_id_2 = row[atom_id_2_name]
                atom_id_3 = row[atom_id_3_name]
                atom_id_4 = row[atom_id_4_name]
                weight = row.get(weight_name)
                set_id.add(row[id_tag])

                peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                data_type =\
                    self.__getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                      chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                                      chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4)

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in emptyValue):
                    if data_type in comb_count:
                        comb_count[data_type] += 1
                    else:
                        comb_count[data_type] = 1

                if index is not None:

                    if index in inconsistent:
                        if data_type in inco_count:
                            inco_count[data_type] += 1
                        else:
                            inco_count[data_type] = 1

                    if index in redundant:
                        if data_type in redu_count:
                            redu_count[data_type] += 1
                        else:
                            redu_count[data_type] = 1

                if peptide:
                    if 'protein' in polymer_types:
                        polymer_types['protein'] += 1
                    else:
                        polymer_types['protein'] = 1

                if nucleotide:
                    if 'nucleic_acid' in polymer_types:
                        polymer_types['nucleic_acid'] += 1
                    else:
                        polymer_types['nucleic_acid'] = 1

                if carbohydrate:
                    if 'carbohydrate' in polymer_types:
                        polymer_types['carbohydrate'] += 1
                    else:
                        polymer_types['carbohydrate'] = 1

                if not peptide and not nucleotide and not carbohydrate:
                    if 'other' in polymer_types:
                        polymer_types['other'] += 1
                    else:
                        polymer_types['other'] = 1

                seq_ids = []
                seq_ids.append(seq_id_1)
                seq_ids.append(seq_id_2)
                seq_ids.append(seq_id_3)
                seq_ids.append(seq_id_4)
                comp_ids = []
                comp_ids.append(comp_id_1)
                comp_ids.append(comp_id_2)
                comp_ids.append(comp_id_3)
                comp_ids.append(comp_id_4)

                seq_id_common = collections.Counter(seq_ids).most_common()
                comp_id_common = collections.Counter(comp_ids).most_common()

                if data_type.startswith('phi_'):
                    phi = {}
                    phi['chain_id'] = chain_id_1
                    phi['seq_id'] = seq_id_common[0][0]
                    phi['comp_id'] = comp_id_common[0][0]
                    phi['value'] = target_value
                    phi['error'] = None if lower_limit is None or upper_limit is None else [lower_limit, upper_limit]
                    phi_list.append(phi)

                elif data_type.startswith('psi_'):
                    psi = {}
                    psi['chain_id'] = chain_id_1
                    psi['seq_id'] = seq_id_common[0][0]
                    psi['comp_id'] = comp_id_common[0][0]
                    psi['value'] = target_value
                    psi['error'] = None if lower_limit is None or upper_limit is None else [lower_limit, upper_limit]
                    psi_list.append(psi)

                elif data_type.startswith('chi1_'):
                    chi1 = {}
                    chi1['chain_id'] = chain_id_1
                    chi1['seq_id'] = seq_id_1
                    chi1['comp_id'] = comp_id_1
                    chi1['value'] = target_value
                    chi1['error'] = None if lower_limit is None or upper_limit is None else [lower_limit, upper_limit]
                    chi1_list.append(chi1)

                elif data_type.startswith('chi2_'):
                    chi2 = {}
                    chi2['chain_id'] = chain_id_1
                    chi2['seq_id'] = seq_id_1
                    chi2['comp_id'] = comp_id_1
                    chi2['value'] = target_value
                    chi2['error'] = None if lower_limit is None or upper_limit is None else [lower_limit, upper_limit]
                    chi2_list.append(chi2)

                # detect weight

                if weight is not None:
                    if data_type not in weights:
                        weights[data_type] = []
                    weights[data_type].append(weight)

                # detect potential type

                # targe_value = row.get(target_value_name)
                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'parabolic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if polymer_sequence is not None:

                    # value per residue

                    for c in value_per_residue:
                        if data_type not in c:
                            c[data_type] = [None] * len(c['seq_id'])
                        if c['chain_id'] == chain_id_1 and target_value is not None and seq_id_common[0][0] in c['seq_id']:
                            b = c['seq_id'].index(seq_id_common[0][0])
                            if c[data_type][b] is None:
                                c[data_type][b] = float(target_value)
                            else:
                                j = 2
                                while True:
                                    _data_type = data_type + '_' + str(j)
                                    if _data_type not in c:
                                        c[_data_type] = [None] * len(c['seq_id'])
                                    if c[_data_type][b] is None:
                                        c[_data_type][b] = float(target_value)
                                        break
                                    j += 1

            if len(count) > 0:
                ent['number_of_constraints'] = count
                ent['number_of_constraint_sets'] = len(set_id)
                if len(comb_count) > 0:
                    ent['number_of_combined_constraints'] = comb_count
                if len(inco_count) > 0:
                    ent['number_of_inconsistent_constraints'] = inco_count
                if len(redu_count) > 0:
                    ent['number_of_redundant_constraints'] = redu_count
                ent['constraints_per_polymer_type'] = polymer_types
                if polymer_sequence is not None:
                    ent['constraints_per_residue'] = value_per_residue
                if len(weights) > 0:
                    _weights = {}
                    for k, v in weights.items():
                        _weights[k] = collections.Counter(v).most_common()
                    ent['weight_of_constraints'] = _weights
                if len(potential_types) > 0:
                    _potential_types = {}
                    for k, v in potential_types.items():
                        _potential_types[k] = collections.Counter(v).most_common()
                    ent['potential_type_of_constraints'] = _potential_types

            if 'phi_angle_constraints' in count and 'psi_angle_constraints' in count:

                phi_psi_value = {}
                phi_psi_error = {}

                for phi in phi_list:

                    comp_id = phi['comp_id']

                    for psi in [psi for psi in psi_list if psi['chain_id'] == phi['chain_id'] and psi['seq_id'] == phi['seq_id']]:

                        if comp_id not in phi_psi_value:
                            phi_psi_value[comp_id] = []

                        phi_psi_value[comp_id].append([phi['value'], psi['value'], phi['chain_id'] + ':' + str(phi['seq_id']) + ':' + phi['comp_id']])

                        if (phi['error'] is not None) or (psi['error'] is not None):

                            if comp_id not in phi_psi_error:
                                phi_psi_error[comp_id] = []

                            phi_psi_error[comp_id].append([phi['value'], psi['value'],
                                                           None if phi['error'] is None else phi['error'][0],
                                                           None if phi['error'] is None else phi['error'][1],
                                                           None if psi['error'] is None else psi['error'][0],
                                                           None if psi['error'] is None else psi['error'][1]])

                if len(phi_psi_value) > 0:

                    phi_psi_plot = {}

                    phi_psi_plot['values'] = phi_psi_value

                    if len(phi_psi_error) > 0:
                        phi_psi_plot['errors'] = phi_psi_error

                    ent['phi_psi_plot'] = phi_psi_plot

            if 'chi1_angle_constraints' in count and 'chi2_angle_constraints' in count:

                chi1_chi2_value = {}
                chi1_chi2_error = {}

                for chi1 in chi1_list:

                    comp_id = chi1['comp_id']

                    for chi2 in [chi2 for chi2 in chi2_list if chi2['chain_id'] == chi1['chain_id'] and chi2['seq_id'] == chi1['seq_id']]:

                        if comp_id not in chi1_chi2_value:
                            chi1_chi2_value[comp_id] = []

                        chi1_chi2_value[comp_id].append([chi1['value'], chi2['value'], chi1['chain_id'] + ':' + str(chi1['seq_id']) + ':' + chi1['comp_id']])

                        if (chi1['error'] is not None) or (chi2['error'] is not None):

                            if comp_id not in chi1_chi2_error:
                                chi1_chi2_error[comp_id] = []

                            chi1_chi2_error[comp_id].append([chi1['value'], chi2['value'],
                                                            None if chi1['error'] is None else chi1['error'][0],
                                                            None if chi1['error'] is None else chi1['error'][1],
                                                            None if chi2['error'] is None else chi2['error'][0],
                                                            None if chi2['error'] is None else chi2['error'][1]])

                if len(chi1_chi2_value) > 0:

                    chi1_chi2_plot = {}

                    chi1_chi2_plot['values'] = chi1_chi2_value

                    if len(chi1_chi2_error) > 0:
                        chi1_chi2_plot['errors'] = chi1_chi2_error

                    ent['chi1_chi2_plot'] = chi1_chi2_plot

            if conflict_id_set is not None:

                max_inclusive = ANGLE_UNCERT_MAX

                max_val = 0.0
                min_val = 0.0

                dihed_ann = []

                for id_set in conflict_id_set:
                    len_id_set = len(id_set)

                    if len_id_set < 2:
                        continue

                    for i in range(len_id_set - 1):

                        for j in range(i + 1, len_id_set):
                            row_1 = lp_data[id_set[i]]
                            row_2 = lp_data[id_set[j]]

                            target_value_1 = row_1.get(target_value_name)

                            if target_value_1 is None:

                                if has_key_value(row_1, lower_limit_name)\
                                        and has_key_value(row_1, upper_limit_name):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif has_key_value(row_1, lower_linear_limit_name)\
                                        and has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif has_key_value(row_1, upper_limit_name):
                                    target_value_1 = row_1[upper_limit_name]

                                elif has_key_value(row_1, lower_linear_limit_name):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif has_key_value(row_1, lower_limit_name):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2.get(target_value_name)

                            if target_value_2 is None:

                                if has_key_value(row_2, lower_limit_name)\
                                        and has_key_value(row_2, upper_limit_name):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif has_key_value(row_2, lower_linear_limit_name)\
                                        and has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif has_key_value(row_2, upper_limit_name):
                                    target_value_2 = row_2[upper_limit_name]

                                elif has_key_value(row_2, lower_linear_limit_name):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif has_key_value(row_2, lower_limit_name):
                                    target_value_2 = row_2[lower_limit_name]

                            if target_value_1 is None or target_value_2 is None:
                                continue

                            while target_value_1 > 180.0:
                                target_value_1 -= 360.0
                            while target_value_1 < -180.0:
                                target_value_1 += 360.0

                            while target_value_2 > 180.0:
                                target_value_2 -= 360.0
                            while target_value_2 < -180.0:
                                target_value_2 += 360.0

                            if target_value_1 == target_value_2:
                                continue

                            discrepancy = abs(target_value_1 - target_value_2)

                            if discrepancy > 180.0:
                                if target_value_1 < target_value_2:
                                    discrepancy = abs(target_value_1 - (target_value_2 - 360.0))
                                if target_value_1 > target_value_2:
                                    discrepancy = abs(target_value_1 - (target_value_2 + 360.0))

                            chain_id_1 = row_1[chain_id_1_name]
                            chain_id_2 = row_1[chain_id_2_name]
                            chain_id_3 = row_1[chain_id_3_name]
                            chain_id_4 = row_1[chain_id_4_name]
                            seq_id_1 = row_1[seq_id_1_name]
                            seq_id_2 = row_1[seq_id_2_name]
                            seq_id_3 = row_1[seq_id_3_name]
                            seq_id_4 = row_1[seq_id_4_name]
                            comp_id_1 = row_1[comp_id_1_name]
                            atom_id_1 = row_1[atom_id_1_name]
                            atom_id_2 = row_1[atom_id_2_name]
                            atom_id_3 = row_1[atom_id_3_name]
                            atom_id_4 = row_1[atom_id_4_name]
                            data_type = row_1[angle_type_name]

                            peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                            data_type = self.__getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                          chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                                                          chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4)[0]

                            if data_type.startswith('phi') or data_type.startswith('psi') or data_type.startswith('omega'):

                                max_val = max(max_val, discrepancy)

                                if discrepancy > max_inclusive * self.inconsist_over_conflicted:
                                    ann = {}
                                    ann['level'] = 'conflicted' if discrepancy > max_inclusive else 'inconsistent'
                                    ann['chain_id'] = row_1[chain_id_2_name]
                                    ann['seq_id'] = row_1[seq_id_2_name]
                                    ann['comp_id'] = row_1[comp_id_2_name]
                                    ann['atom_id_1'] = row_1[atom_id_1_name]
                                    ann['atom_id_2'] = row_1[atom_id_2_name]
                                    ann['atom_id_3'] = row_1[atom_id_3_name]
                                    ann['atom_id_4'] = row_1[atom_id_4_name]
                                    ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                    dihed_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals = []
                    count_of_vals = []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_1 = lp_data[id_set[i]]
                                    row_2 = lp_data[id_set[j]]

                                    target_value_1 = row_1.get(target_value_name)

                                    if target_value_1 is None:

                                        if has_key_value(row_1, lower_limit_name)\
                                                and has_key_value(row_1, upper_limit_name):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif has_key_value(row_1, lower_linear_limit_name)\
                                                and has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif has_key_value(row_1, upper_limit_name):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif has_key_value(row_1, lower_linear_limit_name):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif has_key_value(row_1, lower_limit_name):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2.get(target_value_name)

                                    if target_value_2 is None:

                                        if has_key_value(row_2, lower_limit_name)\
                                                and has_key_value(row_2, upper_limit_name):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif has_key_value(row_2, lower_linear_limit_name)\
                                                and has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif has_key_value(row_2, upper_limit_name):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif has_key_value(row_2, lower_linear_limit_name):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif has_key_value(row_2, lower_limit_name):
                                            target_value_2 = row_2[lower_limit_name]

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if target_value_1 is None or target_value_2 is None:
                                        redundant = False
                                        continue

                                    while target_value_1 > 180.0:
                                        target_value_1 -= 360.0
                                    while target_value_1 < -180.0:
                                        target_value_1 += 360.0

                                    while target_value_2 > 180.0:
                                        target_value_2 -= 360.0
                                    while target_value_2 < -180.0:
                                        target_value_2 += 360.0

                                    if target_value_1 == target_value_2:
                                        continue

                                    redundant = False

                                    discrepancy = abs(target_value_1 - target_value_2)

                                    if discrepancy < v or discrepancy >= v + scale:
                                        continue

                                    chain_id_1 = row_1[chain_id_1_name]
                                    chain_id_2 = row_1[chain_id_2_name]
                                    chain_id_3 = row_1[chain_id_3_name]
                                    chain_id_4 = row_1[chain_id_4_name]
                                    seq_id_1 = row_1[seq_id_1_name]
                                    seq_id_2 = row_1[seq_id_2_name]
                                    seq_id_3 = row_1[seq_id_3_name]
                                    seq_id_4 = row_1[seq_id_4_name]
                                    comp_id_1 = row_1[comp_id_1_name]
                                    atom_id_1 = row_1[atom_id_1_name]
                                    atom_id_2 = row_1[atom_id_2_name]
                                    atom_id_3 = row_1[atom_id_3_name]
                                    atom_id_4 = row_1[atom_id_4_name]

                                    peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                                    data_type = self.__getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                                  chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                                                                  chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4)[0]

                                    if data_type in _count:
                                        _count[data_type] += 1
                                    else:
                                        _count[data_type] = 1

                            if 0.0 <= v < scale and redundant:

                                chain_id_1 = row_1[chain_id_1_name]
                                chain_id_2 = row_1[chain_id_2_name]
                                chain_id_3 = row_1[chain_id_3_name]
                                chain_id_4 = row_1[chain_id_4_name]
                                seq_id_1 = row_1[seq_id_1_name]
                                seq_id_2 = row_1[seq_id_2_name]
                                seq_id_3 = row_1[seq_id_3_name]
                                seq_id_4 = row_1[seq_id_4_name]
                                comp_id_1 = row_1[comp_id_1_name]
                                atom_id_1 = row_1[atom_id_1_name]
                                atom_id_2 = row_1[atom_id_2_name]
                                atom_id_3 = row_1[atom_id_3_name]
                                atom_id_4 = row_1[atom_id_4_name]

                                peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                                data_type = self.__getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                              chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                                                              chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4)[0]

                                if data_type in _count:
                                    _count[data_type] += 1
                                else:
                                    _count[data_type] = 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals, 'number_of_values': transposed, 'annotations': dihed_ann}

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfDihedralRestraint() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfDihedralRestraint() ++ Error  - {str(e)}\n")

    def __getTypeOfDihedralRestraint(self, data_type, peptide, nucleotide, carbohydrate,  # pylint: disable=no-self-use
                                     chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2,
                                     chain_id_3, seq_id_3, atom_id_3, chain_id_4, seq_id_4, atom_id_4):
        """ Return type of dihedral angle restraint.
        """

        if data_type in emptyValue:
            atom1 = {'chain_id': chain_id_1,
                     'seq_id': seq_id_1,
                     'atom_id': atom_id_1}
            atom2 = {'chain_id': chain_id_2,
                     'seq_id': seq_id_2,
                     'atom_id': atom_id_2}
            atom3 = {'chain_id': chain_id_3,
                     'seq_id': seq_id_3,
                     'atom_id': atom_id_3}
            atom4 = {'chain_id': chain_id_4,
                     'seq_id': seq_id_4,
                     'atom_id': atom_id_4}

            data_type = getTypeOfDihedralRestraint(peptide, nucleotide, carbohydrate, [atom1, atom2, atom3, atom4])

            if data_type is not None:
                data_type = data_type.lower()

            if data_type in emptyValue:
                data_type = 'undefined'

        else:
            data_type = data_type.lower()

        if not data_type.endswith('_angle_constraints'):
            data_type += '_angle_constraints'

        return data_type

    def __calculateStatsOfRdcRestraint(self, file_list_id, lp_data, conflict_id_set, inconsistent, redundant, ent):
        """ Calculate statistics of RDC restraints.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        index_tag = self.index_tags[file_type]['rdc_restraint']
        item_names = self.potential_items[file_type]['rdc_restraint']
        target_value_name = item_names['target_value']
        if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
            target_value_name = item_names['target_value_alt']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']

        try:

            max_val = 0.0
            min_val = 0.0

            max_val_ = -100.0
            min_val_ = 100.0

            for row in lp_data:
                target_value = row.get(target_value_name)

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    elif has_key_value(row, upper_linear_limit_name):
                        target_value = row[upper_linear_limit_name]

                    elif has_key_value(row, upper_limit_name):
                        target_value = row[upper_limit_name]

                    elif has_key_value(row, lower_linear_limit_name):
                        target_value = row[lower_linear_limit_name]

                    elif has_key_value(row, lower_limit_name):
                        target_value = row[lower_limit_name]

                    else:
                        continue

                max_val = max(max_val, target_value)
                min_val = min(min_val, target_value)

                max_val_ = max(max_val_, target_value)
                min_val_ = min(min_val_, target_value)

            item_names = self.item_names_in_rdc_loop[file_type]
            combination_id_name = item_names['combination_id']
            chain_id_1_name = item_names['chain_id_1']
            # chain_id_2_name = item_names['chain_id_2']
            seq_id_1_name = item_names['seq_id_1']
            # seq_id_2_name = item_names['seq_id_2']
            comp_id_1_name = item_names['comp_id_1']
            # comp_id_2_name = item_names['comp_id_2']
            atom_id_1_name = item_names['atom_id_1']
            atom_id_2_name = item_names['atom_id_2']
            weight_name = self.weight_tags[file_type]['rdc_restraint']
            id_tag = self.consist_id_tags[file_type]['rdc_restraint']

            count = {}
            comb_count = {}
            inco_count = {}
            redu_count = {}
            weights = {}
            potential_types = {}
            set_id = set()

            value_per_residue = []

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is not None:

                for s in polymer_sequence:
                    struct_conf = self.__extractCoordStructConf(s['chain_id'], s['seq_id'])
                    value_per_residue.append({'chain_id': s['chain_id'], 'seq_id': s['seq_id'], 'comp_id': s['comp_id'],
                                              'struct_conf': struct_conf})

            for row in lp_data:
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)

                chain_id_1 = row[chain_id_1_name]
                seq_id_1 = row[seq_id_1_name]
                atom_id_1 = row[atom_id_1_name]
                atom_id_2 = row[atom_id_2_name]
                weight = row.get(weight_name)
                set_id.add(row[id_tag])

                data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in emptyValue):
                    if data_type in comb_count:
                        comb_count[data_type] += 1
                    else:
                        comb_count[data_type] = 1

                if index is not None:

                    if index in inconsistent:
                        if data_type in inco_count:
                            inco_count[data_type] += 1
                        else:
                            inco_count[data_type] = 1

                    if index in redundant:
                        if data_type in redu_count:
                            redu_count[data_type] += 1
                        else:
                            redu_count[data_type] = 1

                # detect weight

                if weight is not None:
                    if data_type not in weights:
                        weights[data_type] = []
                    weights[data_type].append(weight)

                # detect potential type

                targe_value = row.get(target_value_name)
                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'parabolic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if polymer_sequence is not None:

                    # value per residue

                    for c in value_per_residue:
                        if data_type not in c:
                            c[data_type] = [None] * len(c['seq_id'])
                        if c['chain_id'] == chain_id_1 and targe_value is not None and seq_id_1 in c['seq_id']:
                            b = c['seq_id'].index(seq_id_1)
                            if c[data_type][b] is None:
                                c[data_type][b] = float(targe_value)
                            else:
                                j = 2
                                while True:
                                    _data_type = data_type + '_' + str(j)
                                    if _data_type not in c:
                                        c[_data_type] = [None] * len(c['seq_id'])
                                    if c[_data_type][b] is None:
                                        c[_data_type][b] = float(target_value)
                                        break
                                    j += 1

            if len(count) == 0:
                return

            ent['number_of_constraints'] = count
            ent['number_of_constraint_sets'] = len(set_id)
            if len(comb_count) > 0:
                ent['number_of_combined_constraints'] = comb_count
            if len(inco_count) > 0:
                ent['number_of_inconsistent_constraints'] = inco_count
            if len(redu_count) > 0:
                ent['number_of_redundant_constraints'] = redu_count
            if polymer_sequence is not None:
                ent['constraints_per_residue'] = value_per_residue
            ent['range'] = {'max_value': float(f'{max_val_:.2f}'), 'min_value': float(f'{min_val_:.2f}')}
            if len(weights) > 0:
                _weights = {}
                for k, v in weights.items():
                    _weights[k] = collections.Counter(v).most_common()
                ent['weight_of_constraints'] = _weights
            if len(potential_types) > 0:
                _potential_types = {}
                for k, v in potential_types.items():
                    _potential_types[k] = collections.Counter(v).most_common()
                ent['potential_type_of_constraints'] = _potential_types

            target_scale = (max_val - min_val) / 12.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals = []
            count_of_vals = []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = 0

                for row in lp_data:
                    target_value = row.get(target_value_name)

                    if target_value is None:

                        if has_key_value(row, lower_limit_name)\
                                and has_key_value(row, upper_limit_name):
                            target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                        elif has_key_value(row, lower_linear_limit_name)\
                                and has_key_value(row, upper_linear_limit_name):
                            target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                        elif has_key_value(row, upper_linear_limit_name):
                            target_value = row[upper_linear_limit_name]

                        elif has_key_value(row, upper_limit_name):
                            target_value = row[upper_limit_name]

                        elif has_key_value(row, lower_linear_limit_name):
                            target_value = row[lower_linear_limit_name]

                        elif has_key_value(row, lower_limit_name):
                            target_value = row[lower_limit_name]

                        else:
                            continue

                    if target_value < v or target_value >= v + scale:
                        continue

                    atom_id_1 = row[atom_id_1_name]
                    atom_id_2 = row[atom_id_2_name]

                    data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                    if data_type in _count:
                        _count[data_type] += 1
                    else:
                        _count[data_type] = 1

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                max_inclusive = RDC_UNCERT_MAX

                max_val = 0.0
                min_val = 0.0

                rdc_ann = []

                for id_set in conflict_id_set:
                    len_id_set = len(id_set)

                    if len_id_set < 2:
                        continue

                    for i in range(len_id_set - 1):

                        for j in range(i + 1, len_id_set):
                            row_1 = lp_data[id_set[i]]
                            row_2 = lp_data[id_set[j]]

                            target_value_1 = row_1.get(target_value_name)

                            if target_value_1 is None:

                                if has_key_value(row_1, lower_limit_name)\
                                        and has_key_value(row_1, upper_limit_name):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif has_key_value(row_1, lower_linear_limit_name)\
                                        and has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_1, upper_linear_limit_name):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif has_key_value(row_1, upper_limit_name):
                                    target_value_1 = row_1[upper_limit_name]

                                elif has_key_value(row_1, lower_linear_limit_name):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif has_key_value(row_1, lower_limit_name):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2.get(target_value_name)

                            if target_value_2 is None:

                                if has_key_value(row_2, lower_limit_name)\
                                        and has_key_value(row_2, upper_limit_name):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif has_key_value(row_2, lower_linear_limit_name)\
                                        and has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif has_key_value(row_2, upper_linear_limit_name):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif has_key_value(row_2, upper_limit_name):
                                    target_value_2 = row_2[upper_limit_name]

                                elif has_key_value(row_2, lower_linear_limit_name):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif has_key_value(row_2, lower_limit_name):
                                    target_value_2 = row_2[lower_limit_name]

                            if target_value_1 is None or target_value_2 is None:
                                continue

                            if target_value_1 == target_value_2:
                                continue

                            discrepancy = abs(target_value_1 - target_value_2)

                            max_val = max(max_val, discrepancy)

                            if discrepancy > max_inclusive * self.inconsist_over_conflicted:
                                ann = {}
                                ann['level'] = 'conflicted' if discrepancy > max_inclusive else 'inconsistent'
                                ann['chain_id'] = row_1[chain_id_1_name]
                                ann['seq_id'] = row_1[seq_id_1_name]
                                ann['comp_id'] = row_1[comp_id_1_name]
                                ann['atom_id_1'] = row_1[atom_id_1_name]
                                ann['atom_id_2'] = row_1[atom_id_2_name]
                                ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                rdc_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals = []
                    count_of_vals = []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_1 = lp_data[id_set[i]]
                                    row_2 = lp_data[id_set[j]]

                                    target_value_1 = row_1.get(target_value_name)

                                    if target_value_1 is None:

                                        if has_key_value(row_1, lower_limit_name)\
                                                and has_key_value(row_1, upper_limit_name):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif has_key_value(row_1, lower_linear_limit_name)\
                                                and has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_1, upper_linear_limit_name):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif has_key_value(row_1, upper_limit_name):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif has_key_value(row_1, lower_linear_limit_name):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif has_key_value(row_1, lower_limit_name):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2.get(target_value_name)

                                    if target_value_2 is None:

                                        if has_key_value(row_2, lower_limit_name)\
                                                and has_key_value(row_2, upper_limit_name):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif has_key_value(row_2, lower_linear_limit_name)\
                                                and has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif has_key_value(row_2, upper_linear_limit_name):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif has_key_value(row_2, upper_limit_name):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif has_key_value(row_2, lower_linear_limit_name):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif has_key_value(row_2, lower_limit_name):
                                            target_value_2 = row_2[lower_limit_name]

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if target_value_1 is None or target_value_2 is None:
                                        redundant = False
                                        continue

                                    if target_value_1 == target_value_2:
                                        continue

                                    redundant = False

                                    discrepancy = abs(target_value_1 - target_value_2)

                                    if discrepancy < v or discrepancy >= v + scale:
                                        continue

                                    atom_id_1 = row_1[atom_id_1_name]
                                    atom_id_2 = row_1[atom_id_2_name]

                                    data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                                    _count[data_type] += 1

                            if 0.0 <= v < scale and redundant:

                                atom_id_1 = row_1[atom_id_1_name]
                                atom_id_2 = row_1[atom_id_2_name]

                                data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                                _count[data_type] += 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals, 'number_of_values': transposed, 'annotations': rdc_ann}

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfRdcRestraint() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfRdcRestraint() ++ Error  - {str(e)}\n")

    def __getTypeOfRdcRestraint(self, atom_id_1, atom_id_2):  # pylint: disable=no-self-use
        """ Return type of RDC restraint.
        """

        try:
            iso_number_1 = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id_1[0]][0]
            iso_number_2 = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id_2[0]][0]
        except KeyError:
            pass

        if iso_number_1 < iso_number_2:
            vector_type = atom_id_1 + '-' + atom_id_2
        elif iso_number_2 < iso_number_1:
            vector_type = atom_id_2 + '-' + atom_id_1
        else:
            sorted_atom_ids = sorted([atom_id_1, atom_id_2])
            vector_type = sorted_atom_ids[0] + '-' + sorted_atom_ids[1]

        return vector_type + '_bond_vectors'

    def __calculateStatsOfSpectralPeak(self, file_list_id, sf_framecode, num_dim, lp_data, ent):
        """ Calculate statistics of spectral peaks.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        content_subtype = 'spectral_peak'

        max_dim = num_dim + 1

        item_names = []
        for dim in range(1, max_dim):
            _d = {}
            for k, v in self.item_names_in_pk_loop[file_type].items():
                if '%s' in v:
                    v = v % dim
                _d[k] = v
            item_names.append(_d)

        chain_id_names = []
        seq_id_names = []
        comp_id_names = []
        atom_id_names = []

        try:

            ent['number_of_spectral_dimensions'] = num_dim
            ent['spectral_dim'] = []

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)

            mag_link = []

            if aux_data is not None:
                for sp_dim_trans in aux_data:
                    if file_type == 'nef':
                        if sp_dim_trans['transfer_type'] == 'onebond':  # or sp_dim_trans['transfer_type'].startswith('j') or sp_dim_trans['transfer_type'].startswith('relayed'):
                            dim_1 = sp_dim_trans['dimension_1']
                            dim_2 = sp_dim_trans['dimension_2']
                            mag_link.append((dim_1, dim_2))
                    else:
                        if sp_dim_trans['Type'] == 'onebond':  # or sp_dim_trans['Type'].startswith('j') or sp_dim_trans['Type'].startswith('relayed'):
                            dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                            dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                            mag_link.append((dim_1, dim_2))

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)

            if aux_data is not None:
                for i in range(1, max_dim):
                    for sp_dim in aux_data:
                        sp_freq = None
                        center_point = None
                        under_sampling_type = None
                        encoding_code = None
                        encoded_src_dim_id = None
                        mag_link_id = None
                        if file_type == 'nef':
                            if sp_dim['dimension_id'] != i:
                                continue
                            axis_code = sp_dim['axis_code']
                            atom_type = ''.join(j for j in axis_code if not j.isdigit())
                            atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                            axis_unit = 'Hz' if 'axis_unit' not in sp_dim else sp_dim['axis_unit']
                            first_point = sp_dim.get('value_first_point')
                            sp_width = sp_dim.get('spectral_width')
                            if 'spectrometer_frequency' in sp_dim:
                                sp_freq = sp_dim['spectrometer_frequency']
                            if 'folding' in sp_dim:
                                under_sampling_type = sp_dim['folding']
                        else:
                            if sp_dim['ID'] != i:
                                continue
                            axis_code = sp_dim['Axis_code']
                            atom_type = ''.join(j for j in axis_code if not j.isdigit())
                            atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                            axis_unit = 'Hz' if 'Sweep_width_units' not in sp_dim else sp_dim['Sweep_width_units']
                            first_point = sp_dim.get('Value_first_point')
                            sp_width = sp_dim.get('Sweep_width')
                            if 'Spectrometer_frequency' in sp_dim:
                                sp_freq = sp_dim['Spectrometer_frequency']
                            if 'Under_sampling_type' in sp_dim:
                                under_sampling_type = sp_dim['Under_sampling_type']
                            if 'Center_frequency_offset' in sp_dim:
                                center_point = sp_dim['Center_frequency_offset']
                                if center_point in emptyValue:
                                    center_point = None
                            if 'Encoding_code' in sp_dim:
                                encoding_code = sp_dim['Encoding_code']
                                if encoding_code in emptyValue:
                                    encoding_code = None
                            if 'Encoded_reduced_dimension_ID' in sp_dim:
                                encoded_src_dim_id = sp_dim['Encoded_reduced_dimension_ID']
                                if encoded_src_dim_id in emptyValue:
                                    encoded_src_dim_id = None
                            if 'Magnetization_linkage_ID' in sp_dim:
                                mag_link_id = sp_dim['Magnetization_linkage_ID']
                                if mag_link_id in emptyValue:
                                    mag_link_id = None

                        if sp_freq is not None and sp_freq in emptyValue:
                            sp_freq = None

                        if center_point is None:
                            center_point = None if first_point is None or sp_width is None else (first_point - sp_width / 2.0)

                        if under_sampling_type is not None and under_sampling_type in emptyValue:
                            under_sampling_type = None

                        if under_sampling_type is not None and under_sampling_type in ('circular', 'mirror', 'none'):
                            if under_sampling_type == 'circular':
                                under_sampling_type = 'folded'
                            elif under_sampling_type == 'mirror':
                                under_sampling_type = 'aliased'
                            else:
                                under_sampling_type = 'not observed'

                        if mag_link_id is None:
                            for pair in mag_link:
                                if i in (pair[0], pair[1]):
                                    mag_link_id = mag_link.index(pair) + 1
                                    break

                        spectral_dim = {'id': i, 'atom_type': atom_type, 'atom_isotope_number': atom_isotope_number,
                                        'sweep_width': copy.copy(sp_width), 'sweep_width_units': axis_unit,
                                        'center_frequency_offset': None if center_point is None else float(f"{center_point:.8f}"),
                                        'under_sampling_type': under_sampling_type, 'encoding_code': encoding_code,
                                        'encoded_source_dimension_id': encoded_src_dim_id, 'magnetization_linkage_id': mag_link_id}

                        if axis_unit == 'Hz' and sp_freq is not None and first_point is not None\
                           and center_point is not None and sp_width is not None:
                            first_point /= sp_freq
                            center_point /= sp_freq
                            sp_width /= sp_freq

                        last_point = None if first_point is None or sp_width is None else (first_point - sp_width)

                        if center_point is None or last_point is None:
                            spectral_region = atom_type
                        elif atom_type == 'H':
                            if mag_link_id is None:
                                spectral_region = 'H'
                            else:
                                dim_1, dim_2 = mag_link[mag_link_id - 1]
                                hvy_dim = dim_1 if i == dim_2 else dim_2

                                for _sp_dim in aux_data:
                                    if file_type == 'nef':
                                        if _sp_dim['dimension_id'] != hvy_dim:
                                            continue
                                        _axis_code = _sp_dim['axis_code']
                                        _atom_type = ''.join(j for j in _axis_code if not j.isdigit())
                                    else:
                                        if _sp_dim['ID'] != hvy_dim:
                                            continue
                                        _axis_code = _sp_dim['Axis_code']
                                        _atom_type = ''.join(j for j in _axis_code if not j.isdigit())

                                    if _atom_type == 'C':
                                        _center_point = None
                                        if file_type == 'nef':
                                            _axis_unit = _sp_dim.get('axis_unit', 'Hz')
                                            _first_point = _sp_dim.get('value_first_point')
                                            _sp_width = None if 'axis_unit' not in _sp_dim else _sp_dim.get('spectral_width')
                                            if 'spectrometer_frequency' in _sp_dim:
                                                _sp_freq = _sp_dim['spectrometer_frequency']
                                        else:
                                            _axis_unit = _sp_dim.get('Sweep_width_units', 'Hz')
                                            _first_point = _sp_dim.get('Value_first_point')
                                            _sp_width = None if 'Sweep_width_units' not in _sp_dim else _sp_dim.get('Sweep_width')
                                            if 'Spectrometer_frequency' in _sp_dim:
                                                _sp_freq = _sp_dim['Spectrometer_frequency']
                                            if 'Center_frequency_offset' in _sp_dim:
                                                _center_point = _sp_dim['Center_frequency_offset']
                                                if _center_point in emptyValue:
                                                    _center_point = None

                                        if _sp_freq is not None and _sp_freq in emptyValue:
                                            _sp_freq = None

                                        if _center_point is None:
                                            _center_point = None if _first_point is None or _sp_width is None else (_first_point - _sp_width / 2.0)

                                        if _axis_unit == 'Hz' and _sp_freq is not None and _first_point is not None\
                                           and _center_point is not None and _sp_width is not None:
                                            _first_point /= _sp_freq
                                            _center_point /= _sp_freq
                                            _sp_width /= _sp_freq

                                        _last_point = None if _first_point is None or _sp_width is None else (_first_point - _sp_width)

                                        if _center_point is None or _last_point is None:
                                            spectral_region = 'H'
                                        elif _center_point > 100.0 and _sp_width < 60.0:
                                            spectral_region = 'H-aromatic'
                                        elif _center_point < 20.0 and _sp_width < 30.0:
                                            spectral_region = 'H-methyl'
                                        elif _center_point < 60.0 and _sp_width < 90.0:
                                            spectral_region = 'H-aliphatic'
                                        else:
                                            spectral_region = 'H'

                                    elif _atom_type == 'N':
                                        spectral_region = 'HN'
                                    else:
                                        spectral_region = 'H'
                                    break
                        elif atom_type == 'C':
                            if mag_link_id is None and center_point > 160.0:
                                spectral_region = 'CO'
                            elif center_point > 100.0 and sp_width < 60.0:
                                spectral_region = 'C-aromatic'
                            elif center_point < 20.0 and sp_width < 30.0:
                                spectral_region = 'C-methyl'
                            elif center_point < 60.0 and sp_width < 90.0:
                                spectral_region = 'C-aliphatic'
                            else:
                                spectral_region = 'C'
                        else:
                            spectral_region = atom_type

                        spectral_dim['spectral_region'] = spectral_region

                        ent['spectral_dim'].append(spectral_dim)

                        break

            count = {'assigned_spectral_peaks': 0, 'unassigned_spectral_peaks': 0}

            for j in range(num_dim):
                chain_id_names.append(item_names[j]['chain_id'])
                seq_id_names.append(item_names[j]['seq_id'])
                comp_id_names.append(item_names[j]['comp_id'])
                atom_id_names.append(item_names[j]['atom_id'])

            for row in lp_data:

                has_assignment = True

                for j in range(num_dim):

                    if __pynmrstar_v3__\
                       and not (chain_id_names[j] in row and seq_id_names[j] in row and comp_id_names[j] in row and atom_id_names[j] in row):
                        has_assignment = False
                        break

                    chain_id = row[chain_id_names[j]]
                    seq_id = row[seq_id_names[j]]
                    comp_id = row[comp_id_names[j]]
                    atom_id = row[atom_id_names[j]]

                    if chain_id in emptyValue or seq_id in emptyValue or comp_id in emptyValue or atom_id in emptyValue:
                        has_assignment = False
                        break

                if has_assignment:
                    count['assigned_spectral_peaks'] += 1
                else:
                    count['unassigned_spectral_peaks'] += 1

            ent['number_of_spectral_peaks'] = count

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfSpectralPeak() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfSpectralPeak() ++ Error  - {str(e)}\n")

    def __calculateStatsOfSpectralPeakAlt(self, file_list_id, sf_framecode, num_dim, lp_data, ent):
        """ Calculate statistics of spectral peaks.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if file_type == 'nef':
            return

        content_subtype = 'spectral_peak_alt'

        max_dim = num_dim + 1

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']

        try:

            ent['number_of_spectral_dimensions'] = num_dim
            ent['spectral_dim'] = []

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)

            mag_link = []

            if aux_data is not None:
                for sp_dim_trans in aux_data:
                    if sp_dim_trans['Type'] == 'onebond':  # or sp_dim_trans['Type'].startswith('j') or sp_dim_trans['Type'].startswith('relayed'):
                        dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                        dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                        mag_link.append((dim_1, dim_2))

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)

            if aux_data is not None:
                for i in range(1, max_dim):
                    for sp_dim in aux_data:
                        sp_freq = None
                        center_point = None
                        under_sampling_type = None
                        encoding_code = None
                        encoded_src_dim_id = None
                        mag_link_id = None
                        if sp_dim['ID'] != i:
                            continue
                        axis_code = sp_dim['Axis_code']
                        atom_type = ''.join(j for j in axis_code if not j.isdigit())
                        atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                        axis_unit = 'Hz' if 'Sweep_width_units' not in sp_dim else sp_dim['Sweep_width_units']
                        first_point = sp_dim.get('Value_first_point')
                        sp_width = sp_dim.get('Sweep_width')
                        if 'Spectrometer_frequency' in sp_dim:
                            sp_freq = sp_dim['Spectrometer_frequency']
                        if 'Under_sampling_type' in sp_dim:
                            under_sampling_type = sp_dim['Under_sampling_type']
                        if 'Center_frequency_offset' in sp_dim:
                            center_point = sp_dim['Center_frequency_offset']
                            if center_point in emptyValue:
                                center_point = None
                        if 'Encoding_code' in sp_dim:
                            encoding_code = sp_dim['Encoding_code']
                            if encoding_code in emptyValue:
                                encoding_code = None
                        if 'Encoded_reduced_dimension_ID' in sp_dim:
                            encoded_src_dim_id = sp_dim['Encoded_reduced_dimension_ID']
                            if encoded_src_dim_id in emptyValue:
                                encoded_src_dim_id = None
                        if 'Magnetization_linkage_ID' in sp_dim:
                            mag_link_id = sp_dim['Magnetization_linkage_ID']
                            if mag_link_id in emptyValue:
                                mag_link_id = None

                        if sp_freq is not None and sp_freq in emptyValue:
                            sp_freq = None

                        if center_point is None:
                            center_point = None if first_point is None or sp_width is None else (first_point - sp_width / 2.0)

                        if under_sampling_type is not None and under_sampling_type in emptyValue:
                            under_sampling_type = None

                        if under_sampling_type is not None and under_sampling_type in ('circular', 'mirror', 'none'):
                            if under_sampling_type == 'circular':
                                under_sampling_type = 'folded'
                            elif under_sampling_type == 'mirror':
                                under_sampling_type = 'aliased'
                            else:
                                under_sampling_type = 'not observed'

                        if mag_link_id is None:
                            for pair in mag_link:
                                if i in (pair[0], pair[1]):
                                    mag_link_id = mag_link.index(pair) + 1
                                    break

                        spectral_dim = {'id': i, 'atom_type': atom_type, 'atom_isotope_number': atom_isotope_number,
                                        'sweep_width': copy.copy(sp_width), 'sweep_width_units': axis_unit,
                                        'center_frequency_offset': None if center_point is None else float(f"{center_point:.8f}"),
                                        'under_sampling_type': under_sampling_type, 'encoding_code': encoding_code,
                                        'encoded_source_dimension_id': encoded_src_dim_id, 'magnetization_linkage_id': mag_link_id}

                        if axis_unit == 'Hz' and sp_freq is not None and first_point is not None and center_point is not None and sp_width is not None:
                            first_point /= sp_freq
                            center_point /= sp_freq
                            sp_width /= sp_freq

                        last_point = None if first_point is None or sp_width is None else (first_point - sp_width)

                        if center_point is None or last_point is None:
                            spectral_region = atom_type
                        elif atom_type == 'H':
                            if mag_link_id is None:
                                spectral_region = 'H'
                            else:
                                dim_1, dim_2 = mag_link[mag_link_id - 1]
                                hvy_dim = dim_1 if i == dim_2 else dim_2

                                for _sp_dim in aux_data:
                                    if _sp_dim['ID'] != hvy_dim:
                                        continue
                                    _axis_code = _sp_dim['Axis_code']
                                    _atom_type = ''.join(j for j in _axis_code if not j.isdigit())

                                    if _atom_type == 'C':
                                        _center_point = None
                                        _axis_unit = _sp_dim.get('Sweep_width_units', 'Hz')
                                        _first_point = _sp_dim.get('Value_first_point')
                                        _sp_width = None if 'Sweep_width_units' not in _sp_dim else _sp_dim.get('Sweep_width')
                                        if 'Spectrometer_frequency' in _sp_dim:
                                            _sp_freq = _sp_dim['Spectrometer_frequency']
                                        if 'Center_frequency_offset' in _sp_dim:
                                            _center_point = _sp_dim['Center_frequency_offset']
                                            if _center_point in emptyValue:
                                                _center_point = None

                                        if _sp_freq is not None and _sp_freq in emptyValue:
                                            _sp_freq = None

                                        if _center_point is None:
                                            _center_point = None if _first_point is None or _sp_width is None else (_first_point - _sp_width / 2.0)

                                        if _axis_unit == 'Hz' and _sp_freq is not None and _first_point is not None and _center_point is not None and _sp_width is not None:
                                            _first_point /= _sp_freq
                                            _center_point /= _sp_freq
                                            _sp_width /= _sp_freq

                                        _last_point = None if _first_point is None or _sp_width is None else (_first_point - _sp_width)

                                        if _center_point is None or _last_point is None:
                                            spectral_region = 'H'
                                        elif _center_point > 100.0 and _sp_width < 60.0:
                                            spectral_region = 'H-aromatic'
                                        elif _center_point < 20.0 and _sp_width < 30.0:
                                            spectral_region = 'H-methyl'
                                        elif _center_point < 60.0 and _sp_width < 90.0:
                                            spectral_region = 'H-aliphatic'
                                        else:
                                            spectral_region = 'H'

                                    elif _atom_type == 'N':
                                        spectral_region = 'HN'
                                    else:
                                        spectral_region = 'H'
                                    break
                        elif atom_type == 'C':
                            if mag_link_id is None and center_point > 160.0:
                                spectral_region = 'CO'
                            elif center_point > 100.0 and sp_width < 60.0:
                                spectral_region = 'C-aromatic'
                            elif center_point < 20.0 and sp_width < 30.0:
                                spectral_region = 'C-methyl'
                            elif center_point < 60.0 and sp_width < 90.0:
                                spectral_region = 'C-aliphatic'
                            else:
                                spectral_region = 'C'
                        else:
                            spectral_region = atom_type

                        spectral_dim['spectral_region'] = spectral_region

                        ent['spectral_dim'].append(spectral_dim)

                        break

            count = {'assigned_spectral_peaks': 0, 'unassigned_spectral_peaks': 0}

            aux_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == '_Assigned_peak_chem_shift'), None)

            pk_id_name = 'Peak_ID'
            dim_id_name = 'Spectral_dim_ID'

            pk_id_set = set()

            for row in lp_data:

                has_assignment = aux_data is not None

                pk_id = row['ID']

                if pk_id in pk_id_set:
                    continue

                if has_assignment:

                    for j in range(num_dim):

                        try:
                            k = next(k for k in aux_data if k[pk_id_name] == pk_id and int(k[dim_id_name]) - 1 == j)
                        except StopIteration:
                            has_assignment = False
                            break

                        if __pynmrstar_v3__\
                           and not (chain_id_name in k and seq_id_name in k and comp_id_name in k and atom_id_name in k):
                            has_assignment = False
                            break

                        chain_id = k[chain_id_name]
                        seq_id = k[seq_id_name]
                        comp_id = k[comp_id_name]
                        atom_id = k[atom_id_name]

                        if chain_id in emptyValue or seq_id in emptyValue or comp_id in emptyValue or atom_id in emptyValue:
                            has_assignment = False
                            break

                pk_id_set.add(pk_id)

                if has_assignment:
                    count['assigned_spectral_peaks'] += 1
                else:
                    count['unassigned_spectral_peaks'] += 1

            ent['number_of_spectral_peaks'] = count

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfSpectralPeakAlt() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__calculateStatsOfSpectralPeakAlt() ++ Error  - {str(e)}\n")

    def __extractCoordStructConf(self, nmr_chain_id, nmr_seq_ids):
        """ Extract conformational annotations of coordinate file.
        """

        if nmr_chain_id in self.__nmr_struct_conf:
            return self.__nmr_struct_conf[nmr_chain_id]

        nmr_struct_conf = [None] * len(nmr_seq_ids)

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return nmr_struct_conf

        cif_chain_id = cif_ps['chain_id']

        if 'struct_conf' not in cif_ps:
            return nmr_struct_conf

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return nmr_struct_conf

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id
                       and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            for nmr_seq_id in nmr_seq_ids:

                cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                   in zip(result['ref_seq_id'], result['test_seq_id'])
                                   if ref_seq_id == nmr_seq_id), None)

                if cif_seq_id is None:
                    continue

                if cif_seq_id not in cif_ps['seq_id']:
                    continue

                nmr_struct_conf[nmr_seq_ids.index(nmr_seq_id)] = cif_ps['struct_conf'][cif_ps['seq_id'].index(cif_seq_id)]

        self.__nmr_struct_conf[nmr_chain_id] = nmr_struct_conf

        return nmr_struct_conf

    def __getCoordCompId(self, nmr_chain_id, nmr_seq_id):
        """ Return comp ID of coordinate file for a given NMR sequence.
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return None

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id
                       and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            if cif_seq_id is None:
                return None

            return next((_comp_id for _seq_id, _comp_id
                         in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                         if _seq_id == cif_seq_id), None)

        return None

    def __validateCoordInputSource(self):
        """ Validate coordinate file as secondary input resource.
        """

        file_type = 'pdbx'

        content_type = self.content_type[file_type]

        if self.__parseCoordinate():

            self.report.appendInputSource()

            input_source = self.report.input_sources[-1]

            input_source.setItemValue('file_name', os.path.basename(self.__cifPath))
            input_source.setItemValue('file_type', file_type)
            input_source.setItemValue('content_type', content_type)

            return True

        if self.__entry_id == 'EXTRACT_FROM_COORD':
            self.__entry_id = self.__entry_id__

        return False

    def __parseCoordinate(self):
        """ Parse coordinate file.
        """

        if self.__cifChecked:
            return True

        file_type = 'pdbx'

        if not self.__parseCoordFilePath():

            if 'coordinate_file_path' in self.__inputParamDict:

                err = f"No such {self.__inputParamDict['coordinate_file_path']!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__parseCoordinate() ++ Error  - {err}\n")

            elif not self.__bmrb_only:

                err = f"{self.readable_file_type[file_type]} formatted coordinate file is mandatory."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__parseCoordinate() ++ Error  - {err}\n")

            return False

        file_name = os.path.basename(self.__cifPath)

        try:

            if self.__cifPath is None:

                err = f"{file_name!r} is invalid {self.readable_file_type[file_type]} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__parseCoordinate() ++ Error  - {err}\n")

                return False

            if self.__entry_id == 'EXTRACT_FROM_COORD':
                entry = self.__cR.getDictList('entry')

                if len(entry) == 0 or ('id' not in entry[0]):
                    self.__entry_id = self.__entry_id__
                else:
                    self.__entry_id = entry[0]['id'].strip().replace(' ', '_')  # DAOTHER-9511: replace white space in a datablock name to underscore

            exptl = self.__cR.getDictList('exptl')

            if len(exptl) > 0 and 'method' in exptl[0]:
                self.__exptl_method = exptl[0]['method']

            self.__total_models = 0
            self.__eff_model_ids = []

            ensemble = self.__cR.getDictList('pdbx_nmr_ensemble')

            if self.__trust_pdbx_nmr_ens and len(ensemble) > 0 and 'conformers_submitted_total_number' in ensemble[0]:

                try:
                    self.__total_models = int(ensemble[0]['conformers_submitted_total_number'])
                except ValueError:
                    pass

            if len(ensemble) == 0 or self.__total_models == 0:

                ensemble = self.__cR.getDictList('rcsb_nmr_ensemble')

                if self.__trust_pdbx_nmr_ens and len(ensemble) > 0 and 'conformers_submitted_total_number' in ensemble[0]:

                    try:
                        self.__total_models = int(ensemble[0]['conformers_submitted_total_number'])
                    except ValueError:
                        pass

                else:

                    try:

                        model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                        model_ids = self.__cR.getDictListWithFilter('atom_site',
                                                                    [{'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                                     ])

                        if len(model_ids) > 0:
                            model_ids = set(c['model_id'] for c in model_ids)

                            self.__representative_model_id = min(model_ids)
                            self.__total_models = len(model_ids)
                            self.__eff_model_ids = sorted(model_ids)

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + str(e))
                        self.report.setError()

            if self.__trust_pdbx_nmr_ens and len(ensemble) > 0 and 'representative_conformer' in ensemble[0]:

                try:

                    rep_model_id = int(ensemble[0]['representative_conformer'])

                    if 1 <= rep_model_id <= self.__total_models:
                        self.__representative_model_id = rep_model_id

                except ValueError:
                    pass

            if len(self.__eff_model_ids) == 0:

                try:

                    model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                    model_ids = self.__cR.getDictListWithFilter('atom_site',
                                                                [{'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                                 ])

                    if len(model_ids) > 0:
                        model_ids = set(c['model_id'] for c in model_ids)

                        self.__total_models = len(model_ids)
                        self.__eff_model_ids = sorted(model_ids)

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + str(e))
                    self.report.setError()

            if self.__total_models < 2:

                if not self.__remediation_mode:

                    err = f"Coordinate file has {'no' if self.__total_models == 0 else ('only one' if self.__total_models == 1 else self.__total_models)} model(s). "\
                        "Deposition of minimized average structure must be accompanied with ensemble and must be homogeneous with the ensemble."

                    self.report.error.appendDescription('missing_mandatory_content',
                                                        {'file_name': file_name, 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__parseCoordinate() ++ Error  - {err}\n")

            elif self.__total_models < 5:

                if not self.__remediation_mode:

                    warn = f"Coordinate file has {self.__total_models} models. We encourage you to deposit a sufficient number of models in the ensemble."

                    self.report.warning.appendDescription('encouragement',
                                                          {'file_name': file_name, 'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__parseCoordinate() ++ Warning  - {warn}\n")

            if self.__cR.hasItem('atom_site', 'label_alt_id'):
                alt_ids = self.__cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'label_alt_id', 'type': 'str'}
                                                           ])

                if len(alt_ids) > 0:
                    for a in alt_ids:
                        if a['label_alt_id'] not in emptyValue:
                            self.__representative_alt_id = a['label_alt_id']
                            break

            self.__recvd_nmr_constraints = False
            if self.__cR.hasItem('pdbx_database_status', 'recvd_nmr_constraints'):
                pdbx_database_status = self.__cR.getDictList('pdbx_database_status')
                self.__recvd_nmr_constraints = pdbx_database_status[0]['recvd_nmr_constraints'] == 'Y'
                if not self.__recvd_nmr_constraints and self.__cR.hasItem('pdbx_database_status', 'date_nmr_constraints'):
                    date_nmr_constraints = pdbx_database_status[0]['date_nmr_constraints']
                    if date_nmr_constraints not in emptyValue:
                        self.__recvd_nmr_constraints = True
            if self.__recvd_nmr_constraints:
                self.__remediation_mode = True

            self.__recvd_nmr_data = False
            if self.__cR.hasItem('pdbx_database_status', 'recvd_nmr_data'):
                pdbx_database_status = self.__cR.getDictList('pdbx_database_status')
                self.__recvd_nmr_data = pdbx_database_status[0]['recvd_nmr_data'] == 'Y'

            # DAOTHER-8580: convert working model file if pdbx_poly_seq_scheme category is missing
            # @see: wwpdb.utils.wf.plugins.FormatUtils.pdb2pdbxDepositOp

            if self.__remediation_mode and self.__recvd_nmr_data\
               and not self.__cR.hasCategory('pdbx_poly_seq_scheme') and not self.__cifPath.endswith('~'):

                try:
                    from wwpdb.utils.config.ConfigInfo import ConfigInfo  # pylint: disable=import-outside-toplevel
                    from wwpdb.utils.dp.RcsbDpUtility import RcsbDpUtility  # pylint: disable=import-outside-toplevel
                except ImportError:
                    return False

                try:

                    srcCifPath = self.__cifPath
                    dstCifPath = self.__cifPath + '~'

                    dirPath = os.path.join(self.__dirPath, 'cif2cif')
                    if not os.path.isdir(dirPath):
                        os.makedirs(dirPath)

                    cI = ConfigInfo()
                    siteId = cI.get('SITE_PREFIX')
                    rdU = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self.__verbose, log=self.__lfh)
                    rdU.imp(srcCifPath)
                    rdU.op('annot-cif2cif-dep')
                    rdU.exp(dstCifPath)
                    rdU.cleanup()
                    os.rmdir(dirPath)

                    self.__inputParamDict['coordinate_file_path'] = dstCifPath
                    self.__cifPath = None
                    self.__cifChecked = False

                    return self.__parseCoordinate()

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + str(e))
                    self.report.setError()

                    return False

            self.__cifChecked = True

            if self.__caC is None:
                self.__retrieveCoordAssemblyChecker()

            return True

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordinate() ++ Error  - " + str(e))
            self.report.setError()

            return False

    def __parseCoordFilePath(self):
        """ Parse effective coordinate file path.
        """

        if self.__cifPath is not None:
            return True

        self.__cifHashCode = None

        if 'coordinate_file_path' in self.__inputParamDict:

            fPath = self.__inputParamDict['coordinate_file_path']

            if fPath.endswith('.gz'):

                _fPath = os.path.splitext(fPath)[0]

                if not os.path.exists(_fPath):

                    try:

                        uncompress_gzip_file(fPath, _fPath)

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__parseCoordFilePath() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__parseCoordFilePath() ++ Error  - {str(e)}\n")

                        return False

                fPath = _fPath

            try:

                if self.__dirPath is None:
                    self.__dirPath = os.path.dirname(fPath)

                # rename old chche directory name 'nmr_dp_util' to 'utils_nmr' (temporaly code)
                if self.__sub_dir_name_for_cache != 'nmr_dp_util' and os.path.isdir(os.path.join(self.__dirPath, 'nmr_dp_util')):
                    os.rename(os.path.join(self.__dirPath, 'nmr_dp_util'),
                              os.path.join(self.__dirPath, self.__sub_dir_name_for_cache))

                self.__cacheDirPath = os.path.join(self.__dirPath, self.__sub_dir_name_for_cache)

                if not os.path.isdir(self.__cacheDirPath):
                    os.makedirs(self.__cacheDirPath)

                # move curernt cache files to sub-directory (temporaly code)
                # for cache_file_name in os.listdir(self.__dirPath):
                #     if cache_file_name.endswith('.pkl') and len(cache_file_name) >= 36:
                #         src_path = os.path.join(self.__dirPath, cache_file_name)
                #         dst_path = os.path.join(self.__cacheDirPath, cache_file_name)
                #         if not os.path.exists(dst_path):
                #             shutil.move(src_path, dst_path)

                self.__cifPath = fPath

                if self.__cR.parse(fPath):
                    return True

                # try deposit storage if possible
                if 'proc_coord_file_path' in self.__inputParamDict:

                    fPath = self.__inputParamDict['proc_coord_file_path']

                    self.__cifPath = fPath

                    if self.__cR.parse(fPath):
                        return True

            except Exception:
                pass

            finally:
                self.__symmetric = None
                self.__coord_atom_site = None
                self.__coord_unobs_res = None
                self.__auth_to_label_seq = None
                self.__label_to_auth_seq = None
                self.__coord_tautomer = {}
                self.__coord_rotamer = {}
                self.__coord_near_ring = {}
                self.__coord_near_para_ferro = {}
                self.__coord_bond_length = {}
                self.__caC = None
                self.__ent_asym_id_with_exptl_data = set()
                self.__label_asym_id_with_exptl_data = set()
                self.__auth_asym_ids_with_chem_exch = {}
                self.__auth_seq_ids_with_chem_exch = {}
                self.__nmr_struct_conf = {}
                self.__is_cyclic_polymer = {}
                self.__chain_id_map_for_remediation = {}
                self.__seq_id_map_for_remediation = {}

                self.__cifHashCode = self.__cR.getHashCode()

                self.__coord_atom_site_tags = self.__cR.getItemTags('atom_site')

        return False

    def __detectCoordContentSubType(self):
        """ Detect content subtype of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        if has_key_value(cif_input_source_dic, 'content_subtype'):
            return False

        file_type = cif_input_source_dic['file_type']

        lp_counts = {t: 0 for t in self.cif_content_subtypes}

        for content_subtype in self.cif_content_subtypes:

            lp_category = self.lp_categories[file_type][content_subtype]

            if self.__cR.hasCategory(lp_category):
                lp_counts[content_subtype] = 1

            elif content_subtype != 'branched':

                if content_subtype != 'non_poly':

                    if content_subtype == 'poly_seq' and self.__cR.hasCategory(self.lp_categories[file_type][content_subtype + '_alias']):
                        lp_counts[content_subtype] = 1

                elif self.__cR.hasCategory(self.lp_categories[file_type][content_subtype + '_alias']):
                    lp_counts[content_subtype] = 1

        content_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

        cif_input_source.setItemValue('content_subtype', content_subtypes)

        return True

    def __extractCoordPolymerSequence(self):
        """ Extract reference polymer sequence of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        file_name = cif_input_source_dic['file_name']
        file_type = cif_input_source_dic['file_type']

        if cif_input_source_dic['content_subtype'] is None:
            return False

        content_subtype = 'poly_seq'

        if content_subtype not in cif_input_source_dic['content_subtype']:
            return False

        if has_key_value(cif_input_source_dic, 'polymer_sequence'):
            return False

        alias = False
        lp_category = self.lp_categories[file_type][content_subtype]
        key_items = self.key_items[file_type][content_subtype]

        if self.__cR.hasItem(lp_category, 'pdb_mon_id'):
            _key_items = copy.copy(key_items)
            _key_items.append({'name': 'pdb_mon_id', 'type': 'str', 'alt_name': 'auth_comp_id', 'default-from': 'mon_id'})
            key_items = _key_items

        if self.__cR.hasItem(lp_category, 'auth_mon_id'):
            _key_items = copy.copy(key_items)
            _key_items.append({'name': 'auth_mon_id', 'type': 'str', 'alt_name': 'alt_comp_id', 'default-from': 'mon_id'})
            key_items = _key_items

        if not self.__cR.hasCategory(lp_category):
            alias = True
            lp_category = self.lp_categories[file_type][content_subtype + '_alias']
            key_items = self.key_items[file_type][content_subtype + '_alias']

        try:

            poly_seq = poly_seq_cache_path = None

            if self.__cifHashCode is not None:
                poly_seq_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_poly_seq_full.pkl")
                poly_seq = load_from_pickle(poly_seq_cache_path)

            if poly_seq is None:

                try:
                    poly_seq = self.__cR.getPolymerSequence(lp_category, key_items,
                                                            withStructConf=True, withRmsd=True, alias=alias,
                                                            totalModels=self.__total_models,
                                                            effModelIds=self.__eff_model_ids,
                                                            repAltId=self.__representative_alt_id)
                except KeyError:  # pdbx_PDB_ins_code throws KeyError
                    if content_subtype + ('_ins_alias' if alias else '_ins') in self.key_items[file_type]:
                        key_items = self.key_items[file_type][content_subtype + ('_ins_alias' if alias else '_ins')]
                        poly_seq = self.__cR.getPolymerSequence(lp_category, key_items,
                                                                withStructConf=True, withRmsd=True, alias=alias,
                                                                totalModels=self.__total_models,
                                                                effModelIds=self.__eff_model_ids,
                                                                repAltId=self.__representative_alt_id)
                    else:
                        poly_seq = []

                if len(poly_seq) == 0:
                    return False

                content_subtype = 'branched'

                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__cR.hasCategory(lp_category):

                    key_items = self.key_items[file_type][content_subtype]

                    if self.__cR.hasItem(lp_category, 'pdb_mon_id'):
                        _key_items = copy.copy(key_items)
                        _key_items.append({'name': 'pdb_mon_id', 'type': 'str', 'alt_name': 'auth_comp_id', 'default-from': 'mon_id'})
                        key_items = _key_items

                    if self.__cR.hasItem(lp_category, 'auth_mon_id'):
                        _key_items = copy.copy(key_items)
                        _key_items.append({'name': 'auth_mon_id', 'type': 'str', 'alt_name': 'alt_comp_id', 'default-from': 'mon_id'})
                        key_items = _key_items

                    try:
                        branched_seq = self.__cR.getPolymerSequence(lp_category, key_items,
                                                                    withStructConf=False, withRmsd=False, alias=False,
                                                                    totalModels=self.__total_models,
                                                                    effModelIds=self.__eff_model_ids,
                                                                    repAltId=self.__representative_alt_id)
                        if len(branched_seq) > 0:
                            poly_seq.extend(branched_seq)
                    except Exception:
                        pass

                content_subtype = 'non_poly'

                lp_category = self.lp_categories[file_type][content_subtype]

                if self.__cR.hasCategory(lp_category):

                    key_items = self.key_items[file_type][content_subtype]

                    if self.__cR.hasItem(lp_category, 'pdb_mon_id'):
                        _key_items = copy.copy(key_items)
                        _key_items.append({'name': 'pdb_mon_id', 'type': 'str', 'alt_name': 'auth_comp_id', 'default-from': 'mon_id'})
                        key_items = _key_items

                    if self.__cR.hasItem(lp_category, 'auth_mon_id'):
                        _key_items = copy.copy(key_items)
                        _key_items.append({'name': 'auth_mon_id', 'type': 'str', 'alt_name': 'alt_comp_id', 'default-from': 'mon_id'})
                        key_items = _key_items

                    try:
                        non_poly = self.__cR.getPolymerSequence(lp_category, key_items,
                                                                withStructConf=False, withRmsd=False, alias=False,
                                                                totalModels=self.__total_models,
                                                                effModelIds=self.__eff_model_ids,
                                                                repAltId=self.__representative_alt_id)

                        if len(non_poly) > 0:
                            poly_seq.extend(non_poly)
                    except Exception:
                        pass

                if len(poly_seq) > 0 and poly_seq_cache_path is not None:
                    write_as_pickle(poly_seq, poly_seq_cache_path)

            cif_input_source.setItemValue('polymer_sequence', poly_seq)

            not_superimposed_ensemble = {}
            exactly_overlaid_ensemble = {}
            exactly_overlaid_models = {}

            if not self.__combined_mode and self.__allow_missing_legacy_dist_restraint:  # no exception

                if len(self.__suspended_errors_for_lazy_eval) > 0:
                    for msg in self.__suspended_errors_for_lazy_eval:
                        for k, v in msg.items():
                            self.report.error.appendDescription(k, v)
                            self.report.setError()

                            if k == 'missing_mandatory_content' and 'Deposition of assigned chemical shifts is mandatory' in v['description'] and self.__remediation_mode:
                                dir_path = os.path.dirname(self.__dstPath)

                                touch_file = os.path.join(dir_path, '.entry_without_cs')
                                if not os.path.exists(touch_file):
                                    with open(touch_file, 'w') as ofh:
                                        ofh.write('')

                    self.__suspended_errors_for_lazy_eval = []

                if len(self.__suspended_warnings_for_lazy_eval) > 0:
                    for msg in self.__suspended_warnings_for_lazy_eval:
                        for k, v in msg.items():
                            self.report.warning.appendDescription(k, v)
                            self.report.setWarning()
                    self.__suspended_warnings_for_lazy_eval = []

            for ps in poly_seq:

                if 'type' in ps:

                    poly_type = ps['type']

                    if 'polypeptide' in poly_type:
                        rmsd_label = 'ca_rmsd'

                        if not self.__combined_mode:

                            if len(self.__suspended_errors_for_lazy_eval) > 0:
                                for msg in self.__suspended_errors_for_lazy_eval:
                                    for k, v in msg.items():
                                        self.report.error.appendDescription(k, v)
                                        self.report.setError()
                                self.__suspended_errors_for_lazy_eval = []

                            if len(self.__suspended_warnings_for_lazy_eval) > 0:
                                for msg in self.__suspended_warnings_for_lazy_eval:
                                    for k, v in msg.items():
                                        self.report.warning.appendDescription(k, v)
                                        self.report.setWarning()
                                self.__suspended_warnings_for_lazy_eval = []

                    elif 'ribonucleotide' in poly_type:
                        rmsd_label = 'p_rmsd'
                    else:
                        continue

                    chain_id = ps['chain_id']

                    if rmsd_label in ps and 'well_defined_region' in ps:
                        rmsd = ps[rmsd_label]
                        region = ps['well_defined_region']

                        for r in rmsd:
                            model_id = r['model_id']

                            if 'raw_rmsd_in_well_defined_region' in r and 'rmsd_in_well_defined_region' in r:

                                if r['raw_rmsd_in_well_defined_region'] - r['rmsd_in_well_defined_region'] > self.rmsd_not_superimposed:
                                    rmsd_item = {'model_id': model_id,
                                                 'raw_rmsd': r['raw_rmsd_in_well_defined_region'],
                                                 'rmsd': r['rmsd_in_well_defined_region']}
                                    domain_id = r['domain_id']
                                    domain = next((r for r in region if r['domain_id'] == domain_id), None)
                                    if domain is not None:
                                        rmsd_item['monomers'] = domain['number_of_monomers']
                                        rmsd_item['gaps'] = domain['number_of_gaps']
                                        rmsd_item['core'] = domain['percent_of_core']
                                        rmsd_item['range'] = domain['range_of_seq_id']
                                        if chain_id not in not_superimposed_ensemble:
                                            not_superimposed_ensemble[chain_id] = []
                                        not_superimposed_ensemble[chain_id].append(rmsd_item)

                                if r['rmsd_in_well_defined_region'] < self.rmsd_overlaid_exactly:
                                    domain_id = r['domain_id']
                                    domain = next((r for r in region if r['domain_id'] == domain_id), None)
                                    if domain is not None and domain['mean_rmsd'] < self.rmsd_overlaid_exactly:
                                        region_item = {'monomers': domain['number_of_monomers'],
                                                       'gaps': domain['number_of_gaps'],
                                                       'core': domain['percent_of_core'],
                                                       'mean_rmsd': domain['mean_rmsd'],
                                                       'range': domain['range_of_seq_id']}
                                        exactly_overlaid_ensemble[chain_id] = region_item

                                elif 'exactly_overlaid_model' in r:
                                    domain_id = r['domain_id']
                                    domain = next((r for r in region if r['domain_id'] == domain_id), None)
                                    if domain is not None:
                                        for m in r['exactly_overlaid_model']:
                                            rmsd_item = {'model_id_1': m['ref_model_id'],
                                                         'model_id_2': m['test_model_id'],
                                                         'rmsd': m['rmsd_in_well_defined_region']}
                                            rmsd_item['monomers'] = domain['number_of_monomers']
                                            rmsd_item['gaps'] = domain['number_of_gaps']
                                            rmsd_item['core'] = domain['percent_of_core']
                                            rmsd_item['range'] = domain['range_of_seq_id']
                                            if chain_id not in exactly_overlaid_models:
                                                exactly_overlaid_models[chain_id] = []
                                            exactly_overlaid_models[chain_id].append(rmsd_item)

            if len(not_superimposed_ensemble) > 0:

                for chain_id, rmsd in not_superimposed_ensemble.items():

                    conformer_id = 1

                    nmr_representative = self.__cR.getDictList('pdbx_nmr_representative')

                    if len(nmr_representative) > 0:

                        try:
                            conformer_id = int(nmr_representative[0]['conformer_id'])
                        except ValueError:
                            conformer_id = 1

                    r = next((r for r in rmsd if r['model_id'] == conformer_id), rmsd[0])

                    warn = f"The coordinates (chain_id {chain_id}) are not superimposed. "\
                        f"The RMSD ({r['raw_rmsd']}Å) for a well-defined region "\
                        f"(Sequence ranges {r['range']}) is greater than the predicted value ({r['rmsd']}Å). "\
                        "Please superimpose the coordinates and re-upload the model file."

                    self.report.warning.appendDescription('not_superimposed_model',
                                                          {'file_name': file_name, 'category': 'atom_site', 'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ Warning  - {warn}\n")

            elif len(exactly_overlaid_ensemble) > 0:

                for chain_id, r in exactly_overlaid_ensemble.items():

                    warn = f"The coordinates (chain_id {chain_id}) are overlaid exactly. "\
                        "Please check there has not been an error during the creation of your model file. "\
                        "You are receiving this message because the mean RMSD for a well-defined region "\
                        f"(Sequence ranges {r['range']}) is {r['mean_rmsd']}Å. "\
                        "We require you to deposit an appropriate ensemble of coordinate models."

                    self.report.warning.appendDescription('exactly_overlaid_model',
                                                          {'file_name': file_name, 'category': 'atom_site', 'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ Warning  - {warn}\n")

            elif len(exactly_overlaid_models) > 0:

                for chain_id, rs in exactly_overlaid_models.items():

                    for r in rs:

                        warn = f"Two models in the coordinate file (chain_id {chain_id}) are overlaid exactly. "\
                            "Please check there has not been an error during the creation of your model file. "\
                            "You are receiving this message because the RMSD for a well-defined region "\
                            f"(Sequence ranges {r['range']}) between model {r['model_id_1']!r} and model {r['model_id_2']!r} "\
                            f"is {r['rmsd']}Å. "\
                            "We require you to deposit an appropriate ensemble of coordinate models."

                        self.report.warning.appendDescription('exactly_overlaid_model',
                                                              {'file_name': file_name, 'category': 'atom_site', 'description': warn})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ Warning  - {warn}\n")

            return True

        except KeyError as e:

            self.report.error.appendDescription('sequence_mismatch',
                                                {'file_name': file_name, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ KeyError  - {str(e)}\n")

        except LookupError as e:

            self.report.error.appendDescription('missing_mandatory_item',
                                                {'file_name': file_name, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            self.__lfh.write("+NmrDpUtility.__extractCoordPolymerSequence() ++ LookupError  - "
                             f"{file_name} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.report.error.appendDescription('invalid_data',
                                                {'file_name': file_name, 'category': lp_category,
                                                 'description': str(e).strip("'")})
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ ValueError  - {str(e)}\n")

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordPolymerSequence() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ Error  - {str(e)}\n")

        return False

    def __extractCoordAtomSite(self):
        """ Extract atom_site of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        if self.__coord_atom_site is not None:
            return True

        atom_site_cache_path = unobs_res_cache_path =\
            auth_to_label_cache_path = label_to_auth_cache_path = None

        if self.__cifHashCode is not None:
            atom_site_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_atom_site.pkl")
            self.__coord_atom_site = load_from_pickle(atom_site_cache_path, None)

            unobs_res_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_unobs_res.pkl")
            self.__coord_unobs_res = load_from_pickle(unobs_res_cache_path, [])

            auth_to_label_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_auth_to_label.pkl")
            self.__auth_to_label_seq = load_from_pickle(auth_to_label_cache_path, {})

            label_to_auth_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_label_to_auth.pkl")
            self.__label_to_auth_seq = load_from_pickle(label_to_auth_cache_path, {})

            if self.__coord_atom_site is not None:
                return True

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        file_name = cif_input_source_dic['file_name']

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        polymer_sequence = cif_input_source_dic['polymer_sequence'] if has_poly_seq else []

        if has_poly_seq and any('auth_chain_id' not in ps for ps in polymer_sequence):
            has_poly_seq = False

        try:

            model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'
            has_pdbx_auth_atom_name = 'pdbx_auth_atom_name' in self.__coord_atom_site_tags

            data_items = [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                          {'name': 'label_seq_id', 'type': 'str', 'alt_name': 'seq_id'},
                          {'name': 'auth_asym_id', 'type': 'str', 'alt_name': 'auth_chain_id'},
                          {'name': 'auth_seq_id', 'type': 'int'},  # non-polymer
                          {'name': 'label_comp_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                          {'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                          {'name': 'type_symbol', 'type': 'str'}  # DAOTHER-9084
                          ]

            if has_pdbx_auth_atom_name:  # DAOTHER-7665
                data_items.append({'name': 'pdbx_auth_atom_name', 'type': 'str', 'alt_name': 'auth_atom_id'})

            filter_items = [{'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                            {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                            ]

            if len(polymer_sequence) >= LEN_MAJOR_ASYM_ID:
                filter_items.append({'name': 'auth_asym_id', 'type': 'enum', 'enum': LARGE_ASYM_ID, 'alt_name': 'chain_id',
                                     'fetch_first_match': True})  # to process large assembly avoiding forced timeout

            coord = self.__cR.getDictListWithFilter('atom_site', data_items, filter_items)

            if has_poly_seq:
                label_to_auth_chain = {ps['chain_id']: ps['auth_chain_id'] for ps in polymer_sequence}
            else:
                label_to_auth_chain = {}
                for c in coord:
                    if c['chain_id'] not in emptyValue and c['auth_chain_id'] not in emptyValue and c['chain_id'] not in label_to_auth_chain:
                        label_to_auth_chain[c['chain_id']] = c['auth_chain_id']

            self.__coord_atom_site = {}
            self.__auth_to_label_seq = {}
            chain_ids = set(c['chain_id'] for c in coord)
            for chain_id in chain_ids:
                seq_ids = set((int(c['seq_id']) if c['seq_id'] is not None else c['auth_seq_id']) for c in coord if c['chain_id'] == chain_id)
                for seq_id in seq_ids:
                    seq_key = (chain_id, seq_id)
                    comp_id = next(c['comp_id'] for c in coord
                                   if c['chain_id'] == chain_id and ((c['seq_id'] is not None and int(c['seq_id']) == seq_id)
                                                                     or (c['seq_id'] is None and c['auth_seq_id'] == seq_id)))
                    atom_ids = [c['atom_id'] for c in coord
                                if c['chain_id'] == chain_id and ((c['seq_id'] is not None and int(c['seq_id']) == seq_id)
                                                                  or (c['seq_id'] is None and c['auth_seq_id'] == seq_id))]
                    self.__coord_atom_site[seq_key] = {'comp_id': comp_id, 'atom_id': atom_ids}
                    if has_pdbx_auth_atom_name:
                        auth_atom_ids = [c['auth_atom_id'] for c in coord
                                         if c['chain_id'] == chain_id and ((c['seq_id'] is not None and int(c['seq_id']) == seq_id)
                                                                           or (c['seq_id'] is None and c['auth_seq_id'] == seq_id))]
                        self.__coord_atom_site[seq_key]['auth_atom_id'] = auth_atom_ids
                    elif any(not self.__nefT.validate_comp_atom(comp_id, atom_id) for atom_id in atom_ids):
                        auth_atom_ids = [self.__getRepAtomIdInXplor(comp_id, atom_id) for atom_id in atom_ids]
                        self.__coord_atom_site[seq_key]['auth_atom_id'] = auth_atom_ids
                    auth_seq_id = next((c['auth_seq_id'] for c in coord if c['chain_id'] == chain_id and c['seq_id'] is not None and int(c['seq_id']) == seq_id), None)
                    if auth_seq_id is not None:
                        self.__auth_to_label_seq[(label_to_auth_chain[chain_id], auth_seq_id)] = seq_key
                    else:
                        self.__auth_to_label_seq[seq_key] = seq_key
            self.__label_to_auth_seq = {v: k for k, v in self.__auth_to_label_seq.items()}

            # DAOTHER-9084
            for c in coord:
                comp_id = c['comp_id']
                atom_id = c['atom_id']
                type_symbol = c['type_symbol']

                if atom_id in emptyValue or type_symbol in emptyValue:
                    continue

                if atom_id == type_symbol and atom_id[0] not in protonBeginCode and comp_id in monDict3:

                    is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(comp_id)

                    if is_valid and cc_rel_status == 'REL' or cc_name is not None:
                        self.__ccU.updateChemCompDict(comp_id)

                        try:
                            next(cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaTypeSymbol] == type_symbol)
                        except StopIteration:

                            err = f'Type symbol {type_symbol!r} for a specified atom {c} is not valid. Please re-upload the model file.'

                            self.report.error.appendDescription('coordinate_issue',
                                                                {'file_name': file_name, 'category': 'atom_site',
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__extractCoordAtomSite() ++ Error  - {err}\n")

                elif type_symbol[0] not in protonBeginCode and atom_id[0] in protonBeginCode:

                    err = f'Type symbol {type_symbol!r} for a specified atom {c} is not valid. Please re-upload the model file.'

                    self.report.error.appendDescription('coordinate_issue',
                                                        {'file_name': file_name, 'category': 'atom_site',
                                                         'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractCoordAtomSite() ++ Error  - {err}\n")

            # DAOTHER-7665
            self.__coord_unobs_res = []

            if self.__cR.hasCategory('pdbx_unobs_or_zero_occ_residues'):

                tags = self.__cR.getItemTags('pdbx_unobs_or_zero_occ_residues')

                unobs_has_label_seq = 'label_asym_id' in tags and 'label_seq_id' in tags
                unobs_has_auth_seq = 'auth_asym_id' in tags and 'auth_seq_id' in tags

                filter_item_by_rep_model_id = [{'name': 'PDB_model_num', 'type': 'int', 'value': self.__representative_model_id}]

                if unobs_has_auth_seq and unobs_has_label_seq:
                    unobs = self.__cR.getDictListWithFilter('pdbx_unobs_or_zero_occ_residues',
                                                            [{'name': 'auth_asym_id', 'type': 'str'},
                                                             {'name': 'auth_seq_id', 'type': 'str'},
                                                             {'name': 'label_asym_id', 'type': 'str'},
                                                             {'name': 'label_seq_id', 'type': 'str'}
                                                             ],
                                                            filter_item_by_rep_model_id)

                    if len(unobs) > 0:
                        for u in unobs:
                            if u['auth_asym_id'] is not None and u['auth_seq_id'] is not None and u['label_asym_id'] is not None and u['label_seq_id'] is not None:
                                auth_seq_key = (u['auth_asym_id'], int(u['auth_seq_id']))
                                label_seq_key = (u['label_asym_id'], int(u['label_seq_id']))

                                if auth_seq_key not in self.__auth_to_label_seq:
                                    self.__auth_to_label_seq[auth_seq_key] = label_seq_key
                                if label_seq_key not in self.__label_to_auth_seq:
                                    self.__label_to_auth_seq[label_seq_key] = auth_seq_key

                if unobs_has_label_seq:
                    unobs = self.__cR.getDictListWithFilter('pdbx_unobs_or_zero_occ_residues',
                                                            [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                             {'name': 'label_seq_id', 'type': 'str', 'alt_name': 'seq_id'}
                                                             ],
                                                            filter_item_by_rep_model_id)

                    if len(unobs) > 0:
                        for chain_id in chain_ids:
                            seq_ids = set(int(u['seq_id']) for u in unobs if u['chain_id'] == chain_id and u['seq_id'] is not None)
                            for seq_id in seq_ids:
                                seq_key = (chain_id, seq_id)
                                self.__coord_unobs_res.append(seq_key)

                if unobs_has_auth_seq:
                    unobs = self.__cR.getDictListWithFilter('pdbx_unobs_or_zero_occ_residues',
                                                            [{'name': 'auth_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                             {'name': 'auth_seq_id', 'type': 'str', 'alt_name': 'seq_id'}
                                                             ],
                                                            filter_item_by_rep_model_id)

                    if len(unobs) > 0:
                        for chain_id in chain_ids:
                            seq_ids = set(int(u['seq_id']) for u in unobs if u['chain_id'] == chain_id and u['seq_id'] is not None)
                            for seq_id in seq_ids:
                                seq_key = (chain_id, seq_id)
                                if seq_key in self.__auth_to_label_seq:
                                    _seq_key = self.__auth_to_label_seq[seq_key]
                                    if _seq_key not in self.__coord_unobs_res:
                                        self.__coord_unobs_res.append(_seq_key)

            if self.__cifHashCode is not None:
                write_as_pickle(self.__coord_atom_site, atom_site_cache_path)
                write_as_pickle(self.__coord_unobs_res, unobs_res_cache_path)
                write_as_pickle(self.__auth_to_label_seq, auth_to_label_cache_path)
                write_as_pickle(self.__label_to_auth_seq, label_to_auth_cache_path)

            return True

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordAtomSite() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordAtomSite() ++ Error  - {str(e)}\n")

            return False

    def __extractCoordPolymerSequenceInLoop(self):
        """ Extract polymer sequence in interesting loops of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        __errors = self.report.getTotalErrors()

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        file_name = cif_input_source_dic['file_name']
        file_type = cif_input_source_dic['file_type']

        poly_seq_list_set = {}

        for content_subtype in self.cif_content_subtypes:

            if content_subtype in ('entry_info', 'poly_seq', 'branched', 'non_poly') or (not has_key_value(cif_input_source_dic['content_subtype'], content_subtype)):
                continue

            poly_seq_list_set[content_subtype] = []

            alias = False
            lp_category = self.lp_categories[file_type][content_subtype]
            key_items = self.key_items[file_type][content_subtype]

            if not self.__cR.hasCategory(lp_category):
                alias = True
                lp_category = self.lp_categories[file_type][content_subtype + '_alias']
                key_items = self.key_items[file_type][content_subtype + '_alias']

            elif content_subtype == 'coordinate' and 'pdbx_PDB_model_num' not in self.__coord_atom_site_tags:
                alias = True
                key_items = self.key_items[file_type][content_subtype + '_alias']

            has_poly_seq = False

            list_id = 1

            try:

                poly_seq = poly_seq_cache_path = None

                if self.__cifHashCode is not None:
                    poly_seq_cache_path = os.path.join(self.__cacheDirPath, f"{self.__cifHashCode}_poly_seq.pkl")
                    poly_seq = load_from_pickle(poly_seq_cache_path)

                if poly_seq is None:

                    try:
                        poly_seq = self.__cR.getPolymerSequence(lp_category, key_items)
                    except KeyError:  # pdbx_PDB_ins_code throws KeyError
                        if content_subtype + ('_ins_alias' if alias else '_ins') in self.key_items[file_type]:
                            key_items = self.key_items[file_type][content_subtype + ('_ins_alias' if alias else '_ins')]
                            poly_seq = self.__cR.getPolymerSequence(lp_category, key_items)
                        else:
                            poly_seq = []

                    if len(poly_seq) > 0 and poly_seq_cache_path is not None:
                        write_as_pickle(poly_seq, poly_seq_cache_path)

                if len(poly_seq) > 0:
                    poly_seq_list_set[content_subtype].append({'list_id': list_id, 'polymer_sequence': poly_seq})

                    has_poly_seq = True

            except KeyError as e:

                self.report.error.appendDescription('sequence_mismatch',
                                                    {'file_name': file_name, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ KeyError  - {str(e)}\n")

            except LookupError as e:

                self.report.error.appendDescription('missing_mandatory_item',
                                                    {'file_name': file_name, 'category': lp_category,
                                                     'description': str(e).strip("'")})
                self.report.setError()

                self.__lfh.write("+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ LookupError  - "
                                 f"{file_name} {lp_category} {str(e)}\n")

            except ValueError as e:

                if not (content_subtype == 'non_poly' and alias):
                    self.report.error.appendDescription('invalid_data',
                                                        {'file_name': file_name, 'category': lp_category,
                                                         'description': str(e).strip("'")})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ ValueError  - {str(e)}\n")

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ Error  - {str(e)}\n")

            list_id += 1

            if not has_poly_seq:
                poly_seq_list_set.pop(content_subtype)

        if self.report.getTotalErrors() > __errors:
            return False

        if len(poly_seq_list_set) > 0:
            cif_input_source.setItemValue('polymer_sequence_in_loop', poly_seq_list_set)

        return True

    def __extractCoordCommonPolymerSequence(self):
        """ Extract common polymer sequence of coordinate file if required.
        """

        common_poly_seq = {}

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')
        has_poly_seq_in_loop = has_key_value(cif_input_source_dic, 'polymer_sequence_in_loop')

        if has_poly_seq or (not has_poly_seq_in_loop):
            return False

        polymer_sequence_in_loop = cif_input_source_dic['polymer_sequence_in_loop']

        chain_ids = set()

        for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                polymer_sequence = ps_in_loop['polymer_sequence']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']
                    chain_ids.add(chain_id)

                    if chain_id not in common_poly_seq:
                        common_poly_seq[chain_id] = set()

        _offset_seq_ids = {c: 0 for c in chain_ids}

        for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                polymer_sequence = ps_in_loop['polymer_sequence']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']

                    min_seq_id = min(ps['seq_id'])
                    if min_seq_id < _offset_seq_ids[chain_id]:
                        _offset_seq_ids[chain_id] = min_seq_id

        offset_seq_ids = {k: (0 if v >= 0 else -v) for k, v in _offset_seq_ids.items()}

        for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                polymer_sequence = ps_in_loop['polymer_sequence']

                for ps in polymer_sequence:
                    chain_id = ps['chain_id']

                    for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):
                        common_poly_seq[chain_id].add((seq_id + offset_seq_ids[chain_id], comp_id))

        asm = []  # molecular assembly of a loop

        for chain_id in sorted(common_poly_seq.keys()):

            if len(common_poly_seq[chain_id]) > 0:
                seq_ids = sorted(set(item[0] - offset_seq_ids[chain_id] for item in common_poly_seq[chain_id]))
                comp_ids = []

                for seq_id in seq_ids:
                    _comp_ids = [item[1] for item in common_poly_seq[chain_id]
                                 if item[0] - offset_seq_ids[chain_id] == seq_id]
                    if len(_comp_ids) == 1:
                        comp_ids.append(_comp_ids[0])
                    else:
                        comp_ids.append(next(comp_id for comp_id in _comp_ids if comp_id not in emptyValue))

                asm.append({'chain_id': chain_id, 'seq_id': seq_ids, 'comp_id': comp_ids})

        if len(asm) > 0:
            cif_input_source.setItemValue('polymer_sequence', asm)

        return True

    def __extractCoordNonStandardResidue(self):
        """ Extract non-standard residue of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        polymer_sequence = cif_input_source_dic['polymer_sequence']

        asm = []

        for ps in polymer_sequence:

            has_nstd_res = False

            ent = {'chain_id': ps['chain_id'], 'seq_id': [], 'comp_id': [], 'chem_comp_name': [], 'exptl_data': []}

            for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                if comp_id not in monDict3:
                    has_nstd_res = True

                    ent['seq_id'].append(seq_id)
                    ent['comp_id'].append(comp_id)

                    is_valid, cc_name, cc_rel_status = self.__getChemCompNameAndStatusOf(comp_id)

                    if is_valid:  # matches with comp_id in CCD
                        if cc_rel_status == 'REL' or cc_name is not None:
                            ent['chem_comp_name'].append(cc_name)
                        else:
                            ent['chem_comp_name'].append(f"(Not available due to CCD status code {cc_rel_status})")

                    else:
                        ent['chem_comp_name'].append(cc_name)

                    ent['exptl_data'].append({'coordinate': False})

            if has_nstd_res:
                asm.append(ent)

        if len(asm) > 0:
            cif_input_source.setItemValue('non_standard_residue', asm)

        return True

    def __appendCoordPolymerSequenceAlignment(self):
        """ Append polymer sequence alignment between coordinate and NMR data.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        # sequence alignment inside coordinate file

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')
        has_poly_seq_in_loop = has_key_value(cif_input_source_dic, 'polymer_sequence_in_loop')

        if not has_poly_seq:
            return False

        polymer_sequence = cif_input_source_dic['polymer_sequence']

        if has_poly_seq_in_loop:

            polymer_sequence_in_loop = cif_input_source_dic['polymer_sequence_in_loop']

            for content_subtype in polymer_sequence_in_loop.keys():

                if content_subtype in ('non_poly', 'branched'):
                    continue

                seq_align_set = []

                for i1, s1 in enumerate(polymer_sequence):
                    chain_id = s1['chain_id']

                    if i1 >= LEN_MAJOR_ASYM_ID:  # to process large assembly avoiding forced timeout
                        continue

                    for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                        ps2 = ps_in_loop['polymer_sequence']

                        for s2 in ps2:

                            if chain_id != s2['chain_id']:
                                continue

                            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                            self.__pA.addTestSequence(s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            if length == 0:
                                continue

                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                            if length == unmapped + conflict or _matched <= conflict + (1 if length > 1 else 0):
                                continue

                            _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                            ref_length = len(s1['seq_id'])

                            ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                            test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                            mid_code = getMiddleCode(ref_code, test_code)
                            ref_gauge_code = getGaugeCode(_s1['seq_id'])
                            test_gauge_code = getGaugeCode(_s2['seq_id'])

                            matched = mid_code.count('|')

                            seq_align = {'list_id': ps_in_loop['list_id'], 'chain_id': chain_id, 'length': ref_length,
                                         'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                                         'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                                         'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                                         'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                                         'test_code': test_code, 'test_gauge_code': test_gauge_code}

                            seq_align_set.append(seq_align)

                if len(seq_align_set) > 0:
                    self.report.sequence_alignment.setItemValue('model_poly_seq_vs_' + content_subtype, seq_align_set)

        # sequence alignment between model and NMR data

        nmr_input_source = self.report.input_sources[0]
        nmr_input_source_dic = nmr_input_source.get()

        has_nmr_poly_seq = has_key_value(nmr_input_source_dic, 'polymer_sequence')

        if not has_nmr_poly_seq:
            return False

        nmr_polymer_sequence = nmr_input_source_dic['polymer_sequence']

        seq_align_set = []

        for i1, s1 in enumerate(polymer_sequence):
            chain_id = s1['chain_id']

            if i1 >= LEN_MAJOR_ASYM_ID / 2:  # to process large assembly avoiding forced timeout
                continue

            for i2, s2 in enumerate(nmr_polymer_sequence):
                chain_id2 = s2['chain_id']

                if i2 >= LEN_MAJOR_ASYM_ID / 2:  # to process large assembly avoiding forced timeout
                    continue

                self.__pA.setReferenceSequence(s1['auth_comp_id'] if 'auth_comp_id' in s1 else s2['comp_id'], 'REF' + chain_id)
                self.__pA.addTestSequence(s2['comp_id'], chain_id)
                self.__pA.doAlign()

                myAlign = self.__pA.getAlignment(chain_id)

                length = len(myAlign)

                if length == 0:
                    continue

                _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                if length == unmapped + conflict:
                    if len(s1['seq_id']) == 1 and 'alt_comp_id' in s1 and s1['alt_comp_id'][0] in s2['comp_id']:
                        self.__pA.setReferenceSequence(s1['alt_comp_id'], 'REF' + chain_id)
                        self.__pA.addTestSequence(s2['comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        if length == 0:
                            continue

                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                        if length == unmapped + conflict:
                            continue

                        self.__native_combined = True  # DAOTHER-8817

                    else:
                        continue

                _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                if conflict > 0 and (hasLargeSeqGap(_s1, _s2) or (not hasLargeInnerSeqGap(s1) and hasLargeInnerSeqGap(s2))):  # DAOTHER-7465
                    _s2 = self.__compensateLadderHistidinTag2(chain_id, _s1, _s2)
                    __s1, __s2 = beautifyPolySeq(_s1, _s2)
                    _s1_ = __s1
                    _s2_ = __s2

                    self.__pA.setReferenceSequence(_s1_['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(_s2_['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    __matched, _unmapped, _conflict, _offset_1, _offset_2 = getScoreOfSeqAlign(myAlign)

                    if __matched > 0 and _conflict == 0:  # and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                        _matched, unmapped = __matched, _unmapped
                        conflict = 0
                        offset_1 = _offset_1
                        offset_2 = _offset_2
                        _s1 = __s1
                        _s2 = __s2

                if _matched <= conflict + (1 if length > 1 else 0):
                    continue

                ref_length = len(s1['seq_id'])

                ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                mid_code = getMiddleCode(ref_code, test_code)
                ref_gauge_code = getGaugeCode(_s1['seq_id'])
                test_gauge_code = getGaugeCode(_s2['seq_id'])

                if any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                       in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                       if __c1 != '.' and __c2 != '.' and __c1 != __c2):
                    len_s1 = len(_s1['seq_id'])
                    len_s2 = len(_s2['seq_id'])

                    seq_id1 = []
                    seq_id2 = []
                    comp_id1 = []
                    comp_id2 = []

                    idx1 = 0
                    idx2 = 0
                    for i in range(length):
                        myPr = myAlign[i]
                        myPr0 = str(myPr[0])
                        myPr1 = str(myPr[1])
                        if myPr0 != '.':
                            while idx1 < len_s1:
                                if _s1['comp_id'][idx1] == myPr0:
                                    seq_id1.append(_s1['seq_id'][idx1])
                                    comp_id1.append(myPr0)
                                    idx1 += 1
                                    break
                                idx1 += 1
                        else:
                            seq_id1.append(None)
                            comp_id1.append('.')
                        if myPr1 != '.':
                            while idx2 < len_s2:
                                if _s2['comp_id'][idx2] == myPr1:
                                    seq_id2.append(_s2['seq_id'][idx2])
                                    comp_id2.append(myPr1)
                                    idx2 += 1
                                    break
                                idx2 += 1
                        else:
                            seq_id2.append(None)
                            comp_id2.append('.')
                    ref_code = getOneLetterCodeCanSequence(comp_id1)
                    test_code = getOneLetterCodeCanSequence(comp_id2)
                    mid_code = getMiddleCode(ref_code, test_code)
                    ref_gauge_code = getGaugeCode(seq_id1, offset_1)
                    test_gauge_code = getGaugeCode(seq_id2, offset_2)
                    if ' ' in ref_gauge_code:
                        for p, g in enumerate(ref_gauge_code):
                            if g == ' ':
                                ref_code = ref_code[0:p] + '-' + ref_code[p + 1:]
                    if ' ' in test_gauge_code:
                        for p, g in enumerate(test_gauge_code):
                            if g == ' ':
                                test_code = test_code[0:p] + '-' + test_code[p + 1:]

                matched = mid_code.count('|')

                seq_align = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': ref_length,
                             'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                             'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                             'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                             'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                             'test_code': test_code, 'test_gauge_code': test_gauge_code}

                seq_align_set.append(seq_align)

        if len(seq_align_set) > 0:
            self.report.sequence_alignment.setItemValue('model_poly_seq_vs_nmr_poly_seq', seq_align_set)

        seq_align_set = []

        for i1, s1 in enumerate(nmr_polymer_sequence):
            chain_id = s1['chain_id']

            if i1 >= LEN_MAJOR_ASYM_ID / 2:  # to process large assembly avoiding forced timeout
                continue

            for i2, s2 in enumerate(polymer_sequence):
                chain_id2 = s2['chain_id']

                if i2 >= LEN_MAJOR_ASYM_ID / 2:  # to process large assembly avoiding forced timeout
                    continue

                self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                self.__pA.addTestSequence(s2['auth_comp_id'] if 'auth_comp_id' in s2 else s2['comp_id'], chain_id)
                self.__pA.doAlign()

                myAlign = self.__pA.getAlignment(chain_id)

                length = len(myAlign)

                if length == 0:
                    continue

                _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                if length == unmapped + conflict:
                    if len(s2['seq_id']) == 1 and 'alt_comp_id' in s2 and s2['alt_comp_id'][0] in s1['comp_id']:
                        self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                        self.__pA.addTestSequence(s2['alt_comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        if length == 0:
                            continue

                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                        if length == unmapped + conflict:
                            continue

                        self.__native_combined = True  # DAOTHER-8817

                    else:
                        continue

                _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                if conflict > 0 and (hasLargeSeqGap(_s1, _s2) or (hasLargeInnerSeqGap(s1) and not hasLargeInnerSeqGap(s2))):  # DAOTHER-7465
                    _s1 = self.__compensateLadderHistidinTag2(chain_id, _s2, _s1)
                    __s1, __s2 = beautifyPolySeq(_s1, _s2)
                    _s1_ = __s1
                    _s2_ = __s2

                    self.__pA.setReferenceSequence(_s1_['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(_s2_['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    __matched, _unmapped, _conflict, _offset_1, _offset_2 = getScoreOfSeqAlign(myAlign)

                    if __matched > 0 and _conflict == 0:  # and len(__s1['comp_id']) - len(s1['comp_id']) == conflict:
                        _matched, unmapped = __matched, _unmapped
                        conflict = 0
                        offset_1 = _offset_1
                        offset_2 = _offset_2
                        _s1 = __s1
                        _s2 = __s2

                if _matched <= conflict + (1 if length > 1 else 0):
                    continue

                ref_length = len(s1['seq_id'])

                ref_code = getOneLetterCodeCanSequence(_s1['comp_id'])
                test_code = getOneLetterCodeCanSequence(_s2['comp_id'])
                mid_code = getMiddleCode(ref_code, test_code)
                ref_gauge_code = getGaugeCode(_s1['seq_id'])
                test_gauge_code = getGaugeCode(_s2['seq_id'])

                if any((__s1, __s2) for (__s1, __s2, __c1, __c2)
                       in zip(_s1['seq_id'], _s2['seq_id'], _s1['comp_id'], _s2['comp_id'])
                       if __c1 != '.' and __c2 != '.' and __c1 != __c2):
                    len_s1 = len(_s1['seq_id'])
                    len_s2 = len(_s2['seq_id'])

                    seq_id1 = []
                    seq_id2 = []
                    comp_id1 = []
                    comp_id2 = []

                    idx1 = 0
                    idx2 = 0
                    for i in range(length):
                        myPr = myAlign[i]
                        myPr0 = str(myPr[0])
                        myPr1 = str(myPr[1])
                        if myPr0 != '.':
                            while idx1 < len_s1:
                                if _s1['comp_id'][idx1] == myPr0:
                                    seq_id1.append(_s1['seq_id'][idx1])
                                    comp_id1.append(myPr0)
                                    idx1 += 1
                                    break
                                idx1 += 1
                        else:
                            seq_id1.append(None)
                            comp_id1.append('.')
                        if myPr1 != '.':
                            while idx2 < len_s2:
                                if _s2['comp_id'][idx2] == myPr1:
                                    seq_id2.append(_s2['seq_id'][idx2])
                                    comp_id2.append(myPr1)
                                    idx2 += 1
                                    break
                                idx2 += 1
                        else:
                            seq_id2.append(None)
                            comp_id2.append('.')
                    ref_code = getOneLetterCodeCanSequence(comp_id1)
                    test_code = getOneLetterCodeCanSequence(comp_id2)
                    mid_code = getMiddleCode(ref_code, test_code)
                    ref_gauge_code = getGaugeCode(seq_id1, offset_1)
                    test_gauge_code = getGaugeCode(seq_id2, offset_2)
                    if ' ' in ref_gauge_code:
                        for p, g in enumerate(ref_gauge_code):
                            if g == ' ':
                                ref_code = ref_code[0:p] + '-' + ref_code[p + 1:]
                    if ' ' in test_gauge_code:
                        for p, g in enumerate(test_gauge_code):
                            if g == ' ':
                                test_code = test_code[0:p] + '-' + test_code[p + 1:]

                matched = mid_code.count('|')

                seq_align = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': ref_length,
                             'matched': matched, 'conflict': conflict, 'unmapped': unmapped,
                             'sequence_coverage': float(f"{float(length - (unmapped + conflict)) / ref_length:.3f}"),
                             'ref_seq_id': _s1['seq_id'], 'test_seq_id': _s2['seq_id'],
                             'ref_gauge_code': ref_gauge_code, 'ref_code': ref_code, 'mid_code': mid_code,
                             'test_code': test_code, 'test_gauge_code': test_gauge_code}

                seq_align_set.append(seq_align)

        if len(seq_align_set) > 0:
            self.report.sequence_alignment.setItemValue('nmr_poly_seq_vs_model_poly_seq', seq_align_set)

        return True

    def __compensateLadderHistidinTag2(self, chain_id, s1, s2):
        """ Compensate ladder-like Histidin tag in polymer sequence 2.
        """

        self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
        self.__pA.addTestSequence(s2['comp_id'], chain_id)
        self.__pA.doAlign()

        _s2 = copy.copy(s2)

        len_s2 = len(s2['comp_id'])

        myAlign = self.__pA.getAlignment(chain_id)

        length = len(myAlign)

        _myPr0 = '.'

        idx2 = 0
        for p in range(length):
            myPr = myAlign[p]
            myPr0 = str(myPr[0])
            myPr1 = str(myPr[1])

            if myPr0 == myPr1:
                pass

            elif myPr0 == 'HIS' and myPr1 == '.' and _myPr0 == 'HIS':
                if idx2 < len_s2:
                    _s2['comp_id'][idx2] = 'HIS'
                    idx2 += 1

            _myPr0 = myPr0

            if myPr1 != '.':
                while idx2 < len_s2:
                    if s2['comp_id'][idx2] == myPr1:
                        idx2 += 1
                        break
                    idx2 += 1

        return _s2

    def __assignCoordPolymerSequence(self):
        """ Assign polymer sequences of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        cif_file_name = cif_input_source_dic['file_name']

        has_cif_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_cif_poly_seq:
            return False

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            nmr_input_source = self.report.input_sources[fileListId]
            nmr_input_source_dic = nmr_input_source.get()

            nmr_file_name = nmr_input_source_dic['file_name']

            has_nmr_poly_seq = has_key_value(nmr_input_source_dic, 'polymer_sequence')

            if not has_nmr_poly_seq:
                continue

            seq_align_dic = self.report.sequence_alignment.get()

            if has_key_value(seq_align_dic, 'model_poly_seq_vs_nmr_poly_seq')\
                    and has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):

                cif_polymer_sequence = cif_input_source_dic['polymer_sequence']
                nmr_polymer_sequence = nmr_input_source_dic['polymer_sequence']

                if nmr_polymer_sequence is None:
                    continue

                cif_chains = len(cif_polymer_sequence)
                nmr_chains = len(nmr_polymer_sequence)

                # map polymer sequences between coordinate and NMR data using Hungarian algorithm
                m = Munkres()

                # from model to nmr (first trial, never raise a warning or an error)

                mat = []
                indices = []

                for s1 in cif_polymer_sequence:
                    chain_id = s1['chain_id']

                    cost = [0 for i in range(nmr_chains)]

                    for s2 in nmr_polymer_sequence:
                        chain_id2 = s2['chain_id']

                        result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                                       if seq_align['ref_chain_id'] == chain_id
                                       and seq_align['test_chain_id'] == chain_id2), None)

                        if result is not None:
                            cost[nmr_polymer_sequence.index(s2)] = result['unmapped'] + result['conflict'] - result['length']
                            if not self.__native_combined and result['length'] >= len(s1['seq_id']) - result['unmapped']:
                                indices.append((cif_polymer_sequence.index(s1), nmr_polymer_sequence.index(s2)))

                    mat.append(cost)

                if self.__native_combined:
                    indices = m.compute(mat)

                concatenated_nmr_chain = {}

                for row, col in indices:

                    if mat[row][col] >= 0:

                        if self.__native_combined:
                            continue

                        # DAOTHER-8751
                        has_row = has_col = False
                        for _row, _col in indices:
                            if mat[_row][_col] < 0:
                                if _row == row:
                                    has_row = True
                                if _col == col:
                                    has_col = True

                        if has_row and has_col:
                            continue

                        _cif_chain_ids = [cif_polymer_sequence[_row]['chain_id'] for _row, _col in indices if col == _col]

                        if len(_cif_chain_ids) > 1:
                            chain_id2 = nmr_polymer_sequence[col]['chain_id']
                            concatenated_nmr_chain[chain_id2] = _cif_chain_ids

                    chain_id = cif_polymer_sequence[row]['chain_id']
                    chain_id2 = nmr_polymer_sequence[col]['chain_id']

                    result = next(seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                                  if seq_align['ref_chain_id'] == chain_id and seq_align['test_chain_id'] == chain_id2)
                    _result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                                    if seq_align['ref_chain_id'] == chain_id2 and seq_align['test_chain_id'] == chain_id), None)

                    if result['matched'] == 0 or\
                            (result['conflict'] > 0
                             and result['sequence_coverage'] < LOW_SEQ_COVERAGE < float(result['conflict']) / float(result['matched'])):
                        continue

                    ca = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': result['length'],
                          'matched': result['matched'], 'conflict': result['conflict'], 'unmapped': result['unmapped'],
                          'sequence_coverage': result['sequence_coverage']}

                    # DAOTHER-8751
                    low_evid_chain_mapping = result['sequence_coverage'] < LOW_SEQ_COVERAGE
                    if low_evid_chain_mapping:
                        low_evid_chain_mapping = False
                        for _row, _col in indices:
                            if mat[_row][_col] >= 0:
                                if _row == row or _col == col:
                                    low_evid_chain_mapping = True

                    auth_chain_id = chain_id
                    if 'auth_chain_id' in cif_polymer_sequence[row]:
                        auth_chain_id = cif_polymer_sequence[row]['auth_chain_id']
                        ca['ref_auth_chain_id'] = auth_chain_id

                    s1 = next(s for s in cif_polymer_sequence if s['chain_id'] == chain_id)
                    s2 = next(s for s in nmr_polymer_sequence if s['chain_id'] == chain_id2)

                    self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(s2['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                    _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                    if conflict == 0:
                        has_inner_gap_1 = hasLargeInnerSeqGap(_s1)
                        has_inner_gap_2 = hasLargeInnerSeqGap(_s2)

                        if has_inner_gap_2 and not has_inner_gap_1:
                            _s2 = fillInnerBlankCompId(_s2)
                        elif has_inner_gap_1 and not has_inner_gap_2:
                            _s1 = fillInnerBlankCompId(_s1)

                    if conflict > 0 and (hasLargeSeqGap(_s1, _s2) or (not hasLargeInnerSeqGap(s1) and hasLargeInnerSeqGap(s2))):  # DAOTHER-7465
                        __s1, __s2 = beautifyPolySeq(_s1, _s2)
                        _s1 = __s1
                        _s2 = __s2

                        self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                        self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                        if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                            result['conflict'] = 0
                            s2 = __s2

                    # update residue name in CS loop to follow CCD replacement (6vu1)
                    if conflict > 0 and ('alt_comp_id' in s1 or 'alt_comp_id' in s2) and len(s1['seq_id']) == len(s2['seq_id']):
                        for (k1, k2) in zip(['alt_comp_id', 'comp_id'], ['alt_comp_id', 'comp_id']):

                            if k1 == k2 == 'comp_id' or k1 not in s1 or k2 not in s2:
                                continue

                            self.__pA.setReferenceSequence(s1[k1], 'REF' + chain_id)
                            self.__pA.addTestSequence(s2[k2], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            _matched, unmapped, _conflict, _offset_1, _offset_2 = getScoreOfSeqAlign(myAlign)

                            if _conflict == 0:
                                self.__updateCompIdInCsLoop(fileListId, s1, s2)

                                result['conflict'] = _result['conflict'] = 0
                                s2['comp_id'] = s1['comp_id']

                                break

                    if conflict == 0:
                        # resolve unmapped author sequence in CS loop based on sequence alignment (2kxc)
                        self.__resolveUnmappedAuthSequenceInCsLoop(fileListId, s1, s2)

                    ref_code = getOneLetterCodeCanSequence(s1['comp_id'])
                    test_code = getOneLetterCodeCanSequence(s2['comp_id'])

                    for r_code, t_code, seq_id, seq_id2 in zip(ref_code, test_code, s1['seq_id'], s2['seq_id']):
                        if r_code == 'X' and t_code == 'X':
                            nmr_input_source.updateNonStandardResidueByExptlData(chain_id2, seq_id2, 'coordinate')
                            cif_input_source.updateNonStandardResidueByExptlData(chain_id, seq_id, 'coordinate')

                    if result['unmapped'] > 0 or result['conflict'] > 0:

                        aligned = [True] * length
                        seq_id1 = []
                        seq_id2 = []

                        j = 0
                        for i in range(length):
                            if j < len(s1['seq_id']) and (str(myAlign[i][0]) != '.' or s1['comp_id'][j] == '.'):
                                seq_id1.append(s1['seq_id'][j])
                                j += 1
                            else:
                                seq_id1.append(None)

                        j = 0
                        for i in range(length):
                            if j < len(s2['seq_id']) and (str(myAlign[i][1]) != '.' or s2['comp_id'][j] == '.'):
                                seq_id2.append(s2['seq_id'][j])
                                j += 1
                            else:
                                seq_id2.append(None)

                        for i in range(length):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        for i in reversed(range(length)):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        if not self.__native_combined:

                            _conflicts = 0

                            for i in range(length):
                                myPr = myAlign[i]
                                if myPr[0] == myPr[1]:
                                    continue

                                cif_comp_id = str(myPr[0])
                                nmr_comp_id = str(myPr[1])

                                if nmr_comp_id == '.' and cif_comp_id != '.':
                                    pass

                                elif nmr_comp_id != cif_comp_id and aligned[i]:
                                    _conflicts += 1

                            if _conflicts > ca['unmapped'] and ca['sequence_coverage'] < MIN_SEQ_COVERAGE_W_CONFLICT:
                                continue

                            if _conflicts + offset_1 > _matched and ca['sequence_coverage'] < LOW_SEQ_COVERAGE:  # DAOTHER-7825 (2lyw)
                                if not low_evid_chain_mapping:  # DAOTHER-8751
                                    continue

                        unmapped = []
                        conflict = []
                        # offset_1 = 0
                        # offset_2 = 0

                        for i in range(length):
                            myPr = myAlign[i]
                            if myPr[0] == myPr[1]:
                                continue

                            cif_comp_id = str(myPr[0])
                            nmr_comp_id = str(myPr[1])

                            if nmr_comp_id == '.' and cif_comp_id != '.':
                                unmapped.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': cif_comp_id})

                            elif nmr_comp_id != cif_comp_id and aligned[i]:
                                conflict.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': cif_comp_id,
                                                 'test_seq_id': seq_id2[i], 'test_comp_id': nmr_comp_id})

                        if len(unmapped) > 0:
                            ca['unmapped_sequence'] = unmapped

                        if len(conflict) > 0:
                            ca['conflict_sequence'] = conflict
                            ca['conflict'] = len(conflict)
                            ca['unmapped'] = ca['unmapped'] - len(conflict)
                            if ca['unmapped'] < 0:
                                ca['conflict'] -= ca['unmapped']
                                ca['unmapped'] = 0

                            result['conflict'] = ca['conflict']
                            result['unmapped'] = ca['unmapped']

                            if _result is not None:
                                _result['conflict'] = ca['conflict']
                                _result['unmapped'] = ca['unmapped']

                # from nmr to model

                ca_idx = 0

                mat = []
                indices = []

                for s1 in nmr_polymer_sequence:
                    chain_id = s1['chain_id']

                    cost = [0 for i in range(cif_chains)]

                    for s2 in cif_polymer_sequence:
                        chain_id2 = s2['chain_id']

                        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                                       if seq_align['ref_chain_id'] == chain_id and seq_align['test_chain_id'] == chain_id2), None)

                        if result is not None:
                            cost[cif_polymer_sequence.index(s2)] = result['unmapped'] + result['conflict'] - result['length']
                            if not self.__native_combined and result['length'] >= len(s2['seq_id']) - result['unmapped']:
                                indices.append((nmr_polymer_sequence.index(s1), cif_polymer_sequence.index(s2)))

                    mat.append(cost)

                if self.__native_combined:
                    indices = m.compute(mat)

                chain_assign = []

                for row, col in indices:

                    if mat[row][col] >= 0:

                        if self.__native_combined:
                            continue

                        # DAOTHER-8751
                        has_row = has_col = False
                        for _row, _col in indices:
                            if mat[_row][_col] < 0:
                                if _row == row:
                                    has_row = True
                                if _col == col:
                                    has_col = True

                        if has_row and has_col:
                            continue

                    chain_id = nmr_polymer_sequence[row]['chain_id']
                    chain_id2 = cif_polymer_sequence[col]['chain_id']

                    result = next(seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                                  if seq_align['ref_chain_id'] == chain_id and seq_align['test_chain_id'] == chain_id2)
                    _result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                                    if seq_align['ref_chain_id'] == chain_id2 and seq_align['test_chain_id'] == chain_id), None)

                    if result['matched'] == 0 or\
                            (result['conflict'] > 0
                             and result['sequence_coverage'] < LOW_SEQ_COVERAGE < float(result['conflict']) / float(result['matched'])):
                        continue

                    ca = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': result['length'],
                          'matched': result['matched'], 'conflict': result['conflict'], 'unmapped': result['unmapped'],
                          'sequence_coverage': result['sequence_coverage']}

                    # DAOTHER-8751
                    low_evid_chain_mapping = result['sequence_coverage'] < LOW_SEQ_COVERAGE
                    if low_evid_chain_mapping:
                        low_evid_chain_mapping = False
                        for _row, _col in indices:
                            if mat[_row][_col] >= 0:
                                if _row == row or _col == col:
                                    low_evid_chain_mapping = True

                    auth_chain_id2 = chain_id2
                    if 'auth_chain_id' in cif_polymer_sequence[col]:
                        auth_chain_id2 = cif_polymer_sequence[col]['auth_chain_id']
                        ca['test_auth_chain_id'] = auth_chain_id2

                    s1 = next(s for s in nmr_polymer_sequence if s['chain_id'] == chain_id)
                    s2 = next(s for s in cif_polymer_sequence if s['chain_id'] == chain_id2)

                    self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(s2['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    if conflict > 0 and any(len(c) > 3 for c in s2['comp_id']) and 'alt_comp_id' in s2:
                        self.__pA.addTestSequence(s2['alt_comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                        if conflict > 0:
                            self.__pA.addTestSequence(s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                    _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                    if conflict == 0:
                        has_inner_gap_1 = hasLargeInnerSeqGap(_s1)
                        has_inner_gap_2 = hasLargeInnerSeqGap(_s2)

                        if has_inner_gap_2 and not has_inner_gap_1:
                            _s2 = fillInnerBlankCompId(_s2)
                        elif has_inner_gap_1 and not has_inner_gap_2:
                            _s1 = fillInnerBlankCompId(_s1)

                    if conflict > 0 and (hasLargeSeqGap(_s1, _s2) or (hasLargeInnerSeqGap(s1) and not hasLargeInnerSeqGap(s2))):  # DAOTHER-7465
                        __s1, __s2 = beautifyPolySeq(_s1, _s2)
                        _s1 = __s1
                        _s2 = __s2

                        self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                        self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                        if _conflict == 0 and len(__s1['comp_id']) - len(s1['comp_id']) == conflict:
                            result['conflict'] = 0
                            s1 = __s1

                    if conflict > 0 and 'gap_in_auth_seq' in _s2 and _s2['gap_in_auth_seq'] and 'auth_seq_id' in _s2:
                        __s1 = copy.deepcopy(_s1)
                        for p in range(len(_s2['auth_seq_id']) - 1):
                            s_p = _s2['auth_seq_id'][p]
                            s_q = _s2['auth_seq_id'][p + 1]
                            if s_p is None or s_q is None or s_p + 1 == s_q:
                                continue
                            for s_o in range(s_p + 1, s_q):
                                if s_o in __s1['seq_id']:
                                    idx = __s1['seq_id'].index(s_o)
                                    if __s1['comp_id'][idx] in emptyValue:
                                        __s1['seq_id'].pop(idx)
                                        __s1['comp_id'].pop(idx)

                        if len(_s1['seq_id']) != len(__s1['seq_id']):
                            __s1, __s2 = beautifyPolySeq(__s1, _s2)
                            _s1 = __s1
                            _s2 = __s2

                            self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                            self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                            if _conflict == 0:
                                result['conflict'] = 0
                                s1 = __s1

                    if result['unmapped'] > 0 or result['conflict'] > 0:

                        aligned = [True] * length
                        seq_id1 = []
                        seq_id2 = []

                        j = 0
                        for i in range(length):
                            if j < len(s1['seq_id']) and (str(myAlign[i][0]) != '.' or s1['comp_id'][j] == '.'):  # DAOTHER-7421
                                seq_id1.append(s1['seq_id'][j])
                                j += 1
                            else:
                                seq_id1.append(None)

                        j = 0
                        for i in range(length):
                            if j < len(s2['seq_id']) and (str(myAlign[i][1]) != '.' or s2['comp_id'][j] == '.'):  # DAOTHER-7421
                                seq_id2.append(s2['seq_id'][j])
                                j += 1
                            else:
                                seq_id2.append(None)

                        for i in range(length):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        for i in reversed(range(length)):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        for i in range(length):
                            myPr = myAlign[i]
                            if aligned[i]:
                                if str(myPr[0]) == '.':
                                    if (seq_id2[i] is not None)\
                                       and ((i > 0 and seq_id2[i - 1] is not None and seq_id2[i - 1] + 1 == seq_id2[i])
                                            or (i + 1 < len(seq_id2) and seq_id2[i + 1] is not None and seq_id2[i + 1] - 1 == seq_id2[i])):
                                        aligned[i] = False
                                if str(myPr[1]) == '.':
                                    if (seq_id1[i] is not None)\
                                       and ((i > 0 and seq_id1[i - 1] is not None and seq_id1[i - 1] + 1 == seq_id1[i])
                                            or (i + 1 < len(seq_id1) and seq_id1[i + 1] is not None and seq_id1[i + 1] - 1 == seq_id1[i])):
                                        aligned[i] = False

                        if not self.__native_combined:

                            _conflicts = 0

                            for i in range(length):
                                myPr = myAlign[i]
                                if myPr[0] == myPr[1]:
                                    continue

                                nmr_comp_id = str(myPr[0])
                                cif_comp_id = str(myPr[1])

                                if cif_comp_id == '.' and nmr_comp_id != '.':
                                    pass

                                elif cif_comp_id != nmr_comp_id and aligned[i]:
                                    _conflicts += 1

                            if _conflicts > ca['unmapped'] and ca['sequence_coverage'] < MIN_SEQ_COVERAGE_W_CONFLICT:
                                continue

                            if _conflicts + offset_1 > _matched and ca['sequence_coverage'] < LOW_SEQ_COVERAGE:  # DAOTHER-7825 (2lyw)
                                if not low_evid_chain_mapping:  # DAOTHER-8751
                                    continue

                        unmapped = []
                        conflict = []
                        # offset_1 = 0
                        # offset_2 = 0

                        for i in range(length):
                            myPr = myAlign[i]
                            if myPr[0] == myPr[1]:
                                continue

                            nmr_comp_id = str(myPr[0])
                            cif_comp_id = str(myPr[1])

                            if cif_comp_id == '.' and nmr_comp_id != '.':

                                _seq_id1 = seq_id1[i] - offset_1 if seq_id1[i] is not None else None

                                unmapped.append({'ref_seq_id': _seq_id1, 'ref_comp_id': nmr_comp_id})

                                if not aligned[i]:

                                    if self.__native_combined or chain_id not in concatenated_nmr_chain or chain_id2 not in concatenated_nmr_chain[chain_id]:

                                        warn = f"{chain_id}:{_seq_id1}:{nmr_comp_id} is not present in the coordinates (chain_id {chain_id2}). "\
                                            "Please update the sequence in the Macromolecules page."

                                        self.__suspended_warnings_for_lazy_eval.append({'sequence_mismatch':
                                                                                        {'ca_idx': ca_idx, 'file_name': nmr_file_name, 'description': warn}})

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {warn}\n")

                            elif cif_comp_id != nmr_comp_id and aligned[i]:

                                _seq_id1 = seq_id1[i] - offset_1 if seq_id1[i] is not None else None
                                _seq_id2 = seq_id2[i] - offset_2 if seq_id2[i] is not None else None

                                if _seq_id1 is None and _seq_id2 is None:
                                    continue

                                conflict.append({'ref_seq_id': _seq_id1, 'ref_comp_id': nmr_comp_id,
                                                 'test_seq_id': _seq_id2, 'test_comp_id': cif_comp_id})

                                try:
                                    label_seq_id = _seq_id2
                                    auth_seq_id = s2['auth_seq_id'][s2['seq_id'].index(_seq_id2)]
                                except (KeyError, IndexError, ValueError):
                                    label_seq_id = _seq_id2
                                    auth_seq_id = label_seq_id
                                cif_seq_code = f"{chain_id2}:{label_seq_id}:{cif_comp_id}"
                                if cif_comp_id == '.':
                                    cif_seq_code += ', insertion error'
                                    # DAOTHER-9644: skip insertion error due to truncated loop
                                    cif_ps = next(cif_ps for cif_ps in cif_polymer_sequence if cif_ps['chain_id'] == chain_id2)
                                    if label_seq_id is None:
                                        if self.__caC is not None and 'missing_polymer_linkage' in self.__caC\
                                           and any(mis for mis in self.__caC['missing_polymer_linkage']
                                                   if mis['auth_chain_id'] == (cif_ps['auth_chain_id'] if 'auth_chain_id' in cif_ps else cif_ps['chain_id'])):
                                            continue
                                    else:
                                        if self.__caC is not None and 'missing_polymer_linkage' in self.__caC\
                                           and any(mis for mis in self.__caC['missing_polymer_linkage']
                                                   if mis['auth_chain_id'] == (cif_ps['auth_chain_id'] if 'auth_chain_id' in cif_ps else cif_ps['chain_id'])
                                                   and mis['auth_seq_id_1'] < auth_seq_id < mis['auth_seq_id_2']):
                                            continue
                                nmr_seq_code = f"{chain_id}:{_seq_id1}:{nmr_comp_id}"
                                if nmr_comp_id == '.':
                                    nmr_seq_code += ', insertion error'

                                if cif_comp_id != '.':
                                    if chain_id2 != auth_chain_id2 or auth_seq_id != label_seq_id:
                                        cif_seq_code += f", or {auth_chain_id2}:{auth_seq_id}:{cif_comp_id} in author sequence scheme"

                                err = f"Sequence alignment error between the NMR data ({nmr_seq_code}) and the coordinate ({cif_seq_code}). "\
                                    "Please verify the two sequences and re-upload the correct file(s)."

                                if self.__tolerant_seq_align and self.__equalsRepCompId(cif_comp_id, nmr_comp_id):
                                    self.__suspended_warnings_for_lazy_eval.append({'sequence_mismatch':
                                                                                    {'ca_idx': ca_idx, 'file_name': nmr_file_name, 'description': err}})

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {err}\n")

                                elif not self.__annotation_mode:
                                    self.__suspended_errors_for_lazy_eval.append({'sequence_mismatch':
                                                                                  {'ca_idx': ca_idx, 'file_name': nmr_file_name, 'description': err}})

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Error  - {err}\n")

                        if len(unmapped) > 0:
                            ca['unmapped_sequence'] = unmapped

                        if len(conflict) > 0:
                            ca['conflict_sequence'] = conflict
                            ca['conflict'] = len(conflict)
                            ca['unmapped'] = ca['unmapped'] - len(conflict)
                            if ca['unmapped'] < 0:
                                ca['conflict'] -= ca['unmapped']
                                ca['unmapped'] = 0

                            result['conflict'] = ca['conflict']
                            result['unmapped'] = ca['unmapped']

                            if _result is not None:
                                _result['conflict'] = ca['conflict']
                                _result['unmapped'] = ca['unmapped']

                    chain_assign.append(ca)
                    ca_idx += 1

                if len(chain_assign) > 0 and fileListId == 0:

                    if len(cif_polymer_sequence) > 1:

                        if len(self.__suspended_errors_for_lazy_eval) + len(self.__suspended_warnings_for_lazy_eval) > 0:

                            _del_ca_idx = []

                            for ca_idx, ca in enumerate(chain_assign):

                                if ca['conflict'] == 0:
                                    continue

                                ref_chain_id = ca['ref_chain_id']
                                test_chain_id = ca['test_chain_id']

                                if any(_ca for _ca in chain_assign
                                       if ((_ca['ref_chain_id'] == ref_chain_id and _ca['test_chain_id'] != test_chain_id)
                                           or (_ca['ref_chain_id'] != ref_chain_id and _ca['test_chain_id'] == test_chain_id))
                                       and _ca['conflict'] == 0):
                                    _del_ca_idx.append(ca_idx)

                            if len(_del_ca_idx) > 0:
                                for ca_idx in reversed(_del_ca_idx):
                                    del chain_assign[ca_idx]
                                if len(self.__suspended_errors_for_lazy_eval) > 0:
                                    _del_msg_idx = set()
                                    for msg_idx, msg in enumerate(self.__suspended_errors_for_lazy_eval):
                                        for k, v in msg.items():
                                            if v['ca_idx'] in _del_ca_idx:
                                                _del_msg_idx.add(msg_idx)
                                    if len(_del_msg_idx) > 0:
                                        for msg_idx in reversed(list(_del_msg_idx)):
                                            if msg_idx < len(self.__suspended_errors_for_lazy_eval):
                                                del self.__suspended_errors_for_lazy_eval[msg_idx]
                                if len(self.__suspended_warnings_for_lazy_eval) > 0:
                                    _del_msg_idx = set()
                                    for msg_idx, msg in enumerate(self.__suspended_warnings_for_lazy_eval):
                                        for k, v in msg.items():
                                            if v['ca_idx'] in _del_ca_idx:
                                                _del_msg_idx.add(msg_idx)
                                    if len(_del_msg_idx) > 0:
                                        for msg_idx in reversed(list(_del_msg_idx)):
                                            if msg_idx < len(self.__suspended_warnings_for_lazy_eval):
                                                del self.__suspended_warnings_for_lazy_eval[msg_idx]

                        if any(s for s in cif_polymer_sequence if 'identical_chain_id' in s):

                            _chain_assign = chain_assign.copy()

                            for ca in _chain_assign:

                                if ca['conflict'] > 0:
                                    continue

                                _chain_id = ca['test_chain_id']
                                _auth_chain_id = ca.get('test_auth_chain_id')

                                try:
                                    identity = next(s['identical_chain_id'] for s in cif_polymer_sequence
                                                    if s['chain_id'] == _chain_id and 'identical_chain_id' in s)

                                    for _chain_id in identity:

                                        if not any(_ca for _ca in chain_assign if _ca['test_chain_id'] == _chain_id):
                                            _ca = ca.copy()
                                            _ca['test_chain_id'] = _chain_id
                                            if _auth_chain_id is not None:
                                                _ca['test_auth_chain_id'] = _auth_chain_id
                                            chain_assign.append(_ca)

                                except StopIteration:
                                    pass

                    self.report.chain_assignment.setItemValue('nmr_poly_seq_vs_model_poly_seq', chain_assign)

                    if len(self.__suspended_errors_for_lazy_eval) > 0:
                        for msg in self.__suspended_errors_for_lazy_eval:
                            for k, v in msg.items():
                                if 'ca_idx' in v:
                                    del v['ca_idx']
                                self.report.error.appendDescription(k, v)
                                self.report.setError()
                        self.__suspended_errors_for_lazy_eval = []

                    if len(self.__suspended_warnings_for_lazy_eval) > 0:
                        for msg in self.__suspended_warnings_for_lazy_eval:
                            for k, v in msg.items():
                                if 'ca_idx' in v:
                                    del v['ca_idx']
                                self.report.warning.appendDescription(k, v)
                                self.report.setWarning()
                        self.__suspended_warnings_for_lazy_eval = []

                # from model to nmr (final)

                ca_idx = 0

                mat = []
                indices = []

                for s1 in cif_polymer_sequence:
                    chain_id = s1['chain_id']

                    cost = [0 for i in range(nmr_chains)]

                    for s2 in nmr_polymer_sequence:
                        chain_id2 = s2['chain_id']

                        result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                                       if seq_align['ref_chain_id'] == chain_id
                                       and seq_align['test_chain_id'] == chain_id2), None)

                        if result is not None:
                            cost[nmr_polymer_sequence.index(s2)] = result['unmapped'] + result['conflict'] - result['length']
                            if not self.__native_combined and result['length'] >= len(s1['seq_id']) - result['unmapped']:
                                indices.append((cif_polymer_sequence.index(s1), nmr_polymer_sequence.index(s2)))

                    mat.append(cost)

                if self.__native_combined:
                    indices = m.compute(mat)

                chain_assign = []

                concatenated_nmr_chain = {}

                for row, col in indices:

                    if mat[row][col] >= 0:

                        if self.__native_combined:
                            continue

                        # DAOTHER-8751
                        has_row = has_col = False
                        for _row, _col in indices:
                            if mat[_row][_col] < 0:
                                if _row == row:
                                    has_row = True
                                if _col == col:
                                    has_col = True

                        if has_row and has_col:
                            continue

                        _cif_chain_ids = [cif_polymer_sequence[_row]['chain_id'] for _row, _col in indices if col == _col]

                        if len(_cif_chain_ids) > 1:
                            chain_id2 = nmr_polymer_sequence[col]['chain_id']
                            concatenated_nmr_chain[chain_id2] = _cif_chain_ids

                            warn = f"The chain ID {chain_id2!r} of the sequences in the NMR data "\
                                f"will be re-assigned to the chain IDs {_cif_chain_ids} in the coordinates during biocuration."

                            self.report.warning.appendDescription('concatenated_sequence',
                                                                  {'file_name': nmr_file_name, 'description': warn})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {warn}\n")

                    chain_id = cif_polymer_sequence[row]['chain_id']
                    chain_id2 = nmr_polymer_sequence[col]['chain_id']

                    result = next(seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                                  if seq_align['ref_chain_id'] == chain_id and seq_align['test_chain_id'] == chain_id2)
                    _result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                                    if seq_align['ref_chain_id'] == chain_id2 and seq_align['test_chain_id'] == chain_id), None)

                    if result['matched'] == 0 or\
                            (result['conflict'] > 0
                             and result['sequence_coverage'] < LOW_SEQ_COVERAGE < float(result['conflict']) / float(result['matched'])):
                        continue

                    ca = {'ref_chain_id': chain_id, 'test_chain_id': chain_id2, 'length': result['length'],
                          'matched': result['matched'], 'conflict': result['conflict'], 'unmapped': result['unmapped'],
                          'sequence_coverage': result['sequence_coverage']}

                    # DAOTHER-8751
                    low_evid_chain_mapping = result['sequence_coverage'] < LOW_SEQ_COVERAGE
                    if low_evid_chain_mapping:
                        low_evid_chain_mapping = False
                        for _row, _col in indices:
                            if mat[_row][_col] >= 0:
                                if _row == row or _col == col:
                                    low_evid_chain_mapping = True

                    auth_chain_id = chain_id
                    if 'auth_chain_id' in cif_polymer_sequence[row]:
                        auth_chain_id = cif_polymer_sequence[row]['auth_chain_id']
                        ca['ref_auth_chain_id'] = auth_chain_id

                    s1 = next(s for s in cif_polymer_sequence if s['chain_id'] == chain_id)
                    s2 = next(s for s in nmr_polymer_sequence if s['chain_id'] == chain_id2)

                    self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                    self.__pA.addTestSequence(s2['comp_id'], chain_id)
                    self.__pA.doAlign()

                    myAlign = self.__pA.getAlignment(chain_id)

                    length = len(myAlign)

                    _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    if conflict > 0 and any(len(c) > 3 for c in s1['comp_id']) and 'alt_comp_id' in s1:
                        self.__pA.setReferenceSequence(s1['alt_comp_id'], 'REF' + chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                        if conflict > 0:
                            self.__pA.setReferenceSequence(s1['comp_id'], 'REF' + chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

                    _s1 = s1 if offset_1 == 0 else fillBlankCompIdWithOffset(s1, offset_1)
                    _s2 = s2 if offset_2 == 0 else fillBlankCompIdWithOffset(s2, offset_2)

                    if conflict == 0:
                        has_inner_gap_1 = hasLargeInnerSeqGap(_s1)
                        has_inner_gap_2 = hasLargeInnerSeqGap(_s2)

                        if has_inner_gap_2 and not has_inner_gap_1:
                            _s2 = fillInnerBlankCompId(_s2)
                        elif has_inner_gap_1 and not has_inner_gap_2:
                            _s1 = fillInnerBlankCompId(_s1)

                    if conflict > 0 and (hasLargeSeqGap(_s1, _s2) or (not hasLargeInnerSeqGap(s1) and hasLargeInnerSeqGap(s2))):  # DAOTHER-7465
                        __s1, __s2 = beautifyPolySeq(_s1, _s2)
                        _s1 = __s1
                        _s2 = __s2

                        self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                        self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                        self.__pA.doAlign()

                        myAlign = self.__pA.getAlignment(chain_id)

                        length = len(myAlign)

                        _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                        if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                            result['conflict'] = 0
                            s2 = __s2

                    if conflict > 0 and 'gap_in_auth_seq' in _s1 and _s1['gap_in_auth_seq'] and 'auth_seq_id' in _s1:
                        __s2 = copy.deepcopy(_s2)
                        for p in range(len(_s1['auth_seq_id']) - 1):
                            s_p = _s1['auth_seq_id'][p]
                            s_q = _s1['auth_seq_id'][p + 1]
                            if s_p is None or s_q is None or s_p + 1 == s_q:
                                continue
                            for s_o in range(s_p + 1, s_q):
                                if s_o in __s2['seq_id']:
                                    idx = __s2['seq_id'].index(s_o)
                                    if __s2['comp_id'][idx] in emptyValue:
                                        __s2['seq_id'].pop(idx)
                                        __s2['comp_id'].pop(idx)

                        if len(_s2['seq_id']) != len(__s2['seq_id']):
                            __s1, __s2 = beautifyPolySeq(_s1, __s2)
                            _s1 = __s1
                            _s2 = __s2

                            self.__pA.setReferenceSequence(_s1['comp_id'], 'REF' + chain_id)
                            self.__pA.addTestSequence(_s2['comp_id'], chain_id)
                            self.__pA.doAlign()

                            myAlign = self.__pA.getAlignment(chain_id)

                            length = len(myAlign)

                            _matched, unmapped, _conflict, _, _ = getScoreOfSeqAlign(myAlign)

                            if _conflict == 0:
                                result['conflict'] = 0
                                s2 = __s2

                    ref_code = getOneLetterCodeCanSequence(s1['comp_id'])
                    test_code = getOneLetterCodeCanSequence(s2['comp_id'])

                    for r_code, t_code, seq_id, seq_id2 in zip(ref_code, test_code, s1['seq_id'], s2['seq_id']):
                        if r_code == 'X' and t_code == 'X':
                            nmr_input_source.updateNonStandardResidueByExptlData(chain_id2, seq_id2, 'coordinate')
                            cif_input_source.updateNonStandardResidueByExptlData(chain_id, seq_id, 'coordinate')

                    if result['unmapped'] > 0 or result['conflict'] > 0:

                        aligned = [True] * length
                        seq_id1 = []
                        seq_id2 = []

                        j = 0
                        for i in range(length):
                            if j < len(s1['seq_id']) and (str(myAlign[i][0]) != '.' or s1['comp_id'][j] == '.'):
                                seq_id1.append(s1['seq_id'][j])
                                j += 1
                            else:
                                seq_id1.append(None)

                        j = 0
                        for i in range(length):
                            if j < len(s2['seq_id']) and (str(myAlign[i][1]) != '.' or s2['comp_id'][j] == '.'):
                                seq_id2.append(s2['seq_id'][j])
                                j += 1
                            else:
                                seq_id2.append(None)

                        for i in range(length):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        for i in reversed(range(length)):
                            myPr = myAlign[i]
                            myPr0 = str(myPr[0])
                            myPr1 = str(myPr[1])
                            if myPr0 == '.' or myPr1 == '.':
                                aligned[i] = False
                            elif myPr0 != myPr1:
                                pass
                            else:
                                break

                        if not self.__native_combined:

                            _conflicts = 0

                            for i in range(length):
                                myPr = myAlign[i]
                                if myPr[0] == myPr[1]:
                                    continue

                                cif_comp_id = str(myPr[0])
                                nmr_comp_id = str(myPr[1])

                                if nmr_comp_id == '.' and cif_comp_id != '.':
                                    pass

                                elif nmr_comp_id != cif_comp_id and aligned[i]:
                                    _conflicts += 1

                            if _conflicts > ca['unmapped'] and ca['sequence_coverage'] < MIN_SEQ_COVERAGE_W_CONFLICT:
                                continue

                            if _conflicts + offset_1 > _matched and ca['sequence_coverage'] < LOW_SEQ_COVERAGE:  # DAOTHER-7825 (2lyw)
                                if not low_evid_chain_mapping:  # DAOTHER-8751
                                    continue

                        unmapped = []
                        conflict = []
                        # offset_1 = 0
                        # offset_2 = 0

                        for i in range(length):
                            myPr = myAlign[i]
                            if myPr[0] == myPr[1]:
                                continue

                            cif_comp_id = str(myPr[0])
                            nmr_comp_id = str(myPr[1])

                            if nmr_comp_id == '.' and cif_comp_id != '.':

                                unmapped.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': cif_comp_id})

                                try:
                                    label_seq_id = seq_id1[i]
                                    auth_seq_id = s1['auth_seq_id'][s1['seq_id'].index(label_seq_id)]
                                except (KeyError, IndexError, ValueError):
                                    label_seq_id = seq_id1[i]
                                    auth_seq_id = label_seq_id

                                if not aligned[i]:
                                    cif_seq_code = f"{chain_id}:{label_seq_id}:{cif_comp_id}"
                                    if chain_id != auth_chain_id or label_seq_id != auth_seq_id:
                                        cif_seq_code += f" ({auth_chain_id}:{auth_seq_id}:{cif_comp_id} in author sequence scheme)"

                                    warn = f"{cif_seq_code} is not present in the NMR data (chain_id {chain_id2})."

                                    self.__suspended_warnings_for_lazy_eval.append({'sequence_mismatch':
                                                                                    {'ca_idx': ca_idx, 'file_name': cif_file_name, 'description': warn}})

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {warn}\n")

                            elif nmr_comp_id != cif_comp_id and aligned[i]:

                                _seq_id1 = seq_id1[i]
                                _seq_id2 = seq_id2[i]

                                if _seq_id1 is None and _seq_id2 is None:
                                    continue

                                conflict.append({'ref_seq_id': _seq_id1, 'ref_comp_id': cif_comp_id,
                                                 'test_seq_id': _seq_id2, 'test_comp_id': nmr_comp_id})

                                try:
                                    label_seq_id = _seq_id1
                                    auth_seq_id = s1['auth_seq_id'][s1['seq_id'].index(label_seq_id)]
                                except (KeyError, IndexError, ValueError):
                                    label_seq_id = _seq_id1
                                    auth_seq_id = label_seq_id

                                cif_seq_code = f"{chain_id}:{label_seq_id}:{cif_comp_id}"
                                if cif_comp_id == '.':
                                    cif_seq_code += ', insertion error'
                                    # DAOTHER-9644: skip insertion error due to truncated loop
                                    cif_ps = next(cif_ps for cif_ps in cif_polymer_sequence if cif_ps['chain_id'] == chain_id)
                                    if label_seq_id is None:
                                        if self.__caC is not None and 'missing_polymer_linkage' in self.__caC\
                                           and any(mis for mis in self.__caC['missing_polymer_linkage']
                                                   if mis['auth_chain_id'] == (cif_ps['auth_chain_id'] if 'auth_chain_id' in cif_ps else cif_ps['chain_id'])):
                                            continue
                                    else:
                                        if self.__caC is not None and 'missing_polymer_linkage' in self.__caC\
                                           and any(mis for mis in self.__caC['missing_polymer_linkage']
                                                   if mis['auth_chain_id'] == (cif_ps['auth_chain_id'] if 'auth_chain_id' in cif_ps else cif_ps['chain_id'])
                                                   and mis['auth_seq_id_1'] < auth_seq_id < mis['auth_seq_id_2']):
                                            continue
                                nmr_seq_code = f"{chain_id2}:{_seq_id2}:{nmr_comp_id}"
                                if nmr_comp_id == '.':
                                    nmr_seq_code += ', insertion error'
                                if cif_comp_id != '.':
                                    if chain_id != auth_chain_id or label_seq_id != auth_seq_id:
                                        cif_seq_code += f", or {auth_chain_id}:{auth_seq_id}:{cif_comp_id} in author sequence scheme"

                                err = f"Sequence alignment error between the coordinate ({cif_seq_code}) and the NMR data ({nmr_seq_code}). "\
                                    "Please verify the two sequences and re-upload the correct file(s)."

                                if self.__tolerant_seq_align and self.__equalsRepCompId(nmr_comp_id, cif_comp_id):
                                    self.__suspended_warnings_for_lazy_eval.append({'sequence_mismatch':
                                                                                    {'ca_idx': ca_idx, 'file_name': cif_file_name, 'description': err}})

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {err}\n")

                                elif not self.__annotation_mode:
                                    self.__suspended_errors_for_lazy_eval.append({'sequence_mismatch':
                                                                                  {'ca_idx': ca_idx, 'file_name': cif_file_name, 'description': err}})

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Error  - {err}\n")

                        if len(unmapped) > 0:
                            ca['unmapped_sequence'] = unmapped

                        if len(conflict) > 0:
                            ca['conflict_sequence'] = conflict
                            ca['conflict'] = len(conflict)
                            ca['unmapped'] = ca['unmapped'] - len(conflict)
                            if ca['unmapped'] < 0:
                                ca['conflict'] -= ca['unmapped']
                                ca['unmapped'] = 0

                            result['conflict'] = ca['conflict']
                            result['unmapped'] = ca['unmapped']

                            if _result is not None:
                                _result['conflict'] = ca['conflict']
                                _result['unmapped'] = ca['unmapped']

                    chain_assign.append(ca)
                    ca_idx += 1

                if len(chain_assign) > 0 and fileListId == 0:

                    if len(cif_polymer_sequence) > 1:

                        if len(self.__suspended_errors_for_lazy_eval) + len(self.__suspended_warnings_for_lazy_eval) > 0:

                            _del_ca_idx = []

                            for ca_idx, ca in enumerate(chain_assign):

                                if ca['conflict'] == 0:
                                    continue

                                ref_chain_id = ca['ref_chain_id']
                                test_chain_id = ca['test_chain_id']

                                if any(_ca for _ca in chain_assign
                                       if ((_ca['ref_chain_id'] == ref_chain_id and _ca['test_chain_id'] != test_chain_id)
                                           or (_ca['ref_chain_id'] != ref_chain_id and _ca['test_chain_id'] == test_chain_id))
                                       and _ca['conflict'] == 0):
                                    _del_ca_idx.append(ca_idx)

                            if len(_del_ca_idx) > 0:
                                for ca_idx in reversed(_del_ca_idx):
                                    del chain_assign[ca_idx]
                                if len(self.__suspended_errors_for_lazy_eval) > 0:
                                    _del_msg_idx = set()
                                    for msg_idx, msg in enumerate(self.__suspended_errors_for_lazy_eval):
                                        for k, v in msg.items():
                                            if v['ca_idx'] in _del_ca_idx:
                                                _del_msg_idx.add(msg_idx)
                                    if len(_del_msg_idx) > 0:
                                        for msg_idx in reversed(list(_del_msg_idx)):
                                            if msg_idx < len(self.__suspended_errors_for_lazy_eval):
                                                del self.__suspended_errors_for_lazy_eval[msg_idx]
                                if len(self.__suspended_warnings_for_lazy_eval) > 0:
                                    _del_msg_idx = set()
                                    for msg_idx, msg in enumerate(self.__suspended_warnings_for_lazy_eval):
                                        for k, v in msg.items():
                                            if v['ca_idx'] in _del_ca_idx:
                                                _del_msg_idx.add(msg_idx)
                                    if len(_del_msg_idx) > 0:
                                        for msg_idx in reversed(list(_del_msg_idx)):
                                            if msg_idx < len(self.__suspended_warnings_for_lazy_eval):
                                                del self.__suspended_warnings_for_lazy_eval[msg_idx]

                        if any(s for s in cif_polymer_sequence if 'identical_chain_id' in s):

                            _chain_assign = chain_assign.copy()

                            for ca in _chain_assign:

                                if ca['conflict'] > 0:
                                    continue

                                chain_id = ca['ref_chain_id']
                                auth_chain_id = ca.get('ref_auth_chain_id')

                                try:
                                    identity = next(s['identical_chain_id'] for s in cif_polymer_sequence
                                                    if s['chain_id'] == chain_id and 'identical_chain_id' in s)

                                    for chain_id in identity:

                                        if not any(_ca for _ca in chain_assign if _ca['ref_chain_id'] == chain_id):
                                            _ca = ca.copy()
                                            _ca['ref_chain_id'] = chain_id
                                            if auth_chain_id is not None:
                                                _ca['ref_auth_chain_id'] = auth_chain_id
                                            chain_assign.append(_ca)

                                except StopIteration:
                                    pass

                    self.report.chain_assignment.setItemValue('model_poly_seq_vs_nmr_poly_seq', chain_assign)

                    if len(self.__suspended_errors_for_lazy_eval) > 0:
                        for msg in self.__suspended_errors_for_lazy_eval:
                            for k, v in msg.items():
                                if 'ca_idx' in v:
                                    del v['ca_idx']
                                self.report.error.appendDescription(k, v)
                                self.report.setError()
                        self.__suspended_errors_for_lazy_eval = []

                    if len(self.__suspended_warnings_for_lazy_eval) > 0:
                        for msg in self.__suspended_warnings_for_lazy_eval:
                            for k, v in msg.items():
                                if 'ca_idx' in v:
                                    del v['ca_idx']
                                self.report.warning.appendDescription(k, v)
                                self.report.setWarning()
                        self.__suspended_warnings_for_lazy_eval = []

                chain_assign_dic = self.report.chain_assignment.get()

                if has_key_value(chain_assign_dic, 'nmr_poly_seq_vs_model_poly_seq'):
                    ref_chain_ids = {}
                    for ca in chain_assign_dic['nmr_poly_seq_vs_model_poly_seq']:
                        if ca['ref_chain_id'] in ref_chain_ids or (ca['length'] == 1 and 1 in ref_chain_ids.values()):
                            continue
                        self.__label_asym_id_with_exptl_data.add(ca['test_chain_id'])
                        ref_chain_ids[ca['ref_chain_id']] = ca['length']

            elif not self.__annotation_mode:

                err = "No sequence alignment found."

                self.report.error.appendDescription('sequence_mismatch',
                                                    {'file_name': cif_file_name, 'description': err})
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Error  - {err}\n")

                return False

        return self.report.getTotalErrors() == __errors

    def __updateCompIdInCsLoop(self, file_list_id, cif_ps, nmr_ps):
        """ Update residue name in CS loop to follow CCD replacement.
        """

        if len(cif_ps['seq_id']) != len(nmr_ps['seq_id']) or cif_ps['comp_id'] == nmr_ps['comp_id']:
            return False

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type == 'nef' or file_list_id >= len(self.__star_data) or self.__star_data[file_list_id] is None:
            return False

        if input_source_dic['content_subtype'] is None:
            return False

        content_subtype = 'chem_shift'

        if content_subtype not in input_source_dic['content_subtype']:
            return False

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
        has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if (not has_poly_seq) or (not has_poly_seq_in_loop):
            return False

        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        if content_subtype not in polymer_sequence_in_loop:
            return False

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        ps_in_loop = polymer_sequence_in_loop[content_subtype]

        modified = False

        list_id = 1

        if self.__star_data_type[file_list_id] == 'Loop':
            sf = self.__star_data[file_list_id]

            try:
                ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
            except StopIteration:
                return False

            allow_chain_id_mismatch = len(ps) == 1

            modified |= self.__updateCompIdInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        elif self.__star_data_type[file_list_id] == 'Saveframe':
            sf = self.__star_data[file_list_id]

            try:
                ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
            except StopIteration:
                return False

            allow_chain_id_mismatch = len(ps) == 1

            modified |= self.__updateCompIdInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        else:

            for list_id, sf in enumerate(self.__star_data[file_list_id].get_saveframes_by_category(sf_category), start=1):

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                try:
                    ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                    next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
                except StopIteration:
                    continue

                allow_chain_id_mismatch = len(ps) == 1

                modified |= self.__updateCompIdInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        return modified

    def __updateCompIdInCsLoop__(self, file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch):
        """ Update residue name in CS loop to follow CCD replacement.
        """

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        chain_id_col = loop.tags.index('Entity_assembly_ID')
        seq_id_col = loop.tags.index('Comp_index_ID')
        comp_id_col = loop.tags.index('Comp_ID')
        auth_comp_id_col = loop.tags.index('Auth_comp_ID') if 'Auth_comp_ID' in loop.tags else -1

        nmr_chain_id = nmr_ps['chain_id']

        comp_id_map = {nmr_seq_id: cif_comp_id
                       for cif_comp_id, nmr_seq_id, nmr_comp_id
                       in zip(cif_ps['comp_id'], nmr_ps['seq_id'], nmr_ps['comp_id'])
                       if cif_comp_id != nmr_comp_id}

        modified = False

        for row in loop.data:
            if (not allow_chain_id_mismatch and row[chain_id_col] != nmr_chain_id) or row[seq_id_col] in emptyValue:
                continue

            try:
                nmr_seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
            except ValueError:
                continue

            if nmr_seq_id not in comp_id_map:
                continue

            cif_comp_id = comp_id_map[nmr_seq_id]

            row[comp_id_col] = cif_comp_id

            if auth_comp_id_col != -1:
                row[auth_comp_id_col] = cif_comp_id

            modified = True

        return modified

    def __resolveUnmappedAuthSequenceInCsLoop(self, file_list_id, cif_ps, nmr_ps):
        """ Resolve unmapped author sequence in CS loop based on sequence alignment.
        """

        input_source = self.report.input_sources[file_list_id]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type == 'nef' or file_list_id >= len(self.__star_data) or self.__star_data[file_list_id] is None:
            return False

        if input_source_dic['content_subtype'] is None:
            return False

        content_subtype = 'chem_shift'

        if content_subtype not in input_source_dic['content_subtype']:
            return False

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')
        has_poly_seq_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if (not has_poly_seq) or (not has_poly_seq_in_loop):
            return False

        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        if content_subtype not in polymer_sequence_in_loop:
            return False

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        ps_in_loop = polymer_sequence_in_loop[content_subtype]

        modified = False

        list_id = 1

        if self.__star_data_type[file_list_id] == 'Loop':
            sf = self.__star_data[file_list_id]

            try:
                ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
            except StopIteration:
                return False

            allow_chain_id_mismatch = len(ps) == 1

            modified |= self.__resolveUnmappedAuthSequenceInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        elif self.__star_data_type[file_list_id] == 'Saveframe':
            sf = self.__star_data[file_list_id]

            try:
                ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
            except StopIteration:
                return False

            allow_chain_id_mismatch = len(ps) == 1

            modified |= self.__resolveUnmappedAuthSequenceInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        else:

            for list_id, sf in enumerate(self.__star_data[file_list_id].get_saveframes_by_category(sf_category), start=1):

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                try:
                    ps = next(ps['polymer_sequence'] for ps in ps_in_loop if ps['list_id'] == list_id)
                    next(s for s in ps if s['chain_id'] == nmr_ps['chain_id'])
                except StopIteration:
                    continue

                allow_chain_id_mismatch = len(ps) == 1

                modified |= self.__resolveUnmappedAuthSequenceInCsLoop__(file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch)

        return modified

    def __resolveUnmappedAuthSequenceInCsLoop__(self, file_list_id, sf, lp_category, cif_ps, nmr_ps, allow_chain_id_mismatch):
        """ Resolve unmapped author sequence in CS loop based on sequence alignment.
        """

        if __pynmrstar_v3_2__:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
        else:
            loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

        chain_id_col = loop.tags.index('Entity_assembly_ID')
        seq_id_col = loop.tags.index('Comp_index_ID')
        auth_chain_id_col = loop.tags.index('Auth_asym_ID') if 'Auth_asym_ID' in loop.tags else -1
        auth_seq_id_col = loop.tags.index('Auth_seq_ID') if 'Auth_seq_ID' in loop.tags else -1

        if auth_chain_id_col == -1:
            return False

        chain_id = cif_ps['chain_id']

        self.__pA.setReferenceSequence(cif_ps['comp_id'], 'REF' + chain_id)
        self.__pA.addTestSequence(nmr_ps['comp_id'], chain_id)
        self.__pA.doAlign()

        myAlign = self.__pA.getAlignment(chain_id)

        length = len(myAlign)

        if length == 0:
            return False

        _matched, unmapped, conflict, offset_1, offset_2 = getScoreOfSeqAlign(myAlign)

        if length == unmapped + conflict or conflict > 0:
            return False

        _cif_ps = cif_ps if offset_1 == 0 else fillBlankCompIdWithOffset(cif_ps, offset_1)
        _nmr_ps = nmr_ps if offset_2 == 0 else fillBlankCompIdWithOffset(nmr_ps, offset_2)

        nmr_chain_id = nmr_ps['chain_id']

        modified = False

        for row in loop.data:
            if (not allow_chain_id_mismatch and row[chain_id_col] != nmr_chain_id) or row[seq_id_col] in emptyValue:
                continue

            try:
                nmr_seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
            except ValueError:
                continue

            if nmr_seq_id not in nmr_ps['seq_id']:
                continue

            if row[auth_chain_id_col] in emptyValue or row[auth_chain_id_col] == 'UNMAPPED':
                row[auth_chain_id_col] = cif_ps['auth_chain_id' if 'auth_chain_id' in cif_ps else 'chain_id']
                if auth_seq_id_col != -1:
                    try:
                        str(next(_cif_seq_id for _cif_seq_id, _nmr_seq_id
                                 in zip(_cif_ps['auth_seq_id' if 'auth_seq_id' in _cif_ps else 'seq_id'],
                                        _nmr_ps['seq_id'])
                                 if _nmr_seq_id == nmr_seq_id))
                    except StopIteration:  # D_1300044764
                        return False

        for row in loop.data:
            if (not allow_chain_id_mismatch and row[chain_id_col] != nmr_chain_id) or row[seq_id_col] in emptyValue:
                continue

            try:
                nmr_seq_id = row[seq_id_col] if isinstance(row[seq_id_col], int) else int(row[seq_id_col])
            except ValueError:
                continue

            if nmr_seq_id not in nmr_ps['seq_id']:
                continue

            if row[auth_chain_id_col] in emptyValue or row[auth_chain_id_col] == 'UNMAPPED':
                row[auth_chain_id_col] = cif_ps['auth_chain_id' if 'auth_chain_id' in cif_ps else 'chain_id']
                if auth_seq_id_col != -1:
                    row[auth_seq_id_col] =\
                        str(next(_cif_seq_id for _cif_seq_id, _nmr_seq_id
                                 in zip(_cif_ps['auth_seq_id' if 'auth_seq_id' in _cif_ps else 'seq_id'],
                                        _nmr_ps['seq_id'])
                                 if _nmr_seq_id == nmr_seq_id))

                modified = True

        return modified

    def __testCoordAtomIdConsistency(self):
        """ Perform consistency test on atom names of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        cif_polymer_sequence = cif_input_source_dic['polymer_sequence']

        __errors = self.report.getTotalErrors()

        for fileListId in range(self.__file_path_list_len):

            nmr_input_source = self.report.input_sources[fileListId]
            nmr_input_source_dic = nmr_input_source.get()

            file_name = nmr_input_source_dic['file_name']
            file_type = nmr_input_source_dic['file_type']

            seq_align_dic = self.report.sequence_alignment.get()
            chain_assign_dic = self.report.chain_assignment.get()

            if 'nmr_poly_seq_vs_model_poly_seq' not in chain_assign_dic:

                err = "Chain assignment does not exist, __assignCoordPolymerSequence() should be invoked."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testCoordAtomIdConsistency() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testCoordAtomIdConsistency() ++ Error  - {err}\n")

                continue

            if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
                continue

            if not has_key_value(chain_assign_dic, 'nmr_poly_seq_vs_model_poly_seq'):
                continue

            nmr2ca = {}

            for ca in chain_assign_dic['nmr_poly_seq_vs_model_poly_seq']:

                ref_chain_id = ca['ref_chain_id']
                test_chain_id = ca['test_chain_id']

                result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                               if seq_align['ref_chain_id'] == ref_chain_id and seq_align['test_chain_id'] == test_chain_id), None)

                if ref_chain_id not in nmr2ca:
                    nmr2ca[ref_chain_id] = []

                sa = {'seq_align': result}  # DAOTHER-7465

                if 'unmapped_sequence' in ca:
                    sa['seq_unmap'] = [unmapped['ref_seq_id'] for unmapped in ca['unmapped_sequence']]

                nmr2ca[ref_chain_id].append(sa)

            if nmr_input_source_dic['content_subtype'] is None:
                continue

            modified = False

            for content_subtype in nmr_input_source_dic['content_subtype']:

                if content_subtype in ('entry_info', 'entity'):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                if content_subtype == 'poly_seq':
                    lp_category = self.aux_lp_categories[file_type][content_subtype][0]

                list_id = 1

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    modified |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype, sf,
                                                                    list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                    seq_align_dic, nmr2ca, ref_chain_id)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    modified |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype, sf,
                                                                    list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                    seq_align_dic, nmr2ca, ref_chain_id)

                else:

                    for list_id, sf in enumerate(self.__star_data[fileListId].get_saveframes_by_category(sf_category), start=1):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        modified |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype,
                                                                        sf, list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                        seq_align_dic, nmr2ca, ref_chain_id)

            if modified:
                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __testCoordAtomIdConsistency__(self, file_list_id, file_name, file_type, content_subtype,
                                       sf, list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                       seq_align_dic, nmr2ca, ref_chain_id):
        """ Perform consistency test on atom names of coordinate file.
        """

        modified = False

        index_tag = self.index_tags[file_type][content_subtype] if content_subtype != 'poly_seq' else None

        if file_type == 'nef' or (not self.__nonblk_bad_nterm):

            if content_subtype != 'poly_seq':
                lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)
            else:
                lp_data = next((lp['data'] for lp in self.__aux_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                and lp['category'] == lp_category), None)

        else:

            if content_subtype == 'spectral_peak':

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return False

                max_dim = num_dim + 1

                key_items = []
                for dim in range(1, max_dim):
                    for k in self.pk_key_items[file_type]:
                        if k['type'] == 'float':  # position
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            key_items.append(_k)
                for k in self.pk_key_items[file_type]:
                    if k['type'] == 'positive-int':  # peak_id
                        key_items.append(k)

                data_items = []
                for d in self.data_items[file_type][content_subtype]:
                    data_items.append(d)
                for dim in range(1, max_dim):
                    for d in self.pk_data_items[file_type]:
                        _d = copy.copy(d)
                        if '%s' in d['name']:
                            _d['name'] = d['name'] % dim
                        if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                            _d['default-from'] = d['default-from'] % dim
                        data_items.append(_d)

            else:

                if content_subtype != 'poly_seq':
                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]
                else:
                    key_items = self.aux_key_items[file_type][content_subtype][lp_category]
                    data_items = self.aux_data_items[file_type][content_subtype][lp_category]

            try:

                lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                 enforce_allowed_tags=(file_type == 'nmr-star'),
                                                 excl_missing_data=self.__excl_missing_data)[0]

            except Exception:
                return False

        if lp_data is None:
            return False

        has_seq_align = False

        sa_name = 'nmr_poly_seq_vs_' + content_subtype

        if has_key_value(seq_align_dic, sa_name):

            for seq_align in seq_align_dic[sa_name]:

                if seq_align['list_id'] == list_id:
                    has_seq_align = True
                    break

        if not has_seq_align and content_subtype != 'poly_seq':
            return False

        item_names = []

        if content_subtype == 'chem_shift':
            max_dim = 2

            item_names.append(self.item_names_in_cs_loop[file_type])

        else:

            if content_subtype in ('poly_seq', 'dist_restraint', 'rdc_restraint'):
                max_dim = 3

            elif content_subtype == 'dihed_restraint':
                max_dim = 5

            elif content_subtype == 'spectral_peak':

                try:

                    _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        raise ValueError()

                except ValueError:  # raised error already at __testIndexConsistency()
                    return False

                max_dim = num_dim + 1

            else:
                return False

            for j in range(1, max_dim):
                _item_names = {}
                for k, v in self.item_names_in_pk_loop[file_type].items():
                    if '%s' in v:
                        v = v % j
                    _item_names[k] = v
                item_names.append(_item_names)

        num_dim = max_dim - 1

        chain_id_names = []
        seq_id_names = []
        comp_id_names = []
        atom_id_names = []
        if file_type == 'nmr-star':
            alt_seq_id_names = []

        for j in range(num_dim):
            chain_id_names.append(item_names[j]['chain_id'])
            seq_id_names.append(item_names[j]['seq_id'])
            comp_id_names.append(item_names[j]['comp_id'])
            atom_id_names.append(item_names[j]['atom_id'])
            if file_type == 'nmr-star':
                alt_seq_id_names.append(item_names[j]['alt_seq_id'])

        details_col = -1

        if file_type == 'nmr-star':

            if __pynmrstar_v3_2__:
                loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)
            else:
                loop = sf if self.__star_data_type[file_list_id] == 'Loop' else sf.get_loop_by_category(lp_category)

            if 'Details' in loop.tags:
                details_col = loop.tags.index('Details')

        def get_coord_atom_site_of(chain_id, seq_id, comp_id):

            if (chain_id, seq_id, comp_id) in self.__caC['auth_to_star_seq']:
                seq_key = (chain_id, seq_id)

                if seq_key in self.__caC['coord_unobs_res']:  # DAOTHER-7665
                    return True, None, None

                if seq_key not in self.__caC['coord_atom_site']:
                    return True, None, None

                if seq_key not in self.__caC['auth_to_label_seq']:
                    return True, None, None

                coord_atom_site_ = self.__caC['coord_atom_site'][seq_key]

                cif_comp_id = coord_atom_site_['comp_id']

                if comp_id == cif_comp_id:
                    return True, seq_key, coord_atom_site_

            if (chain_id, seq_id) in self.__caC['label_to_auth_seq']:
                _chain_id, _seq_id = self.__caC['label_to_auth_seq'][(chain_id, seq_id)]

                if (_chain_id, _seq_id, comp_id) in self.__caC['auth_to_star_seq']:
                    seq_key = (_chain_id, _seq_id)

                    if seq_key in self.__caC['coord_unobs_res']:  # DAOTHER-7665
                        return True, None, None

                    if seq_key not in self.__caC['coord_atom_site']:
                        return True, None, None

                    if seq_key not in self.__caC['auth_to_label_seq']:
                        return True, None, None

                    coord_atom_site_ = self.__caC['coord_atom_site'][seq_key]

                    cif_comp_id = coord_atom_site_['comp_id']

                    if comp_id == cif_comp_id:
                        return True, seq_key, coord_atom_site_

            return False, None, None

        for idx, row in enumerate(lp_data):

            for j in range(num_dim):
                chain_id = row[chain_id_names[j]]
                seq_id = alt_seq_id = row[seq_id_names[j]]
                comp_id = row[comp_id_names[j]]
                atom_id = row[atom_id_names[j]]
                if file_type == 'nmr-star' and alt_seq_id_names[j] in row:
                    alt_seq_id = row[alt_seq_id_names[j]]

                if content_subtype.startswith('spectral_peak')\
                   and (chain_id in emptyValue or seq_id in emptyValue or comp_id in emptyValue or atom_id in emptyValue):
                    continue

                if chain_id not in nmr2ca:
                    continue

                ca = next((ca['seq_align'] for ca in nmr2ca[chain_id]
                           if ('seq_unmap' not in ca or (seq_id not in ca['seq_unmap']))), None)  # DAOTHER-7465

                if ca is None:
                    continue

                cif_chain_id = ca['test_chain_id']

                cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                   in zip(ca['ref_seq_id'], ca['test_seq_id'])
                                   if ref_seq_id == seq_id), None)

                if cif_seq_id is None and ca['sequence_coverage'] >= LOW_SEQ_COVERAGE:
                    continue

                cif_ps = next(ps for ps in cif_polymer_sequence if ps['chain_id'] == cif_chain_id)

                if ca['sequence_coverage'] < LOW_SEQ_COVERAGE:  # DAOTHER-8751, issue #2
                    if 'auth_seq_id' in cif_ps:
                        cif_seq_id, cif_comp_id = next(((_seq_id, _comp_id) for _auth_seq_id, _seq_id, _comp_id
                                                        in zip(cif_ps['auth_seq_id'], cif_ps['seq_id'], cif_ps['comp_id'])
                                                        if _auth_seq_id == seq_id), (None, None))
                    else:
                        cif_seq_id, cif_comp_id = next(((_seq_id, _comp_id) for _seq_id, _comp_id
                                                        in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                                        if _seq_id == seq_id), (None, None))

                    if cif_seq_id is None or cif_comp_id is None:
                        continue

                else:
                    cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                        in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                        if _seq_id == cif_seq_id), None)

                    if cif_comp_id is None:
                        continue

                if ca['sequence_coverage'] < LOW_SEQ_COVERAGE:
                    if 'auth_seq_id' in cif_ps:
                        cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                            in zip(cif_ps['auth_seq_id'], cif_ps['comp_id'])
                                            if _seq_id == seq_id), None)
                    else:
                        cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                            in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                            if _seq_id == seq_id), None)

                    if cif_comp_id is None:
                        continue

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id
                        atom_name = atom_id

                        if details is not None:
                            atom_name += f", where {details.rstrip('.')}"

                    else:
                        atom_name = f'{atom_id} (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += f'{atom_id_} '

                        atom_name = f'{atom_name.rstrip()})'

                        # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id
                    atom_name = atom_id

                found, seq_key, coord_atom_site_ = get_coord_atom_site_of(cif_chain_id, seq_id, comp_id)

                if found:

                    if seq_key is None:
                        continue

                    cif_chain_id, cif_seq_id = self.__caC['auth_to_label_seq'][seq_key]
                    cif_comp_id = comp_id

                else:

                    seq_key = (cif_chain_id, cif_seq_id)

                    if seq_key in self.__coord_unobs_res:  # DAOTHER-7665
                        continue

                    coord_atom_site_ = self.__coord_atom_site.get(seq_key)

                    if file_type == 'nmr-star' and seq_id != alt_seq_id:

                        if coord_atom_site_ is None or coord_atom_site_['comp_id'] != cif_comp_id\
                           or (atom_id_ not in coord_atom_site_['atom_id']
                               and (('auth_atom_id' in coord_atom_site_ and atom_id_ not in coord_atom_site_['auth_atom_id'])
                                    or 'auth_atom_id' not in coord_atom_site_)):

                            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                               in zip(ca['ref_seq_id'], ca['test_seq_id'])
                                               if ref_seq_id == alt_seq_id), None)

                            if cif_seq_id is None:
                                continue

                            cif_ps = next(ps for ps in cif_polymer_sequence if ps['chain_id'] == cif_chain_id)

                            cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                                in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                                if _seq_id == cif_seq_id), None)

                            if cif_comp_id is None:
                                continue

                            seq_key = (cif_chain_id, cif_seq_id)

                            if seq_key in self.__coord_unobs_res:  # DAOTHER-7665
                                continue

                            coord_atom_site_ = self.__coord_atom_site.get(seq_key)

                if coord_atom_site_ is None or coord_atom_site_['comp_id'] != cif_comp_id\
                   or (atom_id_ not in coord_atom_site_['atom_id']
                       and (('auth_atom_id' in coord_atom_site_ and atom_id_ not in coord_atom_site_['auth_atom_id'])
                            or 'auth_atom_id' not in coord_atom_site_)):

                    idx_msg = ''
                    if index_tag is not None and index_tag in row:
                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] "

                    err = idx_msg + "Atom ("\
                        + self.__getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                        comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                        + ") is not present in the coordinates."

                    cyclic = self.__isCyclicPolymer(ref_chain_id)

                    if self.__nonblk_bad_nterm\
                       and (seq_id == 1 or cif_seq_id == 1 or (cif_chain_id, cif_seq_id - 1) in self.__coord_unobs_res)\
                       and atom_id_ in aminoProtonCode and (cyclic or comp_id == 'PRO'
                                                            or (atom_id_ in protonBeginCode
                                                                or (coord_atom_site_ is not None and 'auth_atom_id' not in coord_atom_site_))):  # DAOTHER-7665

                        err += " However, it is acceptable if corresponding atom name, H1, is given during biocuration "

                        if cyclic:
                            err += "because of a cyclic-peptide."
                        elif comp_id == 'PRO':
                            err += "because polymer sequence starts with the Proline residue."
                        else:  # DAOTHER-7665
                            err += "because polymer sequence starts with the residue in the coordinates."

                        self.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': err})
                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testCoordAtomIdConsistency() ++ Warning  - {err}\n")

                        if cyclic and self.__bmrb_only and self.__leave_intl_note and file_type == 'nmr-star' and seq_id == 1 and details_col != -1:
                            _details = loop.data[idx][details_col]
                            details = f"{chain_id}:{seq_id}:{comp_id}:{atom_name} is not present in the coordinates. "\
                                "However, it is acceptable if an appropriate atom name, H1, is given because of a cyclic-peptide.\n"
                            if _details in emptyValue or (details not in _details):
                                if _details in emptyValue:
                                    loop.data[idx][details_col] = details
                                else:
                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                modified = True

                    elif self.__nonblk_bad_nterm\
                            and (seq_id == 1 or cif_seq_id == 1 or (cif_chain_id, cif_seq_id - 1) in self.__coord_unobs_res)\
                            and atom_id_ == 'P':
                        continue

                    elif ca['conflict'] == 0:  # no conflict in sequenc alignment

                        if comp_id in monDict3:

                            checked = coord_issue = False
                            if atom_id_[0] in protonBeginCode:
                                self.__ccU.updateChemCompDict(comp_id)
                                cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id_), None)
                                bonded_to = self.__ccU.getBondedAtoms(comp_id, atom_id_)
                                peptide_like = self.__csStat.peptideLike(comp_id)
                                if cca is not None and len(bonded_to) > 0:
                                    if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                       and (cca[self.__ccU.ccaLeavingAtomFlag] != 'Y'
                                            or (peptide_like
                                                and cca[self.__ccU.ccaNTerminalAtomFlag] == 'N'
                                                and cca[self.__ccU.ccaCTerminalAtomFlag] == 'N')):
                                        checked = True
                                        err = idx_msg + "Atom ("\
                                            + self.__getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                            comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                                            + ") is not properly instantiated in the coordinates. Please re-upload the model file."

                            if (self.__remediation_mode or self.__combined_mode) and checked:
                                continue

                            if not checked and err.endswith("not present in the coordinates.") and atom_id_[0] in protonBeginCode:
                                bonded_to = self.__ccU.getBondedAtoms(comp_id, atom_id_)
                                if len(bonded_to) > 0 and coord_atom_site_ is not None and bonded_to[0] not in coord_atom_site_['atom_id']:
                                    err += " Additionally, the attached atom ("\
                                        + self.__getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                        comp_id_names[j], comp_id, atom_id_names[j], bonded_to[0])\
                                        + ") is not instantiated in the coordinates. Please re-upload the model file."
                                    coord_issue = True

                            self.report.error.appendDescription('hydrogen_not_instantiated' if checked else 'coordinate_issue' if coord_issue else 'atom_not_found',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testCoordAtomIdConsistency() ++ Error  - {err}\n")

                        else:

                            if self.__combined_mode and self.__remediation_mode and self.__ccU.updateChemCompDict(comp_id):
                                cca = next((cca for cca in self.__ccU.lastAtomList if cca[self.__ccU.ccaAtomId] == atom_id_), None)
                                bonded_to = self.__ccU.getBondedAtoms(comp_id, atom_id_)
                                peptide_like = self.__csStat.peptideLike(comp_id)
                                if cca is not None and len(bonded_to) > 0:
                                    if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                       and (cca[self.__ccU.ccaLeavingAtomFlag] != 'Y'
                                            or (peptide_like
                                                and cca[self.__ccU.ccaNTerminalAtomFlag] == 'N'
                                                and cca[self.__ccU.ccaCTerminalAtomFlag] == 'N')):
                                        err = idx_msg + "Atom ("\
                                            + self.__getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                            comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                                            + ") is not properly instantiated in the coordinates. Please re-upload the model file."

                                        self.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                               'description': err})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__testCoordAtomIdConsistency() ++ Warning  - {err}\n")

                                        continue

                            self.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                   'description': err})
                            self.report.setWarning()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__testCoordAtomIdConsistency() ++ Warning  - {err}\n")

        return modified

    def __retrieveDpReport(self):
        """ Retrieve NMR data processing report from JSON file.
        """

        if not self.__combined_mode:
            return True

        # retrieve sf_category_list which is required to resolve minor issues
        if len(self.__sf_category_list) == 0:

            _, star_data_type, star_data = self.__nefT.read_input_file(self.__srcPath)

            self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(star_data)

            if len(self.__star_data_type) == 0:
                self.__star_data_type.append(star_data_type)
            else:
                self.__star_data_type[0] = star_data_type

            if len(self.__star_data) == 0:
                self.__star_data.append(star_data)
            else:
                self.__star_data[0] = star_data

            corrections = self.__nefT.resolve_sf_names_for_cif(star_data)  # DAOTHER-7389, issue #4

            if len(self.__sf_name_corr) == 0:
                self.__sf_name_corr.append(corrections)
            else:
                self.__sf_name_corr[0] = corrections

        if 'report_file_path' not in self.__inputParamDict or self.__annotation_mode:
            self.__initializeDpReport()
            self.__dstPath = self.__srcPath

            return False

        fPath = self.__inputParamDict['report_file_path']

        if not os.access(fPath, os.F_OK):
            raise IOError(f"+NmrDpUtility.__retrieveDpReport() ++ Error  - Could not access to file path {fPath}.")

        if os.path.getsize(fPath) == 0:
            raise IOError(f"+NmrDpUtility.__retrieveDpReport() ++ Error  - Could not find any content in file path {fPath}.")

        self.report = NmrDpReport(self.__verbose, self.__lfh)
        self.report.loadFile(fPath)

        self.report_prev = NmrDpReport(self.__verbose, self.__lfh)
        self.report_prev.loadFile(fPath)

        return True

    def __resolveConflictsInLoop(self):
        """ Resolve conflicted rows in loops.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if input_source_dic['content_subtype'] is None:
            return False

        if not self.__resolve_conflict:
            return True

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                            raise ValueError()

                    except ValueError:  # raised error already at __testIndexConsistency()
                        continue

                    max_dim = num_dim + 1

                    key_items = []
                    for dim in range(1, max_dim):
                        for k in self.pk_key_items[file_type]:
                            if k['type'] == 'float':  # position
                                _k = copy.copy(k)
                                if '%s' in k['name']:
                                    _k['name'] = k['name'] % dim
                                key_items.append(_k)
                    for k in self.pk_key_items[file_type]:
                        if k['type'] == 'positive-int':  # peak_id
                            key_items.append(k)

                    data_items = []
                    for d in self.data_items[file_type][content_subtype]:
                        data_items.append(d)
                    for dim in range(1, max_dim):
                        for d in self.pk_data_items[file_type]:
                            _d = copy.copy(d)
                            if '%s' in d['name']:
                                _d['name'] = d['name'] % dim
                            if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                                _d['default-from'] = d['default-from'] % dim
                            data_items.append(_d)

                else:

                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]

                    if len(key_items) == 0:
                        continue

                modified = False

                if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

                    try:

                        conflict_id = self.__nefT.get_conflict_atom_id(sf, file_type, lp_category, key_items)[0]

                        if len(conflict_id) > 0:
                            modified = True

                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop(lp_category)
                            else:
                                loop = sf.get_loop_by_category(lp_category)

                            for _id in conflict_id:
                                del loop.data[_id]

                        conflict_id = self.__nefT.get_bad_pattern_id(sf, lp_category, key_items, data_items)[0]

                        if len(conflict_id) > 0:
                            modified = True

                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop(lp_category)
                            else:
                                loop = sf.get_loop_by_category(lp_category)

                            for _id in conflict_id:
                                del loop.data[_id]

                        if modified:

                            lp = next((lp for lp in self.__lp_data[content_subtype]
                                       if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                            if lp is not None:
                                lp['data'] = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                                    enforce_allowed_tags=(file_type == 'nmr-star'),
                                                                    excl_missing_data=self.__excl_missing_data)[0]

                    except Exception:
                        pass

        return True

    def __resolveConflictsInAuxLoop(self):
        """ Resolve conflicted rows in auxiliary loops.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if input_source_dic['content_subtype'] is None:
            return False

        if not self.__resolve_conflict:
            return True

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype == 'entity':
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                if content_subtype.startswith('spectral_peak'):

                    try:

                        _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                            raise ValueError()

                    except ValueError:  # raised error already at __testIndexConsistency()
                        pass

                for loop in sf.loops:

                    lp_category = loop.category

                    if lp_category is None:
                        continue

                    # main content of loop has been processed in __testDataConsistencyInLoop()
                    if lp_category in self.lp_categories[file_type][content_subtype]:
                        continue

                    if self.aux_lp_categories[file_type][content_subtype] is None:
                        continue

                    if lp_category in self.aux_lp_categories[file_type][content_subtype]:

                        key_items = self.aux_key_items[file_type][content_subtype][lp_category]

                        if len(key_items) == 0:
                            continue

                        try:

                            conflict_id = self.__nefT.get_conflict_id(sf, lp_category, key_items)[0]

                            if len(conflict_id) > 0:
                                if __pynmrstar_v3_2__:
                                    _loop = sf.get_loop(lp_category)
                                else:
                                    _loop = sf.get_loop_by_category(lp_category)

                                for _id in conflict_id:
                                    del _loop.data[_id]

                        except Exception:
                            pass

        return True

    def __appendIndexTag(self):
        """ Append index tag if required.
        """

        if not self.__combined_mode:
            return

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype == 'entity':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                index_tag = self.index_tags[file_type][content_subtype]

                if index_tag is None:
                    continue

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    try:
                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(lp_category)
                        else:
                            loop = sf.get_loop_by_category(lp_category)
                    except KeyError:
                        continue

                    if index_tag in loop.tags:
                        continue

                    lp_tag = lp_category + '.' + index_tag
                    err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                    if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):

                        if self.__rescue_mode:
                            self.report.error.appendDescription('missing_mandatory_item',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                 'description': err})
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write("+NmrDpUtility.__appendIndexTag() ++ LookupError  - "
                                                 f"{file_name} {sf_framecode} {lp_category} {err}\n")

                    lp = pynmrstar.Loop.from_scratch(lp_category)

                    lp.add_tag(lp_tag)

                    for tag in loop.tags:
                        lp.add_tag(lp_category + '.' + tag)

                    for idx, row in enumerate(loop, start=1):
                        lp.add_data([str(idx)] + row)

                    del sf[loop]

                    sf.add_loop(lp)

    def __deleteSkippedSf(self):
        """ Delete skipped saveframes.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('skipped_saveframe_category', file_name)

        if warnings is None:
            return True

        if self.__retain_original:
            return True

        for w in warnings:

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_category' not in w:

                    err = "Could not specify 'sf_category' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedSf() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__deleteSkippedSf() ++ Error  - {err}\n")

                else:

                    sf_list = self.__star_data[0].get_saveframes_by_category(w['sf_category'])

                    if sf_list is None:

                        err = f"Could not specify sf_category {w['sf_category']} unexpectedly."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedSf() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__deleteSkippedSf() ++ Error  - {err}\n")

                    else:

                        for sf in reversed(sf_list):
                            del self.__star_data[0][sf]

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedSf() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__deleteSkippedSf() ++ Error  - {err}\n")

        return True

    def __deleteSkippedLoop(self):
        """ Delete skipped loops.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('skipped_loop_category', file_name)

        if warnings is None:
            return True

        if self.__retain_original:
            return True

        for w in warnings:

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_framecode' not in w:

                    err = "Could not specify 'sf_framecode' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedLoop() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__deleteSkippedLoop() ++ Error  - {err}\n")

                else:

                    sf = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf is None:

                        err = f"Could not specify {w['sf_framecode']!r} saveframe unexpectedly in {file_name!r} file."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedLoop() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__deleteSkippedLoop() ++ Error  - {err}\n")

                        continue

                    if 'category' not in w:

                        err = "Could not specify 'category' in NMR data processing report."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedLoop() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__deleteSkippedLoop() ++ Error  - {err}\n")

                    else:

                        if __pynmrstar_v3_2__:
                            del sf[sf.get_loop(w['category'])]
                        else:
                            del sf[sf.get_loop_by_category(w['category'])]

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteSkippedLoop() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__deleteSkippedLoop() ++ Error  - {err}\n")

        return True

    def __deleteUnparsedEntryLoop(self):
        """ Delete unparsed entry loops.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if self.__retain_original:
            return True

        content_subtype = 'entry_info'

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

            if sf_category in self.__sf_category_list:

                for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                    loops = []

                    for loop in sf.loops:

                        if loop.category == lp_category:
                            continue

                        loops.append(loop)

                    for loop in reversed(loops):
                        del sf[loop]

        else:

            err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__deleteUnparsedEntryLoop() ++ Error  - " + err)
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__deleteUnparsedEntryLoop() ++ Error  - {err}\n")

        return True

    def __convertCsToEntry(self, src_data=None, list_id=1):
        """ Convert NMR-STAR CS loop/saveframe to pynmrstar Entry object.
        """

        if src_data is None:
            return None

        file_type = 'nmr-star'

        def update_entry_info_saveframe(master_entry):
            content_subtype = 'entry_info'

            sf_category = self.sf_categories[file_type][content_subtype]

            orig_ent_sf = next((sf for sf in master_entry.frame_list if sf_category in (sf.category, sf.name)), None)

            if orig_ent_sf is not None:

                tagNames = [t[0] for t in orig_ent_sf.tags]

                if 'Sf_category' not in tagNames:
                    orig_ent_sf.add_tag('Sf_category', sf_category)
                if 'Sf_framecode' not in tagNames:
                    orig_ent_sf.add_tag('Sf_framecode', orig_ent_sf.name)
                set_sf_tag(orig_ent_sf, 'ID', self.__entry_id)

            else:

                ent_sf = pynmrstar.Saveframe.from_scratch(sf_category, self.sf_tag_prefixes[file_type][content_subtype])
                ent_sf.add_tag('Sf_category', sf_category)
                ent_sf.add_tag('Sf_framecode', sf_category)
                ent_sf.add_tag('ID', self.__entry_id)

                master_entry.add_saveframe(ent_sf)

            return master_entry

        if isinstance(src_data, pynmrstar.Entry):
            return update_entry_info_saveframe(src_data)

        content_subtype = 'chem_shift'

        master_entry = pynmrstar.Entry.from_scratch(self.__entry_id)

        if isinstance(src_data, (pynmrstar.Saveframe, pynmrstar.Loop)):

            if isinstance(src_data, pynmrstar.Saveframe):
                set_sf_tag(src_data, 'Sf_category', self.sf_categories[file_type][content_subtype])
                set_sf_tag(src_data, 'Entry_ID', self.__entry_id)
                set_sf_tag(src_data, 'ID', list_id)
                set_sf_tag(src_data, 'Data_file_name', self.__srcName)

                master_entry.add_saveframe(src_data)

            else:
                sf_framecode = f'assigned_chemical_shifts_{list_id}'
                sf_tag_prefix = self.sf_tag_prefixes[file_type][content_subtype]

                acs_sf = pynmrstar.Saveframe.from_scratch(sf_framecode, sf_tag_prefix)

                acs_sf.add_tag('Sf_category', self.sf_categories[file_type][content_subtype])
                acs_sf.add_tag('Sf_framecode', sf_framecode)
                acs_sf.add_tag('Entry_ID', self.__entry_id)
                acs_sf.add_tag('ID', list_id)
                acs_sf.add_tag('Data_file_name', self.__srcName)

                acs_sf.add_loop(src_data)

                master_entry.add_saveframe(acs_sf)

            src_data = update_entry_info_saveframe(master_entry)

        return src_data

    def __updatePolymerSequence(self):
        """ Update polymer sequence.
        """

        if len(self.__star_data) == 0 or self.__star_data[0] is None or self.__star_data_type[0] != 'Entry':
            return False

        self.__extractPolymerSequence()
        self.__extractPolymerSequenceInLoop()

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        self.__cleanUpSf()

        master_entry = self.__star_data[0]

        # strip citation author names

        sf_category = 'citations'

        if sf_category in self.__sf_category_list:

            for sf in master_entry.get_saveframes_by_category(sf_category):

                lp_category = '_Citation_author'

                try:

                    if __pynmrstar_v3_2__:
                        lp = sf.get_loop(lp_category)
                    else:
                        lp = sf.get_loop_by_category(lp_category)

                    test_tags = ['Given_name', 'Family_name', 'First_initial', 'Middle_initials', 'Family_title']

                    dat = get_lp_tag(lp, test_tags)

                    for idx, row in enumerate(dat):
                        for col in range(5):
                            if row[col].startswith(' ') or row[col].endswith(' '):
                                lp.data[idx][lp.tags.index(test_tags[col])] = row[col].strip()

                except KeyError:
                    pass

        if self.__caC is None:
            return self.__remediateCsLoop()

        orig_poly_seq = input_source_dic['polymer_sequence']

        content_subtype = 'poly_seq'

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        key_items = self.key_items[file_type][content_subtype]
        data_items = self.data_items[file_type][content_subtype]

        orig_lp_data = None

        has_res_var_dat = False

        has_nef_index = False
        has_entry_id = False

        sf_framecode = 'assembly'

        for sf in master_entry.get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

            try:

                _lp_category = '_Entity_assembly'

                if __pynmrstar_v3_2__:
                    _loop = sf.get_loop(_lp_category)
                else:
                    _loop = sf.get_loop_by_category(_lp_category)

                tags = ['Conformational_isomer', 'Details']

                dat = get_lp_tag(_loop, tags)

                for row in dat:
                    if row[0] == 'yes' and 'Conformational isomer' in row[1]:
                        return True

            except KeyError:
                pass

            orig_lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                 if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if orig_lp_data is None:

                try:

                    orig_lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                          enforce_allowed_tags=(file_type == 'nmr-star'),
                                                          excl_missing_data=self.__excl_missing_data)[0]

                except Exception:
                    pass

            if orig_lp_data is not None and len(orig_lp_data) > 0:

                if file_type == 'nef':
                    if 'residue_variant' in orig_lp_data[0]:
                        if any(_row for _row in orig_lp_data if _row['residue_variant'] not in emptyValue):
                            has_res_var_dat = True

                else:
                    if 'Auth_variant_ID' in orig_lp_data[0]:
                        if any(_row for _row in orig_lp_data if _row['Auth_variant_ID'] not in emptyValue):
                            has_res_var_dat = True

                    if 'NEF_index' in orig_lp_data[0]:
                        if any(_row for _row in orig_lp_data if _row['NEF_index'] not in emptyValue):
                            has_nef_index = True

                    if 'Entry_ID' in orig_lp_data[0]:
                        has_entry_id = True

            elif not self.__has_star_entity and not self.__update_poly_seq and file_type == 'nef':  # DAOTHER-6694, 8751
                return False

        orig_asm_sf = None

        try:
            orig_asm_sf = master_entry.get_saveframes_by_category(sf_category)[0]
        except IndexError:
            pass

        if self.__caC is None:
            self.__retrieveCoordAssemblyChecker()

        if self.__caC['entity_assembly'] is None:
            return False

        components_ex_water = 0
        for item in self.__caC['entity_assembly']:
            if isinstance(item['entity_copies'], int):
                components_ex_water += item['entity_copies']

        ligand_total = sum(len(item['label_asym_id'].split(',') if 'fixed_label_asym_id' not in item else item['fixed_label_asym_id'].split(','))
                           for item in self.__caC['entity_assembly']
                           if item['entity_type'] == 'non-polymer' and 'ION' not in item['entity_desc'])
        ion_total = sum(len(item['label_asym_id'].split(',') if 'fixed_label_asym_id' not in item else item['fixed_label_asym_id'].split(','))
                        for item in self.__caC['entity_assembly']
                        if item['entity_type'] == 'non-polymer' and 'ION' in item['entity_desc'])

        self.__sail_flag = False

        if self.__cR.hasItem('struct_keywords', 'text'):
            struct_keywords = self.__cR.getDictList('struct_keywords')
            text = struct_keywords[0]['text'].lower()
            if 'sail' in text or 'stereo-array isotope labeling' in text:
                self.__sail_flag = True

        if self.__cR.hasItem('pdbx_nmr_exptl_sample', 'isotopic_labeling'):
            exptl_sample = self.__cR.getDictList('pdbx_nmr_exptl_sample')
            for item in exptl_sample:
                text = item['isotopic_labeling'].lower()
                if 'sail' in text or 'stereo-array isotope labeling' in text:
                    self.__sail_flag = True
                    break

        chem_comp = self.__cR.getDictList('chem_comp')

        paramag = len(chem_comp) > 0 and any(cc for cc in chem_comp if cc['type'] == 'non-poly' and cc['id'] in PARAMAGNETIC_ELEMENTS)

        has_cys = any(cc for cc in chem_comp
                      if ((cc['type'] == 'L-peptide linking' and cc['id'] == 'CYS')
                          or (cc['type'] == 'D-peptide linking' and cc['id'] == 'DCY')))
        if has_cys:
            cys_total = 0
            for ps in self.__caC['polymer_sequence']:
                cys_total += ps['comp_id'].count('CYS') + ps['comp_id'].count('DCY')
            disul_cys = other_cys = 0
            if self.__cR.hasCategory('struct_conn'):
                bonds = self.__cR.getDictList('struct_conn')
                for bond in bonds:
                    # auth_seq_id_1 = bond['ptnr1_auth_seq_id']
                    auth_comp_id_1 = bond['ptnr1_auth_comp_id']
                    atom_id_1 = bond['ptnr1_label_atom_id']
                    # auth_seq_id_2 = bond['ptnr2_auth_seq_id']
                    auth_comp_id_2 = bond['ptnr2_auth_comp_id']
                    atom_id_2 = bond['ptnr2_label_atom_id']

                    if auth_comp_id_1 in ('CYS', 'DCY') and atom_id_1 == 'SG':
                        if auth_comp_id_2 in ('CYS', 'DCY') and atom_id_2 == 'SG':
                            disul_cys += 1
                        else:
                            other_cys += 1

                    if auth_comp_id_2 in ('CYS', 'DCY') and atom_id_2 == 'SG':
                        if auth_comp_id_1 in ('CYS', 'DCY') and atom_id_1 == 'SG':
                            disul_cys += 1
                        else:
                            other_cys += 1

            free_cys = cys_total - disul_cys - other_cys

            if free_cys > 0:
                if free_cys == cys_total:
                    thiol_state = 'all free'
                elif disul_cys > 0 and other_cys > 0:
                    thiol_state = 'free disulfide and other bound'
                elif other_cys == 0:
                    thiol_state = 'free and disulfide bound'
                else:
                    thiol_state = 'free and other bound'
            else:
                if disul_cys > 0 and other_cys > 0:
                    thiol_state = 'disulfide and other bound'
                elif other_cys == 0:
                    thiol_state = 'all disulfide bound'
                else:
                    thiol_state = 'all other bound'
        else:
            thiol_state = 'not present'

        formula_weight = 0.0
        for item in self.__caC['entity_assembly']:
            fw = item['entity_fw']
            num = item['entity_copies']
            if isinstance(fw, float) and isinstance(num, int):
                formula_weight += fw * num
            else:
                formula_weight = '.'
                break

        ec_numbers = []
        for item in self.__caC['entity_assembly']:
            if 'entity_ec' in item and item['entity_ec'] not in emptyValue and item['entity_ec'] not in ec_numbers:
                ec_numbers.append(item['entity_ec'])
        if len(ec_numbers) == 0:
            ec_number = '.'
        else:
            ec_number = ','.join(ec_numbers)

        details = ''
        for item in self.__caC['entity_assembly']:
            if 'entity_details' in item and item['entity_details'] not in emptyValue and item['entity_details'] + '\n' not in details:
                if len(details.strip()) > 0:
                    details += details + '\n'
        if len(details) == 0:
            details = '.'
        else:
            details = details[:-1]
            if len(details) == 0:
                details = '.'

        asm_sf = pynmrstar.Saveframe.from_scratch(sf_framecode)
        asm_sf.set_tag_prefix(self.sf_tag_prefixes[file_type][content_subtype])

        if file_type == 'nef':
            asm_sf.add_tag('sf_category', self.sf_categories[file_type][content_subtype])
            asm_sf.add_tag('sf_framecode', sf_framecode)

        else:
            asm_sf.add_tag('Sf_category', self.sf_categories[file_type][content_subtype])
            asm_sf.add_tag('Sf_framecode', sf_framecode)
            asm_sf.add_tag('Entry_ID', self.__entry_id)
            asm_sf.add_tag('ID', 1)
            assembly_name = '?'
            if self.__cR.hasItem('struct', 'pdbx_descriptor'):
                struct = self.__cR.getDictList('struct')
                assembly_name = struct[0]['pdbx_descriptor']
            asm_sf.add_tag('Name', assembly_name)
            asm_sf.add_tag('BMRB_code', None)
            asm_sf.add_tag('Number_of_components', components_ex_water if components_ex_water > 0 else None)
            asm_sf.add_tag('Organic_ligands', ligand_total if ligand_total > 0 else None)
            asm_sf.add_tag('Metal_ions', ion_total if ion_total > 0 else None)
            asm_sf.add_tag('Non_standard_bonds', None)  # filled 'yes' if the assembly contains non-standard bonds
            asm_sf.add_tag('Ambiguous_conformational_states', None)
            asm_sf.add_tag('Ambiguous_chem_comp_sites', None)
            asm_sf.add_tag('Molecules_in_chemical_exchange', None)  # filled 'yes' if conformational isomers exist
            asm_sf.add_tag('Paramagnetic', 'yes' if paramag else 'no')
            asm_sf.add_tag('Thiol_state', thiol_state)
            asm_sf.add_tag('Molecular_mass', f'{formula_weight:.3f}' if isinstance(formula_weight, float) else None)
            asm_sf.add_tag('Enzyme_commission_number', ec_number)
            asm_sf.add_tag('Details', details)
            asm_sf.add_tag('DB_query_date', None)
            asm_sf.add_tag('DB_query_revised_last_date', None)

        entity_type_of = {item['entity_id']: item['entity_type'] for item in self.__caC['entity_assembly']}
        entity_total = {entity_id: len([item for item in self.__caC['entity_assembly'] if item['entity_id'] == entity_id])
                        for entity_id in entity_type_of.keys()}
        entity_count = {entity_id: 0 for entity_id in entity_type_of.keys()}

        if file_type == 'nmr-star':

            # Refresh _Entity_assembly loop

            lp_category = '_Entity_assembly'

            ea_loop = pynmrstar.Loop.from_scratch(lp_category)

            ea_key_items = [{'name': 'ID', 'type': 'positive-int'},
                            {'name': 'Entity_assembly_name', 'type': 'str'},
                            {'name': 'Entity_ID', 'type': 'positive-int', 'default': '1'},
                            {'name': 'Entity_label', 'type': 'str'},
                            ]
            ea_data_items = [{'name': 'Asym_ID', 'type': 'str', 'mandatory': False},  # label_asym_id
                             {'name': 'PDB_chain_ID', 'type': 'str', 'mandatory': False},  # auth_asym_id
                             {'name': 'Experimental_data_reported', 'type': 'enum', 'mandatory': False,
                              'enum': ('no', 'yes')},
                             {'name': 'Physical_state', 'type': 'enum', 'mandatory': False,
                              'enum': ('native', 'denatured', 'molten globule', 'unfolded',
                                       'intrinsically disordered', 'partially disordered', 'na')},
                             {'name': 'Conformational_isomer', 'type': 'enum', 'mandatory': False,
                              'enum': ('no', 'yes')},
                             {'name': 'Chemical_exchange_state', 'type': 'enum', 'mandatory': False,
                              'enum': ('no', 'yes')},
                             {'name': 'Magnetic_equivalence_group_code', 'type': 'str', 'mandatory': False},
                             {'name': 'Role', 'type': 'str', 'mandatory': False},
                             {'name': 'Details', 'type': 'str', 'default': '.', 'mandatory': False},
                             {'name': 'Assembly_ID', 'type': 'pointer-index', 'mandatory': False, 'default': '1', 'default-from': 'parent'},
                             {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                             ]

            tags = [lp_category + '.' + _item['name'] for _item in ea_key_items]
            tags.extend([lp_category + '.' + _item['name'] for _item in ea_data_items])

            for tag in tags:
                ea_loop.add_tag(tag)

            for item in self.__caC['entity_assembly']:
                entity_id = item['entity_id']
                entity_type = item['entity_type']
                entity_count[entity_id] += 1

                row = [None] * len(tags)

                row[0] = item['entity_assembly_id']
                row[1] = (f'entity_{entity_id}' + ('' if entity_total[entity_id] == 1 else f'_{entity_count[entity_id]}'))\
                    if entity_type not in ('non-polymer', 'water') else f"entity_{item['comp_id']}"
                item['entity_assembly_name'] = row[1]
                row[2] = item['entity_id']
                row[3] = f'$entity_{entity_id}' if entity_type not in ('non-polymer', 'water') else f"$entity_{item['comp_id']}"
                _label_asym_id = 'label_asym_id' if 'fixed_label_asym_id' not in item else 'fixed_label_asym_id'
                _auth_asym_id = 'auth_asym_id' if 'fixed_auth_asym_id' not in item else 'fixed_auth_asym_id'
                row[4] = item[_label_asym_id]
                row[5] = item[_auth_asym_id]
                if len(self.__label_asym_id_with_exptl_data) > 0:
                    if any(label_asym_id for label_asym_id in item[_label_asym_id].split(',')
                           if label_asym_id in self.__label_asym_id_with_exptl_data):
                        row[6] = 'yes'
                # Physical_state
                # Conformational_isomer
                if len(self.__auth_asym_ids_with_chem_exch) > 0:
                    if any(auth_asym_id for auth_asym_id in item[_auth_asym_id].split(',')
                           if auth_asym_id in self.__auth_asym_ids_with_chem_exch.keys()):
                        row[8] = row[9] = 'yes'
                if entity_total[entity_id] > 0 and entity_type == 'polymer' and len(self.__label_asym_id_with_exptl_data) > 0:
                    equiv_entity_assemblies = [_item for _item in self.__caC['entity_assembly'] if _item['entity_id'] == entity_id]
                    _item = next((_item for _item in equiv_entity_assemblies if any(label_asym_id for label_asym_id in _item[_label_asym_id].split(',')
                                                                                    if label_asym_id in self.__label_asym_id_with_exptl_data)), None)
                    if _item is not None:
                        group_id = sorted(sorted(set(_item[_label_asym_id].split(','))), key=len)[0]
                        if any(__item for __item in equiv_entity_assemblies if not any(label_asym_id for label_asym_id in __item[_label_asym_id].split(',')
                                                                                       if label_asym_id in self.__label_asym_id_with_exptl_data)):
                            if _item == item or row[6] is None or row[6] == 'no':
                                row[10] = group_id
                row[11], row[12] = item['entity_role'], item['entity_details']
                row[13], row[14] = 1, self.__entry_id

                if len(self.__auth_asym_ids_with_chem_exch) > 0:
                    auth_asym_id = row[5]
                    if auth_asym_id in self.__auth_asym_ids_with_chem_exch.keys():
                        conformational_states = len(self.__auth_asym_ids_with_chem_exch[auth_asym_id]) + 1
                        beg_model_id = 1
                        end_model_id = self.__total_models // conformational_states
                        seq_ids = [k for k, v in self.__auth_seq_ids_with_chem_exch.items()
                                   if v['chain_id'] == auth_asym_id]
                        row[12] = f'Conformational isomer 1, PDB_model_num range: {beg_model_id}-{end_model_id}, '\
                            f'original sequence number range: {min(seq_ids)}-{max(seq_ids)}'
                        set_sf_tag(asm_sf, 'Molecules_in_chemical_exchange', 'yes')

                ea_loop.add_data(row)

            if len(self.__auth_asym_ids_with_chem_exch) > 0:
                _entity_assembly_id = ea_loop.data[-1][0]
                for idx, item in enumerate(self.__caC['entity_assembly']):
                    entity_type = item['entity_type']
                    if entity_type in ('non-polymer', 'water'):
                        continue
                    auth_asym_id = item['auth_asym_id']
                    if auth_asym_id in self.__auth_asym_ids_with_chem_exch.keys():
                        for offset, _auth_asym_id in enumerate(self.__auth_asym_ids_with_chem_exch[auth_asym_id], start=2):
                            row = ea_loop.data[idx]
                            _row = copy.copy(row)
                            _entity_assembly_id += 1
                            _row[0] = _entity_assembly_id
                            _row[1] = f'entity_{entity_id}_{offset}'
                            _row[5] = _auth_asym_id
                            conformational_states = len(self.__auth_asym_ids_with_chem_exch[auth_asym_id]) + 1
                            model_ids_per_state = self.__total_models // conformational_states
                            beg_model_id = 1 + model_ids_per_state * (offset - 1)
                            end_model_id = model_ids_per_state * offset
                            seq_ids = [k for k, v in self.__auth_seq_ids_with_chem_exch.items()
                                       if v['chain_id'] == _auth_asym_id]
                            _row[12] = f'Conformational isomer {offset}, PDB_model_num range: {beg_model_id}-{end_model_id}, '\
                                f'original sequence number range: {min(seq_ids)}-{max(seq_ids)}'

                            ea_loop.add_data(_row)

            asm_sf.add_loop(ea_loop)

        # Refresh _nef_sequence or _Chem_comp_assembly loop

        lp_category = self.lp_categories[file_type][content_subtype]

        loop = pynmrstar.Loop.from_scratch(lp_category)

        has_index_tag = self.index_tags[file_type][content_subtype] is not None

        if has_index_tag:
            loop.add_tag(lp_category + '.' + self.index_tags[file_type][content_subtype])

        for key_item in key_items:
            loop.add_tag(lp_category + '.' + key_item['name'])

        for data_item in data_items:
            data_name = data_item['name']
            if data_name != 'NEF_index' or (data_name == 'NEF_index' and has_nef_index):
                loop.add_tag(lp_category + '.' + data_name)

        if has_entry_id:
            loop.add_tag(lp_category + '.Entry_ID')

        entity_assembly = self.__caC['entity_assembly'] if self.__caC is not None else []
        auth_to_star_seq = self.__caC['auth_to_star_seq'] if self.__caC is not None else {}
        auth_to_orig_seq = self.__caC['auth_to_orig_seq'] if self.__caC is not None else {}

        # DAOTHER-9644: sort by Entity_assembly_ID and Comp_index_ID due to inserted sequence for truncated loop
        _auth_to_star_seq = dict(sorted(auth_to_star_seq.items(), key=lambda item: item[1]))

        entity_type_of = {item['entity_id']: item['entity_type'] for item in entity_assembly}

        seq_keys = set()

        nef_index = 1
        _entity_assembly_id = index = 0

        if file_type == 'nef':

            idx_col = loop.tags.index('index')
            chain_id_col = loop.tags.index('chain_code')
            seq_id_col = loop.tags.index('sequence_code')
            comp_id_col = loop.tags.index('residue_name')
            seq_link_col = loop.tags.index('linking')
            auth_var_id_col = loop.tags.index('residue_variant')
            cis_res_col = loop.tags.index('cis_peptide')

            for k, v in _auth_to_star_seq.items():
                auth_asym_id, auth_seq_id, comp_id = k
                entity_assembly_id, seq_id, entity_id, genuine = v

                if auth_seq_id is None or not genuine:
                    continue

                seq_key = (entity_assembly_id, seq_id)

                if seq_key in seq_keys:
                    continue

                if entity_assembly_id != _entity_assembly_id:
                    _entity_assembly_id = entity_assembly_id
                    index = 1

                seq_keys.add(seq_key)

                if self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                   and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < auth_seq_id):
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < auth_seq_id and 'touch' not in d:
                            _auth_seq_id, _auth_comp_id = d['auth_seq_id'], d['auth_comp_id']
                            _seq_key = (entity_assembly_id, _auth_seq_id)
                            if _seq_key in seq_keys or _auth_comp_id in emptyValue or not _auth_comp_id[0].isalnum():  # 2l1f, DAOTHER-9694
                                continue
                            seq_keys.add(_seq_key)
                            row = [None] * len(loop.tags)
                            row[chain_id_col], row[seq_id_col], row[comp_id_col], row[idx_col] =\
                                auth_asym_id, _auth_seq_id, _auth_comp_id, nef_index
                            row[seq_link_col] = 'start' if index == 1 else 'middle' if _auth_comp_id not in unknownResidue else 'dummy'

                            loop.add_data(row)

                            d['touch'] = True

                            nef_index += 1
                            index += 1

                auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == k), comp_id)

                row = [None] * len(loop.tags)

                row[chain_id_col], row[seq_id_col] = auth_asym_id, auth_seq_id

                entity_type = entity_type_of[entity_id]

                row[comp_id_col] = auth_comp_id

                if entity_type == 'polymer':
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['auth_chain_id'] == auth_asym_id)
                    nmr_ps = self.report.getNmrPolymerSequenceWithModelChainId(auth_asym_id, label_scheme=False)
                    if nmr_ps is None and 'identical_auth_chain_id' in ps:
                        nmr_ps = self.report.getNmrPolymerSequenceWithModelChainId(ps['identical_auth_chain_id'][0], label_scheme=False)

                    if nmr_ps is not None:
                        try:
                            j = ps['auth_seq_id'].index(auth_seq_id)
                            label_seq_id = ps['seq_id'][j]
                            length = len(ps['seq_id'])
                            cyclic = self.__isCyclicPolymer(nmr_ps['chain_id'])
                            if cyclic and label_seq_id in (1, length):
                                row[seq_link_col] = 'cyclic'
                            elif label_seq_id == 1 and length == 1:
                                row[seq_link_col] = 'single'
                            elif index == 1:
                                row[seq_link_col] = 'start'
                            elif j == length - 1:
                                row[seq_link_col] = 'end'
                            elif label_seq_id - 1 == ps['seq_id'][j - 1] and label_seq_id + 1 == ps['seq_id'][j + 1]:
                                row[seq_link_col] = 'middle'
                            elif label_seq_id == 1:
                                row[seq_link_col] = 'middle'
                            else:
                                row[seq_link_col] = 'break'

                            entity_poly_type = next((item['entity_poly_type'] for item in entity_assembly
                                                     if item['entity_id'] == entity_id and item['entity_type'] == 'polymer'), None)
                            if entity_poly_type is not None and entity_poly_type.startswith('polypeptide'):
                                if self.__isProtCis(nmr_ps['chain_id'], seq_id):
                                    row[cis_res_col] = 'true'
                                elif auth_comp_id in ('PRO', 'GLY'):
                                    row[cis_res_col] = 'false'
                                else:
                                    row[cis_res_col] = '.'
                        except ValueError:
                            pass

                row[idx_col] = nef_index

                if auth_var_id_col != -1 and has_res_var_dat:
                    orig_row = next((_row for _row in orig_lp_data
                                     if _row['chain_code'] == auth_asym_id
                                     and _row['sequence_code'] == auth_seq_id
                                     and _row['residue_name'] == auth_comp_id), None)
                    if orig_row is not None:
                        row[auth_var_id_col] = orig_row['residue_variant']

                if auth_comp_id not in emptyValue:
                    loop.add_data(row)

                nef_index += 1
                index += 1

                if row[seq_link_col] == 'end' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                   and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > auth_seq_id):
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > auth_seq_id and 'touch' not in d:
                            if loop.data[-1][seq_link_col] == 'end':
                                loop.data[-1][seq_link_col] = 'middle'
                            _auth_seq_id, _auth_comp_id = d['auth_seq_id'], d['auth_comp_id']
                            _seq_key = (entity_assembly_id, _auth_seq_id)
                            if _seq_key in seq_keys or _auth_comp_id in emptyValue or not _auth_comp_id[0].isalnum():  # 2l1f, DAOTHER-9694:
                                continue
                            seq_keys.add(_seq_key)
                            row = [None] * len(loop.tags)
                            row[chain_id_col], row[seq_id_col], row[comp_id_col], row[idx_col] =\
                                auth_asym_id, _auth_seq_id, _auth_comp_id, nef_index
                            row[seq_link_col] = 'end' if _auth_comp_id not in unknownResidue else 'dummy'

                            loop.add_data(row)

                            d['touch'] = True

                            nef_index += 1

            if len(self.__auth_asym_ids_with_chem_exch) > 0:
                for item in entity_assembly:
                    entity_type = item['entity_type']
                    if entity_type in ('non-polymer', 'water'):
                        continue
                    auth_asym_id = item['auth_asym_id']
                    if auth_asym_id in self.__auth_asym_ids_with_chem_exch.keys():
                        for _auth_asym_id in self.__auth_asym_ids_with_chem_exch[auth_asym_id]:
                            for row in loop:
                                if row[chain_id_col] == auth_asym_id:
                                    _row = copy.copy(row)
                                    _row[chain_id_col] = _auth_asym_id
                                    _row[idx_col] = nef_index

                                    loop.add_data(_row)

                                    nef_index += 1

            asm_sf.add_loop(loop)

            if self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0:
                for d in self.__nmr_ext_poly_seq:
                    if 'touch' in d:
                        del d['touch']

            # Refresh _nef_covalent_links loop

            if self.__cR.hasCategory('struct_conn'):

                lp_category = '_nef_covalent_links'

                b_loop = pynmrstar.Loop.from_scratch(lp_category)

                b_key_items = [{'name': 'chain_code_1', 'type': 'str'},
                               {'name': 'sequence_code_1', 'type': 'int'},
                               {'name': 'residue_name_1', 'type': 'str'},
                               {'name': 'atom_name_1', 'type': 'str'},
                               {'name': 'chain_code_2', 'type': 'str'},
                               {'name': 'sequence_code_2', 'type': 'int'},
                               {'name': 'residue_name_2', 'type': 'str'},
                               {'name': 'atom_name_2', 'type': 'str'}
                               ]

                tags = [lp_category + '.' + _item['name'] for _item in b_key_items]

                for tag in tags:
                    b_loop.add_tag(tag)

                bonds = self.__cR.getDictList('struct_conn')

                for bond in bonds:
                    bond_type = bond['conn_type_id']
                    auth_asym_id_1 = bond['ptnr1_auth_asym_id']
                    auth_seq_id_1 = bond['ptnr1_auth_seq_id']
                    auth_comp_id_1 = bond['ptnr1_auth_comp_id']
                    atom_id_1 = bond['ptnr1_label_atom_id']
                    auth_asym_id_2 = bond['ptnr2_auth_asym_id']
                    auth_seq_id_2 = bond['ptnr2_auth_seq_id']
                    auth_comp_id_2 = bond['ptnr2_auth_comp_id']
                    atom_id_2 = bond['ptnr2_label_atom_id']

                    if bond_type == 'covale':
                        pass
                    elif bond_type.startswith('covale_'):  # 'covale_base', 'covale_phosphate', 'covale_sugar'
                        pass
                    elif bond_type == 'disulf':
                        pass
                    elif bond_type == 'hydrog':
                        continue
                    elif bond_type == 'metalc':
                        pass
                    elif bond_type == 'mismat':
                        continue
                    elif bond_type == 'modres':
                        continue
                    elif bond_type == 'saltbr':
                        continue

                    row = [None] * len(tags)

                    try:

                        seq_key_1 = (auth_asym_id_1, int(auth_seq_id_1), auth_comp_id_1)

                        if seq_key_1 in auth_to_star_seq:
                            row[0], row[1], row[2], row[3] = auth_asym_id_1, auth_seq_id_1, auth_comp_id_1, atom_id_1

                    except ValueError:
                        pass

                    try:

                        seq_key_2 = (auth_asym_id_2, int(auth_seq_id_2), auth_comp_id_2)

                        if seq_key_2 in auth_to_star_seq:
                            row[4], row[5], row[6], row[7] = auth_asym_id_2, auth_seq_id_2, auth_comp_id_2, atom_id_2

                    except ValueError:
                        pass

                    if row[0] is not None and row[4] is not None:
                        b_loop.add_data(row)

                if len(b_loop.data) > 0:
                    asm_sf.add_loop(b_loop)

        else:

            chain_id_col = loop.tags.index('Entity_assembly_ID')
            ent_id_col = loop.tags.index('Entity_ID')
            seq_id_col = loop.tags.index('Comp_index_ID')
            alt_seq_id_col = loop.tags.index('Seq_ID')
            comp_id_col = loop.tags.index('Comp_ID')
            auth_asym_id_col = loop.tags.index('Auth_asym_ID')
            auth_seq_id_col = loop.tags.index('Auth_seq_ID')
            auth_comp_id_col = loop.tags.index('Auth_comp_ID')
            seq_link_col = loop.tags.index('Sequence_linking')
            cis_res_col = loop.tags.index('Cis_residue')
            asm_id_col = loop.tags.index('Assembly_ID')
            idx_col = loop.tags.index('NEF_index') if 'NEF_index' in loop.tags else -1
            auth_var_id_col = loop.tags.index('Auth_variant_ID') if 'Auth_variant_ID' in loop.tags else -1
            entry_id_col = loop.tags.index('Entry_ID') if 'Entry_ID' in loop.tags else -1

            for k, v in _auth_to_star_seq.items():
                auth_asym_id, auth_seq_id, comp_id = k
                entity_assembly_id, seq_id, entity_id, genuine = v

                if auth_seq_id is None or not genuine:
                    continue

                seq_key = (entity_assembly_id, seq_id)

                if seq_key in seq_keys:
                    continue

                if entity_assembly_id != _entity_assembly_id:
                    _entity_assembly_id = entity_assembly_id
                    index = 1

                seq_keys.add(seq_key)

                if self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                   and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < auth_seq_id):
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < auth_seq_id and 'touch' not in d:
                            _offset = seq_id - auth_seq_id
                            _auth_seq_id, _auth_comp_id = d['auth_seq_id'], d['auth_comp_id']
                            _seq_id = _auth_seq_id + _offset
                            _seq_key = (entity_assembly_id, _seq_id)
                            if _seq_key in seq_keys or _auth_comp_id in emptyValue or not _auth_comp_id[0].isalnum():  # 2l1f, DAOTHER-9694
                                continue
                            seq_keys.add(_seq_key)
                            row = [None] * len(loop.tags)
                            row[chain_id_col], row[ent_id_col], row[seq_id_col], row[alt_seq_id_col] =\
                                entity_assembly_id, entity_id, _seq_id, _seq_id
                            row[comp_id_col], row[auth_asym_id_col], row[auth_seq_id_col], row[auth_comp_id_col] =\
                                _auth_comp_id, auth_asym_id, _auth_seq_id, _auth_comp_id
                            row[seq_link_col] = 'start' if index == 1 else 'middle' if _auth_comp_id not in unknownResidue else 'dummy'
                            row[asm_id_col] = 1
                            if idx_col != -1:
                                row[idx_col] = nef_index
                            if entry_id_col != -1:
                                row[entry_id_col] = self.__entry_id

                            loop.add_data(row)

                            d['touch'] = True

                            nef_index += 1
                            index += 1

                auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == k), comp_id)

                row = [None] * len(loop.tags)

                row[chain_id_col], row[ent_id_col] = entity_assembly_id, entity_id
                row[seq_id_col] = row[alt_seq_id_col] = seq_id
                seq_keys.add((entity_assembly_id, seq_id))

                entity_type = entity_type_of[entity_id]

                row[comp_id_col], row[auth_asym_id_col], row[auth_seq_id_col], row[auth_comp_id_col] = auth_comp_id, auth_asym_id, auth_seq_id, auth_comp_id

                if entity_type == 'polymer':
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['auth_chain_id'] == auth_asym_id)
                    nmr_ps = self.report.getNmrPolymerSequenceWithModelChainId(auth_asym_id, label_scheme=False)
                    if nmr_ps is None and 'identical_auth_chain_id' in ps:
                        nmr_ps = self.report.getNmrPolymerSequenceWithModelChainId(ps['identical_auth_chain_id'][0], label_scheme=False)

                    if nmr_ps is not None:
                        try:
                            j = ps['auth_seq_id'].index(auth_seq_id)
                            label_seq_id = ps['seq_id'][j]
                            if label_seq_id is not None:
                                try:
                                    length = len(ps['seq_id'])
                                    cyclic = self.__isCyclicPolymer(nmr_ps['chain_id'])
                                    if cyclic and label_seq_id in (1, length):
                                        row[seq_link_col] = 'cyclic'
                                    elif label_seq_id == 1 and length == 1:
                                        row[seq_link_col] = 'single'
                                    elif index == 1:
                                        row[seq_link_col] = 'start'
                                    elif j == length - 1:
                                        row[seq_link_col] = 'end'
                                    elif label_seq_id - 1 == ps['seq_id'][j - 1] and label_seq_id + 1 == ps['seq_id'][j + 1]:
                                        row[seq_link_col] = 'middle'
                                    elif label_seq_id == 1:
                                        row[seq_link_col] = 'middle'
                                    else:
                                        row[seq_link_col] = 'break'
                                except IndexError:
                                    pass

                            entity_poly_type = next((item['entity_poly_type'] for item in entity_assembly
                                                     if item['entity_id'] == entity_id and item['entity_type'] == 'polymer'), None)
                            if entity_poly_type is not None and entity_poly_type.startswith('polypeptide'):
                                if self.__isProtCis(nmr_ps['chain_id'], seq_id):
                                    row[cis_res_col] = 'yes'
                                elif auth_comp_id in ('PRO', 'GLY'):
                                    row[cis_res_col] = 'no'
                                else:
                                    row[cis_res_col] = '.'
                        except ValueError:
                            pass

                row[asm_id_col] = 1

                if idx_col != -1:
                    row[idx_col] = nef_index

                if auth_var_id_col != -1 and has_res_var_dat:
                    orig_row = next((_row for _row in orig_lp_data
                                     if _row['Entity_assembly_ID'] == str(entity_assembly_id)
                                     and _row['Comp_index_ID'] == seq_id
                                     and _row['Comp_ID'] == auth_comp_id), None)
                    if orig_row is not None:
                        row[auth_var_id_col] = orig_row['Auth_variant_ID']

                if entry_id_col != -1:
                    row[entry_id_col] = self.__entry_id

                if comp_id not in emptyValue:
                    loop.add_data(row)

                nef_index += 1
                index += 1

                if row[seq_link_col] == 'end' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                   and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > auth_seq_id):
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > auth_seq_id and 'touch' not in d:
                            if loop.data[-1][seq_link_col] == 'end':
                                loop.data[-1][seq_link_col] = 'middle'
                            _offset = seq_id - auth_seq_id
                            _auth_seq_id, _auth_comp_id = d['auth_seq_id'], d['auth_comp_id']
                            _seq_id = _auth_seq_id + _offset
                            _seq_key = (entity_assembly_id, _seq_id)
                            if _seq_key in seq_keys or _auth_comp_id in emptyValue or not _auth_comp_id[0].isalnum():  # 2l1f, DAOTHER-9694:
                                continue
                            seq_keys.add(_seq_key)
                            row = [None] * len(loop.tags)
                            row[chain_id_col], row[ent_id_col], row[seq_id_col], row[alt_seq_id_col] =\
                                entity_assembly_id, entity_id, _seq_id, _seq_id
                            row[comp_id_col], row[auth_asym_id_col], row[auth_seq_id_col], row[auth_comp_id_col] =\
                                _auth_comp_id, auth_asym_id, _auth_seq_id, _auth_comp_id
                            row[seq_link_col] = 'end' if _auth_comp_id not in unknownResidue else 'dummy'
                            row[asm_id_col] = 1
                            if idx_col != -1:
                                row[idx_col] = nef_index
                            if entry_id_col != -1:
                                row[entry_id_col] = self.__entry_id

                            loop.add_data(row)

                            d['touch'] = True

                            nef_index += 1

            if len(self.__auth_asym_ids_with_chem_exch) > 0:
                _entity_assembly_id = loop.data[-1][chain_id_col]
                for item in entity_assembly:
                    entity_type = item['entity_type']
                    if entity_type in ('non-polymer', 'water'):
                        continue
                    entity_id = item['entity_id']
                    auth_asym_id = item['auth_asym_id']
                    if auth_asym_id in self.__auth_asym_ids_with_chem_exch.keys():
                        for _auth_asym_id in self.__auth_asym_ids_with_chem_exch[auth_asym_id]:
                            _entity_assembly_id += 1
                            for row in loop:
                                if row[ent_id_col] == entity_id and row[auth_asym_id_col] == auth_asym_id:
                                    _row = copy.copy(row)
                                    _row[chain_id_col] = _entity_assembly_id
                                    _row[auth_asym_id_col] = _auth_asym_id

                                    if idx_col != -1:
                                        _row[idx_col] = nef_index

                                    loop.add_data(_row)

                                    nef_index += 1

            asm_sf.add_loop(loop)

            if self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0:
                for d in self.__nmr_ext_poly_seq:
                    if 'touch' in d:
                        del d['touch']

            self.__cca_dat = get_lp_tag(loop, ['Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Seq_ID', 'Comp_ID', 'Auth_asym_ID', 'Auth_seq_ID'])

            # Refresh _Bond loop

            if self.__cR.hasCategory('struct_conn'):

                lp_category = '_Bond'

                b_loop = pynmrstar.Loop.from_scratch(lp_category)

                b_key_items = [{'name': 'ID', 'type': 'positive-int'},
                               {'name': 'Type', 'type': 'enum',
                                'enum': ('amide', 'covalent', 'directed', 'disulfide', 'ester', 'ether',
                                         'hydrogen', 'metal coordination', 'peptide', 'thioether', 'oxime',
                                         'thioester', 'phosphoester', 'phosphodiester', 'diselenide', 'na')},
                               {'name': 'Value_order', 'type': 'enum',
                                'enum': ('sing', 'doub', 'trip', 'quad', 'arom', 'poly', 'delo', 'pi', 'directed')},
                               {'name': 'Entity_assembly_ID_1', 'type': 'positive-int-as-str'},
                               {'name': 'Entity_assembly_name_1', 'type': 'str'},
                               {'name': 'Entity_ID_1', 'type': 'positive-int'},
                               {'name': 'Comp_ID_1', 'type': 'str'},
                               {'name': 'Comp_index_ID_1', 'type': 'int'},
                               {'name': 'Seq_ID_1', 'type': 'int'},
                               {'name': 'Atom_ID_1', 'type': 'str'},
                               {'name': 'Entity_assembly_ID_2', 'type': 'positive-int-as-str'},
                               {'name': 'Entity_assembly_name_2', 'type': 'str'},
                               {'name': 'Entity_ID_2', 'type': 'positive-int'},
                               {'name': 'Comp_ID_2', 'type': 'str'},
                               {'name': 'Comp_index_ID_2', 'type': 'int'},
                               {'name': 'Seq_ID_2', 'type': 'int'},
                               {'name': 'Atom_ID_2', 'type': 'str'},
                               ]
                b_data_items = [{'name': 'Auth_asym_ID_1', 'type': 'str', 'mandaory': False},
                                {'name': 'Auth_seq_ID_1', 'type': 'int', 'mandatory': False},
                                {'name': 'Auth_comp_ID_1', 'type': 'str', 'mandatory': False},
                                {'name': 'Auth_atom_ID_1', 'type': 'str', 'mandatory': False},
                                {'name': 'Auth_asym_ID_2', 'type': 'str', 'mandaory': False},
                                {'name': 'Auth_seq_ID_2', 'type': 'int', 'mandatory': False},
                                {'name': 'Auth_comp_ID_2', 'type': 'str', 'mandatory': False},
                                {'name': 'Auth_atom_ID_2', 'type': 'str', 'mandatory': False},
                                {'name': 'Assembly_ID', 'type': 'pointer-index', 'mandatory': False, 'default': '1', 'default-from': 'parent'},
                                {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                ]

                tags = [lp_category + '.' + _item['name'] for _item in b_key_items]
                tags.extend([lp_category + '.' + _item['name'] for _item in b_data_items])

                for tag in tags:
                    b_loop.add_tag(tag)

                bonds = self.__cR.getDictList('struct_conn')

                index = 1

                non_std_bond = False

                for bond in bonds:

                    try:

                        bond_type = bond['conn_type_id']
                        auth_asym_id_1 = bond['ptnr1_auth_asym_id']
                        auth_seq_id_1 = int(bond['ptnr1_auth_seq_id'])
                        auth_comp_id_1 = bond['ptnr1_auth_comp_id']
                        atom_id_1 = bond['ptnr1_label_atom_id']
                        auth_asym_id_2 = bond['ptnr2_auth_asym_id']
                        auth_seq_id_2 = int(bond['ptnr2_auth_seq_id'])
                        auth_comp_id_2 = bond['ptnr2_auth_comp_id']
                        atom_id_2 = bond['ptnr2_label_atom_id']

                    except ValueError:
                        continue

                    row = [None] * len(tags)

                    row[0] = index

                    if bond_type == 'covale':
                        row[1], row[2] = 'covalent', 'sing'
                        non_std_bond = True
                    elif bond_type.startswith('covale_'):  # 'covale_base', 'covale_phosphate', 'covale_sugar'
                        row[1] = 'covalent'
                        non_std_bond = True
                    elif bond_type == 'disulf':
                        row[1], row[2] = 'disulfide', 'sing'
                    elif bond_type == 'hydrog':
                        row[1], row[2] = 'hydrogen', 'sing'
                        continue
                    elif bond_type == 'metalc':
                        row[1], row[2] = 'metal coordination', 'sing'
                        non_std_bond = True
                    elif bond_type == 'mismat':
                        row[1] = 'na'
                        non_std_bond = True
                        continue
                    elif bond_type == 'modres':
                        row[1] = 'na'
                        non_std_bond = True
                        continue
                    elif bond_type == 'saltbr':
                        row[1] = 'na'
                        continue

                    seq_key_1 = (auth_asym_id_1, auth_seq_id_1, auth_comp_id_1)

                    entity_id_1 = entity_id_2 = None

                    if seq_key_1 in auth_to_star_seq:
                        entity_assembly_id_1, seq_id_1, entity_id_1, _ = auth_to_star_seq[seq_key_1]
                        entity_assembly_name_1 = next((item['entity_assembly_name'] for item in entity_assembly
                                                       if item['entity_id'] == entity_id_1), None)
                        row[3], row[4], row[5], row[6], row[7], row[8], row[9] =\
                            entity_assembly_id_1, entity_assembly_name_1, entity_id_1, auth_comp_id_1, seq_id_1, seq_id_1, atom_id_1

                        row[17], row[18], row[19], row[20] =\
                            auth_asym_id_1, auth_seq_id_1, auth_comp_id_1, atom_id_1

                    seq_key_2 = (auth_asym_id_2, auth_seq_id_2, auth_comp_id_2)

                    if seq_key_2 in auth_to_star_seq:
                        entity_assembly_id_2, seq_id_2, entity_id_2, _ = auth_to_star_seq[seq_key_2]
                        entity_assembly_name_2 = next((item['entity_assembly_name'] for item in entity_assembly
                                                       if item['entity_id'] == entity_id_2), None)
                        row[10], row[11], row[12], row[13], row[14], row[15], row[16] =\
                            entity_assembly_id_2, entity_assembly_name_2, entity_id_2, auth_comp_id_2, seq_id_2, seq_id_2, atom_id_2

                        row[21], row[22], row[23], row[24] =\
                            auth_asym_id_2, auth_seq_id_2, auth_comp_id_2, atom_id_2

                    if entity_id_1 is not None and entity_id_2 is not None and entity_id_1 == entity_id_2:
                        entity_poly_type = next((item['entity_poly_type'] for item in entity_assembly
                                                 if item['entity_id'] == entity_id_1 and item['entity_type'] == 'polymer'), None)
                        if entity_poly_type is not None and entity_poly_type.startswith('polypeptide')\
                           and {atom_id_1, atom_id_2} == {'C', 'N'} and abs(auth_seq_id_1 - auth_seq_id_2) > 1:
                            row[1], row[2] = 'peptide', "sing"

                    row[25], row[26] = 1, self.__entry_id

                    b_loop.add_data(row)

                    index += 1

                if index > 1:
                    asm_sf.add_loop(b_loop)

                if non_std_bond:
                    set_sf_tag(asm_sf, 'Non_standard_bonds', 'yes')

                bonds_w_leaving = [bond for bond in bonds
                                   if ('pdbx_leaving_atom_flag' in bond and bond['pdbx_leaving_atom_flag'] in ('both', 'one'))
                                   or (bond['ptnr1_label_comp_id'] in ('CYS', 'DCS') and bond['ptnr1_label_atom_id'] == 'SG')
                                   or (bond['ptnr2_label_comp_id'] in ('CYS', 'DCS') and bond['ptnr2_label_atom_id'] == 'SG')
                                   or (bond['ptnr1_label_comp_id'] == 'HIS' and bond['ptnr1_label_atom_id'] in ('ND1', 'NE2'))
                                   or (bond['ptnr2_label_comp_id'] == 'HIS' and bond['ptnr2_label_atom_id'] in ('ND1', 'NE2'))]

                if len(bonds_w_leaving) > 0:

                    # _Entity_deleted_atom loop

                    lp_category = '_Entity_deleted_atom'

                    eda_loop = pynmrstar.Loop.from_scratch(lp_category)

                    eda_key_items = [{'name': 'ID', 'type': 'positive-int'},
                                     {'name': 'Entity_assembly_ID', 'type': 'positive-int-as-str'},
                                     {'name': 'Comp_index_ID', 'type': 'int'},
                                     {'name': 'Seq_ID', 'type': 'int'},
                                     {'name': 'Comp_ID', 'type': 'str'},
                                     {'name': 'Atom_ID', 'type': 'str'}
                                     ]
                    eda_data_items = [{'name': 'Auth_entity_assembly_ID', 'type': 'positive-int-as-str'},
                                      {'name': 'Auth_seq_ID', 'type': 'int'},
                                      {'name': 'Auth_comp_ID', 'type': 'str'},
                                      {'name': 'Auth_atom_ID', 'type': 'str'},
                                      {'name': 'Assembly_ID', 'type': 'pointer-index', 'mandatory': False, 'default': '1', 'default-from': 'parent'},
                                      {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                      ]

                    tags = [lp_category + '.' + _item['name'] for _item in eda_key_items]
                    tags.extend([lp_category + '.' + _item['name'] for _item in eda_data_items])

                    for tag in tags:
                        eda_loop.add_tag(tag)

                    index = 1

                    for bond in bonds_w_leaving:

                        leaving_flag = bond.get('pdbx_leaving_atom_flag', '')

                        if leaving_flag in ('one', 'both'):
                            leaving_atom_id = None

                            comp_id = bond['ptnr1_label_comp_id']
                            atom_id = bond['ptnr1_label_atom_id']
                            auth_asym_id = bond['ptnr1_auth_asym_id']
                            auth_seq_id = bond['ptnr1_auth_seq_id']

                            if not auth_seq_id.isdigit():
                                continue

                            if self.__ccU.updateChemCompDict(comp_id):
                                for b in self.__ccU.lastBonds:
                                    if atom_id in (b[self.__ccU.ccbAtomId1], b[self.__ccU.ccbAtomId2]):
                                        _atom_id = b[self.__ccU.ccbAtomId1] if b[self.__ccU.ccbAtomId1] != atom_id else b[self.__ccU.ccbAtomId2]
                                        if any(a for a in self.__ccU.lastAtomList
                                               if _atom_id == a[self.__ccU.ccaAtomId] and a[self.__ccU.ccaLeavingAtomFlag] == 'Y'):
                                            leaving_atom_id = _atom_id
                                            break

                                if leaving_atom_id is not None:

                                    seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                    if seq_key in auth_to_star_seq:
                                        auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                        row = [None] * len(tags)

                                        row[0] = index

                                        entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                        row[1], row[4], row[5] =\
                                            entity_assembly_id, auth_comp_id, leaving_atom_id
                                        row[2] = row[3] = seq_id

                                        row[6], row[7], row[8], row[9] =\
                                            auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                        row[10], row[11] = 1, self.__entry_id

                                        eda_loop.add_data(row)

                                        index += 1

                            if leaving_flag == 'both' or leaving_atom_id is None:
                                leaving_atom_id = None

                                comp_id = bond['ptnr2_label_comp_id']
                                atom_id = bond['ptnr2_label_atom_id']
                                auth_asym_id = bond['ptnr2_auth_asym_id']
                                auth_seq_id = bond['ptnr2_auth_seq_id']

                                if not auth_seq_id.isdigit():
                                    continue

                                if self.__ccU.updateChemCompDict(comp_id):
                                    for b in self.__ccU.lastBonds:
                                        if atom_id in (b[self.__ccU.ccbAtomId1], b[self.__ccU.ccbAtomId2]):
                                            _atom_id = b[self.__ccU.ccbAtomId1] if b[self.__ccU.ccbAtomId1] != atom_id else b[self.__ccU.ccbAtomId2]
                                            if any(a for a in self.__ccU.lastAtomList
                                                   if _atom_id == a[self.__ccU.ccaAtomId] and a[self.__ccU.ccaLeavingAtomFlag] == 'Y'):
                                                leaving_atom_id = _atom_id
                                                break

                                    if leaving_atom_id is not None:

                                        seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                        if seq_key in auth_to_star_seq:
                                            auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                            row = [None] * len(tags)

                                            row[0] = index

                                            entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                            row[1], row[4], row[5] =\
                                                entity_assembly_id, auth_comp_id, leaving_atom_id
                                            row[2] = row[3] = seq_id

                                            row[6], row[7], row[8], row[9] =\
                                                auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                            row[10], row[11] = 1, self.__entry_id

                                            eda_loop.add_data(row)

                                            index += 1

                        else:

                            if bond['ptnr1_label_comp_id'] in ('CYS', 'DCS') and bond['ptnr1_label_atom_id'] == 'SG':
                                comp_id = bond['ptnr1_label_comp_id']
                                atom_id = 'SG'
                                auth_asym_id = bond['ptnr1_auth_asym_id']
                                auth_seq_id = bond['ptnr1_auth_seq_id']

                                if not auth_seq_id.isdigit():
                                    continue

                                leaving_atom_id = 'HG'

                                seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                if seq_key in auth_to_star_seq:
                                    auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                    row = [None] * len(tags)

                                    row[0] = index

                                    entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                    row[1], row[4], row[5] =\
                                        entity_assembly_id, auth_comp_id, leaving_atom_id
                                    row[2] = row[3] = seq_id

                                    row[6], row[7], row[8], row[9] =\
                                        auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                    row[10], row[11] = 1, self.__entry_id

                                    eda_loop.add_data(row)

                                    index += 1

                            elif bond['ptnr1_label_comp_id'] == 'HIS' and bond['ptnr1_label_atom_id'] in ('ND1', 'NE2'):
                                comp_id = 'HIS'
                                atom_id = bond['ptnr1_label_atom_id']
                                auth_asym_id = bond['ptnr1_auth_asym_id']
                                auth_seq_id = bond['ptnr1_auth_seq_id']

                                if not auth_seq_id.isdigit():
                                    continue

                                leaving_atom_id = 'HD1' if atom_id == 'ND1' else 'HE2'

                                seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                if seq_key in auth_to_star_seq:
                                    auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                    row = [None] * len(tags)

                                    row[0] = index

                                    entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                    row[1], row[4], row[5] =\
                                        entity_assembly_id, auth_comp_id, leaving_atom_id
                                    row[2] = row[3] = seq_id

                                    row[6], row[7], row[8], row[9] =\
                                        auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                    row[10], row[11] = 1, self.__entry_id

                                    eda_loop.add_data(row)

                                    index += 1

                            if bond['ptnr2_label_comp_id'] in ('CYS', 'DCS') and bond['ptnr2_label_atom_id'] == 'SG':
                                comp_id = bond['ptnr2_label_comp_id']
                                atom_id = 'SG'
                                auth_asym_id = bond['ptnr2_auth_asym_id']
                                auth_seq_id = bond['ptnr2_auth_seq_id']

                                if not auth_seq_id.isdigit():
                                    continue

                                leaving_atom_id = 'HG'

                                seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                if seq_key in auth_to_star_seq:
                                    auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                    row = [None] * len(tags)

                                    row[0] = index

                                    entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                    row[1], row[4], row[5] =\
                                        entity_assembly_id, auth_comp_id, leaving_atom_id
                                    row[2] = row[3] = seq_id

                                    row[6], row[7], row[8], row[9] =\
                                        auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                    row[10], row[11] = 1, self.__entry_id

                                    eda_loop.add_data(row)

                                    index += 1

                            elif bond['ptnr2_label_comp_id'] == 'HIS' and bond['ptnr2_label_atom_id'] in ('ND1', 'NE2'):
                                comp_id = 'HIS'
                                atom_id = bond['ptnr2_label_atom_id']
                                auth_asym_id = bond['ptnr2_auth_asym_id']
                                auth_seq_id = bond['ptnr2_auth_seq_id']

                                if not auth_seq_id.isdigit():
                                    continue

                                leaving_atom_id = 'HD1' if atom_id == 'ND1' else 'HE2'

                                seq_key = (auth_asym_id, int(auth_seq_id), comp_id)

                                if seq_key in auth_to_star_seq:
                                    auth_comp_id = next((_v[1] for _k, _v in auth_to_orig_seq.items() if _k == seq_key), comp_id)

                                    row = [None] * len(tags)

                                    row[0] = index

                                    entity_assembly_id, seq_id, _, _ = auth_to_star_seq[seq_key]

                                    row[1], row[4], row[5] =\
                                        entity_assembly_id, auth_comp_id, leaving_atom_id
                                    row[2] = row[3] = seq_id

                                    row[6], row[7], row[8], row[9] =\
                                        auth_asym_id, auth_seq_id, auth_comp_id, leaving_atom_id

                                    row[10], row[11] = 1, self.__entry_id

                                    eda_loop.add_data(row)

                                    index += 1

                    asm_sf.add_loop(eda_loop)

        if orig_asm_sf is not None:

            # append extra categories

            if self.__retain_original and file_type == 'nmr-star':

                for loop in orig_asm_sf.loops:

                    if loop.category == self.lp_categories[file_type][content_subtype]:
                        continue

                    if loop.category in self.aux_lp_categories[file_type][content_subtype]:
                        continue

                    asm_sf.add_loop(loop)

            del master_entry[orig_asm_sf]

        for sf in master_entry.frame_list:
            if sf.name == sf_framecode:
                master_entry.remove_saveframe(sf_framecode)
                break

        master_entry.add_saveframe(asm_sf)

        try:
            poly_seq = self.__getPolymerSequence(0, asm_sf, content_subtype)[0]
        except KeyError:
            return False
        except UserWarning:
            poly_seq = []

        identical = True

        if len(poly_seq) < LEN_MAJOR_ASYM_ID or len(poly_seq) != len(orig_poly_seq):  # to process large assembly avoiding forced timeout
            seq_align, _ = alignPolymerSequence(self.__pA, poly_seq, orig_poly_seq, conservative=False)
            chain_assign, _ = assignPolymerSequence(self.__pA, self.__ccU, file_type, poly_seq, orig_poly_seq, seq_align)

            self.__chain_id_map_for_remediation = {}
            self.__seq_id_map_for_remediation = {}

            for ps in orig_poly_seq:
                chain_id = ps['chain_id']
                for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):
                    seq_key = (chain_id, seq_id)

                    _chain_id = _seq_id = _comp_id = None

                    if chain_assign is not None:
                        _chain_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                        if _chain_id is not None:
                            sa = next((sa for sa in seq_align if sa['ref_chain_id'] == _chain_id and sa['test_chain_id'] == chain_id and seq_id in sa['test_seq_id']), None)
                            if sa is not None:
                                _seq_id = next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa['ref_seq_id'], sa['test_seq_id']) if test_seq_id == seq_id), None)
                                if _seq_id is not None:
                                    _ps = next(_ps for _ps in poly_seq if _ps['chain_id'] == _chain_id)
                                    if _seq_id in _ps['seq_id']:
                                        try:
                                            _comp_id = _ps['comp_id'][_ps['seq_id'].index(_seq_id)]
                                        except IndexError:
                                            pass

                    if _chain_id is not None and _seq_id is not None and comp_id == _comp_id:
                        if chain_id not in self.__chain_id_map_for_remediation:
                            self.__chain_id_map_for_remediation[chain_id] = _chain_id
                        self.__seq_id_map_for_remediation[seq_key] = (_chain_id, _seq_id)
                        if chain_id != _chain_id or seq_id != _seq_id:
                            identical = False
                    else:
                        _ps = next((_ps for _ps in poly_seq if _ps['chain_id'] == _chain_id and _ps['seq_id'] == _seq_id), None)
                        if _ps is not None:
                            try:
                                _comp_id = _ps['comp_id'][_ps['seq_id'].index(_seq_id)]
                                if comp_id == _comp_id:
                                    if chain_id not in self.__chain_id_map_for_remediation:
                                        self.__chain_id_map_for_remediation[chain_id] = _chain_id
                                    self.__seq_id_map_for_remediation[seq_key] = (_chain_id, _seq_id)
                            except IndexError:
                                pass

        self.__mergePolymerSequenceInCsLoop(0)

        self.__remediateCsLoop()

        if not identical:
            self.__syncMrLoop()

        self.__removeUnusedPdbInsCode()

        if file_type == 'nef':
            return True

        # Refresh _Entity saveframe

        content_subtype = 'entity'

        ent_sfs = master_entry.get_saveframes_by_category(self.sf_categories[file_type][content_subtype])

        for sf in reversed(ent_sfs):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
            master_entry.remove_saveframe(sf_framecode)
        # """
        # sf_key_items = [{'name': 'Sf_category', 'type': 'str', 'mandatory': True},
        #                 {'name': 'Sf_framecode', 'type': 'str', 'mandatory': True},
        #                 {'name': 'Entry_ID', 'type': 'str', 'mandatory': True},
        #                 {'name': 'ID', 'type': 'positive-int', 'mandatory': True},
        #                 ]
        # sf_items = [{'name': 'BMRB_code', 'type': 'str'},
        #                  {'name': 'Name', 'type': 'str'},
        #                  {'name': 'Type', 'type': 'enum',
        #                   'enum': ('polymer', 'non-polymer', 'water', 'aggregate', 'solvent')},
        #                  {'name': 'Polymer_common_type', 'type': 'enum',
        #                   'enum': ('protein', 'DNA', 'RNA', 'DNA/RNA hybrid', 'polysaccharide')},
        #                  {'name': 'Polymer_type', 'type': 'enum',
        #                   'enum': ('cyclic-pseudo-peptide', 'polypeptide(L)', 'polydeoxyribonucleotide', 'polyribonucleotide',
        #                            'polydeoxyribonucleotide/polyribonucleotide hybrid',
        #                            'polypeptide(D)', 'polysaccharide(D)', 'polysaccharide(L)', 'other')},
        #                  {'name': 'Polymer_type_details', 'type': 'str'},
        #                  {'name': 'Polymer_strand_ID', 'type': 'str'},
        #                  {'name': 'Polymer_seq_one_letter_code_can', 'type': 'str'},
        #                  {'name': 'Polymer_seq_one_letter_code', 'type': 'str'},
        #                  {'name': 'Target_identifier', 'type': 'str'},
        #                  {'name': 'Polymer_author_defined_seq', 'type': 'str'},
        #                  {'name': 'Polymer_author_seq_details', 'type': 'str'},
        #                  {'name': 'Ambiguous_conformational_states', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Ambiguous_chem_comp_sites', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Nstd_monomer', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Nstd_chirality', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Nstd_linkage', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Nonpolymer_comp_ID', 'type': 'str'},
        #                  {'name': 'Nonpolymer_comp_label', 'type': 'str'},
        #                  {'name': 'Number_of_monomers', 'type': 'int'},
        #                  {'name': 'Number_of_nonpolymer_components', 'type': 'int'},
        #                  {'name': 'Paramagnetic', 'type': 'enum',
        #                   'enum': ('yes', 'no')},
        #                  {'name': 'Thiol_state', 'type': 'enum',
        #                   'enum': ('all disulfide bound', 'all other bound', 'all free', 'not present', 'not available', 'unknown', 'not reported',
        #                            'free and disulfide bound', 'free and other bound', 'free disulfide and other bound', 'disulfide and other bound')},
        #                  {'name': 'Src_method', 'type': 'str'},
        #                  {'name': 'Parent_entity_ID}, 'type': 'int'},
        #                  {'name': 'Fragment', 'type': 'str'},
        #                  {'name': 'Mutation', 'type': 'str'},
        #                  {'name': 'EC_number', 'type': 'str'},
        #                  {'name': 'Calc_isoelectric_point', 'type': 'float'},
        #                  {'name': 'Formula_weight', 'type': 'float'},
        #                  {'name': 'Formula_weight_exptl', 'type': 'float'},
        #                  {'name': 'Formula_weight_exptl_meth', 'type': 'str'},
        #                  {'name': 'Details', 'type': 'str'},
        #                  {'name': 'DB_query_date', 'type': 'str'},
        #                  {'name': 'DB_query_revised_last_date', 'type': 'str'}
        #                  ]
        # """
        entity_ids = []

        for item in entity_assembly:
            entity_id = item['entity_id']

            if entity_id in entity_ids:
                continue

            entity_ids.append(entity_id)

            entity_type = item['entity_type']

            sf_framecode = f'entity_{entity_id}' if entity_type not in ('non-polymer', 'water') else f"entity_{item['comp_id']}"

            ent_sf = pynmrstar.Saveframe.from_scratch(sf_framecode)
            ent_sf.set_tag_prefix(self.sf_tag_prefixes[file_type][content_subtype])
            ent_sf.add_tag('Sf_category', self.sf_categories[file_type][content_subtype])
            ent_sf.add_tag('Sf_framecode', sf_framecode)
            ent_sf.add_tag('Entry_ID', self.__entry_id)
            ent_sf.add_tag('ID', entity_id)
            ent_sf.add_tag('BMRB_code', None if entity_type not in ('non-polymer', 'water') else item['comp_id'])
            ent_sf.add_tag('Name', item['entity_desc'])
            ent_sf.add_tag('Type', entity_type)

            if entity_type == 'polymer':
                poly_type = item['entity_poly_type']
                if poly_type.startswith('polypeptide'):
                    common_type = 'protein'
                elif any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('DA', 'DC', 'DG', 'DT'))\
                        and any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('A', 'C', 'G', 'U')):
                    common_type = 'DNA/RNA hybrid'
                elif poly_type == 'polydeoxyribonucleotide':
                    common_type = 'DNA'
                elif poly_type == 'polyribonucleotide':
                    common_type = 'RNA'
                else:
                    common_type = None
            elif entity_type == 'branched':
                common_type = 'polysaccharide'
            else:
                common_type = None
            ent_sf.add_tag('Polymer_common_type', common_type)

            if entity_type == 'polymer':
                poly_type = item['entity_poly_type']
                if poly_type.startswith('polypeptide'):
                    _poly_type = poly_type

                    if self.__cR.hasCategory('struct_conn'):
                        auth_asym_ids = item['auth_asym_id'].split(',')

                        bonds = self.__cR.getDictList('struct_conn')

                        for bond in bonds:

                            try:

                                auth_asym_id_1 = bond['ptnr1_auth_asym_id']
                                auth_seq_id_1 = int(bond['ptnr1_auth_seq_id'])
                                atom_id_1 = bond['ptnr1_label_atom_id']
                                auth_asym_id_2 = bond['ptnr2_auth_asym_id']
                                auth_seq_id_2 = int(bond['ptnr2_auth_seq_id'])
                                atom_id_2 = bond['ptnr2_label_atom_id']

                                if auth_asym_id_1 == auth_asym_id_2 and auth_asym_id_1 in auth_asym_ids\
                                   and {atom_id_1, atom_id_2} == {'C', 'N'} and abs(auth_seq_id_1 - auth_seq_id_2) > 1:
                                    _poly_type = 'cyclic-pseudo-peptide'

                            except ValueError:
                                continue

                elif any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('DA', 'DC', 'DG', 'DT'))\
                        and any(comp_id for comp_id in item['comp_id_set'] if comp_id in ('A', 'C', 'G', 'U')):
                    _poly_type = 'polydeoxyribonucleotide/polyribonucleotide hybrid'
                else:
                    _poly_type = poly_type
            elif entity_type == 'branched':
                _poly_type = item['entity_poly_type']
            else:
                _poly_type = None

            ent_sf.add_tag('Polymer_type', _poly_type)
            ent_sf.add_tag('Polymer_type_details', None)

            auth_asym_ids = []
            for _item in entity_assembly:
                if _item['entity_id'] != entity_id:
                    continue
                if _item['auth_asym_id'] in auth_asym_ids:
                    continue
                auth_asym_ids.append(_item['auth_asym_id'])
            auth_asym_id_list = ','.join(auth_asym_ids)
            if len(auth_asym_id_list) > 12 and ',' in auth_asym_id_list:
                last_asym_id = ',..,' + auth_asym_id_list.rsplit(',', maxsplit=1)[-1]
                max_len = 11 - len(last_asym_id)
                while True:
                    if auth_asym_id_list[max_len] == ',':
                        break
                    max_len -= 1
                auth_asym_id_list = auth_asym_id_list[:max_len] + last_asym_id
            ent_sf.add_tag('Polymer_strand_ID', auth_asym_id_list)

            one_letter_code_can = one_letter_code = None
            nmr_ext_monomers = 0
            nmr_ext_fw = 0.0
            if entity_type == 'polymer':
                one_letter_code_can = item['one_letter_code_can']
                one_letter_code = item['one_letter_code']
                if self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                   and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] in auth_asym_ids):
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['auth_chain_id'] in auth_asym_ids)
                    auth_seq_ids = list(filter(None, ps['auth_seq_id']))
                    min_auth_seq_id = min(auth_seq_ids)
                    max_auth_seq_id = max(auth_seq_ids)
                    comp_ids = []
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] in auth_asym_ids:
                            if d['auth_seq_id'] < min_auth_seq_id:
                                comp_ids.append(d['auth_comp_id'])
                                nmr_ext_monomers += 1
                                nmr_ext_fw += self.__ccU.getEffectiveFormulaWeight(d['auth_comp_id'])
                    comp_ids.extend(ps['comp_id'])
                    for d in self.__nmr_ext_poly_seq:
                        if d['auth_chain_id'] in auth_asym_ids:
                            if d['auth_seq_id'] > max_auth_seq_id:
                                comp_ids.append(d['auth_comp_id'])
                                nmr_ext_monomers += 1
                                nmr_ext_fw += self.__ccU.getEffectiveFormulaWeight(d['auth_comp_id'])
                    one_letter_code_can = getOneLetterCodeCanSequence(comp_ids)
                    one_letter_code = getOneLetterCodeSequence(comp_ids)

            ent_sf.add_tag('Polymer_seq_one_letter_code_can', None if entity_type != 'polymer' else one_letter_code_can)
            ent_sf.add_tag('Polymer_seq_one_letter_code', None if entity_type != 'polymer' else one_letter_code)
            ent_sf.add_tag('Target_identifier', None if entity_type != 'polymer' else item['target_identifier'])
            ent_sf.add_tag('Polymer_author_defined_seq', None)
            ent_sf.add_tag('Polymer_author_seq_details', None)
            ent_sf.add_tag('Ambiguous_conformational_states', None)
            ent_sf.add_tag('Ambiguous_chem_comp_sites', None)
            ent_sf.add_tag('Nstd_monomer', None if entity_type != 'polymer' else item['nstd_monomer'])
            ent_sf.add_tag('Nstd_chirality', None if entity_type != 'polymer' else item['nstd_chirality'])
            ent_sf.add_tag('Nstd_linkage', None if entity_type != 'polymer' else item['nstd_linkage'])
            ent_sf.add_tag('Nonpolymer_comp_ID', None if entity_type not in ('non-polymer', 'water') else item['comp_id'])
            ent_sf.add_tag('Nonpolymer_comp_label', None if entity_type != 'non-polymer' else f"$chem_comp_{item['comp_id']}")
            ent_sf.add_tag('Number_of_monomers', None if entity_type in ('non-polymer', 'water') else item['num_of_monomers'] + nmr_ext_monomers)
            ent_sf.add_tag('Number_of_nonpolymer_components', None if entity_type not in ('non-polymer', 'water') else 1)
            ent_sf.add_tag('Paramagnetic', 'no' if not paramag or entity_type not in ('non-polymer', 'water') or item['comp_id'] not in PARAMAGNETIC_ELEMENTS else 'yes')

            _label_asym_id = 'label_asym_id' if 'fixed_label_asym_id' not in item else 'fixed_label_asym_id'

            cys_total = 0
            label_asym_ids = set(item[_label_asym_id].split(','))
            for chain_id in label_asym_ids:
                if entity_type == 'polymer':
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['chain_id'] == chain_id)
                    cys_total += ps['comp_id'].count('CYS') + ps['comp_id'].count('DCY')

            if cys_total > 0:
                disul_cys = other_cys = 0
                if self.__cR.hasCategory('struct_conn'):
                    bonds = self.__cR.getDictList('struct_conn')
                    for bond in bonds:
                        label_asym_id_1 = bond['ptnr1_label_asym_id']
                        # auth_seq_id_1 = bond['ptnr1_auth_seq_id']
                        auth_comp_id_1 = bond['ptnr1_auth_comp_id']
                        label_asym_id_2 = bond['ptnr2_label_asym_id']
                        atom_id_1 = bond['ptnr1_label_atom_id']
                        # auth_seq_id_2 = bond['ptnr2_auth_seq_id']
                        auth_comp_id_2 = bond['ptnr2_auth_comp_id']
                        atom_id_2 = bond['ptnr2_label_atom_id']

                        if label_asym_id_1 in label_asym_ids and auth_comp_id_1 in ('CYS', 'DCY') and atom_id_1 == 'SG':
                            if auth_comp_id_2 in ('CYS', 'DCY') and atom_id_2 == 'SG':
                                disul_cys += 1
                            else:
                                other_cys += 1

                        if label_asym_id_2 in label_asym_ids and auth_comp_id_2 in ('CYS', 'DCY') and atom_id_2 == 'SG':
                            if auth_comp_id_1 in ('CYS', 'DCY') and atom_id_1 == 'SG':
                                disul_cys += 1
                            else:
                                other_cys += 1

                free_cys = cys_total - disul_cys - other_cys

                if free_cys > 0:
                    if free_cys == cys_total:
                        thiol_state = 'all free'
                    elif disul_cys > 0 and other_cys > 0:
                        thiol_state = 'free disulfide and other bound'
                    elif other_cys == 0:
                        thiol_state = 'free and disulfide bound'
                    else:
                        thiol_state = 'free and other bound'
                else:
                    if disul_cys > 0 and other_cys > 0:
                        thiol_state = 'disulfide and other bound'
                    elif other_cys == 0:
                        thiol_state = 'all disulfide bound'
                    else:
                        thiol_state = 'all other bound'
            else:
                thiol_state = 'not present'
            ent_sf.add_tag('Thiol_state', thiol_state)
            ent_sf.add_tag('Src_method', item['entity_src_method'])
            ent_sf.add_tag('Parent_entity_ID', None if entity_type != 'polymer' else item['entity_parent'])
            ent_sf.add_tag('Fragment', None if entity_type != 'polymer' else item['entity_fragment'])
            ent_sf.add_tag('Mutation', None if entity_type != 'polymer' else item['entity_mutation'])
            ent_sf.add_tag('EC_number', None if entity_type != 'polymer' else item['entity_ec'])
            ent_sf.add_tag('Calc_isoelectric_point', None)
            ent_sf.add_tag('Formula_weight', item['entity_fw'] if nmr_ext_monomers == 0 else round(item['entity_fw'] + nmr_ext_fw, 3))
            ent_sf.add_tag('Formula_weight_exptl', None)
            ent_sf.add_tag('Formula_weight_exptl_meth', None)
            ent_sf.add_tag('Details', item['entity_details'])
            ent_sf.add_tag('DB_query_date', None)
            ent_sf.add_tag('DB_query_revised_last_date', None)

            # Refresh _Entity_common_name loop

            if self.__cR.hasCategory('entity_name_com'):
                lp_category = '_Entity_common_name'
                ecn_loop = pynmrstar.Loop.from_scratch(lp_category)

                ecn_key_items = [{'name': 'Name', 'type': 'str'},
                                 {'name': 'Type', 'type': 'enum',
                                  'enum': ('common', 'abbreviation', 'synonym')}
                                 ]
                ecn_data_items = [{'name': 'Entity_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                                  {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                  ]

                tags = [lp_category + '.' + _item['name'] for _item in ecn_key_items]
                tags.extend([lp_category + '.' + _item['name'] for _item in ecn_data_items])

                for tag in tags:
                    ecn_loop.add_tag(tag)

                ent_name_coms = self.__cR.getDictList('entity_name_com')
                for ent_name_com in ent_name_coms:
                    if int(ent_name_com['entity_id']) == entity_id:
                        row = [None] * len(tags)

                        row[0], row[1], row[2], row[3] =\
                            ent_name_com['name'], 'common', entity_id, self.__entry_id

                        ecn_loop.add_data(row)

                if not ecn_loop.empty:
                    ent_sf.add_loop(ecn_loop)

            # Refresh _Entity_systematic_name loop

            if self.__cR.hasCategory('entity_name_sys'):
                lp_category = '_Entity_systematic_name'
                esn_loop = pynmrstar.Loop.from_scratch(lp_category)

                esn_key_items = [{'name': 'Name', 'type': 'str'},
                                 {'name': 'Naming_system', 'type': 'enum',
                                  'enum': ('IUPAC', 'CAS name', 'CAS registry number', 'BMRB',
                                           'Three letter code', 'Pfam', 'Swiss-Prot', 'EC', 'NCBI')}
                                 ]
                esn_data_items = [{'name': 'Entity_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                                  {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                  ]

                tags = [lp_category + '.' + _item['name'] for _item in esn_key_items]
                tags.extend([lp_category + '.' + _item['name'] for _item in esn_data_items])

                for tag in tags:
                    esn_loop.add_tag(tag)

                ent_name_syss = self.__cR.getDictList('entity_name_sys')
                for ent_name_sys in ent_name_syss:
                    if int(ent_name_sys['entity_id']) == entity_id:
                        row = [None] * len(tags)

                        row[0], row[1], row[2], row[3] =\
                            ent_name_sys['name'], ent_name_sys.get('system'), entity_id, self.__entry_id

                        esn_loop.add_data(row)

                if not esn_loop.empty:
                    ent_sf.add_loop(esn_loop)

            # Refresh _Entity_keyword loop

            if self.__cR.hasCategory('entity_keywords'):
                lp_category = '_Entity_keyword'
                ek_loop = pynmrstar.Loop.from_scratch(lp_category)

                ek_key_items = [{'name': 'Keyword', 'type': 'str'}
                                ]
                ek_data_items = [{'name': 'Entity_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                                 {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                 ]

                tags = [lp_category + '.' + _item['name'] for _item in ek_key_items]
                tags.extend([lp_category + '.' + _item['name'] for _item in ek_data_items])

                for tag in tags:
                    ek_loop.add_tag(tag)

                ent_keys = self.__cR.getDictList('entity_keywords')
                for ent_key in ent_keys:
                    if int(ent_key['entity_id']) == entity_id and 'text' in ent_key and ent_key['text'] not in emptyValue:
                        row = [None] * len(tags)

                        row[0], row[1], row[2] =\
                            ent_key['text'], entity_id, self.__entry_id

                        ek_loop.add_data(row)

                if not ek_loop.empty:
                    ent_sf.add_loop(ek_loop)

            # Refresh _Entity_comp_index loop

            lp_category = '_Entity_comp_index'
            eci_loop = pynmrstar.Loop.from_scratch(lp_category)

            eci_key_items = [{'name': 'ID', 'type': 'positive-int'},
                             {'name': 'Auth_seq_ID', 'type': 'int'},
                             {'name': 'Comp_ID', 'type': 'str'}
                             ]
            eci_data_items = [{'name': 'Comp_label', 'type': 'str'},
                              {'name': 'Entity_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                              {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                              ]

            tags = [lp_category + '.' + _item['name'] for _item in eci_key_items]
            tags.extend([lp_category + '.' + _item['name'] for _item in eci_data_items])

            for tag in tags:
                eci_loop.add_tag(tag)

            index = 1

            label_asym_ids = []
            for chain_id in item['label_asym_id'].split(','):
                if chain_id not in label_asym_ids:
                    label_asym_ids.append(chain_id)

            min_auth_seq_id = max_auth_seq_id = max_seq_id = -1

            for chain_id in label_asym_ids:
                if entity_type == 'polymer':
                    ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['chain_id'] == chain_id)
                    auth_seq_ids = list(filter(None, ps['auth_seq_id']))
                    seq_ids = list(filter(None, ps['seq_id']))
                    min_auth_seq_id = min(auth_seq_ids)
                    max_auth_seq_id = max(auth_seq_ids)
                    max_seq_id = max(seq_ids)
                elif entity_type == 'branched':
                    ps = next(ps for ps in self.__caC['branched'] if ps['chain_id'] == chain_id)
                else:
                    ps = next(ps for ps in self.__caC['non_polymer'] if ps['chain_id'] == chain_id)

                seq_keys = set()

                for auth_seq_id, seq_id, comp_id in zip(ps['auth_seq_id'], ps['seq_id'],
                                                        ps['auth_comp_id'] if 'auth_comp_id' in ps else ps['comp_id']):
                    seq_key = (ps['auth_chain_id'], seq_id)

                    if seq_key in seq_keys:
                        continue

                    if entity_type in ('non-polymer', 'water'):
                        if comp_id != item['comp_id']:
                            continue
                        auth_seq_id = seq_id

                    if entity_type == 'polymer' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                       and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == ps['auth_chain_id']
                               and d['auth_seq_id'] < min_auth_seq_id):
                        for d in self.__nmr_ext_poly_seq:
                            auth_asym_id = ps['auth_chain_id']
                            _auth_seq_id = ps['auth_seq_id'][ps['seq_id'].index(seq_id)]
                            if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < _auth_seq_id:
                                _offset = seq_id - _auth_seq_id
                                _seq_id = d['auth_seq_id'] + _offset
                                _seq_key = (auth_asym_id, _seq_id)
                                if _seq_key in seq_keys:
                                    continue
                                seq_keys.add(_seq_key)
                                row = [None] * len(tags)
                                row[0], row[1], row[2] = _seq_id, d['auth_seq_id'], d['auth_comp_id']
                                if d['auth_comp_id'] not in monDict3 and d['auth_comp_id'] != 'HOH':
                                    row[3] = f"$chem_comp_{d['auth_comp_id']}"
                                row[4], row[5] = entity_id, self.__entry_id

                                eci_loop.add_data(row)

                    row = [None] * len(tags)

                    seq_keys.add(seq_key)

                    row[0], row[1], row[2] = seq_id if entity_type == 'polymer' else index, auth_seq_id, comp_id

                    if comp_id not in monDict3 and comp_id != 'HOH':
                        row[3] = f"$chem_comp_{comp_id}"

                    row[4], row[5] = entity_id, self.__entry_id

                    if comp_id not in emptyValue:
                        eci_loop.add_data(row)

                    index += 1

            if entity_type == 'polymer' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
               and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == ps['auth_chain_id']
                       and d['auth_seq_id'] > max_auth_seq_id):
                _offset = max_seq_id - max_auth_seq_id
                for d in self.__nmr_ext_poly_seq:
                    auth_asym_id = ps['auth_chain_id']
                    if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > max_auth_seq_id:
                        _seq_id = d['auth_seq_id'] + _offset
                        row = [None] * len(tags)
                        row[0], row[1], row[2] = _seq_id, d['auth_seq_id'], d['auth_comp_id']
                        if d['auth_comp_id'] not in monDict3 and d['auth_comp_id'] != 'HOH':
                            row[3] = f"$chem_comp_{d['auth_comp_id']}"
                        row[4], row[5] = entity_id, self.__entry_id

                        eci_loop.add_data(row)

            ent_sf.add_loop(eci_loop)

            # Refresh _Entity_poly_seq loop

            if entity_type not in ('non-polymer', 'water'):
                lp_category = '_Entity_poly_seq'
                eps_loop = pynmrstar.Loop.from_scratch(lp_category)

                eps_key_items = [{'name': 'Hetero', 'type': 'str'},
                                 {'name': 'Mon_ID', 'type': 'str'},
                                 {'name': 'Num', 'type': 'int'},
                                 {'name': 'Comp_index_ID', 'type': 'int'}
                                 ]
                eps_data_items = [{'name': 'Entity_ID', 'type': 'pointer-index', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                                  {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                                  ]

                tags = [lp_category + '.' + _item['name'] for _item in eps_key_items]
                tags.extend([lp_category + '.' + _item['name'] for _item in eps_data_items])

                for tag in tags:
                    eps_loop.add_tag(tag)

                seq_keys = set()

                label_asym_ids = list(set(item['label_asym_id'].split(',')))
                for chain_id in sorted(sorted(label_asym_ids), key=len):
                    if entity_type == 'polymer':
                        ps = next(ps for ps in self.__caC['polymer_sequence'] if ps['chain_id'] == chain_id)
                        auth_seq_ids = list(filter(None, ps['auth_seq_id']))
                        seq_ids = list(filter(None, ps['seq_id']))
                        min_auth_seq_id = min(auth_seq_ids)
                        max_auth_seq_id = max(auth_seq_ids)
                        max_seq_id = max(seq_ids)
                    else:  # 'branched':
                        ps = next(ps for ps in self.__caC['branched'] if ps['chain_id'] == chain_id)

                    for seq_id, comp_id in zip(ps['seq_id'], ps['auth_comp_id'] if 'auth_comp_id' in ps else ps['comp_id']):
                        seq_key = (ps['auth_chain_id'], seq_id)

                        if seq_key in seq_keys:
                            continue

                        if entity_type in ('non-polymer', 'water'):
                            if comp_id != item['comp_id']:
                                continue

                        if entity_type == 'polymer' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                           and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == ps['auth_chain_id']
                                   and d['auth_seq_id'] < min_auth_seq_id):
                            for d in self.__nmr_ext_poly_seq:
                                auth_asym_id = ps['auth_chain_id']
                                auth_seq_id = ps['auth_seq_id'][ps['seq_id'].index(seq_id)]
                                if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] < auth_seq_id:
                                    _offset = seq_id - auth_seq_id
                                    _seq_id = d['auth_seq_id'] + _offset
                                    _seq_key = (auth_asym_id, _seq_id)
                                    if _seq_key in seq_keys:
                                        continue
                                    seq_keys.add(_seq_key)
                                    row = [None] * len(tags)
                                    row[1], row[2], row[3], row[4], row[5] =\
                                        d['auth_comp_id'], _seq_id, _seq_id, entity_id, self.__entry_id

                                    eps_loop.add_data(row)

                        row = [None] * len(tags)

                        seq_keys.add(seq_key)

                        row[1], row[4], row[5] = comp_id, entity_id, self.__entry_id
                        row[2] = row[3] = seq_id

                        if comp_id not in emptyValue:
                            eps_loop.add_data(row)

                    if entity_type == 'polymer' and self.__nmr_ext_poly_seq is not None and len(self.__nmr_ext_poly_seq) > 0\
                       and any(d for d in self.__nmr_ext_poly_seq if d['auth_chain_id'] == ps['auth_chain_id']
                               and d['auth_seq_id'] > max_auth_seq_id):
                        _offset = max_seq_id - max_auth_seq_id
                        for d in self.__nmr_ext_poly_seq:
                            auth_asym_id = ps['auth_chain_id']
                            if d['auth_chain_id'] == auth_asym_id and d['auth_seq_id'] > max_auth_seq_id:
                                _seq_id = d['auth_seq_id'] + _offset
                                row = [None] * len(tags)
                                row[1], row[2], row[3], row[4], row[5] =\
                                    d['auth_comp_id'], _seq_id, _seq_id, entity_id, self.__entry_id

                                eps_loop.add_data(row)

                ent_sf.add_loop(eps_loop)

            master_entry.add_saveframe(ent_sf)

        return True

    def __updateAuthSequence(self):
        """ Update auth sequence in NMR-STAR.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type == 'nef':
            return True

        has_poly_seq = has_key_value(input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        if self.__srcPath == self.__dstPath:
            return True

        chain_assign_dic = self.report.chain_assignment.get()

        if 'nmr_poly_seq_vs_model_poly_seq' not in chain_assign_dic:
            return False

        if not has_key_value(chain_assign_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return False

        if input_source_dic['content_subtype'] is None:
            return False

        seqAlignMap = {}

        polymer_sequence = input_source_dic['polymer_sequence']

        for s in polymer_sequence:
            chain_id = s['chain_id']
            seqAlignMap[chain_id] = self.report.getSequenceAlignmentWithNmrChainId(chain_id)

        if len(seqAlignMap) == 0:
            return False

        tags = ['Entity_assembly_ID', 'Comp_index_ID', 'Auth_asym_ID', 'Auth_seq_ID']

        self.authSeqMap = {}

        content_subtype = 'poly_seq'

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

            try:
                if __pynmrstar_v3_2__:
                    loop = sf.get_loop(lp_category)
                else:
                    loop = sf.get_loop_by_category(lp_category)
            except KeyError:
                continue

            star_chain_index = loop.tags.index(tags[0])
            star_seq_index = loop.tags.index(tags[1])

            for row in loop:
                star_chain = row[star_chain_index]
                star_seq = row[star_seq_index]

                if star_chain in seqAlignMap:
                    seq_align = seqAlignMap[star_chain]

                    if seq_align is None:
                        continue

                    try:
                        auth_seq = seq_align['test_seq_id'][seq_align['ref_seq_id'].index(star_seq)]
                        self.authSeqMap[(star_chain, star_seq)] = (seq_align['test_chain_id'], auth_seq)
                    except (IndexError, ValueError):
                        pass

        if len(self.authSeqMap) == 0:
            return False

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                try:
                    if __pynmrstar_v3_2__:
                        loop = sf.get_loop(lp_category)
                    else:
                        loop = sf.get_loop_by_category(lp_category)
                except KeyError:
                    continue

                if set(tags) & set(loop.tags) == set(tags):
                    self.__updateAuthSequence__(loop, tags)

                else:
                    for i in range(1, MAX_DIM_NUM_OF_SPECTRA):
                        _tags = [t + '_' + str(i) for t in tags]

                        if set(_tags) & set(loop.tags) == set(_tags):
                            self.__updateAuthSequence__(loop, _tags)
                        else:
                            break

        return True

    def __updateAuthSequence__(self, loop, tags):
        """ Update auth sequence in NMR-STAR.
        """

        # Entity_assembly_ID*
        star_chain_index = loop.tags.index(tags[0])
        # Comp_index_ID*
        star_seq_index = loop.tags.index(tags[1])
        # Auth_asym_ID*
        auth_chain_index = loop.tags.index(tags[2])
        # Auth_seq_ID*
        auth_seq_index = loop.tags.index(tags[3])

        for row in loop:
            star_chain = row[star_chain_index]
            star_seq = row[star_seq_index]

            if star_chain in emptyValue or star_seq in emptyValue:
                continue

            seq_key = (star_chain, star_seq)

            if seq_key in self.authSeqMap:
                row[auth_chain_index], row[auth_seq_index] = self.authSeqMap[seq_key]

    def __hasCoordSeq(self, nmr_chain_id, nmr_seq_id):
        """ Return whether a given sequence is in the coordinates.
            @return: True for corresponding sequence in the coordinates exist, False otherwise
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return False

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            return cif_seq_id is not None

        return False

    def __isCyclicPolymer(self, nmr_chain_id):
        """ Return whether a given chain is cyclic polymer based on coordinate annotation.
            @return: True for cyclic polymer, False otherwise
        """

        if nmr_chain_id in self.__is_cyclic_polymer:
            return self.__is_cyclic_polymer[nmr_chain_id]

        try:

            is_cyclic = self.__isCyclicPolymer__(nmr_chain_id)

            return is_cyclic

        finally:
            self.__is_cyclic_polymer[nmr_chain_id] = is_cyclic

    def __isCyclicPolymer__(self, nmr_chain_id):
        """ Return whether a given chain is cyclic polymer based on coordinate annotation.
            @return: True for cyclic polymer, False otherwise
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']
        beg_cif_seq_id = cif_ps['seq_id'][0]
        end_cif_seq_id = cif_ps['seq_id'][-1]

        try:

            if self.__cR.hasCategory('struct_conn'):
                filter_items = [{'name': 'ptnr1_label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                {'name': 'ptnr2_label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                {'name': 'ptnr1_label_seq_id', 'type': 'int', 'value': beg_cif_seq_id},
                                {'name': 'ptnr2_label_seq_id', 'type': 'int', 'value': end_cif_seq_id}
                                ]

                if not self.__bmrb_only and self.__cR.hasItem('struct_conn', 'pdbx_leaving_atom_flag'):
                    filter_items.append({'name': 'pdbx_leaving_atom_flag', 'type': 'str', 'value': 'both'})

                struct_conn = self.__cR.getDictListWithFilter('struct_conn',
                                                              [{'name': 'conn_type_id', 'type': 'str'}
                                                               ],
                                                              filter_items)

            else:
                struct_conn = []

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__isCyclicPolymer__() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__isCyclicPolymer__() ++ Error  - {str(e)}\n")

            return False

        if len(struct_conn) == 0:

            seq_key_1 = (cif_chain_id, beg_cif_seq_id)
            seq_key_2 = (cif_chain_id, end_cif_seq_id)
            close_contact = []

            if seq_key_1 in self.__label_to_auth_seq and seq_key_2 in self.__label_to_auth_seq:
                auth_cif_chain_id, auth_beg_cif_seq_id = self.__label_to_auth_seq[seq_key_1]
                _, auth_end_cif_seq_id = self.__label_to_auth_seq[seq_key_2]

                try:

                    if self.__cR.hasCategory('pdbx_validate_close_contact'):
                        close_contact = self.__cR.getDictListWithFilter('pdbx_validate_close_contact',
                                                                        [{'name': 'dist', 'type': 'float'}
                                                                         ],
                                                                        [{'name': 'PDB_model_num', 'type': 'int', 'value': self.__representative_model_id},
                                                                         {'name': 'auth_asym_id_1', 'type': 'str', 'value': auth_cif_chain_id},
                                                                         {'name': 'auth_seq_id_1', 'type': 'int', 'value': auth_beg_cif_seq_id},
                                                                         {'name': 'auth_atom_id_1', 'type': 'str', 'value': 'N'},
                                                                         {'name': 'auth_asym_id_2', 'type': 'str', 'value': auth_cif_chain_id},
                                                                         {'name': 'auth_seq_id_2', 'type': 'int', 'value': auth_end_cif_seq_id},
                                                                         {'name': 'auth_atom_id_2', 'type': 'str', 'value': 'C'}
                                                                         ])

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__isCyclicPolymer__() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__isCyclicPolymer__() ++ Error  - {str(e)}\n")

                    return False

            if len(close_contact) == 0:

                bond = self.__getCoordBondLength(cif_chain_id, beg_cif_seq_id, 'N', cif_chain_id, end_cif_seq_id, 'C')

                if bond is None:
                    return False

                dist = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)

                if dist is None:
                    return False

                return 1.0 < dist < 2.4

            return 1.0 < close_contact[0]['dist'] < 2.4

        return struct_conn[0]['conn_type_id'].startswith('covale')

    def __isProtCis(self, nmr_chain_id, nmr_seq_id):
        """ Return whether type of peptide conformer of a given sequence is cis based on coordinate annotation.
            @return: True for cis peptide conformer, False otherwise
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return False

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            if cif_seq_id is None:
                return False

            try:

                if self.__cR.hasCategory('struct_mon_prot_cis'):
                    alias = not self.__cR.hasItem('struct_mon_prot_cis', 'pdbx_PDB_model_num')

                    model_num_name = 'ndb_model_num' if alias else 'pdbx_PDB_model_num'
                    label_asym_id_2_name = 'ndb_label_asym_id_2' if alias else 'pdbx_label_asym_id_2'
                    label_seq_id_2_name = 'ndb_label_seq_id_2' if alias else 'pdbx_label_seq_id_2'

                    prot_cis = self.__cR.getDictListWithFilter('struct_mon_prot_cis',
                                                               [{'name': model_num_name, 'type': 'int'}
                                                                ],
                                                               [{'name': label_asym_id_2_name, 'type': 'str', 'value': cif_chain_id},
                                                                {'name': label_seq_id_2_name, 'type': 'int', 'value': cif_seq_id}
                                                                ])

                else:
                    prot_cis = []

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__isProtCis() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__isProtCis() ++ Error  - {str(e)}\n")

                return False

            return len(prot_cis) > 0

        return False

    def __testTautomerOfHistidinePerModel(self):
        """ Check tautomeric state of a given histidine per model. (DAOTHER-9252)
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        file_name = cif_input_source_dic['file_name']
        cif_polymer_sequence = cif_input_source_dic['polymer_sequence']

        model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

        for ps in cif_polymer_sequence:
            chain_id = ps['chain_id']

            auth_chain_id = chain_id
            if 'auth_chain_id' in ps:
                auth_chain_id = ps['auth_chain_id']

            if len(cif_polymer_sequence) >= LEN_MAJOR_ASYM_ID:
                if auth_chain_id not in LARGE_ASYM_ID:
                    continue

            for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                if not isLikeHis(comp_id, self.__ccU):
                    continue

                if comp_id == 'HIS':
                    hd1_name = 'HD1'
                    he2_name = 'HE2'
                else:
                    _hd1_name = self.__ccU.getBondedAtoms(comp_id, 'ND1', onlyProton=True)
                    _he2_name = self.__ccU.getBondedAtoms(comp_id, 'NE2', onlyProton=True)
                    if len(_hd1_name) != 1 or len(_he2_name) != 1:
                        continue
                    hd1_name = _hd1_name[0]
                    he2_name = _he2_name[0]

                try:
                    auth_seq_id = ps['auth_seq_id'][ps['seq_id'].index(seq_id)]
                except (KeyError, IndexError, ValueError):
                    auth_seq_id = seq_id

                try:

                    protons = self.__cR.getDictListWithFilter('atom_site',
                                                              [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                               {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'},
                                                               ],
                                                              [{'name': 'label_asym_id', 'type': 'str', 'value': chain_id},
                                                               {'name': 'label_seq_id', 'type': 'int', 'value': seq_id},
                                                               {'name': 'label_comp_id', 'type': 'str', 'value': comp_id},
                                                               {'name': 'type_symbol', 'type': 'str', 'value': 'H'},
                                                               {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                               ])

                except Exception as e:

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testTautomerOfHistidinePerModel() ++ Error  - " + str(e))
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__testTautomerOfHistidinePerModel() ++ Error  - {str(e)}\n")

                    return False

                if len(protons) > 0:

                    tautomer_per_model = {}

                    for model_id in self.__eff_model_ids:

                        _protons = [h for h in protons if h['model_id'] == model_id]

                        has_hd1 = False
                        has_he2 = False

                        for h in _protons:
                            if h['atom_id'] == hd1_name:
                                has_hd1 = True
                            elif h['atom_id'] == he2_name:
                                has_he2 = True

                        if has_hd1 and has_he2:
                            tautomer_per_model[model_id] = 'biprotonated'

                        elif has_hd1:
                            tautomer_per_model[model_id] = 'pi-tautomer'

                        elif has_he2:
                            tautomer_per_model[model_id] = 'tau-tautomer'

                        else:
                            tautomer_per_model[model_id] = 'unknown'

                    rep_tautomer = tautomer_per_model[self.__representative_model_id]

                    if any(tautomer != rep_tautomer for tautomer in tautomer_per_model.values()):
                        cif_seq_code = f"{chain_id}:{seq_id}:{comp_id}"
                        if chain_id != auth_chain_id or seq_id != auth_seq_id:
                            cif_seq_code += f" ({auth_chain_id}:{auth_seq_id}:{comp_id} in author sequence scheme)"

                        err = f'{cif_seq_code} has been instantiated with different tautomeric states across models, {tautomer_per_model}. '\
                            'Please re-upload the model file.'

                        self.report.error.appendDescription('coordinate_issue',
                                                            {'file_name': file_name, 'category': 'atom_site',
                                                             'description': err})
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testTautomerOfHistidinePerModel() ++ Error  - {err}\n")

        return True

    def __getTautomerOfHistidine(self, nmr_chain_id, nmr_seq_id):
        """ Return tautomeric state of a given histidine.
            @return: One of 'biprotonated', 'tau-tautomer', 'pi-tautomer', 'unknown'
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return 'unknown'

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return 'unknown'

        seq_key = (nmr_chain_id, nmr_seq_id)

        if seq_key in self.__coord_tautomer:
            return self.__coord_tautomer[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, ref_code, test_seq_id
                               in zip(result['ref_seq_id'], result['ref_code'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id and ref_code == 'H'), None)

            if cif_seq_id is None:
                self.__coord_tautomer[seq_key] = 'unknown'
                return 'unknown'

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                protons = self.__cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'}
                                                           ],
                                                          [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                           {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                           {'name': 'label_comp_id', 'type': 'str', 'value': 'HIS'},
                                                           {'name': 'type_symbol', 'type': 'str', 'value': 'H'},
                                                           {'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                                                           {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                           ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getTautomerOfHistidine() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getTautomerOfHistidine() ++ Error  - {str(e)}\n")

                return 'unknown'

            if len(protons) > 0:

                has_hd1 = False
                has_he2 = False

                for h in protons:
                    if h['atom_id'] == 'HD1':
                        has_hd1 = True
                    elif h['atom_id'] == 'HE2':
                        has_he2 = True

                if has_hd1 and has_he2:
                    self.__coord_tautomer[seq_key] = 'biprotonated'
                    return 'biprotonated'

                if has_hd1:
                    self.__coord_tautomer[seq_key] = 'pi-tautomer'
                    return 'pi-tautomer'

                if has_he2:
                    self.__coord_tautomer[seq_key] = 'tau-tautomer'
                    return 'tau-tautomer'

        self.__coord_tautomer[seq_key] = 'unknown'
        return 'unknown'

    def __getRotamerOfValine(self, nmr_chain_id, nmr_seq_id):
        """ Return rotameric state distribution of a given valine.
            @return: One of 'gauche+', 'trans', 'gauche-', 'unknown'
        """

        none = [{'name': 'chi1', 'unknown': 1.0}]

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'VAL')

        if seq_key in self.__coord_rotamer:
            return self.__coord_rotamer[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, ref_code, test_seq_id
                               in zip(result['ref_seq_id'], result['ref_code'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id and ref_code == 'V'), None)

            if cif_seq_id is None:
                self.__coord_rotamer[seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                         {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                         {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                         {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                         {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                         ],
                                                        [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                         {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                         {'name': 'label_comp_id', 'type': 'str', 'value': 'VAL'},
                                                         {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                         ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getRotamerOfValine() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getRotamerOfValine() ++ Error  - {str(e)}\n")

                return none

            model_ids = set(a['model_id'] for a in atoms)
            total_models = float(len(model_ids))

            rot1 = {'name': 'chi1', 'gauche-': 0.0, 'trans': 0.0, 'gauche+': 0.0, 'unknown': 0.0}

            for model_id in model_ids:
                _atoms = [a for a in atoms if a['model_id'] == model_id]

                try:
                    n = to_np_array(next(a for a in _atoms if a['atom_id'] == 'N'))
                    ca = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CA'))
                    cb = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CB'))
                    cg1 = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CG1'))

                    chi1 = dihedral_angle(n, ca, cb, cg1)

                    if 0.0 <= chi1 < 120.0:
                        rot1['gauche+'] += 1.0
                    elif -120.0 <= chi1 < 0.0:
                        rot1['gauche-'] += 1.0
                    else:
                        rot1['trans'] += 1.0
                except StopIteration:
                    rot1['unknown'] += 1.0

            if rot1['unknown'] == total_models:
                self.__coord_rotamer[seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']

            _rot1 = rot1.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            self.__coord_rotamer[seq_key] = [rot1]
            return [rot1]

        self.__coord_rotamer[seq_key] = none
        return none

    def __getRotamerOfLeucine(self, nmr_chain_id, nmr_seq_id):
        """ Return rotameric state distribution of a given leucine.
            @return: One of 'gauche+', 'trans', 'gauche-', 'unknown'
        """

        none = [{'name': 'chi1', 'unknown': 1.0}, {'name': 'chi2', 'unknown': 1.0}]

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'LEU')

        if seq_key in self.__coord_rotamer:
            return self.__coord_rotamer[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, ref_code, test_seq_id
                               in zip(result['ref_seq_id'], result['ref_code'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id and ref_code == 'L'), None)

            if cif_seq_id is None:
                self.__coord_rotamer[seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                         {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                         {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                         {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                         {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                         ],
                                                        [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                         {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                         {'name': 'label_comp_id', 'type': 'str', 'value': 'LEU'},
                                                         {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                         ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getRotamerOfLeucine() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getRotamerOfLeucine() ++ Error  - {str(e)}\n")

                return none

            model_ids = set(a['model_id'] for a in atoms)
            total_models = float(len(model_ids))

            rot1 = {'name': 'chi1', 'gauche-': 0.0, 'trans': 0.0, 'gauche+': 0.0, 'unknown': 0.0}
            rot2 = {'name': 'chi2', 'gauche-': 0.0, 'trans': 0.0, 'gauche+': 0.0, 'unknown': 0.0}

            for model_id in model_ids:
                _atoms = [a for a in atoms if a['model_id'] == model_id]

                try:
                    n = to_np_array(next(a for a in _atoms if a['atom_id'] == 'N'))
                    ca = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CA'))
                    cb = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CB'))
                    cg = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CG'))
                    cd1 = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CD1'))

                    chi1 = dihedral_angle(n, ca, cb, cg)

                    if 0.0 <= chi1 < 120.0:
                        rot1['gauche+'] += 1.0
                    elif -120.0 <= chi1 < 0.0:
                        rot1['gauche-'] += 1.0
                    else:
                        rot1['trans'] += 1.0

                    chi2 = dihedral_angle(ca, cb, cg, cd1)

                    if 0.0 <= chi2 < 120.0:
                        rot2['gauche+'] += 1.0
                    elif -120.0 <= chi2 < 0.0:
                        rot2['gauche-'] += 1.0
                    else:
                        rot2['trans'] += 1.0

                except StopIteration:
                    rot1['unknown'] += 1.0
                    rot2['unknown'] += 1.0

            if rot1['unknown'] == total_models:
                self.__coord_rotamer[seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__coord_rotamer[seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__coord_rotamer[seq_key] = none
        return none

    def __getRotamerOfIsoleucine(self, nmr_chain_id, nmr_seq_id):
        """ Return rotameric state distribution of a given isoleucine.
            @return: One of 'gauche+', 'trans', 'gauche-', 'unknown'
        """

        none = [{'name': 'chi1', 'unknown': 1.0}, {'name': 'chi2', 'unknown': 1.0}]

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'ILE')

        if seq_key in self.__coord_rotamer:
            return self.__coord_rotamer[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, ref_code, test_seq_id
                               in zip(result['ref_seq_id'], result['ref_code'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id and ref_code == 'I'), None)

            if cif_seq_id is None:
                self.__coord_rotamer[seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                         {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                         {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                         {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                         {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                         ],
                                                        [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                         {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                         {'name': 'label_comp_id', 'type': 'str', 'value': 'ILE'},
                                                         {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                         ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getRotamerOfIsoleucine() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getRotamerOfIsoleucine() ++ Error  - {str(e)}\n")

                return none

            model_ids = set(a['model_id'] for a in atoms)
            total_models = float(len(model_ids))

            rot1 = {'name': 'chi1', 'gauche-': 0.0, 'trans': 0.0, 'gauche+': 0.0, 'unknown': 0.0}
            rot2 = {'name': 'chi2', 'gauche-': 0.0, 'trans': 0.0, 'gauche+': 0.0, 'unknown': 0.0}

            for model_id in model_ids:
                _atoms = [a for a in atoms if a['model_id'] == model_id]

                try:
                    n = to_np_array(next(a for a in _atoms if a['atom_id'] == 'N'))
                    ca = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CA'))
                    cb = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CB'))
                    cg1 = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CG1'))
                    cd1 = to_np_array(next(a for a in _atoms if a['atom_id'] == 'CD1'))

                    chi1 = dihedral_angle(n, ca, cb, cg1)

                    if 0.0 <= chi1 < 120.0:
                        rot1['gauche+'] += 1.0
                    elif -120.0 <= chi1 < 0.0:
                        rot1['gauche-'] += 1.0
                    else:
                        rot1['trans'] += 1.0

                    chi2 = dihedral_angle(ca, cb, cg1, cd1)

                    if 0.0 <= chi2 < 120.0:
                        rot2['gauche+'] += 1.0
                    elif -120.0 <= chi2 < 0.0:
                        rot2['gauche-'] += 1.0
                    else:
                        rot2['trans'] += 1.0

                except StopIteration:
                    rot1['unknown'] += 1.0
                    rot2['unknown'] += 1.0

            if rot1['unknown'] == total_models:
                self.__coord_rotamer[seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__coord_rotamer[seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__coord_rotamer[seq_key] = none
        return none

    def __extractCoordDisulfideBond(self):
        """ Extract disulfide bond of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]

        chain_assign_dic = self.report.chain_assignment.get()

        if 'model_poly_seq_vs_nmr_poly_seq' not in chain_assign_dic:

            err = "Chain assignment does not exist, __assignCoordPolymerSequence() should be invoked."

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordDisulfideBond() ++ Error  - " + err)
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordDisulfideBond() ++ Error  - {err}\n")

            return False

        if not has_key_value(chain_assign_dic, 'model_poly_seq_vs_nmr_poly_seq'):
            return False

        try:

            if self.__cR.hasCategory('struct_conn'):
                struct_conn = self.__cR.getDictListWithFilter('struct_conn',
                                                              [{'name': 'conn_type_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_asym_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_seq_id', 'type': 'int'},
                                                               {'name': 'ptnr1_label_comp_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_atom_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_asym_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_seq_id', 'type': 'int'},
                                                               {'name': 'ptnr2_label_comp_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_atom_id', 'type': 'str'},
                                                               {'name': 'pdbx_dist_value', 'type': 'float'}
                                                               ])

            else:
                struct_conn = []

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordDisulfideBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordDisulfideBond() ++ Error  - {str(e)}\n")

            return False

        if len(struct_conn) > 0:

            asm = []

            for sc in struct_conn:

                if sc['conn_type_id'] != 'disulf':
                    continue

                disulf = {}
                disulf['chain_id_1'] = sc['ptnr1_label_asym_id']
                disulf['seq_id_1'] = sc['ptnr1_label_seq_id']
                disulf['comp_id_1'] = sc['ptnr1_label_comp_id']
                disulf['atom_id_1'] = sc['ptnr1_label_atom_id']
                disulf['chain_id_2'] = sc['ptnr2_label_asym_id']
                disulf['seq_id_2'] = sc['ptnr2_label_seq_id']
                disulf['comp_id_2'] = sc['ptnr2_label_comp_id']
                disulf['atom_id_2'] = sc['ptnr2_label_atom_id']
                disulf['distance_value'] = sc['pdbx_dist_value']
                # DAOTHER-7475
                disulf['warning_description_1'] = None
                disulf['warning_description_2'] = None
                asm.append(disulf)

            if len(asm) > 0:
                cif_input_source.setItemValue('disulfide_bond', asm)

                self.report.setDisulfideBond(True)

                return self.__mapCoordDisulfideBond2Nmr(asm)

        return True

    def __mapCoordDisulfideBond2Nmr(self, bond_list):
        """ Map disulfide bond of coordinate file to NMR data.
        """

        is_done = False

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is None:
                continue

            seq_align_dic = self.report.sequence_alignment.get()

            content_subtype = 'chem_shift'

            if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                continue

            if not has_key_value(seq_align_dic, 'model_poly_seq_vs_nmr_poly_seq'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            asm = []

            for bond in bond_list:

                cif_chain_id_1 = bond['chain_id_1']
                cif_seq_id_1 = bond['seq_id_1']
                cif_chain_id_2 = bond['chain_id_2']
                cif_seq_id_2 = bond['seq_id_2']

                s1 = self.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_1)

                if s1 is None:
                    continue

                nmr_chain_id_1 = s1['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == cif_chain_id_1 and seq_align['test_chain_id'] == nmr_chain_id_1), None)

                if result is None:
                    continue

                nmr_seq_id_1 = next((test_seq_id for ref_seq_id, test_seq_id
                                     in zip(result['ref_seq_id'], result['test_seq_id'])
                                     if ref_seq_id == cif_seq_id_1), None)

                if nmr_seq_id_1 is None:
                    continue

                nmr_comp_id_1 = next((comp_id for seq_id, comp_id
                                      in zip(s1['seq_id'], s1['comp_id'])
                                      if seq_id == nmr_seq_id_1), None)

                if nmr_comp_id_1 is None:
                    continue

                s2 = self.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_2)

                if s2 is None:
                    continue

                nmr_chain_id_2 = s2['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == cif_chain_id_2 and seq_align['test_chain_id'] == nmr_chain_id_2), None)

                if result is None:
                    continue

                nmr_seq_id_2 = next((test_seq_id for ref_seq_id, test_seq_id
                                     in zip(result['ref_seq_id'], result['test_seq_id'])
                                     if ref_seq_id == cif_seq_id_2), None)

                if nmr_seq_id_2 is None:
                    continue

                nmr_comp_id_2 = next((comp_id for seq_id, comp_id
                                      in zip(s2['seq_id'], s2['comp_id'])
                                      if seq_id == nmr_seq_id_2), None)

                if nmr_comp_id_2 is None:
                    continue

                disulf = {}
                disulf['chain_id_1'] = nmr_chain_id_1
                disulf['seq_id_1'] = nmr_seq_id_1
                disulf['comp_id_1'] = nmr_comp_id_1
                disulf['atom_id_1'] = bond['atom_id_1']
                disulf['chain_id_2'] = nmr_chain_id_2
                disulf['seq_id_2'] = nmr_seq_id_2
                disulf['comp_id_2'] = nmr_comp_id_2
                disulf['atom_id_2'] = bond['atom_id_2']
                disulf['distance_value'] = bond['distance_value']
                disulf['warning_description_1'] = None
                disulf['warning_description_2'] = None

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordDisulfideBond2Nmr__(file_name, file_type, content_subtype,
                                                           sf, sf_framecode, lp_category,
                                                           nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                           nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordDisulfideBond2Nmr__(file_name, file_type, content_subtype,
                                                           sf, sf_framecode, lp_category,
                                                           nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                           nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)
                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                            self.__mapCoordDisulfideBond2Nmr__(file_name, file_type, content_subtype,
                                                               sf, sf_framecode, lp_category,
                                                               nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                               nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                        if ca_chem_shift_1 is None or cb_chem_shift_1 is None or ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                            pass
                        else:
                            break

                disulf['ca_chem_shift_1'] = ca_chem_shift_1
                disulf['cb_chem_shift_1'] = cb_chem_shift_1
                disulf['ca_chem_shift_2'] = ca_chem_shift_2
                disulf['cb_chem_shift_2'] = cb_chem_shift_2

                if cb_chem_shift_1 is not None:
                    if cb_chem_shift_1 < 32.0:
                        disulf['redox_state_pred_1'] = 'reduced'
                    elif cb_chem_shift_1 > 35.0:
                        disulf['redox_state_pred_1'] = 'oxidized'
                    elif cb_chem_shift_2 is not None:
                        if cb_chem_shift_2 < 32.0:
                            disulf['redox_state_pred_1'] = 'reduced'
                        elif cb_chem_shift_2 > 35.0:
                            disulf['redox_state_pred_1'] = 'oxidized'
                        else:
                            disulf['redox_state_pred_1'] = 'ambiguous'
                    else:
                        disulf['redox_state_pred_1'] = 'ambiguous'
                else:
                    disulf['redox_state_pred_1'] = 'unknown'

                if cb_chem_shift_2 is not None:
                    if cb_chem_shift_2 < 32.0:
                        disulf['redox_state_pred_2'] = 'reduced'
                    elif cb_chem_shift_2 > 35.0:
                        disulf['redox_state_pred_2'] = 'oxidized'
                    elif cb_chem_shift_1 is not None:
                        if cb_chem_shift_1 < 32.0:
                            disulf['redox_state_pred_2'] = 'reduced'
                        elif cb_chem_shift_1 > 35.0:
                            disulf['redox_state_pred_2'] = 'oxidized'
                        else:
                            disulf['redox_state_pred_2'] = 'ambiguous'
                    else:
                        disulf['redox_state_pred_2'] = 'ambiguous'
                else:
                    disulf['redox_state_pred_2'] = 'unknown'

                if disulf['redox_state_pred_1'] == 'ambiguous' and ((ca_chem_shift_1 is not None) or (cb_chem_shift_1 is not None)):
                    oxi, red = predict_redox_state_of_cystein(ca_chem_shift_1, cb_chem_shift_1)
                    disulf['redox_state_pred_1'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                if disulf['redox_state_pred_2'] == 'ambiguous' and ((ca_chem_shift_2 is not None) or (cb_chem_shift_2 is not None)):
                    oxi, red = predict_redox_state_of_cystein(ca_chem_shift_2, cb_chem_shift_2)
                    disulf['redox_state_pred_2'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                if disulf['redox_state_pred_1'] != 'oxidized' and disulf['redox_state_pred_1'] != 'unknown':

                    warn = "Disulfide bond "\
                        f"({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1} - {nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}) can not be verified with "\
                        f"the assigned chemical shift values ({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1}:CA {ca_chem_shift_1} ppm, "\
                        f"{nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1}:CB {cb_chem_shift_1} ppm, redox_state_pred {disulf['redox_state_pred_1']})."

                    item = 'anomalous_chemical_shift' if disulf['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                    self.report.warning.appendDescription(item,
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mapCoordDisulfideBond2Nmr() ++ Warning  - {warn}\n")

                    disulf['warning_description_1'] = item + ': ' + warn

                if disulf['redox_state_pred_2'] != 'oxidized' and disulf['redox_state_pred_2'] != 'unknown':

                    warn = "Disulfide bond "\
                        f"({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1} - {nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}) can not be verified with "\
                        f"the assigned chemical shift values ({nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}:CA {ca_chem_shift_2} ppm, "\
                        f"{nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}:CB {cb_chem_shift_2} ppm, redox_state_pred {disulf['redox_state_pred_2']})."

                    item = 'anomalous_chemical_shift' if disulf['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                    self.report.warning.appendDescription(item,
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mapCoordDisulfideBond2Nmr() ++ Warning  - {warn}\n")

                    disulf['warning_description_2'] = item + ': ' + warn

                asm.append(disulf)

            if len(asm) > 0:
                input_source.setItemValue('disulfide_bond', asm)
                is_done = True

        return is_done

    def __mapCoordDisulfideBond2Nmr__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category,
                                      nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1, nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2):
        """ Map disulfide bond of coordinate file to NMR data.
        """

        ca_chem_shift_1 = None
        cb_chem_shift_1 = None
        ca_chem_shift_2 = None
        cb_chem_shift_2 = None

        key_items = self.key_items[file_type][content_subtype]
        data_items = self.data_items[file_type][content_subtype]

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        value_name = item_names['value']

        if not self.report.error.exists(file_name, sf_framecode):

            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                     excl_missing_data=self.__excl_missing_data)[0]

                    self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                except Exception:
                    pass

            if lp_data is not None:

                for row in lp_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    atom_id = row[atom_id_name]

                    if chain_id == nmr_chain_id_1 and seq_id == nmr_seq_id_1 and comp_id == nmr_comp_id_1:
                        if atom_id == 'CA' and ca_chem_shift_1 is None:
                            ca_chem_shift_1 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = row[value_name]

                    elif chain_id == nmr_chain_id_2 and seq_id == nmr_seq_id_2 and comp_id == nmr_comp_id_2:
                        if atom_id == 'CA' and ca_chem_shift_2 is None:
                            ca_chem_shift_2 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = row[value_name]

                    if ca_chem_shift_1 is None or cb_chem_shift_1 is None or ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                        pass
                    else:
                        break

        return ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2

    def __extractCoordOtherBond(self):
        """ Extract other bond (neither disulfide nor covalent bond) of coordinate file.
        """

        src_id = self.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.report.input_sources[src_id]

        chain_assign_dic = self.report.chain_assignment.get()

        if 'model_poly_seq_vs_nmr_poly_seq' not in chain_assign_dic:

            err = "Chain assignment does not exist, __assignCoordPolymerSequence() should be invoked."

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordOtherBond() ++ Error  - " + err)
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordOtherBond() ++ Error  - {err}\n")

            return False

        if not has_key_value(chain_assign_dic, 'model_poly_seq_vs_nmr_poly_seq'):
            return False

        try:

            if self.__cR.hasCategory('struct_conn'):
                struct_conn = self.__cR.getDictListWithFilter('struct_conn',
                                                              [{'name': 'conn_type_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_asym_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_seq_id', 'type': 'int'},
                                                               {'name': 'ptnr1_label_comp_id', 'type': 'str'},
                                                               {'name': 'ptnr1_label_atom_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_asym_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_seq_id', 'type': 'int'},
                                                               {'name': 'ptnr2_label_comp_id', 'type': 'str'},
                                                               {'name': 'ptnr2_label_atom_id', 'type': 'str'},
                                                               {'name': 'pdbx_dist_value', 'type': 'float'}
                                                               ])

            else:
                struct_conn = []

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordOtherBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordOtherBond() ++ Error  - {str(e)}\n")

            return False

        if len(struct_conn) > 0:

            asm = []

            for sc in struct_conn:

                if sc['conn_type_id'] in ('disulf', 'hydrog') or sc['conn_type_id'].startswith('covale'):
                    continue

                other = {}
                other['chain_id_1'] = sc['ptnr1_label_asym_id']
                other['seq_id_1'] = sc['ptnr1_label_seq_id']
                other['comp_id_1'] = sc['ptnr1_label_comp_id']
                other['atom_id_1'] = sc['ptnr1_label_atom_id']
                other['chain_id_2'] = sc['ptnr2_label_asym_id']
                other['seq_id_2'] = sc['ptnr2_label_seq_id']
                other['comp_id_2'] = sc['ptnr2_label_comp_id']
                other['atom_id_2'] = sc['ptnr2_label_atom_id']
                other['distance_value'] = sc['pdbx_dist_value']
                # DAOTHER-7475
                other['warning_description_1'] = None
                other['warning_description_2'] = None
                asm.append(other)

            if len(asm) > 0:
                cif_input_source.setItemValue('other_bond', asm)

                self.report.setOtherBond(True)

                return self.__mapCoordOtherBond2Nmr(asm)

        return True

    def __mapCoordOtherBond2Nmr(self, bond_list):
        """ Map other bond (neither disulfide nor covalent bond) of coordinate file to NMR data.
        """

        is_done = False

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            polymer_sequence = input_source_dic['polymer_sequence']

            if polymer_sequence is None:
                continue

            seq_align_dic = self.report.sequence_alignment.get()

            content_subtype = 'chem_shift'

            if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                continue

            if not has_key_value(seq_align_dic, 'model_poly_seq_vs_nmr_poly_seq'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            asm = []

            for bond in bond_list:

                cif_chain_id_1 = bond['chain_id_1']
                cif_seq_id_1 = bond['seq_id_1']
                cif_chain_id_2 = bond['chain_id_2']
                cif_seq_id_2 = bond['seq_id_2']

                s1 = self.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_1)

                if s1 is None:
                    continue

                nmr_chain_id_1 = s1['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == cif_chain_id_1 and seq_align['test_chain_id'] == nmr_chain_id_1), None)

                if result is None:
                    continue

                nmr_seq_id_1 = next((test_seq_id for ref_seq_id, test_seq_id
                                     in zip(result['ref_seq_id'], result['test_seq_id'])
                                     if ref_seq_id == cif_seq_id_1), None)

                if nmr_seq_id_1 is None:
                    continue

                nmr_comp_id_1 = next((comp_id for seq_id, comp_id
                                      in zip(s1['seq_id'], s1['comp_id'])
                                      if seq_id == nmr_seq_id_1), None)

                if nmr_comp_id_1 is None:
                    continue

                s2 = self.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_2)

                if s2 is None:
                    continue

                nmr_chain_id_2 = s2['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == cif_chain_id_2 and seq_align['test_chain_id'] == nmr_chain_id_2), None)

                if result is None:
                    continue

                nmr_seq_id_2 = next((test_seq_id for ref_seq_id, test_seq_id
                                     in zip(result['ref_seq_id'], result['test_seq_id'])
                                     if ref_seq_id == cif_seq_id_2), None)

                if nmr_seq_id_2 is None:
                    continue

                nmr_comp_id_2 = next((comp_id for seq_id, comp_id
                                      in zip(s2['seq_id'], s2['comp_id'])
                                      if seq_id == nmr_seq_id_2), None)

                if nmr_comp_id_2 is None:
                    continue

                other = {}
                other['chain_id_1'] = nmr_chain_id_1
                other['seq_id_1'] = nmr_seq_id_1
                other['comp_id_1'] = nmr_comp_id_1
                other['atom_id_1'] = bond['atom_id_1']
                other['chain_id_2'] = nmr_chain_id_2
                other['seq_id_2'] = nmr_seq_id_2
                other['comp_id_2'] = nmr_comp_id_2
                other['atom_id_2'] = bond['atom_id_2']
                other['distance_value'] = bond['distance_value']
                other['warning_description_1'] = None
                other['warning_description_2'] = None

                if self.__star_data_type[fileListId] == 'Loop':
                    sf = self.__star_data[fileListId]
                    sf_framecode = ''

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordOtherBond2Nmr__(file_name, file_type, content_subtype,
                                                       sf, sf_framecode, lp_category,
                                                       nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                       nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                elif self.__star_data_type[fileListId] == 'Saveframe':
                    sf = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordOtherBond2Nmr__(file_name, file_type, content_subtype,
                                                       sf, sf_framecode, lp_category,
                                                       nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                       nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                else:

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                            self.__mapCoordOtherBond2Nmr__(file_name, file_type, content_subtype,
                                                           sf, sf_framecode, lp_category,
                                                           nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                           nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                        if ca_chem_shift_1 is None or cb_chem_shift_1 is None or ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                            pass
                        else:
                            break

                other['ca_chem_shift_1'] = ca_chem_shift_1
                other['cb_chem_shift_1'] = cb_chem_shift_1
                other['ca_chem_shift_2'] = ca_chem_shift_2
                other['cb_chem_shift_2'] = cb_chem_shift_2

                if cb_chem_shift_1 is not None:
                    if cb_chem_shift_1 < 32.0:
                        other['redox_state_pred_1'] = 'reduced'
                    elif cb_chem_shift_1 > 35.0:
                        other['redox_state_pred_1'] = 'oxidized'
                    elif cb_chem_shift_2 is not None:
                        if cb_chem_shift_2 < 32.0:
                            other['redox_state_pred_1'] = 'reduced'
                        elif cb_chem_shift_2 > 35.0:
                            other['redox_state_pred_1'] = 'oxidized'
                        else:
                            other['redox_state_pred_1'] = 'ambiguous'
                    else:
                        other['redox_state_pred_1'] = 'ambiguous'
                else:
                    other['redox_state_pred_1'] = 'unknown'

                if cb_chem_shift_2 is not None:
                    if cb_chem_shift_2 < 32.0:
                        other['redox_state_pred_2'] = 'reduced'
                    elif cb_chem_shift_2 > 35.0:
                        other['redox_state_pred_2'] = 'oxidized'
                    elif cb_chem_shift_1 is not None:
                        if cb_chem_shift_1 < 32.0:
                            other['redox_state_pred_2'] = 'reduced'
                        elif cb_chem_shift_1 > 35.0:
                            other['redox_state_pred_2'] = 'oxidized'
                        else:
                            other['redox_state_pred_2'] = 'ambiguous'
                    else:
                        other['redox_state_pred_2'] = 'ambiguous'
                else:
                    other['redox_state_pred_2'] = 'unknown'

                if other['redox_state_pred_1'] == 'ambiguous' and ((ca_chem_shift_1 is not None) or (cb_chem_shift_1 is not None)):
                    oxi, red = predict_redox_state_of_cystein(ca_chem_shift_1, cb_chem_shift_1)
                    other['redox_state_pred_1'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                if other['redox_state_pred_2'] == 'ambiguous' and ((ca_chem_shift_2 is not None) or (cb_chem_shift_2 is not None)):
                    oxi, red = predict_redox_state_of_cystein(ca_chem_shift_2, cb_chem_shift_2)
                    other['redox_state_pred_2'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                if other['redox_state_pred_1'] != 'oxidized' and other['redox_state_pred_1'] != 'unknown':

                    warn = "Other bond "\
                        f"({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1} - {nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}) can not be verified with "\
                        f"the assigned chemical shift values ({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1}:CA {ca_chem_shift_1} ppm, "\
                        f"{nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1}:CB {cb_chem_shift_1} ppm, redox_state_pred {other['redox_state_pred_1']})."

                    item = 'anomalous_chemical_shift' if other['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                    self.report.warning.appendDescription(item,
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mapCoordOtherBond2Nmr() ++ Warning  - {warn}\n")

                    other['warning_description_1'] = item + ': ' + warn

                if other['redox_state_pred_2'] != 'oxidized' and other['redox_state_pred_2'] != 'unknown':

                    warn = "Other bond "\
                        f"({nmr_chain_id_1}:{nmr_seq_id_1}:{nmr_comp_id_1} - {nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}) can not be verified with "\
                        f"the assigned chemical shift values ({nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}:CA {ca_chem_shift_2} ppm, "\
                        f"{nmr_chain_id_2}:{nmr_seq_id_2}:{nmr_comp_id_2}:CB {cb_chem_shift_2} ppm, redox_state_pred {other['redox_state_pred_2']})."

                    item = 'anomalous_chemical_shift' if other['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                    self.report.warning.appendDescription(item,
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': warn})
                    self.report.setWarning()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mapCoordOtherBond2Nmr() ++ Warning  - {warn}\n")

                    other['warning_description_2'] = item + ': ' + warn

                asm.append(other)

            if len(asm) > 0:
                input_source.setItemValue('other_bond', asm)
                is_done = True

        return is_done

    def __mapCoordOtherBond2Nmr__(self, file_name, file_type, content_subtype, sf, sf_framecode, lp_category,
                                  nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1, nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2):
        """ Map other bond (neither disulfide nor covalent bond) of coordinate file to NMR data.
        """

        ca_chem_shift_1 = None
        cb_chem_shift_1 = None
        ca_chem_shift_2 = None
        cb_chem_shift_2 = None

        key_items = self.key_items[file_type][content_subtype]
        data_items = self.data_items[file_type][content_subtype]

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        value_name = item_names['value']

        if not self.report.error.exists(file_name, sf_framecode):

            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                     excl_missing_data=self.__excl_missing_data)[0]

                    self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                except Exception:
                    pass

            if lp_data is not None:

                for row in lp_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    atom_id = row[atom_id_name]

                    if chain_id == nmr_chain_id_1 and seq_id == nmr_seq_id_1 and comp_id == nmr_comp_id_1:
                        if atom_id == 'CA' and ca_chem_shift_1 is None:
                            ca_chem_shift_1 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = row[value_name]

                    elif chain_id == nmr_chain_id_2 and seq_id == nmr_seq_id_2 and comp_id == nmr_comp_id_2:
                        if atom_id == 'CA' and ca_chem_shift_2 is None:
                            ca_chem_shift_2 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = row[value_name]

                    if ca_chem_shift_1 is None or cb_chem_shift_1 is None or ca_chem_shift_2 is None or cb_chem_shift_2 is None:
                        pass
                    else:
                        break

        return ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2

    def __getNearestAromaticRing(self, nmr_chain_id, nmr_seq_id, nmr_atom_id, cutoff):
        """ Return the nearest aromatic ring around a given atom.
            @return: the nearest aromatic ring
        """

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return None

        seq_key = (nmr_chain_id, nmr_seq_id, nmr_atom_id)

        if seq_key in self.__coord_near_ring:
            return self.__coord_near_ring[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            if cif_seq_id is None:
                self.__coord_near_ring[seq_key] = None
                return None

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                _origin = self.__cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                           {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                           {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'}
                                                           ],
                                                          [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                           {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                           {'name': 'label_atom_id', 'type': 'str', 'value': nmr_atom_id},
                                                           {'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                                                           {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                           ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestAromaticRing() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestAromaticRing() ++ Error  - {str(e)}\n")

                return None

            if len(_origin) != 1:
                self.__coord_near_ring[seq_key] = None
                return None

            o = to_np_array(_origin[0])

            try:

                _neighbor = self.__cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                             {'name': 'label_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                             {'name': 'label_comp_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                             {'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': 'type_symbol', 'type': 'str'}
                                                             ],
                                                            [{'name': 'Cartn_x', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[0] - cutoff), 'max_exclusive': (o[0] + cutoff)}},
                                                             {'name': 'Cartn_y', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[1] - cutoff), 'max_exclusive': (o[1] + cutoff)}},
                                                             {'name': 'Cartn_z', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[2] - cutoff), 'max_exclusive': (o[2] + cutoff)}},
                                                             {'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                                                             {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                             ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestAromaticRing() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestAromaticRing() ++ Error  - {str(e)}\n")

                return None

            if len(_neighbor) == 0:
                self.__coord_near_ring[seq_key] = None
                return None

            neighbor = [n for n in _neighbor
                        if n['seq_id'] != cif_seq_id
                        and n['type_symbol'] not in protonBeginCode
                        and distance(to_np_array(n), o) < cutoff
                        and n['atom_id'] in self.__csStat.getAromaticAtoms(n['comp_id'])]

            if len(neighbor) == 0:
                self.__coord_near_ring[seq_key] = None
                return None

            if not has_key_value(seq_align_dic, 'model_poly_seq_vs_nmr_poly_seq'):
                self.__coord_near_ring[seq_key] = None
                return None

            atom_list = []

            for n in neighbor:

                _cif_chain_id = n['chain_id']

                _s = self.report.getNmrPolymerSequenceWithModelChainId(_cif_chain_id)

                if _s is None:
                    continue

                _nmr_chain_id = _s['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == _cif_chain_id and seq_align['test_chain_id'] == _nmr_chain_id), None)

                if result is not None:

                    _nmr_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                        in zip(result['ref_seq_id'], result['test_seq_id'])
                                        if ref_seq_id == n['seq_id']), None)

                    atom_list.append({'chain_id': _nmr_chain_id,
                                      'seq_id': _nmr_seq_id,
                                      'cif_chain_id': _cif_chain_id,
                                      'cif_seq_id': n['seq_id'],
                                      'comp_id': n['comp_id'],
                                      'atom_id': n['atom_id'],
                                      'distance': distance(to_np_array(n), o)})

            if len(atom_list) == 0:
                return None

            na = sorted(atom_list, key=itemgetter('distance'))[0]

            na_atom_id = na['atom_id']

            if not self.__ccU.updateChemCompDict(na['comp_id']):
                self.__coord_near_ring[seq_key] = None
                return None

            # matches with comp_id in CCD

            half_ring_traces = []

            for b1 in self.__ccU.lastBonds:

                if b1[self.__ccU.ccbAromaticFlag] != 'Y':
                    continue

                if b1[self.__ccU.ccbAtomId1] == na_atom_id and b1[self.__ccU.ccbAtomId2][0] not in protonBeginCode:
                    na_ = b1[self.__ccU.ccbAtomId2]

                elif b1[self.__ccU.ccbAtomId2] == na_atom_id and b1[self.__ccU.ccbAtomId1][0] not in protonBeginCode:
                    na_ = b1[self.__ccU.ccbAtomId1]

                else:
                    continue

                for b2 in self.__ccU.lastBonds:

                    if b2[self.__ccU.ccbAromaticFlag] != 'Y':
                        continue

                    if b2[self.__ccU.ccbAtomId1] == na_ and b2[self.__ccU.ccbAtomId2][0] not in protonBeginCode and b2[self.__ccU.ccbAtomId2] != na_atom_id:
                        na__ = b2[self.__ccU.ccbAtomId2]

                    elif b2[self.__ccU.ccbAtomId2] == na_ and b2[self.__ccU.ccbAtomId1][0] not in protonBeginCode and b2[self.__ccU.ccbAtomId1] != na_atom_id:
                        na__ = b2[self.__ccU.ccbAtomId1]

                    else:
                        continue

                    for b3 in self.__ccU.lastBonds:

                        if b3[self.__ccU.ccbAromaticFlag] != 'Y':
                            continue

                        if b3[self.__ccU.ccbAtomId1] == na__ and b3[self.__ccU.ccbAtomId2][0] not in protonBeginCode and b3[self.__ccU.ccbAtomId2] != na_:
                            na___ = b3[self.__ccU.ccbAtomId2]

                        elif b3[self.__ccU.ccbAtomId2] == na__ and b3[self.__ccU.ccbAtomId1][0] not in protonBeginCode and b3[self.__ccU.ccbAtomId1] != na_:
                            na___ = b3[self.__ccU.ccbAtomId1]

                        else:
                            continue

                        half_ring_traces.append(na_atom_id + ':' + na_ + ':' + na__ + ':' + na___)

            len_half_ring_traces = len(half_ring_traces)

            if len_half_ring_traces < 2:
                self.__coord_near_ring[seq_key] = None
                return None

            ring_traces = []

            for i in range(len_half_ring_traces - 1):

                half_ring_trace_1 = half_ring_traces[i].split(':')

                for j in range(i + 1, len_half_ring_traces):

                    half_ring_trace_2 = half_ring_traces[j].split(':')

                    # hexagonal ring
                    if half_ring_trace_1[3] == half_ring_trace_2[3]:
                        ring_traces.append(half_ring_traces[i] + ':' + half_ring_trace_2[2] + ':' + half_ring_trace_2[1])

                    # pentagonal ring
                    elif half_ring_trace_1[3] == half_ring_trace_2[2] and half_ring_trace_1[2] == half_ring_trace_2[3]:
                        ring_traces.append(half_ring_traces[i] + ':' + half_ring_trace_2[1])

            if len(ring_traces) == 0:
                self.__coord_near_ring[seq_key] = None
                return None

            ring_atoms = None
            ring_trace_score = 0

            for ring_trace in ring_traces:

                _ring_atoms = ring_trace.split(':')

                score = 0

                for a in atom_list:

                    if a['chain_id'] != na['chain_id'] or a['seq_id'] != na['seq_id'] or a['comp_id'] != na['comp_id']:
                        continue

                    if a['atom_id'] in _ring_atoms:
                        score += 1

                if score > ring_trace_score:
                    ring_atoms = _ring_atoms
                    ring_trace_score = score

            try:

                _na = self.__cR.getDictListWithFilter('atom_site',
                                                      [{'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                       {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                       {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                       {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                       {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                       ],
                                                      [{'name': 'label_asym_id', 'type': 'str', 'value': na['cif_chain_id']},
                                                       {'name': 'label_seq_id', 'type': 'int', 'value': na['cif_seq_id']},
                                                       {'name': 'label_comp_id', 'type': 'str', 'value': na['comp_id']},
                                                       {'name': 'label_atom_id', 'type': 'enum', 'enum': ring_atoms},
                                                       {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                       ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestAromaticRing() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestAromaticRing() ++ Error  - {str(e)}\n")

                return None

            if len(_na) == 0:
                self.__coord_near_ring[seq_key] = None
                return None

            model_ids = set(a['model_id'] for a in _na)

            len_model_ids = 0

            dist = 0.0
            ring_dist = 0.0
            ring_angle = 0.0

            for model_id in model_ids:

                rc = numpy.array([0.0] * 3)

                total = 0

                for a in _na:

                    if a['model_id'] == model_id:

                        _a = to_np_array(a)

                        if a['atom_id'] == na_atom_id:
                            dist += distance(_a, o)

                        rc = numpy.add(rc, _a)

                        total += 1

                if total == len(ring_atoms):

                    rc = rc / total

                    ring_dist += distance(rc, o)

                    na_ = next(to_np_array(na_) for na_ in _na if na_['atom_id'] == ring_atoms[0])
                    na__ = next(to_np_array(na__) for na__ in _na if na__['atom_id'] == ring_atoms[1])
                    na___ = next(to_np_array(na___) for na___ in _na if na___['atom_id'] == ring_atoms[-1])

                    ring_vector = numpy.cross(na__ - na_, na___ - na_)

                    ring_angle += math.acos(abs(numpy.dot(to_unit_vector(o - rc), to_unit_vector(ring_vector))))

                    len_model_ids += 1

            if len_model_ids == 0:  # DAOTHER-8840
                return None

            na['ring_atoms'] = ring_atoms
            na['distance'] = float(f"{dist / len_model_ids:.1f}")
            na['ring_distance'] = float(f"{ring_dist / len_model_ids:.1f}")
            na['ring_angle'] = float(f"{numpy.degrees(ring_angle / len_model_ids):.1f}")

            self.__coord_near_ring[seq_key] = na
            return na

        self.__coord_near_ring[seq_key] = None
        return None

    def __getNearestParaFerroMagneticAtom(self, nmr_chain_id, nmr_seq_id, nmr_atom_id, cutoff):
        """ Return the nearest paramagnetic/ferromagnetic atom around a given atom.
            @return: the nearest paramagnetic/ferromagnetic atom
        """

        if self.report.isDiamagnetic():
            return None

        cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return None

        seq_key = (nmr_chain_id, nmr_seq_id, nmr_atom_id)

        if seq_key in self.__coord_near_para_ferro:
            return self.__coord_near_para_ferro[seq_key]

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            if cif_seq_id is None:
                self.__coord_near_para_ferro[seq_key] = None
                return None

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__coord_atom_site_tags else 'ndb_model'

                _origin = self.__cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                           {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                           {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'}
                                                           ],
                                                          [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                           {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                           {'name': 'label_atom_id', 'type': 'str', 'value': nmr_atom_id},
                                                           {'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                                                           {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                           ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - {str(e)}\n")

                return None

            if len(_origin) != 1:
                self.__coord_near_para_ferro[seq_key] = None
                return None

            o = to_np_array(_origin[0])

            try:

                _neighbor = self.__cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'auth_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                             {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},  # non-polymer
                                                             {'name': 'label_comp_id', 'type': 'starts-with-alnum', 'alt_name': 'comp_id'},
                                                             {'name': 'label_atom_id', 'type': 'starts-with-alnum', 'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': 'type_symbol', 'type': 'str'}
                                                             ],
                                                            [{'name': 'Cartn_x', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[0] - cutoff), 'max_exclusive': (o[0] + cutoff)}},
                                                             {'name': 'Cartn_y', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[1] - cutoff), 'max_exclusive': (o[1] + cutoff)}},
                                                             {'name': 'Cartn_z', 'type': 'range-float',
                                                              'range': {'min_exclusive': (o[2] - cutoff), 'max_exclusive': (o[2] + cutoff)}},
                                                             {'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id},
                                                             {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                             ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - {str(e)}\n")

                return None

            if len(_neighbor) == 0:
                self.__coord_near_para_ferro[seq_key] = None
                return None

            neighbor = [n for n in _neighbor
                        if n['seq_id'] != cif_seq_id
                        and distance(to_np_array(n), o) < cutoff
                        and (n['type_symbol'] in PARAMAGNETIC_ELEMENTS
                             or n['type_symbol'] in FERROMAGNETIC_ELEMENTS)]

            if len(neighbor) == 0:
                self.__coord_near_para_ferro[seq_key] = None
                return None

            atom_list = []

            for n in neighbor:
                atom_list.append({'chain_id': n['chain_id'], 'seq_id': n['seq_id'], 'comp_id': n['comp_id'], 'atom_id': n['atom_id'],
                                  'distance': distance(to_np_array(n), o)})

            if len(atom_list) == 0:
                return None

            p = sorted(atom_list, key=itemgetter('distance'))[0]

            try:

                _p = self.__cR.getDictListWithFilter('atom_site',
                                                     [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                      {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                      {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'}
                                                      ],
                                                     [{'name': 'auth_asym_id', 'type': 'str', 'value': p['chain_id']},
                                                      {'name': 'auth_seq_id', 'type': 'int', 'value': p['seq_id']},  # non-polymer
                                                      {'name': 'label_comp_id', 'type': 'str', 'value': p['comp_id']},
                                                      {'name': 'label_atom_id', 'type': 'str', 'value': p['atom_id']},
                                                      {'name': 'label_alt_id', 'type': 'enum', 'enum': (self.__representative_alt_id,)}
                                                      ])

            except Exception as e:

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - " + str(e))
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getNearestParaFerroMagneticAtom() ++ Error  - {str(e)}\n")

                return None

            if len(_p) == 0:
                self.__coord_near_para_ferro[seq_key] = None
                return None

            dist = 0.0

            for __p in _p:
                dist += distance(to_np_array(__p), o)

            p['distance'] = float(f"{dist / len(_p):.1f}")

            self.__coord_near_para_ferro[seq_key] = p
            return p

        self.__coord_near_para_ferro[seq_key] = None
        return None

    def __appendElemAndIsoNumOfNefCsLoop(self):
        """ Append element and isotope_number columns in NEF CS loop if required.
        """

        if not self.__combined_mode:
            return True

        try:

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_type = input_source_dic['file_type']

                if file_type != 'nef':
                    continue

                content_subtype = 'chem_shift'

                if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                cs_item_names = self.item_names_in_cs_loop[file_type]
                cs_atom_id_name = cs_item_names['atom_id']
                cs_atom_type = cs_item_names['atom_type']
                cs_iso_number = cs_item_names['isotope_number']

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    try:
                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(lp_category)
                        else:
                            loop = sf.get_loop_by_category(lp_category)
                    except KeyError:
                        continue

                    has_atom_type = cs_atom_type in loop.tags
                    has_iso_number = cs_iso_number in loop.tags

                    atomIdCol = loop.tags.index(cs_atom_id_name)

                    if has_atom_type and has_iso_number:

                        atomTypeCol = loop.tags.index(cs_atom_type)
                        isoNumCol = loop.tags.index(cs_iso_number)

                        for row in loop:

                            atom_id = row[atomIdCol]

                            if row[atomTypeCol] in emptyValue:
                                row[atomTypeCol] = atom_id[0]

                            if row[isoNumCol] in emptyValue:

                                try:
                                    row[isoNumCol] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id[0]][0]
                                except KeyError:
                                    pass

                    elif has_atom_type:

                        atomTypeCol = loop.tags.index(cs_atom_type)

                        for row in loop:

                            atom_id = row[atomIdCol]

                            if row[atomTypeCol] in emptyValue:
                                row[atomTypeCol] = atom_id[0]

                            try:
                                iso_num = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id[0]][0]
                                row.append(iso_num)
                            except KeyError:
                                row.append('.')

                        loop.add_tag(cs_iso_number)

                    elif has_iso_number:

                        isoNumCol = loop.tags.index(cs_iso_number)

                        for row in loop:

                            atom_id = row[atomIdCol]

                            if row[isoNumCol] in emptyValue:

                                try:
                                    row[isoNumCol] = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id[0]][0]
                                except KeyError:
                                    pass

                            row.append(atom_id[0] if atom_id[0] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS else '.')

                        loop.add_tag(cs_atom_type)

                    else:

                        for row in loop:

                            atom_id = row[atomIdCol]

                            row.append(atom_id[0] if atom_id[0] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS else '.')

                            try:
                                iso_num = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id[0]][0]
                                row.append(iso_num)
                            except KeyError:
                                row.append('.')

                        loop.add_tag(cs_atom_type)
                        loop.add_tag(cs_iso_number)

            return True

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__appendElemAndIsoNumOfNefCsLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__appendElemAndIsoNumOfNefCsLoop() ++ Error  - {str(e)}\n")

            return False

    def __appendWeightInLoop(self):
        """ Append weight column in interesting loops, if required.
        """

        if not self.__combined_mode:
            return True

        try:

            is_done = True

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_name = input_source_dic['file_name']
                file_type = input_source_dic['file_type']

                if input_source_dic['content_subtype'] is None:
                    is_done = False
                    continue

                for content_subtype in input_source_dic['content_subtype']:

                    if content_subtype == 'entity':
                        continue

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    weight_tag = self.weight_tags[file_type][content_subtype]

                    if weight_tag is None:
                        continue

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        try:
                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop(lp_category)
                            else:
                                loop = sf.get_loop_by_category(lp_category)
                        except KeyError:
                            continue

                        if weight_tag in loop.tags:
                            continue

                        lp_tag = lp_category + '.' + weight_tag
                        err = self.__err_template_for_missing_mandatory_lp_tag % (lp_tag, file_type.upper())

                        if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(lp_tag, file_type):

                            if self.__rescue_mode:
                                self.report.error.appendDescription('missing_mandatory_item',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                                     'description': err})
                                self.report.setError()

                                if self.__verbose:
                                    self.__lfh.write("+NmrDpUtility.__appendWeightInLoop() ++ LookupError  - "
                                                     f"{file_name} {sf_framecode} {lp_category} {err}\n")

                        for row in loop:
                            row.append('1.0')

                        loop.add_tag(weight_tag)

            return is_done

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__appendWeightInLoop() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__appendWeightInLoop() ++ Error  - {str(e)}\n")

            return False

    def __appendDihedAngleType(self):
        """ Append dihedral angle type column, if required.
        """

        if not self.__combined_mode:
            return True

        try:

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_type = input_source_dic['file_type']

                content_subtype = 'dihed_restraint'

                if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                angle_type_tag = self.angle_types[file_type]

                for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    try:
                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(lp_category)
                        else:
                            loop = sf.get_loop_by_category(lp_category)
                    except KeyError:
                        continue

                    if angle_type_tag in loop.tags:
                        continue

                    for row in loop:
                        row.append('.')

                    loop.add_tag(angle_type_tag)

            return True

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__appendDihedAngleType() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__appendDihedAngleType() ++ Error  - {str(e)}\n")

            return False

    def __appendSfTagItem(self):
        """ Append saveframe tag items, if required.
        """

        if not self.__combined_mode:
            return True

        try:

            for fileListId in range(self.__file_path_list_len):

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                file_name = input_source_dic['file_name']
                file_type = input_source_dic['file_type']

                if input_source_dic['content_subtype'] is None:
                    continue

                for content_subtype in input_source_dic['content_subtype']:

                    if content_subtype in ('entry_info', 'poly_seq', 'entity'):
                        continue

                    sf_category = self.sf_categories[file_type][content_subtype]
                    # lp_category = self.lp_categories[file_type][content_subtype]

                    has_data_file_name = file_type == 'nmr-star' and 'Data_file_name' in self.sf_allowed_tags[file_type][content_subtype]
                    tag_items = self._sf_tag_items[file_type][content_subtype]

                    if not has_data_file_name and tag_items is None:
                        continue

                    for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        tagNames = [t[0] for t in sf.tags]

                        # DAOTHER-9520: fill data_file_name by default
                        if has_data_file_name:
                            original_file_name = input_source_dic['file_name']
                            if 'original_file_name' in input_source_dic and input_source_dic['original_file_name'] is not None:
                                original_file_name = os.path.basename(input_source_dic['original_file_name'])

                            if 'Data_file_name' in tagNames:
                                data_file_name = get_first_sf_tag(sf, 'Data_file_name').strip()
                                if len(data_file_name) == 0 or data_file_name in emptyValue:
                                    set_sf_tag(sf, 'Data_file_name', original_file_name)
                            else:
                                set_sf_tag(sf, 'Data_file_name', original_file_name)

                        if tag_items is None:
                            continue

                        for tag_item in tag_items:

                            if tag_item in tagNames:
                                continue

                            sf_tag = '_' + sf_category + '.' + tag_item
                            warn = self.__warn_template_for_missing_mandatory_sf_tag % (sf_tag, file_type.upper())

                            if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(sf_tag, file_type):

                                if self.__rescue_mode:
                                    self.report.warning.appendDescription('missing_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': sf_category,
                                                                           'description': warn})
                                    self.report.setWarning()

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__appendSfTagItem() ++ Warning  - {warn}\n")

                            sf.add_tag(tag_item, '.')

            return True

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__appendSfTagItem() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__appendSfTagItem() ++ Error  - {str(e)}\n")

            return False

    def __updateDihedralAngleType(self):
        """ Update dihedral angle types if possible.
        """

        if not self.__combined_mode:
            return True

        for fileListId in range(self.__file_path_list_len):

            input_source = self.report.input_sources[fileListId]
            input_source_dic = input_source.get()

            file_name = input_source_dic['file_name']
            file_type = input_source_dic['file_type']

            content_subtype = 'dihed_restraint'

            if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                continue

            item_names = self.item_names_in_dh_loop[file_type]
            index_id_name = self.index_tags[file_type][content_subtype]
            chain_id_1_name = item_names['chain_id_1']
            chain_id_2_name = item_names['chain_id_2']
            chain_id_3_name = item_names['chain_id_3']
            chain_id_4_name = item_names['chain_id_4']
            seq_id_1_name = item_names['seq_id_1']
            seq_id_2_name = item_names['seq_id_2']
            seq_id_3_name = item_names['seq_id_3']
            seq_id_4_name = item_names['seq_id_4']
            comp_id_1_name = item_names['comp_id_1']
            atom_id_1_name = item_names['atom_id_1']
            atom_id_2_name = item_names['atom_id_2']
            atom_id_3_name = item_names['atom_id_3']
            atom_id_4_name = item_names['atom_id_4']
            angle_type_name = item_names['angle_type']

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[fileListId].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                if lp_data is None:

                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]

                    try:

                        lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__excl_missing_data)[0]

                        self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                    except Exception:
                        pass

                if lp_data is not None:

                    update = False
                    update_index = {}

                    try:

                        for row in lp_data:
                            index_id = row[index_id_name]
                            chain_id_1 = row[chain_id_1_name]
                            chain_id_2 = row[chain_id_2_name]
                            chain_id_3 = row[chain_id_3_name]
                            chain_id_4 = row[chain_id_4_name]
                            seq_id_1 = row[seq_id_1_name]
                            seq_id_2 = row[seq_id_2_name]
                            seq_id_3 = row[seq_id_3_name]
                            seq_id_4 = row[seq_id_4_name]
                            comp_id_1 = row[comp_id_1_name]
                            atom_id_1 = row[atom_id_1_name]
                            atom_id_2 = row[atom_id_2_name]
                            atom_id_3 = row[atom_id_3_name]
                            atom_id_4 = row[atom_id_4_name]
                            angle_type = row[angle_type_name]

                            if angle_type not in emptyValue:
                                continue

                            atom1 = {'chain_id': chain_id_1,
                                     'seq_id': seq_id_1,
                                     'atom_id': atom_id_1}
                            atom2 = {'chain_id': chain_id_2,
                                     'seq_id': seq_id_2,
                                     'atom_id': atom_id_2}
                            atom3 = {'chain_id': chain_id_3,
                                     'seq_id': seq_id_3,
                                     'atom_id': atom_id_3}
                            atom4 = {'chain_id': chain_id_4,
                                     'seq_id': seq_id_4,
                                     'atom_id': atom_id_4}

                            peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                            data_type = getTypeOfDihedralRestraint(peptide, nucleotide, carbohydrate, [atom1, atom2, atom3, atom4])

                            if data_type in emptyValue:
                                continue

                            update = True

                            if data_type not in update_index:
                                update_index[data_type] = []

                            update_index[data_type].append(index_id)

                    except Exception as e:

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__updateDihedralAngleType() ++ Error  - " + str(e))
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__updateDihedralAngleType() ++ Error  - {str(e)}\n")

                        continue

                    if update:

                        try:
                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop(lp_category)
                            else:
                                loop = sf.get_loop_by_category(lp_category)
                        except KeyError:
                            continue

                        idxCol = loop.tags.index(index_id_name)
                        aglCol = loop.tags.index(angle_type_name)

                        for row in loop:

                            index_id = int(row[idxCol])

                            for k, v in update_index.items():
                                if index_id in v:
                                    row[aglCol] = k

        return True

    def __fixDisorderedIndex(self):
        """ Fix disordered indices.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        warnings = self.report.warning.getValueList('disordered_index', file_name)

        if warnings is None:
            return True

        for w in warnings:

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_framecode' not in w:

                    err = "Could not specify 'sf_framecode' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixDisorderedIndex() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__fixDisorderedIndex() ++ Error  - {err}\n")

                else:

                    sf = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf is None:

                        err = f"Could not specify {w['sf_framecode']!r} saveframe unexpectedly in {file_name!r} file."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixDisorderedIndex() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__fixDisorderedIndex() ++ Error  - {err}\n")

                        continue

                    if 'category' not in w:

                        err = "Could not specify 'category' in NMR data processing report."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixDisorderedIndex() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__fixDisorderedIndex() ++ Error  - {err}\n")

                    else:

                        try:

                            category = w['category'] if w['category'].startswith('_') else '_' + w['category']  # pynmrstar v2.6.5.1

                            content_subtype = next(c for c in input_source_dic['content_subtype']
                                                   if self.lp_categories[file_type][c] == category and self.index_tags[file_type][c] is not None)

                            if __pynmrstar_v3_2__:
                                loop = sf.get_loop(w['category'])
                            else:
                                loop = sf.get_loop_by_category(w['category'])
                            loop.renumber_rows(self.index_tags[file_type][content_subtype])

                        except StopIteration:

                            err = "Could not specify content_subtype in NMR data processing report."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixDisorderedIndex() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__fixDisorderedIndex() ++ Error  - {err}\n")

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixDisorderedIndex() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__fixDisorderedIndex() ++ Error  - {err}\n")

        return True

    def __removeNonSenseZeroValue(self):
        """ Remove non-sense zero values.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('missing_data', file_name)

        if warnings is None:
            return True

        for w in warnings:

            if "should not have zero value" not in w['description']:
                continue

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_framecode' not in w:

                    err = "Could not specify 'sf_framecode' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - {err}\n")

                else:

                    sf = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf is None:

                        err = f"Could not specify {w['sf_framecode']!r} saveframe unexpectedly in {file_name!r} file."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - {err}\n")

                        continue

                    if 'category' not in w:

                        err = "Could not specify 'category' in NMR data processing report."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - {err}\n")

                    else:

                        itName = w['description'].split(' ')[0]

                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(w['category'])
                        else:
                            loop = sf.get_loop_by_category(w['category'])

                        if itName not in loop.tags:

                            err = f"Could not find loop tag {itName} in {w['category']} category, {w['sf_framecode']!r} saveframe, {file_name!r} file."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - {err}\n")

                        else:

                            itCol = loop.tags.index(itName)

                            for row in loop:

                                val = row[itCol]

                                if val in emptyValue:
                                    continue

                                try:
                                    if float(val) == 0:
                                        row[itCol] = '.'
                                except ValueError:
                                    row[itCol] = '.'

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__removeNonSenseZeroValue() ++ Error  - {err}\n")

        return True

    def __fixNonSenseNegativeValue(self):
        """ Fix non-sense negative values.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('unusual_data', file_name)

        if warnings is None:
            return True

        for w in warnings:

            if "should not have negative value" not in w['description']:
                continue

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_framecode' not in w:

                    err = "Could not specify 'sf_framecode' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - {err}\n")

                else:

                    sf = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf is None:

                        err = f"Could not specify {w['sf_framecode']!r} saveframe unexpectedly in {file_name!r} file."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - {err}\n")

                        continue

                    if 'category' not in w:

                        err = "Could not specify 'category' in NMR data processing report."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - {err}\n")

                    else:

                        itName = w['description'].split(' ')[0]

                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(w['category'])
                        else:
                            loop = sf.get_loop_by_category(w['category'])

                        if itName not in loop.tags:

                            err = f"Could not find loop tag {itName} in {w['category']} category, {w['sf_framecode']!r} saveframe, {file_name!r} file."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - {err}\n")

                        else:

                            itCol = loop.tags.index(itName)

                            for row in loop:

                                val = row[itCol]

                                if val in emptyValue:
                                    continue

                                try:
                                    if float(val) < 0.0:
                                        row[itCol] = abs(float(val))
                                except ValueError:
                                    row[itCol] = '.'

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__fixNonSenseNegativeValue() ++ Error  - {err}\n")

        return True

    def __fixEnumMismatch(self):
        """ Fix enumeration mismatches if possible.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('enum_mismatch', file_name)

        if warnings is None:
            return True

        return self.__fixEnumerationFailure(warnings)

    def __fixEnumMismatchIgnorable(self):
        """ Fix enumeration mismatches (ignorable) if possible.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']

        warnings = self.report.warning.getValueList('enum_mismatch_ignorable', file_name)

        if warnings is None:
            return True

        return self.__fixEnumerationFailure(warnings)

    def __fixEnumerationFailure(self, warnings):
        """ Fix enumeration failures if possible.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if warnings is None:
            return True

        for w in warnings:

            if "be one of" not in w['description']:
                continue

            if w['description'].startswith('The mandatory type'):
                try:
                    g = self.chk_desc_pat_mand.search(w['description']).groups()
                except AttributeError:
                    g = self.chk_desc_pat_mand_one.search(w['description']).groups()
                mandatory_tag = True
            else:
                try:
                    g = self.chk_desc_pat.search(w['description']).groups()
                except AttributeError:
                    g = self.chk_desc_pat_one.search(w['description']).groups()
                mandatory_tag = False

            itName = g[0]
            itValue = None if g[1] in emptyValue else g[1]
            itEnum = [str(e.strip("'")) for e in re.sub(r"\', \'", "\',\'", g[2]).split(',')]

            if self.__star_data_type[0] == 'Entry' or self.__star_data_type[0] == 'Saveframe':

                if 'sf_framecode' not in w:

                    err = "Could not specify 'sf_framecode' in NMR data processing report."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

                else:

                    sf = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf is None:

                        err = f"Could not specify {w['sf_framecode']!r} saveframe unexpectedly in {file_name!r} file."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                        self.report.setError()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

                        continue

                    if 'category' not in w:

                        tagNames = [t[0] for t in sf.tags]

                        if itName not in tagNames:

                            err = f"Could not find saveframe tag {itName} in {w['sf_framecode']!r} saveframe, {file_name!r} file."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

                        else:

                            itCol = tagNames.index(itName)

                            val = sf.tags[itCol][1]
                            if val in emptyValue:
                                val = None

                            if val is itValue or val == itValue:

                                undefined_enums = ('undefined', 'unknown')

                                # assumes 'undefined', 'unknown' enum values at the end of the array
                                if (len(itEnum) == 2 and itEnum[1] in undefined_enums) or\
                                   (len(itEnum) == 3 and itEnum[1] in undefined_enums and itEnum[2] in undefined_enums):
                                    sf.tags[itCol][1] = itEnum[0]

                                # specific remediation follows
                                else:

                                    sf_category = get_first_sf_tag(sf, 'sf_category')

                                    try:

                                        content_subtype = next(c for c in input_source_dic['content_subtype'] if self.sf_categories[file_type][c] == sf_category)

                                        if (file_type == 'nef' and itName == 'restraint_origin') or (file_type == 'nmr-star' and itName == 'Constraint_type'):

                                            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                                            if lp['file_name'] == file_name and lp['sf_framecode'] == w['sf_framecode']), None)

                                            if lp_data is None:
                                                lp_category = self.lp_categories[file_type][content_subtype]

                                                key_items = self.key_items[file_type][content_subtype]
                                                data_items = self.data_items[file_type][content_subtype]

                                                try:

                                                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                                                     excl_missing_data=self.__excl_missing_data)[0]

                                                    self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': w['sf_framecode'], 'data': lp_data})

                                                except Exception:
                                                    pass

                                            if lp_data is not None:

                                                if content_subtype == 'dist_restraint':

                                                    if mandatory_tag:
                                                        sf.tags[itCol][1] = 'undefined' if file_type == 'nef' else 'general distance'

                                                    # 'NOE', 'NOE build-up', 'NOE not seen', 'ROE', 'ROE build-up', 'hydrogen bond',
                                                    # 'disulfide bond', 'paramagnetic relaxation', 'symmetry', 'general distance'

                                                    elif self.__testDistRestraintAsHydrogenBond(lp_data):
                                                        sf.tags[itCol][1] = 'hbond' if file_type == 'nef' else 'hydrogen bond'

                                                    elif self.__testDistRestraintAsDisulfideBond(lp_data):
                                                        sf.tags[itCol][1] = 'disulfide_bond' if file_type == 'nef' else 'disulfide bond'

                                                    elif self.__testDistRestraintAsSymmetry(lp_data):
                                                        sf.tags[itCol][1] = 'symmetry'

                                                    else:
                                                        sf.tags[itCol][1] = 'undefined' if file_type == 'nef' else 'general distance'

                                                elif content_subtype == 'dihed_restraint':

                                                    if mandatory_tag:
                                                        sf.tags[itCol][1] = 'undefined'

                                                    # 'J-couplings', 'backbone chemical shifts'

                                                    elif self.__testDihedRestraintAsBackBoneChemShifts(lp_data):
                                                        sf.tags[itCol][1] = 'chemical_shift' if file_type == 'nef' else 'backbone chemical shifts'

                                                    # else:
                                                    #    sf.tags[itCol][1] = 'J-couplings'

                                                    else:
                                                        sf.tags[itCol][1] = 'undefined'

                                                elif content_subtype == 'rdc_restraint':

                                                    if mandatory_tag:
                                                        sf.tags[itCol][1] = 'undefined'
                                                    else:
                                                        sf.tags[itCol][1] = 'measured' if file_type == 'nef' else 'RDC'

                                        if (file_type == 'nef' and itName == 'potential_type') or (file_type == 'nmr-star' and itName == 'Potential_type'):

                                            lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                                            if lp['file_name'] == file_name and lp['sf_framecode'] == w['sf_framecode']), None)

                                            if lp_data is None:
                                                lp_category = self.lp_categories[file_type][content_subtype]

                                                key_items = self.key_items[file_type][content_subtype]
                                                data_items = self.data_items[file_type][content_subtype]

                                                try:

                                                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                                                     excl_missing_data=self.__excl_missing_data)[0]

                                                    self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': w['sf_framecode'], 'data': lp_data})

                                                except Exception:
                                                    pass

                                            if lp_data is not None:

                                                # 'log-harmonic', 'parabolic'
                                                # 'square-well-parabolic', 'square-well-parabolic-linear',
                                                # 'upper-bound-parabolic', 'lower-bound-parabolic',
                                                # 'upper-bound-parabolic-linear', 'lower-bound-parabolic-linear'

                                                if mandatory_tag:
                                                    sf.tags[itCol][1] = 'undefined'
                                                elif self.__testRestraintPotentialSWP(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'square-well-parabolic'
                                                elif self.__testRestraintPotentialSWPL(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'square-well-parabolic-linear'
                                                elif self.__testRestraintPotentialUBP(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'upper-bound-parabolic'
                                                elif self.__testRestraintPotentialLBP(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'lower-bound-parabolic'
                                                elif self.__testRestraintPotentialUBPL(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'upper-bound-parabolic-linear'
                                                elif self.__testRestraintPotentialLBPL(content_subtype, lp_data):
                                                    sf.tags[itCol][1] = 'lower-bound-parabolic-linear'
                                                elif self.__testRestraintPonentialLHorP(content_subtype, lp_data):
                                                    if content_subtype == 'dist_restraint':
                                                        sf.tags[itCol][1] = 'log-harmonic'
                                                    else:
                                                        sf.tags[itCol][1] = 'parabolic'
                                                else:
                                                    sf.tags[itCol][1] = 'undefined'

                                    except StopIteration:

                                        err = "Could not specify content_subtype in NMR data processing report."

                                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                                        self.report.setError()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

                    else:

                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(w['category'])
                        else:
                            loop = sf.get_loop_by_category(w['category'])

                        if itName not in loop.tags:

                            err = f"Could not find loop tag {itName} in {w['category']} category, {w['sf_framecode']!r} saveframe, {file_name!r} file."

                            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                            self.report.setError()

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

                        else:

                            itCol = loop.tags.index(itName)

                            for row in loop:

                                val = row[itCol]

                                if val in emptyValue:
                                    continue

                                if val == itValue:

                                    if len(itEnum) == 1:
                                        row[itCol] = itEnum[0]

                                    elif file_type == 'nef' and itName == 'folding':

                                        # 'circular', 'mirror', 'none'

                                        if val in ('aliased', 'folded', 'not observed'):
                                            if val == 'aliased':
                                                row[itCol] = 'mirror'
                                            elif val == 'folded':
                                                row[itCol] = 'circular'
                                            else:
                                                row[itCol] = 'none'

                                    elif file_type == 'nmr-star' and itName == 'Under_sampling_type':

                                        # 'aliased', 'folded', 'not observed'

                                        if val in ('circular', 'mirror', 'none'):
                                            if val == 'circular':
                                                row[itCol] = 'folded'
                                            elif val == 'mirror':
                                                row[itCol] = 'aliased'
                                            else:
                                                row[itCol] = 'not observed'

            else:

                err = f"Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixEnumerationFailure() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__fixEnumerationFailure() ++ Error  - {err}\n")

        return True

    def __testDistRestraintAsHydrogenBond(self, lp_data):
        """ Detect whether given distance restraints are derived from hydrogen bonds.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None or len(lp_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']
        target_value_name = item_names['target_value']
        if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
            target_value_name = item_names['target_value_alt']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']

        try:

            for row in lp_data:
                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]

                if chain_id_1 == chain_id_2 and seq_id_1 == seq_id_2:
                    return False

                target_value = row.get(target_value_name)

                upper_limit = None
                lower_limit = None

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    elif has_key_value(row, upper_linear_limit_name):
                        target_value = row[upper_linear_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, upper_limit_name):
                        target_value = row[upper_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, lower_linear_limit_name):
                        target_value = row[lower_linear_limit_name]
                        lower_limit = target_value

                    elif has_key_value(row, lower_limit_name):
                        target_value = row[lower_limit_name]
                        lower_limit = target_value

                    else:
                        return False

                atom_id_1_ = row[atom_id_1_name][0]
                atom_id_2_ = row[atom_id_2_name][0]

                if upper_limit is not None:
                    target_value -= 0.4

                if lower_limit is not None:
                    target_value += 0.4

                if (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):

                    if target_value < 1.2 or target_value > 1.5:
                        return False

                elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):

                    if target_value < 2.2 or target_value > 2.5:
                        return False

                elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):

                    if target_value < 1.5 or target_value > 2.5:
                        return False

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):

                    if target_value < 2.5 or target_value > 3.5:
                        return False

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):

                    if target_value < 2.5 or target_value > 3.5:
                        return False

                elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):

                    if target_value < 1.5 or target_value > 2.5:
                        return False

                elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):

                    if target_value < 2.5 or target_value > 3.5:
                        return False

                else:
                    return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDistRestraintAsHydrogenBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDistRestraintAsHydrogenBond() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testDistRestraintAsDisulfideBond(self, lp_data):
        """ Detect whether given distance restraints are derived from disulfide bonds.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None or len(lp_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']
        target_value_name = item_names['target_value']
        if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
            target_value_name = item_names['target_value_alt']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']

        try:

            for row in lp_data:
                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]

                if chain_id_1 == chain_id_2 and seq_id_1 == seq_id_2:
                    return False

                target_value = row.get(target_value_name)

                upper_limit = None
                lower_limit = None

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    elif has_key_value(row, upper_linear_limit_name):
                        target_value = row[upper_linear_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, upper_limit_name):
                        target_value = row[upper_limit_name]
                        upper_limit = target_value

                    elif has_key_value(row, lower_linear_limit_name):
                        target_value = row[lower_linear_limit_name]
                        lower_limit = target_value

                    elif has_key_value(row, lower_limit_name):
                        target_value = row[lower_limit_name]
                        lower_limit = target_value

                    else:
                        return False

                atom_id_1_ = row[atom_id_1_name][0]
                atom_id_2_ = row[atom_id_2_name][0]

                if upper_limit is not None:
                    target_value -= 0.4

                if lower_limit is not None:
                    target_value += 0.4

                if atom_id_1_ == 'S' and atom_id_2_ == 'S':

                    if target_value < 1.9 or target_value > 2.3:
                        return False

                else:
                    return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDistRestraintAsDisulfideBond() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDistRestraintAsDisulfideBond() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testDistRestraintAsSymmetry(self, lp_data):
        """ Detect whether given distance restraints are derived from symmetric assembly.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_ds_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']

        try:

            for row in lp_data:
                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]
                comp_id_1 = row[comp_id_1_name]
                comp_id_2 = row[comp_id_2_name]

                if chain_id_1 == chain_id_2:
                    return False

                has_symmetry = False

                for _row in lp_data:

                    if _row is row:
                        continue

                    _chain_id_1 = _row[chain_id_1_name]
                    _chain_id_2 = _row[chain_id_2_name]
                    _seq_id_1 = _row[seq_id_1_name]
                    _seq_id_2 = _row[seq_id_2_name]
                    _comp_id_1 = _row[comp_id_1_name]
                    _comp_id_2 = _row[comp_id_2_name]

                    if _chain_id_1 != _chain_id_2 and _chain_id_1 != chain_id_1 and _chain_id_2 != chain_id_2:

                        if seq_id_1 == _seq_id_1 and comp_id_1 == _comp_id_1 and\
                           seq_id_2 == _seq_id_2 and comp_id_2 == _comp_id_2:
                            has_symmetry = True
                            break

                        if seq_id_1 == _seq_id_2 and comp_id_1 == _comp_id_2 and\
                           seq_id_2 == _seq_id_1 and comp_id_2 == _comp_id_1:
                            has_symmetry = True
                            break

                if not has_symmetry:
                    return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDistRestraintAsSymmetry() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDistRestraintAsSymmetry() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testDihedRestraintAsBackBoneChemShifts(self, lp_data):
        """ Detect whether given dihedral angle restraints are derived from backbone chemical shifts.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        item_names = self.item_names_in_dh_loop[file_type]
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        chain_id_3_name = item_names['chain_id_3']
        chain_id_4_name = item_names['chain_id_4']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        seq_id_3_name = item_names['seq_id_3']
        seq_id_4_name = item_names['seq_id_4']
        comp_id_1_name = item_names['comp_id_1']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']
        atom_id_3_name = item_names['atom_id_3']
        atom_id_4_name = item_names['atom_id_4']
        angle_type_name = item_names['angle_type']

        dh_chain_ids = set()
        dh_seq_ids = {}
        cs_chain_ids = set()
        cs_seq_ids = {}

        try:

            for row in lp_data:
                chain_id_1 = row[chain_id_1_name]
                chain_id_2 = row[chain_id_2_name]
                chain_id_3 = row[chain_id_3_name]
                chain_id_4 = row[chain_id_4_name]
                seq_id_1 = row[seq_id_1_name]
                seq_id_2 = row[seq_id_2_name]
                seq_id_3 = row[seq_id_3_name]
                seq_id_4 = row[seq_id_4_name]
                comp_id_1 = row[comp_id_1_name]
                atom_id_1 = row[atom_id_1_name]
                atom_id_2 = row[atom_id_2_name]
                atom_id_3 = row[atom_id_3_name]
                atom_id_4 = row[atom_id_4_name]
                angle_type = row[angle_type_name]

                if angle_type in emptyValue:
                    continue

                angle_type = angle_type.lower()

                if angle_type not in ('phi', 'psi'):
                    return False

                atom1 = {'chain_id': chain_id_1,
                         'seq_id': seq_id_1,
                         'atom_id': atom_id_1}
                atom2 = {'chain_id': chain_id_2,
                         'seq_id': seq_id_2,
                         'atom_id': atom_id_2}
                atom3 = {'chain_id': chain_id_3,
                         'seq_id': seq_id_3,
                         'atom_id': atom_id_3}
                atom4 = {'chain_id': chain_id_4,
                         'seq_id': seq_id_4,
                         'atom_id': atom_id_4}

                peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_1)

                if not peptide:
                    return False

                data_type = getTypeOfDihedralRestraint(peptide, nucleotide, carbohydrate, [atom1, atom2, atom3, atom4])

                if data_type is None or data_type.lower() not in ('phi', 'psi'):
                    return False

                dh_chain_ids.add(chain_id_1)

                seq_ids = [seq_id_1, seq_id_2, seq_id_3, seq_id_4]
                seq_id_common = collections.Counter(seq_ids).most_common()

                chain_id = chain_id_1

                if chain_id not in dh_seq_ids:
                    dh_seq_ids[chain_id] = set()

                dh_seq_ids[chain_id].add(seq_id_common[0][0])

            # check backbone CA atoms

            content_subtype = 'chem_shift'

            if not has_key_value(input_source_dic['content_subtype'], content_subtype):
                return False

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            key_items = self.key_items[file_type][content_subtype]
            data_items = self.data_items[file_type][content_subtype]

            item_names = self.item_names_in_cs_loop[file_type]
            chain_id_name = item_names['chain_id']
            seq_id_name = item_names['seq_id']
            atom_id_name = item_names['atom_id']

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):
                sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                if self.report.error.exists(file_name, sf_framecode):
                    continue

                lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                if lp_data is None:

                    try:

                        lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__excl_missing_data)[0]

                        self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                    except Exception:
                        pass

                if lp_data is not None:

                    for row in lp_data:
                        chain_id = row[chain_id_name]
                        seq_id = row[seq_id_name]
                        atom_id = row[atom_id_name]

                        if chain_id in dh_chain_ids and seq_id in dh_seq_ids[chain_id] and atom_id == 'CA':
                            cs_chain_ids.add(chain_id)

                            if chain_id not in cs_seq_ids:
                                cs_seq_ids[chain_id] = set()

                            cs_seq_ids[chain_id].add(seq_id)

            if cs_chain_ids != dh_chain_ids:
                return False

            for k, v in dh_seq_ids.items():

                if len(cs_seq_ids[k] & v) < len(v) * 0.8:
                    return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testDihedRestraintAsBackBoneChemShifts() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDihedRestraintAsBackBoneChemShifts() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialSWP(self, content_subtype, lp_data):
        """ Detect square-well-parabolic potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if has_key_value(row, lower_limit_name) and\
                   has_key_value(row, upper_limit_name) and\
                   (not has_key_value(row, lower_linear_limit_name)) and\
                   (not has_key_value(row, upper_linear_limit_name)):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialSWP() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialSWP() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialSWPL(self, content_subtype, lp_data):
        """ Detect square-well-parabolic-linear potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if has_key_value(row, lower_limit_name) and\
                   has_key_value(row, upper_limit_name) and\
                   has_key_value(row, lower_linear_limit_name) and\
                   has_key_value(row, upper_linear_limit_name):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialSWPL() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialSWPL() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialUBP(self, content_subtype, lp_data):
        """ Detect upper-bound-parabolic potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if (not has_key_value(row, lower_limit_name)) and\
                   has_key_value(row, upper_limit_name) and\
                   (not has_key_value(row, lower_linear_limit_name)) and\
                   (not has_key_value(row, upper_linear_limit_name)):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialUBP() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialUBP() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialLBP(self, content_subtype, lp_data):
        """ Detect lower-bound-parabolic potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if has_key_value(row, lower_limit_name) and\
                   (not has_key_value(row, upper_limit_name)) and\
                   (not has_key_value(row, lower_linear_limit_name)) and\
                   (not has_key_value(row, upper_linear_limit_name)):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialLBP() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialLBP() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialUBPL(self, content_subtype, lp_data):
        """ Detect upper-bound-parabolic-linear potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if (not has_key_value(row, lower_limit_name)) and\
                   has_key_value(row, upper_limit_name) and\
                   (not has_key_value(row, lower_linear_limit_name)) and\
                   has_key_value(row, upper_linear_limit_name):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialUBPL() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialUBPL() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPotentialLBPL(self, content_subtype, lp_data):
        """ Detect lower-bound-parabolic-linear potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if has_key_value(row, lower_limit_name) and\
                   (not has_key_value(row, upper_limit_name)) and\
                   has_key_value(row, lower_linear_limit_name) and\
                   (not has_key_value(row, upper_linear_limit_name)):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialLBPL() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialLBPL() ++ Error  - {str(e)}\n")

            return False

        return True

    def __testRestraintPonentialLHorP(self, content_subtype, lp_data):
        """ Detect log-harmonic or parabolic potential.
        """

        if not self.__combined_mode:
            return True

        if lp_data is None or len(lp_data) == 0:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        try:

            item_names = self.potential_items[file_type][content_subtype]
            target_value_name = item_names['target_value']
            if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
                target_value_name = item_names['target_value_alt']
            lower_limit_name = item_names['lower_limit']
            upper_limit_name = item_names['upper_limit']
            lower_linear_limit_name = item_names['lower_linear_limit']
            upper_linear_limit_name = item_names['upper_linear_limit']

            for row in lp_data:
                if has_key_value(row, target_value_name) and\
                   (not has_key_value(row, lower_limit_name)) and\
                   (not has_key_value(row, upper_limit_name)) and\
                   (not has_key_value(row, lower_linear_limit_name)) and\
                   (not has_key_value(row, upper_linear_limit_name)):
                    continue

                return False

        except Exception as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__testRestraintPotentialLHorP() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testRestraintPotentialLHorP() ++ Error  - {str(e)}\n")

            return False

        return True

    def __getSaveframeByName(self, file_list_id, sf_framecode):
        """ Retrieve saveframe content from a given name.
        """

        try:

            return self.__star_data[file_list_id].get_saveframe_by_name(sf_framecode)

        except KeyError:  # DAOTHER-7389, issue #4

            if file_list_id < len(self.__sf_name_corr) and sf_framecode in self.__sf_name_corr[file_list_id]:

                try:
                    return self.__star_data[file_list_id].get_saveframe_by_name(self.__sf_name_corr[file_list_id][sf_framecode])
                except KeyError:
                    return None

            else:

                try:
                    g = self.chk_unresolved_sf_name_pat.search(sf_framecode).groups()
                    return self.__star_data[file_list_id].get_saveframe_by_name(g[0])
                except AttributeError:
                    return None
                except KeyError:
                    return None

    def __resetCapitalStringInLoop(self):
        """ Reset capital string values (chain_id, comp_id, atom_id) in loops depending on file type.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if input_source_dic['content_subtype'] is None:
            return False

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                            raise ValueError()

                    except ValueError:  # raised error already at __testIndexConsistency()
                        continue

                    max_dim = num_dim + 1

                    key_items = []
                    for dim in range(1, max_dim):
                        for k in self.pk_key_items[file_type]:
                            if k['type'] == 'float':  # position
                                _k = copy.copy(k)
                                if '%s' in k['name']:
                                    _k['name'] = k['name'] % dim
                                key_items.append(_k)
                    for k in self.pk_key_items[file_type]:
                        if k['type'] == 'positive-int':  # peak_id
                            key_items.append(k)

                    data_items = []
                    for d in self.data_items[file_type][content_subtype]:
                        data_items.append(d)
                    for dim in range(1, max_dim):
                        for d in self.pk_data_items[file_type]:
                            _d = copy.copy(d)
                            if '%s' in d['name']:
                                _d['name'] = d['name'] % dim
                            if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                                _d['default-from'] = d['default-from'] % dim
                            data_items.append(_d)

                    if max_dim < MAX_DIM_NUM_OF_SPECTRA:
                        disallowed_tags = []
                        for dim in range(max_dim, MAX_DIM_NUM_OF_SPECTRA):
                            for t in self.spectral_peak_disallowed_tags[file_type]:
                                if '%s' in t:
                                    t = t % dim
                                disallowed_tags.append(t)

                else:

                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]

                if __pynmrstar_v3_2__:
                    loop = sf.get_loop(lp_category)
                else:
                    loop = sf.get_loop_by_category(lp_category)

                if file_type == 'nef':
                    key_names = [k['name'] for k in key_items
                                 if k['name'].startswith('chain_code') or k['name'].startswith('residue_name')
                                 or k['name'].startswith('atom_name') or k['name'] == 'element']
                else:
                    key_names = [k['name'] for k in key_items
                                 if k['name'].startswith('Comp_ID') or k['name'].startswith('Atom_ID') or k['name'] == 'Atom_type']

                for itName in key_names:

                    if itName in loop.tags:

                        itCol = loop.tags.index(itName)

                        for row in loop:

                            val = row[itCol]

                            if val in emptyValue:
                                continue

                            if (file_type == 'nef' and itName.startswith('atom_name'))\
                               or (file_type == 'nmr-star' and (itName.startswith('Auth_atom_ID') or itName == 'Original_PDB_atom_name')):
                                continue

                            row[itCol] = val.upper()

                if file_type == 'nef':
                    data_names = [d['name'] for d in data_items
                                  if d['name'].startswith('chain_code') or d['name'].startswith('residue_name')
                                  or d['name'].startswith('atom_name') or d['name'] == 'element']
                else:
                    data_names = [d['name'] for d in data_items
                                  if d['name'].startswith('Comp_ID') or d['name'].startswith('Atom_ID') or d['name'] == 'Atom_type']

                for itName in data_names:

                    if itName in loop.tags:

                        itCol = loop.tags.index(itName)

                        for row in loop:

                            val = row[itCol]

                            if val in emptyValue:
                                continue

                            if (file_type == 'nef' and itName.startswith('atom_name'))\
                               or (file_type == 'nmr-star' and (itName.startswith('Auth_atom_ID') or itName == 'Original_PDB_atom_name')):
                                continue

                            row[itCol] = val.upper()

        return True

    def __resetBoolValueInLoop(self):
        """ Reset bool values in loops depending on file type.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        yes_value = 'true' if file_type == 'nef' else 'yes'
        no_value = 'false' if file_type == 'nef' else 'no'

        if input_source_dic['content_subtype'] is None:
            return False

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):

                if not any(loop for loop in sf.loops if loop.category == lp_category):
                    continue

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = get_first_sf_tag(sf, self.num_dim_items[file_type])
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
                            raise ValueError()

                    except ValueError:  # raised error already at __testIndexConsistency()
                        continue

                    max_dim = num_dim + 1

                    key_items = []
                    for dim in range(1, max_dim):
                        for k in self.pk_key_items[file_type]:
                            if k['type'] == 'float':  # position
                                _k = copy.copy(k)
                                if '%s' in k['name']:
                                    _k['name'] = k['name'] % dim
                                key_items.append(_k)
                    for k in self.pk_key_items[file_type]:
                        if k['type'] == 'positive-int':  # peak_id
                            key_items.append(k)

                    data_items = []
                    for d in self.data_items[file_type][content_subtype]:
                        data_items.append(d)
                    for dim in range(1, max_dim):
                        for d in self.pk_data_items[file_type]:
                            _d = copy.copy(d)
                            if '%s' in d['name']:
                                _d['name'] = d['name'] % dim
                            if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                                _d['default-from'] = d['default-from'] % dim
                            data_items.append(_d)

                    if max_dim < MAX_DIM_NUM_OF_SPECTRA:
                        disallowed_tags = []
                        for dim in range(max_dim, MAX_DIM_NUM_OF_SPECTRA):
                            for t in self.spectral_peak_disallowed_tags[file_type]:
                                if '%s' in t:
                                    t = t % dim
                                disallowed_tags.append(t)

                else:

                    key_items = self.key_items[file_type][content_subtype]
                    data_items = self.data_items[file_type][content_subtype]

                has_bool_key = False

                if key_items is not None:
                    has_bool_key = next((k['type'] == 'bool' for k in key_items if k['type'] == 'bool'), False)

                has_bool_data = False

                if data_items is not None:
                    has_bool_data = next((d['type'] == 'bool' for d in data_items if d['type'] == 'bool'), False)

                if has_bool_key or has_bool_data:

                    if __pynmrstar_v3_2__:
                        loop = sf.get_loop(lp_category)
                    else:
                        loop = sf.get_loop_by_category(lp_category)

                    if has_bool_key:

                        for itName in [k['name'] for k in key_items if k['type'] == 'bool']:

                            if itName in loop.tags:

                                itCol = loop.tags.index(itName)

                                for row in loop:

                                    val = row[itCol]

                                    if val in emptyValue:
                                        continue

                                    if val.lower() in trueValue:
                                        row[itCol] = yes_value
                                    else:
                                        row[itCol] = no_value

                    if has_bool_data:

                        for itName in [d['name'] for d in data_items if d['type'] == 'bool']:

                            if itName in loop.tags:

                                itCol = loop.tags.index(itName)

                                for row in loop:

                                    val = row[itCol]

                                    if val in emptyValue:
                                        continue

                                    if val.lower() in trueValue:
                                        row[itCol] = yes_value
                                    else:
                                        row[itCol] = no_value

        return True

    def __resetBoolValueInAuxLoop(self):
        """ Reset bool values in auxiliary loops depending on file type.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        yes_value = 'true' if file_type == 'nef' else 'yes'
        no_value = 'false' if file_type == 'nef' else 'no'

        if input_source_dic['content_subtype'] is None:
            return False

        for content_subtype in input_source_dic['content_subtype']:

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]

            for sf in self.__star_data[0].get_saveframes_by_category(sf_category):
                # sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                for loop in sf.loops:

                    lp_category = loop.category

                    if lp_category is None:
                        continue

                    # main content of loop has been processed in __resetBoolValueInLoop()
                    if lp_category in self.lp_categories[file_type][content_subtype]:
                        continue

                    if self.aux_lp_categories[file_type][content_subtype] is None:
                        continue

                    if lp_category in self.aux_lp_categories[file_type][content_subtype]:

                        key_items = self.aux_key_items[file_type][content_subtype][lp_category]
                        data_items = self.aux_data_items[file_type][content_subtype][lp_category]

                        has_bool_key = False

                        if key_items is not None:
                            has_bool_key = next((k['type'] == 'bool' for k in key_items if k['type'] == 'bool'), False)

                        has_bool_data = False

                        if data_items is not None:
                            has_bool_data = next((d['type'] == 'bool' for d in data_items if d['type'] == 'bool'), False)

                        if has_bool_key or has_bool_data:

                            if __pynmrstar_v3_2__:
                                _loop = sf.get_loop(lp_category)
                            else:
                                _loop = sf.get_loop_by_category(lp_category)

                            if has_bool_key:

                                for itName in [k['name'] for k in key_items if k['type'] == 'bool']:

                                    if itName in _loop.tags:

                                        itCol = _loop.tags.index(itName)

                                        for row in _loop:

                                            val = row[itCol]

                                            if val in emptyValue:
                                                continue

                                            if val.lower() in trueValue:
                                                row[itCol] = yes_value
                                            else:
                                                row[itCol] = no_value

                            if has_bool_data:

                                for itName in [d['name'] for d in data_items if d['type'] == 'bool']:

                                    if itName in _loop.tags:

                                        itCol = _loop.tags.index(itName)

                                        for row in _loop:

                                            val = row[itCol]

                                            if val in emptyValue:
                                                continue

                                            if val.lower() in trueValue:
                                                row[itCol] = yes_value
                                            else:
                                                row[itCol] = no_value

        return True

    def __appendParentSfTag(self):
        """ Append parent tag of saveframe if not exists.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if file_type == 'nef':
            return True

        if input_source_dic['content_subtype'] is None:
            return False

        try:

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype in ('entry_info', 'entity'):
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]

                data_items = self.data_items[file_type][content_subtype]

                list_id_tag_in_lp = None

                if data_items is not None:
                    list_id_tag_in_lp = next((d for d in data_items if d['type'] == 'pointer-index'), None)

                if list_id_tag_in_lp is not None:

                    for sf in self.__star_data[0].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if not any(loop for loop in sf.loops if loop.category == lp_category):
                            continue

                        warn_desc = self.report.warning.getDescription('duplicated_index', file_name, sf_framecode)

                        if (warn_desc is not None) and warn_desc.split(' ')[0] == self.sf_tag_prefixes[file_type][content_subtype].lstrip('_') + '.ID':
                            continue

                        if __pynmrstar_v3_2__:
                            loop = sf.get_loop(lp_category)
                        else:
                            loop = sf.get_loop_by_category(lp_category)

                        itName = list_id_tag_in_lp['name']

                        if itName in loop.tags:

                            itCol = loop.tags.index(itName)

                            list_ids = []

                            for row in loop:

                                val = row[itCol]

                                if val in emptyValue:
                                    continue

                                list_ids.append(val)

                            list_id = collections.Counter(list_ids).most_common()[0][0]

                            tagNames = [t[0] for t in sf.tags]

                            if 'ID' in tagNames:

                                itCol = tagNames.index('ID')

                                sf.tags[itCol][1] = list_id

                            else:

                                sf_tag = '_' + sf_category + '.ID'
                                warn = self.__warn_template_for_missing_mandatory_sf_tag % (sf_tag, file_type.upper())

                                if self.__check_mandatory_tag and self.__nefT.is_mandatory_tag(sf_tag, file_type):

                                    if self.__rescue_mode:
                                        self.report.warning.appendDescription('missing_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': sf_category,
                                                                               'description': warn})
                                        self.report.setWarning()

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__appendParentSfTag() ++ Warning  - {warn}\n")

                                sf.add_tag('ID', list_id)

            return True

        except ValueError as e:

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__appendParentSfTag() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__appendParentSfTag() ++ Error  - {str(e)}\n")

            return False

    def __addUnnamedEntryId(self):
        """ Add UNNAMED entry id.
        """

        if not self.__combined_mode:
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        # update datablock name

        if self.__star_data_type[0] == 'Entry':
            if self.__bmrb_only and self.__internal_mode and self.__bmrb_id is not None:
                self.__star_data[0].entry_id = self.__bmrb_id
            else:
                self.__star_data[0].entry_id = f'nef_{self.__entry_id.lower()}'

        self.__sortCsLoop()

        if file_type == 'nef':
            return True

        self.__c2S.set_entry_id(self.__star_data[0], self.__entry_id)

        return True

    def __sortCsLoop(self):
        """ Sort assigned chemical shift loop if required.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_name = input_source_dic['file_name']
        file_type = input_source_dic['file_type']

        if input_source_dic['content_subtype'] is None:
            return False

        content_subtype = 'chem_shift'

        if content_subtype not in input_source_dic['content_subtype']:
            return False

        sf_category = self.sf_categories[file_type][content_subtype]
        lp_category = self.lp_categories[file_type][content_subtype]

        key_items = self.key_items[file_type][content_subtype]
        data_items = self.data_items[file_type][content_subtype]
        allowed_tags = self.allowed_tags[file_type][content_subtype]

        item_names = self.item_names_in_cs_loop[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        iso_number_name = item_names['isotope_number']
        atom_id_name = item_names['atom_id']
        idx_name = 'ID'

        modified = False

        for sf in self.__star_data[0].get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

            if self.report.error.exists(file_name, sf_framecode):
                continue

            try:

                lp_data = next((lp['data'] for lp in self.__lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                if lp_data is None:
                    lp_data = self.__nefT.check_data(sf, lp_category, key_items, data_items, allowed_tags, None, None,
                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                     excl_missing_data=self.__excl_missing_data)[0]

                    self.__lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': lp_data})

                _key_items = copy.copy(key_items)
                _key_items.append({'name': idx_name, 'type': 'positive-int'})

                _lp_data = self.__nefT.check_data(sf, lp_category, _key_items, data_items, allowed_tags, None, None,
                                                  enforce_allowed_tags=(file_type == 'nmr-star'),
                                                  excl_missing_data=self.__excl_missing_data)[0]

            except Exception:
                continue

            atoms = []

            chain_ids = set()

            for row in _lp_data:
                chain_ids.add(row[chain_id_name])

            min_seq_ids = {c: 0 for c in chain_ids}

            for row in _lp_data:
                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]

                if seq_id < min_seq_ids[chain_id]:
                    min_seq_ids[chain_id] = seq_id

            for row in _lp_data:
                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]
                iso_number = row[iso_number_name]
                atom_id = row[atom_id_name]
                idx = row[idx_name]

                atoms.append((chain_id if isinstance(chain_id, int)
                              else int(chain_id) if chain_id.isdigit()
                              else letterToDigit(chain_id),
                              seq_id - min_seq_ids[chain_id],
                              iso_number, atom_id, idx))

            sorted_atoms = sorted(atoms, key=itemgetter(0, 1, 2, 3))

            sorted_idx = [atom[4] for atom in sorted_atoms]

            if sorted_idx != list(range(1, len(_lp_data) + 1)):

                if __pynmrstar_v3_2__:
                    loop = sf.get_loop(lp_category)
                else:
                    loop = sf.get_loop_by_category(lp_category)

                lp = pynmrstar.Loop.from_scratch(lp_category)

                for tag in loop.tags:
                    lp.add_tag(lp_category + '.' + tag)

                dat = [int(idx) for idx in get_lp_tag(loop, [idx_name])]

                idx_col = lp.tags.index(idx_name)

                for new_idx, old_idx in enumerate(sorted_idx, start=1):
                    row = loop.data[dat.index(old_idx)]
                    row[idx_col] = new_idx

                    lp.add_data(row)

                del sf[loop]

                sf.add_loop(lp)

                modified = True

        if modified:
            self.__depositNmrData()

        return True

    def __depositNmrData(self):
        """ Deposit next NMR unified data file.
        """

        if not self.__combined_mode:

            if self.__bmrb_only and self.__internal_mode:
                master_entry = self.__star_data[0]

                master_entry = self.__c2S.normalize(master_entry)

                if __pynmrstar_v3__:
                    master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
                else:
                    master_entry.write_to_file(self.__dstPath)

            return True

        if self.__dstPath is None:

            if not self.__op.endswith('consistency-check'):

                err = "Not found destination file path."

                self.report.error.appendDescription('internal_error', "+NmrDpUtility.__depositNmrData() ++ Error  - " + err)
                self.report.setError()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__depositNmrData() ++ Error  - {err}\n")

            return False

        if self.__dstPath == self.__srcPath and self.__release_mode:
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        master_entry = self.__star_data[0]

        master_entry = self.__c2S.normalize(master_entry)

        if not self.__annotation_mode or self.__dstPath != self.__srcPath:
            if __pynmrstar_v3__:
                master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
            else:
                master_entry.write_to_file(self.__dstPath)

        if self.__op in ('nmr-str2str-deposit', 'nmr-str2cif-deposit', 'nmr-str2cif-annotate') and self.__remediation_mode:

            dir_path = os.path.dirname(self.__dstPath)

            rem_dir = os.path.join(dir_path, 'remediation')

            try:

                if not os.path.isdir(rem_dir):
                    os.makedirs(rem_dir)

                nmr_file_name = os.path.basename(self.__dstPath)

                if nmr_file_name.endswith('_nmr_data.str'):
                    nmr_file_link = os.path.join(rem_dir, nmr_file_name)

                    if os.path.exists(nmr_file_link):
                        os.remove(nmr_file_link)

                    os.symlink(self.__dstPath, nmr_file_link)

            except OSError:
                pass

        if 'nef' not in self.__op and ('deposit' in self.__op or 'annotate' in self.__op) and 'nmr_cif_file_path' in self.__outputParamDict:

            try:

                myIo = IoAdapterPy(False, sys.stderr)
                containerList = myIo.readFile(self.__dstPath__)

                if containerList is not None and len(containerList) > 1:

                    if self.__verbose:
                        self.__lfh.write(f"Input container list is {[(c.getName(), c.getType()) for c in containerList]!r}\n")

                    for c in containerList:
                        c.setType('data')

                    myIo.writeFile(self.__outputParamDict['nmr_cif_file_path'], containerList=containerList[1:])

            except Exception as e:
                self.__lfh.write(f"+NmrDpUtility.__depositNmrData() ++ Error  - {str(e)}\n")

        return not self.report.isError()

    def __depositLegacyNmrData(self):
        """ Deposit next NMR legacy data files.
        """

        if self.__combined_mode or self.__dstPath is None:
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None or self.__star_data_type[0] != 'Entry':
            return False

        master_entry = self.__star_data[0]

        master_entry.entry_id = f'cs_{self.__entry_id.lower()}'

        self.__c2S.set_entry_id(master_entry, self.__entry_id)
        self.__c2S.normalize(master_entry)

        master_entry = self.__c2S.normalize_str(master_entry)

        if __pynmrstar_v3__:
            master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
        else:
            master_entry.write_to_file(self.__dstPath)

        if 'nmr_cif_file_path' in self.__outputParamDict:

            try:

                myIo = IoAdapterPy(False, sys.stderr)
                containerList = myIo.readFile(self.__dstPath)

                if containerList is not None and len(containerList) > 1:

                    if self.__verbose:
                        self.__lfh.write(f"Input container list is {[(c.getName(), c.getType()) for c in containerList]!r}\n")

                    for c in containerList:
                        c.setType('data')

                    myIo.writeFile(self.__outputParamDict['nmr_cif_file_path'], containerList=containerList[1:])

                    return True

            except Exception as e:
                self.__lfh.write(f"+NmrDpUtility.__depositLegacyNmrData() ++ Error  - {str(e)}\n")

        return False

    def __mergeLegacyCsAndMr(self):
        """ Merge CS+MR into next NMR unifed data files.
        """

        if self.__combined_mode or not self.__remediation_mode or self.__dstPath is None:
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        master_entry = self.__star_data[0]

        if not isinstance(master_entry, pynmrstar.Entry):
            return False

        sf_framecode = 'constraint_statistics'

        cst_sfs = master_entry.get_saveframes_by_category(sf_framecode)

        if len(cst_sfs) > 0:
            for cst_sf in reversed(cst_sfs):
                del master_entry[cst_sf]

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        original_file_name = input_source_dic['file_name']
        if 'original_file_name' in input_source_dic and input_source_dic['original_file_name'] is not None:
            original_file_name = os.path.basename(input_source_dic['original_file_name'])

        file_type = 'nmr-star'

        master_entry.entry_id = f'cs_{self.__entry_id.lower()}'

        self.__c2S.set_entry_id(master_entry, self.__entry_id)
        self.__c2S.normalize(master_entry)

        master_entry = self.__c2S.normalize_str(master_entry)

        if self.__remediation_mode and self.__internal_mode:

            cs_file_path_list = 'chem_shift_file_path_list'

            if isinstance(self.__inputParamDict[cs_file_path_list][0], str):
                dir_path = os.path.dirname(self.__inputParamDict[cs_file_path_list][0])
            else:
                dir_path = os.path.dirname(self.__inputParamDict[cs_file_path_list][0]['file_name'])

            dst_cs_path = os.path.join(dir_path, input_source_dic['file_name'])

            if __pynmrstar_v3__:
                master_entry.write_to_file(dst_cs_path, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
            else:
                master_entry.write_to_file(dst_cs_path)

        if self.__bmrb_only and self.__internal_mode and self.__bmrb_id is not None:
            master_entry.entry_id = self.__bmrb_id
        else:
            master_entry.entry_id = f'nef_{self.__entry_id.lower()}'

        self.__c2S.set_entry_id(master_entry, self.__entry_id)

        # remove _Audit loop if exists

        content_subtype = 'entry_info'

        sf_category = self.sf_categories[file_type][content_subtype]

        try:

            sf = master_entry.get_saveframes_by_category(sf_category)[0]

            try:
                if __pynmrstar_v3_2__:
                    loop = sf.get_loop('_Audit')
                else:
                    loop = sf.get_loop_by_category('_Audit')

                del sf[loop]

            except KeyError:
                pass

        except IndexError:
            pass

        # Refresh _Constraint_stat_list saveframe

        sf_framecode = 'constraint_statistics'

        cst_sf = pynmrstar.Saveframe.from_scratch(sf_framecode)
        cst_sf.set_tag_prefix('_Constraint_stat_list')
        cst_sf.add_tag('Sf_category', sf_framecode)
        cst_sf.add_tag('Sf_framecode', sf_framecode)
        cst_sf.add_tag('Entry_ID', self.__entry_id)
        cst_sf.add_tag('ID', 1)

        if self.__remediation_mode:

            ar_file_path_list = 'atypical_restraint_file_path_list'

            if ar_file_path_list in self.__inputParamDict:

                fileListId = self.__file_path_list_len

                for ar in self.__inputParamDict[ar_file_path_list]:

                    input_source = self.report.input_sources[fileListId]
                    input_source_dic = input_source.get()

                    file_name = input_source_dic['file_name']
                    cst_sf.add_tag('Data_file_name', file_name)

                    break

        # statistics

        if self.__mr_sf_dict_holder is not None:

            content_subtype = 'dist_restraint'

            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    if 'NOE_dist_averaging_method' in sf_item:
                        cst_sf.add_tag('NOE_dist_averaging_method', sf_item['NOE_dist_averaging_method'])
                        break

                NOE_tot_num = 0

                NOE_intraresidue_tot_num = 0
                NOE_sequential_tot_num = 0
                NOE_medium_range_tot_num = 0
                NOE_long_range_tot_num = 0
                NOE_unique_tot_num = 0
                NOE_intraresidue_unique_tot_num = 0
                NOE_sequential_unique_tot_num = 0
                NOE_medium_range_unique_tot_num = 0
                NOE_long_range_unique_tot_num = 0
                NOE_unamb_intramol_tot_num = 0
                NOE_unamb_intermol_tot_num = 0
                NOE_ambig_intramol_tot_num = 0
                NOE_ambig_intermol_tot_num = 0
                NOE_interentity_tot_num = 0
                NOE_other_tot_num = 0

                for sf_item in self.__mr_sf_dict_holder[content_subtype]:

                    sf = sf_item['saveframe']
                    sf_framecode = get_first_sf_tag(sf, 'Sf_framecode')

                    # MR parser for XPLOR-NIH/CNS/CHARMM already fills _Gen_dist_constraint.ID with genuine IDs
                    if not sf_framecode.startswith('XPLOR') and not sf_framecode.startswith('CNS')\
                       and not sf_framecode.startswith('CHARMM'):
                        self.__updateGenDistConstIdInMrStr(sf_item)

                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if 'NOE' in constraint_type:
                        # NOE_tot_num += sf_item['id']

                        lp = sf_item['loop']

                        item_names = self.item_names_in_ds_loop[file_type]
                        id_col = lp.tags.index('ID')
                        chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                        chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                        seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                        seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                        comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                        comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                        atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                        atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                        # try:
                        #     member_logic_code_col = lp.tags.index(item_names['member_logic_code'])
                        # except ValueError:
                        #     member_logic_code_col = -1
                        try:
                            combination_id_col = lp.tags.index(item_names['combination_id'])
                        except ValueError:
                            combination_id_col = -1
                        try:
                            upper_limit_col = lp.tags.index(item_names['upper_limit'])
                        except ValueError:
                            upper_limit_col = -1

                        prev_id = -1
                        # _atom1 = _atom2 = None

                        for row in lp:
                            _id = int(row[id_col])
                            # member_logic_code = row[member_logic_code_col] if member_logic_code_col != -1 else None
                            try:
                                chain_id_1 = int(row[chain_id_1_col])
                                chain_id_2 = int(row[chain_id_2_col])
                                seq_id_1 = int(row[seq_id_1_col])
                                seq_id_2 = int(row[seq_id_2_col])
                            except (ValueError, TypeError):
                                continue
                            comp_id_1 = row[comp_id_1_col]
                            comp_id_2 = row[comp_id_2_col]
                            atom_id_1 = row[atom_id_1_col]
                            atom_id_2 = row[atom_id_2_col]

                            if atom_id_1 in emptyValue or atom_id_2 in emptyValue or _id == prev_id:
                                continue

                            prev_id = _id

                            combination_id = row[combination_id_col] if combination_id_col != -1 else None
                            upper_limit = float(row[upper_limit_col]) if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue else None

                            offset = abs(seq_id_1 - seq_id_2)
                            ambig = upper_limit is not None and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP)
                            uniq = combination_id in emptyValue and not ambig

                            NOE_tot_num += 1

                            if uniq:
                                NOE_unique_tot_num += 1

                            if chain_id_1 == chain_id_2:
                                if uniq:
                                    NOE_unamb_intramol_tot_num += 1
                                else:
                                    NOE_ambig_intramol_tot_num += 1
                                if offset == 0:
                                    NOE_intraresidue_tot_num += 1
                                    if uniq:
                                        NOE_intraresidue_unique_tot_num += 1
                                elif offset == 1:
                                    NOE_sequential_tot_num += 1
                                    if uniq:
                                        NOE_sequential_unique_tot_num += 1
                                elif offset < 5:
                                    NOE_medium_range_tot_num += 1
                                    if uniq:
                                        NOE_medium_range_unique_tot_num += 1
                                else:
                                    NOE_long_range_tot_num += 1
                                    if uniq:
                                        NOE_long_range_unique_tot_num += 1
                            else:
                                NOE_interentity_tot_num += 1
                                if uniq:
                                    NOE_unamb_intermol_tot_num += 1
                                else:
                                    NOE_ambig_intermol_tot_num += 1

                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if constraint_type in ('paramagnetic relaxation',
                                           'photo cidnp',
                                           'chemical shift perturbation',
                                           'mutation',
                                           'symmetry'):
                        NOE_other_tot_num += sf_item['id']

                if NOE_tot_num > 0:
                    cst_sf.add_tag('NOE_tot_num', NOE_tot_num)
                    cst_sf.add_tag('NOE_intraresidue_tot_num', NOE_intraresidue_tot_num)
                    cst_sf.add_tag('NOE_sequential_tot_num', NOE_sequential_tot_num)
                    cst_sf.add_tag('NOE_medium_range_tot_num', NOE_medium_range_tot_num)
                    cst_sf.add_tag('NOE_long_range_tot_num', NOE_long_range_tot_num)
                    cst_sf.add_tag('NOE_unique_tot_num', NOE_unique_tot_num)
                    cst_sf.add_tag('NOE_intraresidue_unique_tot_num', NOE_intraresidue_unique_tot_num)
                    cst_sf.add_tag('NOE_sequential_unique_tot_num', NOE_sequential_unique_tot_num)
                    cst_sf.add_tag('NOE_medium_range_unique_tot_num', NOE_medium_range_unique_tot_num)
                    cst_sf.add_tag('NOE_long_range_unique_tot_num', NOE_long_range_unique_tot_num)
                    cst_sf.add_tag('NOE_unamb_intramol_tot_num', NOE_unamb_intramol_tot_num)
                    cst_sf.add_tag('NOE_unamb_intermol_tot_num', NOE_unamb_intermol_tot_num)
                    cst_sf.add_tag('NOE_ambig_intramol_tot_num', NOE_ambig_intramol_tot_num)
                    cst_sf.add_tag('NOE_ambig_intermol_tot_num', NOE_ambig_intermol_tot_num)
                    cst_sf.add_tag('NOE_interentity_tot_num', NOE_interentity_tot_num)
                    cst_sf.add_tag('NOE_other_tot_num', NOE_other_tot_num)

                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    if 'ROE_dist_averaging_method' in sf_item:
                        cst_sf.add_tag('ROE_dist_averaging_method', sf_item['ROE_dist_averaging_method'])
                        break

                ROE_tot_num = 0

                ROE_intraresidue_tot_num = 0
                ROE_sequential_tot_num = 0
                ROE_medium_range_tot_num = 0
                ROE_long_range_tot_num = 0
                ROE_unambig_intramol_tot_num = 0
                ROE_unambig_intermol_tot_num = 0
                ROE_ambig_intramol_tot_num = 0
                ROE_ambig_intermol_tot_num = 0
                ROE_other_tot_num = 0
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if 'ROE' in constraint_type:
                        # ROE_tot_num += sf_item['id']

                        lp = sf_item['loop']

                        item_names = self.item_names_in_ds_loop[file_type]
                        id_col = lp.tags.index('ID')
                        chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                        chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                        seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                        seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                        comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                        comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                        atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                        atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                        # try:
                        #     member_logic_code_col = lp.tags.index(item_names['member_logic_code'])
                        # except ValueError:
                        #     member_logic_code_col = -1
                        try:
                            combination_id_col = lp.tags.index(item_names['combination_id'])
                        except ValueError:
                            combination_id_col = -1
                        try:
                            upper_limit_col = lp.tags.index(item_names['upper_limit'])
                        except ValueError:
                            upper_limit_col = -1

                        prev_id = -1
                        # _atom1 = _atom2 = None

                        for row in lp:
                            _id = int(row[id_col])
                            # member_logic_code = row[member_logic_code_col] if member_logic_code_col != -1 else None
                            try:
                                chain_id_1 = int(row[chain_id_1_col])
                                chain_id_2 = int(row[chain_id_2_col])
                                seq_id_1 = int(row[seq_id_1_col])
                                seq_id_2 = int(row[seq_id_2_col])
                            except (ValueError, TypeError):
                                continue
                            comp_id_1 = row[comp_id_1_col]
                            comp_id_2 = row[comp_id_2_col]
                            atom_id_1 = row[atom_id_1_col]
                            atom_id_2 = row[atom_id_2_col]

                            if atom_id_1 in emptyValue or atom_id_2 in emptyValue or _id == prev_id:
                                continue

                            prev_id = _id

                            combination_id = row[combination_id_col] if combination_id_col != -1 else None
                            upper_limit = float(row[upper_limit_col]) if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue else None

                            offset = abs(seq_id_1 - seq_id_2)
                            ambig = upper_limit is not None and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP)
                            uniq = combination_id in emptyValue and not ambig

                            ROE_tot_num += 1

                            if chain_id_1 == chain_id_2:
                                if uniq:
                                    ROE_unambig_intramol_tot_num += 1
                                else:
                                    ROE_ambig_intramol_tot_num += 1
                                if offset == 0:
                                    ROE_intraresidue_tot_num += 1
                                elif offset == 1:
                                    ROE_sequential_tot_num += 1
                                elif offset < 5:
                                    ROE_medium_range_tot_num += 1
                                else:
                                    ROE_long_range_tot_num += 1
                            else:
                                ROE_other_tot_num += 1
                                if uniq:
                                    ROE_unambig_intermol_tot_num += 1
                                else:
                                    ROE_ambig_intermol_tot_num += 1

                if ROE_tot_num > 0:
                    cst_sf.add_tag('ROE_tot_num', ROE_tot_num)
                    cst_sf.add_tag('ROE_intraresidue_tot_num', ROE_intraresidue_tot_num)
                    cst_sf.add_tag('ROE_sequential_tot_num', ROE_sequential_tot_num)
                    cst_sf.add_tag('ROE_medium_range_tot_num', ROE_medium_range_tot_num)
                    cst_sf.add_tag('ROE_long_range_tot_num', ROE_long_range_tot_num)
                    cst_sf.add_tag('ROE_unambig_intramol_tot_num', ROE_unambig_intramol_tot_num)
                    cst_sf.add_tag('ROE_unambig_intermol_tot_num', ROE_unambig_intermol_tot_num)
                    cst_sf.add_tag('ROE_ambig_intramol_tot_num', ROE_ambig_intramol_tot_num)
                    cst_sf.add_tag('ROE_ambig_intermol_tot_num', ROE_ambig_intermol_tot_num)
                    cst_sf.add_tag('ROE_other_tot_num', ROE_other_tot_num)

            content_subtype = 'dihed_restraint'

            auth_to_entity_type = self.__caC['auth_to_entity_type'] if self.__caC is not None else {}

            Dihedral_angle_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    Dihedral_angle_tot_num += sf_item['id']

            if Dihedral_angle_tot_num > 0:
                cst_sf.add_tag('Dihedral_angle_tot_num', Dihedral_angle_tot_num)

            Protein_dihedral_angle_tot_num = 0

            Protein_phi_angle_tot_num = 0
            Protein_psi_angle_tot_num = 0
            Protein_chi_one_angle_tot_num = 0
            Protein_other_angle_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    self.__updateTorsionAngleConstIdInMrStr(sf_item)

                    lp = sf_item['loop']

                    id_col = lp.tags.index('ID')
                    auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                    auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                    auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                    angle_name_col = lp.tags.index('Torsion_angle_name')

                    _protein_angles = _other_angles = 0
                    _protein_bb_angles = _protein_oth_angles = 0

                    prev_id = -1
                    for row in lp:
                        _id = int(row[id_col])
                        if _id == prev_id:
                            continue
                        prev_id = _id
                        auth_asym_id = row[auth_asym_id_col]
                        try:
                            auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                        except (ValueError, TypeError):
                            continue
                        auth_comp_id = row[auth_comp_id_col]
                        angle_name = row[angle_name_col]
                        if angle_name is None:
                            continue

                        seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                        if seq_key in auth_to_entity_type:
                            entity_type = auth_to_entity_type[seq_key]

                            if 'peptide' in entity_type:
                                Protein_dihedral_angle_tot_num += 1
                                _protein_angles += 1
                                if angle_name == 'PHI':
                                    Protein_phi_angle_tot_num += 1
                                    _protein_bb_angles += 1
                                elif angle_name == 'PSI':
                                    Protein_psi_angle_tot_num += 1
                                    _protein_bb_angles += 1
                                elif angle_name == 'CHI1':
                                    Protein_chi_one_angle_tot_num += 1
                                    _protein_oth_angles += 1
                                else:
                                    Protein_other_angle_tot_num += 1
                                    _protein_oth_angles += 1
                            else:
                                _other_angles += 1

                    if _protein_angles > _other_angles:
                        sf_item['constraint_type'] = 'protein dihedral angle'

                        sf = sf_item['saveframe']

                        if 'jcoup_restraint' not in self.__mr_sf_dict_holder:
                            set_sf_tag(sf, 'Constraint_type', 'backbone chemical shifts')

                        else:

                            _protein_jcoups = 0
                            _protein_bb_jcoups = 0
                            _protein_oth_jcoups = 0

                            for _sf_item in self.__mr_sf_dict_holder['jcoup_restraint']:

                                _lp = _sf_item['loop']

                                auth_asym_id_col = _lp.tags.index('Auth_asym_ID_2')
                                auth_seq_id_col = _lp.tags.index('Auth_seq_ID_2')
                                auth_comp_id_col = _lp.tags.index('Auth_comp_ID_2')
                                atom_id_1_col = _lp.tags.index('Atom_ID_1')
                                atom_id_4_col = _lp.tags.index('Atom_ID_4')

                                for _row in _lp:
                                    auth_asym_id = _row[auth_asym_id_col]
                                    try:
                                        auth_seq_id = int(_row[auth_seq_id_col])
                                    except (ValueError, TypeError):
                                        continue
                                    auth_comp_id = _row[auth_comp_id_col]
                                    atom_id_1 = _row[atom_id_1_col]
                                    atom_id_4 = _row[atom_id_4_col]

                                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                                    if seq_key in auth_to_entity_type:
                                        entity_type = auth_to_entity_type[seq_key]

                                        if 'peptide' in entity_type:
                                            _protein_jcoups += 1
                                            if 'H' in (atom_id_1, atom_id_4):
                                                _protein_bb_jcoups += 1
                                            else:
                                                _protein_oth_jcoups += 1

                            if (_protein_bb_angles > 0 and _protein_oth_angles == 0 and _protein_bb_jcoups > 0 and _protein_oth_jcoups == 0)\
                               or (_protein_bb_angles > 0 and _protein_oth_angles > 0 and _protein_bb_jcoups > 0 and _protein_oth_jcoups > 0)\
                               or (_protein_bb_angles == 0 and _protein_oth_angles > 0 and _protein_bb_jcoups == 0 and _protein_oth_jcoups > 0):
                                set_sf_tag(sf, 'Constraint_type', 'J-couplings')

                            elif _protein_jcoups == 0:
                                set_sf_tag(sf, 'Constraint_type', 'backbone chemical shifts')

                            else:
                                set_sf_tag(sf, 'Constraint_type', 'unknown')

            if Protein_dihedral_angle_tot_num > 0:
                cst_sf.add_tag('Protein_dihedral_angle_tot_num', Protein_dihedral_angle_tot_num)
                cst_sf.add_tag('Protein_phi_angle_tot_num', Protein_phi_angle_tot_num)
                cst_sf.add_tag('Protein_psi_angle_tot_num', Protein_psi_angle_tot_num)
                cst_sf.add_tag('Protein_chi_one_angle_tot_num', Protein_chi_one_angle_tot_num)
                cst_sf.add_tag('Protein_other_angle_tot_num', Protein_other_angle_tot_num)

            NA_dihedral_angle_tot_num = 0

            NA_alpha_angle_tot_num = 0
            NA_beta_angle_tot_num = 0
            NA_gamma_angle_tot_num = 0
            NA_delta_angle_tot_num = 0
            NA_epsilon_angle_tot_num = 0
            NA_chi_angle_tot_num = 0
            NA_other_angle_tot_num = 0
            NA_amb_dihedral_angle_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:

                    lp = sf_item['loop']

                    id_col = lp.tags.index('ID')
                    auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                    auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                    auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                    angle_name_col = lp.tags.index('Torsion_angle_name')

                    _na_angles = _other_angles = 0

                    prev_id = -1
                    for row in lp:
                        _id = int(row[id_col])
                        if _id == prev_id:
                            continue
                        prev_id = _id
                        auth_asym_id = row[auth_asym_id_col]
                        try:
                            auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                        except (ValueError, TypeError):
                            continue
                        auth_comp_id = row[auth_comp_id_col]
                        angle_name = row[angle_name_col]
                        if angle_name is None:
                            continue

                        seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                        if seq_key in auth_to_entity_type:
                            entity_type = auth_to_entity_type[seq_key]

                            if 'nucleotide' in entity_type:
                                NA_dihedral_angle_tot_num += 1
                                _na_angles += 1
                                if angle_name == 'ALPHA':
                                    NA_alpha_angle_tot_num += 1
                                elif angle_name == 'BETA':
                                    NA_beta_angle_tot_num += 1
                                elif angle_name == 'GAMMA':
                                    NA_gamma_angle_tot_num += 1
                                elif angle_name == 'DELTA':
                                    NA_delta_angle_tot_num += 1
                                elif angle_name == 'EPSILON':
                                    NA_epsilon_angle_tot_num += 1
                                elif angle_name == 'CHI':
                                    NA_chi_angle_tot_num += 1
                                elif angle_name == 'PPA':
                                    NA_amb_dihedral_angle_tot_num += 1
                                else:
                                    NA_other_angle_tot_num += 1
                            else:
                                _other_angles += 1

                    if _na_angles > _other_angles:
                        sf_item['constraint_type'] = 'nucleic acid dihedral angle'

                        sf = sf_item['saveframe']

                        if 'jcoup_restraint' not in self.__mr_sf_dict_holder:
                            set_sf_tag(sf, 'Constraint_type', 'unknown')

                        else:

                            _na_jcoups = 0

                            for _sf_item in self.__mr_sf_dict_holder['jcoup_restraint']:

                                _lp = _sf_item['loop']

                                auth_asym_id_col = _lp.tags.index('Auth_asym_ID_2')
                                auth_seq_id_col = _lp.tags.index('Auth_seq_ID_2')
                                auth_comp_id_col = _lp.tags.index('Auth_comp_ID_2')

                                for _row in _lp:
                                    auth_asym_id = _row[auth_asym_id_col]
                                    try:
                                        auth_seq_id = int(_row[auth_seq_id_col])
                                    except (ValueError, TypeError):
                                        continue
                                    auth_comp_id = _row[auth_comp_id_col]

                                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                                    if seq_key in auth_to_entity_type:
                                        entity_type = auth_to_entity_type[seq_key]

                                        if 'nucleotide' in entity_type:
                                            _na_jcoups += 1

                            set_sf_tag(sf, 'Constraint_type', 'J-couplings' if _na_jcoups > 0 else 'unknown')

            if NA_dihedral_angle_tot_num > 0:
                cst_sf.add_tag('NA_dihedral_angle_tot_num', NA_dihedral_angle_tot_num)
                cst_sf.add_tag('NA_alpha_angle_tot_num', NA_alpha_angle_tot_num)
                cst_sf.add_tag('NA_beta_angle_tot_num', NA_beta_angle_tot_num)
                cst_sf.add_tag('NA_gamma_angle_tot_num', NA_gamma_angle_tot_num)
                cst_sf.add_tag('NA_delta_angle_tot_num', NA_delta_angle_tot_num)
                cst_sf.add_tag('NA_epsilon_angle_tot_num', NA_epsilon_angle_tot_num)
                cst_sf.add_tag('NA_chi_angle_tot_num', NA_chi_angle_tot_num)
                cst_sf.add_tag('NA_other_angle_tot_num', NA_other_angle_tot_num)
                cst_sf.add_tag('NA_amb_dihedral_angle_tot_num', NA_amb_dihedral_angle_tot_num)

            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:

                    lp = sf_item['loop']

                    id_col = lp.tags.index('ID')
                    auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                    auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                    auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                    angle_name_col = lp.tags.index('Torsion_angle_name')

                    _br_angles = _other_angles = 0

                    prev_id = -1
                    for row in lp:
                        _id = int(row[id_col])
                        if _id == prev_id:
                            continue
                        prev_id = _id
                        auth_asym_id = row[auth_asym_id_col]
                        try:
                            auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                        except (ValueError, TypeError):
                            continue
                        auth_comp_id = row[auth_comp_id_col]
                        angle_name = row[angle_name_col]
                        if angle_name is None:
                            continue

                        seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                        if seq_key in auth_to_entity_type:
                            entity_type = auth_to_entity_type[seq_key]

                            if 'saccharide' in entity_type:
                                _br_angles += 1
                            else:
                                _other_angles += 1

                    if _br_angles > _other_angles:
                        sf_item['constraint_type'] = 'carbohydrate dihedral angle'  # DAOTHER-9471

                        sf = sf_item['saveframe']

                        if 'jcoup_restraint' not in self.__mr_sf_dict_holder:
                            set_sf_tag(sf, 'Constraint_type', 'unknown')

                        else:

                            _br_jcoups = 0

                            for _sf_item in self.__mr_sf_dict_holder['jcoup_restraint']:

                                _lp = _sf_item['loop']

                                auth_asym_id_col = _lp.tags.index('Auth_asym_ID_2')
                                auth_seq_id_col = _lp.tags.index('Auth_seq_ID_2')
                                auth_comp_id_col = _lp.tags.index('Auth_comp_ID_2')

                                for _row in _lp:
                                    auth_asym_id = _row[auth_asym_id_col]
                                    try:
                                        auth_seq_id = int(_row[auth_seq_id_col])
                                    except (ValueError, TypeError):
                                        continue
                                    auth_comp_id = _row[auth_comp_id_col]

                                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                                    if seq_key in auth_to_entity_type:
                                        entity_type = auth_to_entity_type[seq_key]

                                        if 'saccharide' in entity_type:
                                            _br_jcoups += 1

                            set_sf_tag(sf, 'Constraint_type', 'J-couplings' if _br_jcoups > 0 else 'unknown')

            content_subtype = 'rdc_restraint'

            RDC_tot_num = 0

            RDC_HH_tot_num = 0
            RDC_HNC_tot_num = 0
            RDC_NH_tot_num = 0
            RDC_CC_tot_num = 0
            RDC_CN_i_1_tot_num = 0
            RDC_CAHA_tot_num = 0
            RDC_HNHA_tot_num = 0
            RDC_HNHA_i_1_tot_num = 0
            RDC_CAC_tot_num = 0
            RDC_CAN_tot_num = 0
            RDC_other_tot_num = 0

            RDC_intraresidue_tot_num = 0
            RDC_sequential_tot_num = 0
            RDC_medium_range_tot_num = 0
            RDC_long_range_tot_num = 0

            RDC_unambig_intramol_tot_num = 0
            RDC_unambig_intermol_tot_num = 0
            RDC_ambig_intramol_tot_num = 0
            RDC_ambig_intermol_tot_num = 0
            RDC_intermol_tot_num = 0

            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    lp = sf_item['loop']

                    # RDC_tot_num += sf_item['id']

                    item_names = self.item_names_in_rdc_loop[file_type]
                    id_col = lp.tags.index('ID')
                    chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                    chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                    seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                    seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                    comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                    atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                    atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                    try:
                        combination_id_col = lp.tags.index(item_names['combination_id'])
                    except ValueError:
                        combination_id_col = -1

                    prev_id = -1
                    for row in lp:
                        _id = int(row[id_col])
                        if _id == prev_id:
                            continue
                        prev_id = _id
                        try:
                            chain_id_1 = int(row[chain_id_1_col])
                            chain_id_2 = int(row[chain_id_2_col])
                            seq_id_1 = int(row[seq_id_1_col])
                            seq_id_2 = int(row[seq_id_2_col])
                        except (ValueError, TypeError):
                            continue
                        comp_id_1 = row[comp_id_1_col]
                        atom_id_1 = row[atom_id_1_col]
                        atom_id_2 = row[atom_id_2_col]

                        if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                            continue

                        combination_id = row[combination_id_col] if combination_id_col != -1 else None

                        vector = {atom_id_1, atom_id_2}
                        offset = abs(seq_id_1 - seq_id_2)

                        RDC_tot_num += 1

                        if chain_id_1 == chain_id_2:
                            if vector == {'H', 'C'} and offset == 1:
                                RDC_HNC_tot_num += 1
                            elif vector == {'H', 'N'} and offset == 0:
                                RDC_NH_tot_num += 1
                            elif vector == {'C', 'N'} and offset == 1:
                                RDC_CN_i_1_tot_num += 1
                            elif vector == {'CA', 'HA'} and offset == 0:
                                RDC_CAHA_tot_num += 1
                            elif vector == {'H', 'HA'} and offset == 0:
                                RDC_HNHA_tot_num += 1
                            elif vector == {'H', 'HA'} and offset == 1:
                                RDC_HNHA_i_1_tot_num += 1
                            elif vector == {'CA', 'C'} and offset == 0:
                                RDC_CAC_tot_num += 1
                            elif vector == {'CA', 'N'} and offset == 0:
                                RDC_CAN_tot_num += 1
                            elif atom_id_1[0] == atom_id_2[0]:
                                if atom_id_1[0] in protonBeginCode:
                                    RDC_HH_tot_num += 1
                                elif atom_id_1[0] == 'C':
                                    RDC_CC_tot_num += 1
                                else:
                                    RDC_other_tot_num += 1
                            elif offset == 0 and comp_id_1 == 'TRP' and vector == {'HE1', 'NE1'}:
                                RDC_NH_tot_num += 1
                            elif offset == 0 and comp_id_1 == 'ARG' and vector == {'HE', 'NE'}:
                                RDC_NH_tot_num += 1
                            else:
                                RDC_other_tot_num += 1

                        if chain_id_1 == chain_id_2:
                            if offset == 0:
                                RDC_intraresidue_tot_num += 1
                            elif offset == 1:
                                RDC_sequential_tot_num += 1
                            elif offset < 5:
                                RDC_medium_range_tot_num += 1
                            else:
                                RDC_long_range_tot_num += 1
                            if combination_id in emptyValue:
                                RDC_unambig_intramol_tot_num += 1
                            else:
                                RDC_ambig_intramol_tot_num += 1

                        else:
                            RDC_intermol_tot_num += 1
                            if combination_id in emptyValue:
                                RDC_unambig_intermol_tot_num += 1
                            else:
                                RDC_ambig_intermol_tot_num += 1

            if RDC_tot_num > 0:
                cst_sf.add_tag('RDC_tot_num', RDC_tot_num)
                cst_sf.add_tag('RDC_HH_tot_num', RDC_HH_tot_num)
                cst_sf.add_tag('RDC_HNC_tot_num', RDC_HNC_tot_num)
                cst_sf.add_tag('RDC_NH_tot_num', RDC_NH_tot_num)
                cst_sf.add_tag('RDC_CC_tot_num', RDC_CC_tot_num)
                cst_sf.add_tag('RDC_CN_i_1_tot_num', RDC_CN_i_1_tot_num)
                cst_sf.add_tag('RDC_CAHA_tot_num', RDC_CAHA_tot_num)
                cst_sf.add_tag('RDC_HNHA_tot_num', RDC_HNHA_tot_num)
                cst_sf.add_tag('RDC_HNHA_i_1_tot_num', RDC_HNHA_i_1_tot_num)
                cst_sf.add_tag('RDC_CAC_tot_num', RDC_CAC_tot_num)
                cst_sf.add_tag('RDC_CAN_tot_num', RDC_CAN_tot_num)
                cst_sf.add_tag('RDC_other_tot_num', RDC_other_tot_num)
                cst_sf.add_tag('RDC_intraresidue_tot_num', RDC_intraresidue_tot_num)
                cst_sf.add_tag('RDC_sequential_tot_num', RDC_sequential_tot_num)
                cst_sf.add_tag('RDC_medium_range_tot_num', RDC_medium_range_tot_num)
                cst_sf.add_tag('RDC_long_range_tot_num', RDC_long_range_tot_num)
                cst_sf.add_tag('RDC_unambig_intramol_tot_num', RDC_unambig_intramol_tot_num)
                cst_sf.add_tag('RDC_unambig_intermol_tot_num', RDC_unambig_intermol_tot_num)
                cst_sf.add_tag('RDC_ambig_intramol_tot_num', RDC_ambig_intramol_tot_num)
                cst_sf.add_tag('RDC_ambig_intermol_tot_num', RDC_ambig_intermol_tot_num)
                cst_sf.add_tag('RDC_intermol_tot_num', RDC_intermol_tot_num)

            content_subtype = 'dist_restraint'

            hbond_pairs = set()
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if constraint_type != 'hydrogen bond':
                        continue

                    lp = sf_item['loop']

                    item_names = self.item_names_in_ds_loop[file_type]
                    chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                    chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                    seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                    seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                    comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                    comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                    atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                    atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                    for row in lp:
                        try:
                            chain_id_1 = int(row[chain_id_1_col])
                            chain_id_2 = int(row[chain_id_2_col])
                            seq_id_1 = int(row[seq_id_1_col])
                            seq_id_2 = int(row[seq_id_2_col])
                        except (ValueError, TypeError):
                            continue
                        comp_id_1 = row[comp_id_1_col]
                        comp_id_2 = row[comp_id_2_col]
                        atom_id_1 = row[atom_id_1_col]
                        atom_id_2 = row[atom_id_2_col]

                        if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                            continue

                        if atom_id_1[0] in protonBeginCode:
                            if self.__ccU.updateChemCompDict(comp_id_1):
                                bonded_atom_id_1 = self.__ccU.getBondedAtoms(comp_id_1, atom_id_1)
                                if len(bonded_atom_id_1) == 0:
                                    continue
                                if any(_row for _row in lp
                                       if (_row[chain_id_1_col] is not None and int(_row[chain_id_1_col]) == chain_id_1
                                           and _row[seq_id_1_col] is not None and int(_row[seq_id_1_col]) == seq_id_1
                                           and _row[atom_id_1_col] == bonded_atom_id_1[0])
                                       or (_row[chain_id_2_col] is not None and int(_row[chain_id_2_col]) == chain_id_1
                                           and _row[seq_id_2_col] is not None and int(_row[seq_id_2_col]) == seq_id_1
                                           and _row[atom_id_2_col] == bonded_atom_id_1[0])):
                                    continue
                        if atom_id_2[0] in protonBeginCode:
                            if self.__ccU.updateChemCompDict(comp_id_2):
                                bonded_atom_id_2 = self.__ccU.getBondedAtoms(comp_id_2, atom_id_2)
                                if len(bonded_atom_id_2) == 0:
                                    continue
                                if any(_row for _row in lp
                                       if (_row[chain_id_1_col] is not None and int(_row[chain_id_1_col]) == chain_id_2
                                           and _row[seq_id_1_col] is not None and int(_row[seq_id_1_col]) == seq_id_2
                                           and _row[atom_id_1_col] == bonded_atom_id_2[0])
                                       or (_row[chain_id_2_col] is not None and int(_row[chain_id_2_col]) == chain_id_2
                                           and _row[seq_id_2_col] is not None and int(_row[seq_id_2_col]) == seq_id_2
                                           and _row[atom_id_2_col] == bonded_atom_id_2[0])):
                                    continue
                        p1 = (chain_id_1, seq_id_1, atom_id_1)
                        p2 = (chain_id_2, seq_id_2, atom_id_2)
                        hbond_pair = sorted([p1, p2], key=itemgetter(0, 1, 2))
                        hbond_pairs.add(str(hbond_pair))

            H_bonds_constrained_tot_num = len(hbond_pairs)
            if H_bonds_constrained_tot_num > 0:
                cst_sf.add_tag('H_bonds_constrained_tot_num', H_bonds_constrained_tot_num)

            ssbond_pairs = set()
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if constraint_type != 'disulfide bond':
                        continue

                    lp = sf_item['loop']

                    item_names = self.item_names_in_ds_loop[file_type]
                    chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                    chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                    seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                    seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                    comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                    comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                    atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                    atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                    for row in lp:
                        try:
                            chain_id_1 = int(row[chain_id_1_col])
                            chain_id_2 = int(row[chain_id_2_col])
                            seq_id_1 = int(row[seq_id_1_col])
                            seq_id_2 = int(row[seq_id_2_col])
                        except (ValueError, TypeError):
                            continue
                        comp_id_1 = row[comp_id_1_col]
                        comp_id_2 = row[comp_id_2_col]
                        atom_id_1 = row[atom_id_1_col]
                        atom_id_2 = row[atom_id_2_col]

                        if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                            continue

                        if atom_id_1[0] in protonBeginCode:
                            if self.__ccU.updateChemCompDict(comp_id_1):
                                bonded_atom_id_1 = self.__ccU.getBondedAtoms(comp_id_1, atom_id_1)
                                if len(bonded_atom_id_1) == 0:
                                    continue
                                if any(_row for _row in lp
                                       if (_row[chain_id_1_col] is not None and int(_row[chain_id_1_col]) == chain_id_1
                                           and _row[seq_id_1_col] is not None and int(_row[seq_id_1_col]) == seq_id_1
                                           and _row[atom_id_1_col] == bonded_atom_id_1[0])
                                       or (_row[chain_id_2_col] is not None and int(_row[chain_id_2_col]) == chain_id_1
                                           and _row[seq_id_2_col] is not None and int(_row[seq_id_2_col]) == seq_id_1
                                           and _row[atom_id_2_col] == bonded_atom_id_1[0])):
                                    continue
                        if atom_id_2[0] in protonBeginCode:
                            if self.__ccU.updateChemCompDict(comp_id_2):
                                bonded_atom_id_2 = self.__ccU.getBondedAtoms(comp_id_2, atom_id_2)
                                if len(bonded_atom_id_2) == 0:
                                    continue
                                if any(_row for _row in lp
                                       if (_row[chain_id_1_col] is not None and int(_row[chain_id_1_col]) == chain_id_2
                                           and _row[seq_id_1_col] is not None and int(_row[seq_id_1_col]) == seq_id_2
                                           and _row[atom_id_1_col] == bonded_atom_id_2[0])
                                       or (_row[chain_id_2_col] is not None and int(_row[chain_id_2_col]) == chain_id_2
                                           and _row[seq_id_2_col] is not None and int(_row[seq_id_2_col]) == seq_id_2
                                           and _row[atom_id_2_col] == bonded_atom_id_2[0])):
                                    continue
                        p1 = (chain_id_1, seq_id_1, atom_id_1)
                        p2 = (chain_id_2, seq_id_2, atom_id_2)
                        ssbond_pair = sorted([p1, p2], key=itemgetter(0, 1, 2))
                        ssbond_pairs.add(str(ssbond_pair))

            SS_bonds_constrained_tot_num = len(ssbond_pairs)
            if SS_bonds_constrained_tot_num > 0:
                cst_sf.add_tag('SS_bonds_constrained_tot_num', SS_bonds_constrained_tot_num)

            content_subtype = 'jcoup_restraint'

            Derived_coupling_const_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    Derived_coupling_const_tot_num += sf_item['id']

            if Derived_coupling_const_tot_num > 0:
                cst_sf.add_tag('Derived_coupling_const_tot_num', Derived_coupling_const_tot_num)

            content_subtype = 'hvycs_restraint'

            Derived_CACB_chem_shift_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    Derived_CACB_chem_shift_tot_num += sf_item['id']

            if Derived_CACB_chem_shift_tot_num > 0:
                cst_sf.add_tag('Derived_CACB_chem_shift_tot_num', Derived_CACB_chem_shift_tot_num)

            content_subtype = 'procs_restraint'

            Derived_1H_chem_shift_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    Derived_1H_chem_shift_tot_num += sf_item['id']

            if Derived_1H_chem_shift_tot_num > 0:
                cst_sf.add_tag('Derived_1H_chem_shift_tot_num', Derived_1H_chem_shift_tot_num)

            content_subtype = 'dist_restraint'

            Derived_photo_cidnps_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if constraint_type != 'photo cidnp':
                        continue
                    Derived_photo_cidnps_tot_num += sf_item['id']

            if Derived_photo_cidnps_tot_num > 0:
                cst_sf.add_tag('Derived_photo_cidnps_tot_num', Derived_photo_cidnps_tot_num)

            Derived_paramag_relax_tot_num = 0
            if content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    sf = sf_item['saveframe']
                    potential_type = get_first_sf_tag(sf, 'Potential_type')
                    if 'lower' in potential_type:
                        continue
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    if constraint_type != 'paramagnetic relaxation':
                        continue
                    Derived_paramag_relax_tot_num += sf_item['id']

            if Derived_paramag_relax_tot_num > 0:
                cst_sf.add_tag('Derived_paramag_relax_tot_num', Derived_paramag_relax_tot_num)

            content_subtype = 'other_restraint'

            if content_subtype in self.__mr_sf_dict_holder:
                Protein_other_tot_num = 0
                NA_other_tot_num = 0
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    lp = sf_item['loop']
                    lp_tags = lp['tags']
                    lp_data = lp['data']

                    auth_asym_id_col = lp_tags.index('auth_asym_id') if 'auth_asym_id' in lp_tags\
                        else lp_tags.index('auth_asym_id_1') if 'auth_asym_id_1' in lp_tags\
                        else lp_tags.index('plane_1_auth_asym_id_1')
                    auth_seq_id_col = lp_tags.index('auth_seq_id') if 'auth_seq_id' in lp_tags\
                        else lp_tags.index('auth_seq_id_1') if 'auth_seq_id_1' in lp_tags\
                        else lp_tags.index('plane_1_auth_seq_id_1')
                    auth_comp_id_col = lp_tags.index('auth_comp_id') if 'auth_comp_id' in lp_tags\
                        else lp_tags.index('auth_comp_id_1') if 'auth_comp_id_1' in lp_tags\
                        else lp_tags.index('plane_1_auth_comp_id_1')

                    for row in lp_data:
                        auth_asym_id = row[auth_asym_id_col]
                        try:
                            auth_seq_id = int(row[auth_seq_id_col])
                        except (ValueError, TypeError):
                            continue
                        auth_comp_id = row[auth_comp_id_col]

                        seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                        if seq_key in auth_to_entity_type:
                            entity_type = auth_to_entity_type[seq_key]

                            if 'peptide' in entity_type:
                                Protein_other_tot_num += 1
                            elif 'nucleotide' in entity_type:
                                NA_other_tot_num += 1

                if Protein_other_tot_num > 0:
                    cst_sf.add_tag('Protein_other_tot_num', Protein_other_tot_num)
                if NA_other_tot_num > 0:
                    cst_sf.add_tag('NA_other_tot_num', NA_other_tot_num)

        lp_category = '_Constraint_file'
        cf_loop = pynmrstar.Loop.from_scratch(lp_category)

        cf_key_items = [{'name': 'ID', 'type': 'int'},
                        {'name': 'Constraint_filename', 'type': 'str'},
                        {'name': 'Software_ID', 'type': 'int'},
                        {'name': 'Software_label', 'type': 'str'},
                        {'name': 'Software_name', 'type': 'str'},
                        {'name': 'Block_ID', 'type': 'int'},
                        {'name': 'Constraint_type', 'type': 'enum',
                         'enum': ('distance', 'dipolar coupling', 'protein dihedral angle', 'nucleic acid dihedral angle',
                                  'coupling constant', 'chemical shift', 'other angle', 'chemical shift anisotropy',
                                  'hydrogen exchange', 'line broadening', 'pseudocontact shift', 'intervector projection angle',
                                  'protein peptide planarity', 'protein other kinds of constraints',
                                  'nucleic acid base planarity', 'nucleic acid other kinds of constraints',
                                  'carbohydrate dihedral angle')},
                        {'name': 'Constraint_subtype', 'type': 'enum',
                         'enum': ('Not applicable', 'NOE', 'NOE buildup', 'NOE not seen', 'general distance',
                                  'alignment tensor', 'chirality', 'prochirality', 'disulfide bond', 'hydrogen bond',
                                  'symmetry', 'ROE', 'peptide', 'ring', 'PRE')},
                        {'name': 'Constraint_subsubtype', 'type': 'enum',
                         'enum': ('ambi', 'simple')}
                        ]
        cf_data_items = [{'name': 'Constraint_number', 'type': 'int'},
                         {'name': 'Constraint_stat_list_ID', 'type': 'int', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                         {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                         ]

        tags = [lp_category + '.' + _item['name'] for _item in cf_key_items]
        tags.extend([lp_category + '.' + _item['name'] for _item in cf_data_items])

        for tag in tags:
            cf_loop.add_tag(tag)

        # inspect _Software saveframes to extend Software_ID in _Constraint_file loop

        defined_software = []
        software_dict = {}
        software_id = 0

        if 'software' in self.__sf_category_list:
            for sf in master_entry.get_saveframes_by_category('software'):
                _id = get_first_sf_tag('ID')
                _name = get_first_sf_tag(sf, 'Name')
                _code = get_first_sf_tag(sf, 'Sf_framecode')
                defined_software.append(_name)
                if _id not in emptyValue and _name not in emptyValue \
                   and _id.isdigit() and _name not in software_dict:
                    _id_ = int(_id)
                    software_dict[_name] = (_id_, _code)
                    software_id = max(software_id, _id_)

        file_name_dict = {}
        file_id = 0
        block_id = 0

        for content_subtype in self.mr_content_subtypes:
            if self.__mr_sf_dict_holder is not None and content_subtype in self.__mr_sf_dict_holder:
                for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                    row = [None] * len(tags)

                    sf = sf_item['saveframe']
                    file_name = get_first_sf_tag(sf, 'Data_file_name')
                    if file_name not in file_name_dict:
                        file_id += 1
                        file_name_dict[file_name] = file_id
                    row[0], row[1] = file_name_dict[file_name], file_name if len(file_name) > 0 else None
                    sf_allowed_tags = self.sf_allowed_tags[file_type][content_subtype]
                    if 'Constraint_file_ID' in sf_allowed_tags:
                        sf.add_tag('Constraint_file_ID', file_name_dict[file_name])
                    _name = get_first_sf_tag(sf, 'Sf_framecode').split('_')[0]
                    _name_ = _name.upper()
                    if _name == _name_:
                        _name = getPdbxNmrSoftwareName(_name_)
                        if _name in software_dict:
                            row[2], row[3], row[4] =\
                                software_dict[_name][0], \
                                f'${software_dict[_name][1]}' if _name in defined_software else None, \
                                _name
                        else:
                            software_id += 1
                            _code = f'software_{software_id}'
                            row[2], row[3], row[4] =\
                                software_id, f'${_code}' if _name in defined_software else None, \
                                _name
                            software_dict[_name] = (software_id, _code)
                    if 'Block_ID' in sf_allowed_tags:
                        block_id += 1
                        _block_id = str(block_id)
                        sf.add_tag('Block_ID', _block_id)
                        row[5] = _block_id
                    constraint_type = sf_item['constraint_type']
                    if constraint_type == 'planarity':
                        try:
                            for item in sf_item['loop']['data']:
                                auth_comp_id = item[4]
                                peptide, nucleotide, _ = self.__csStat.getTypeOfCompId(auth_comp_id)
                                if peptide:
                                    constraint_type = 'protein peptide planarity'
                                    break
                                if nucleotide:
                                    constraint_type = 'nucleic acid base planarity'
                        except (KeyError, IndexError):
                            pass

                    if constraint_type == 'scalar J-coupling':  # DAOTHER-9471
                        constraint_type = 'coupling constant'
                    if constraint_type == 'angle database':  # DAOTHER-9471
                        constraint_type = 'protein dihedral angle'
                    if constraint_type == 'paramagnetic relaxation enhancement':  # DAOTHER-9471
                        constraint_type = 'line broadening'
                    constraint_subtype = get_first_sf_tag(sf, 'Constraint_type') if content_subtype != 'other_restraint' else get_first_sf_tag(sf, 'Definition')
                    if len(constraint_subtype) == 0:
                        constraint_subtype = None
                    if content_subtype == 'auto_relax_restraint' and get_first_sf_tag(sf, 'Common_relaxation_type_name') == 'paramagnetic relaxation enhancement':
                        constraint_subtype = 'PRE'
                    if sf_item['file_type'] == 'nm-res-sax':
                        constraint_subtype = 'SAXS'
                    if constraint_subtype is not None and constraint_subtype == 'RDC':
                        constraint_type = 'dipolar coupling'  # DAOTHER-9471
                    if constraint_type == 'scalar J-coupling':  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'coupling constant', 'J-couplings'
                    if constraint_type in ('hydrogen bond', 'disulfide bond', 'diselenide bond'):  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'distance', constraint_type
                    if constraint_type in ('carbon chemical shift', 'proton chemical shift'):  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'chemical shift', f'{constraint_type}s'
                    if constraint_type == 'floating chiral stereo assignments':  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'chemical shift', constraint_type
                    if constraint_type == 'NOESY peak volume':  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'peak volume', 'NOE'
                    if constraint_type == 'radius of gyration':  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'coordinate geometry', constraint_type
                    if constraint_type == 'small angle X-ray scattering':  # DAOTHER-9471
                        constraint_type, constraint_subtype = 'coordinate geometry', 'SAXS'

                    if constraint_subtype is not None:
                        if 'prochirality' in constraint_subtype:  # DAOTHER-9471
                            constraint_subtype = 'prochirality'
                        if 'NCS restraint' in constraint_subtype:  # DAOTHER-9471
                            constraint_subtype = 'symmetry'
                        if constraint_subtype == 'angle restraint':  # DAOTHER-9471
                            constraint_subtype = 'general angle'
                        if constraint_subtype == 'chemical shift perturbation':  # DAOTHER-9471
                            constraint_subtype = 'CSP'
                        if constraint_subtype == 'covalent bond linkage':  # DAOTHER-9471
                            constraint_subtype = 'covalent bond'
                        if constraint_subtype == 'paramagnetic relaxation':  # DAOTHER-9471
                            constraint_subtype = 'PRE'
                        if 'planality' in constraint_subtype:  # DAOTHER-9471
                            constraint_subtype = None
                        if 'radius of gyration' in constraint_subtype:  # DAOTHER-9471
                            constraint_type, constraint_subtype = 'coordinate geometry', constraint_type
                        if constraint_subtype == 'unknown':  # DAOTHER-9471
                            constraint_subtype = 'Not applicable'

                    constraint_subsubtype = sf_item.get('constraint_subsubtype')

                    try:
                        id_col = sf_item['loop'].tags.index('ID')
                        count = 0

                        prev_id = -1
                        for _row in sf_item['loop']:
                            _id = int(_row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            count += 1

                        sf_item['id'] = count
                    except AttributeError:
                        pass

                    row[6], row[7], row[8], row[9] =\
                        constraint_type, constraint_subtype, constraint_subsubtype, sf_item['id']
                    row[10], row[11] = 1, self.__entry_id

                    cf_loop.add_data(row)

        ext_mr_sf_holder = []

        ar_file_path_list = 'atypical_restraint_file_path_list'

        if ar_file_path_list in self.__inputParamDict:

            fileListId = self.__file_path_list_len

            for ar in self.__inputParamDict[ar_file_path_list]:

                file_path = ar['file_name']

                input_source = self.report.input_sources[fileListId]
                input_source_dic = input_source.get()

                mr_file_type = input_source_dic['file_type']

                fileListId += 1

                if mr_file_type != 'nm-res-oth':
                    continue

                original_file_name = None
                if 'original_file_name' in input_source_dic:
                    if input_source_dic['original_file_name'] is not None:
                        original_file_name = os.path.basename(input_source_dic['original_file_name'])

                self.__list_id_counter = incListIdCounter(None, self.__list_id_counter)

                list_id = self.__list_id_counter['other_restraint']

                sf_framecode = f'NMR_restraints_not_interpreted_{list_id}'

                dir_path = os.path.dirname(file_path)

                details = None
                data_format = None

                unknown_mr_desc = os.path.join(dir_path, '.entry_with_unknown_mr')
                if os.path.exists(unknown_mr_desc):
                    with open(unknown_mr_desc, 'r') as ifh:
                        details = ifh.read().splitlines()
                        data_format = details.split(' ')[0]
                        if not data_format.isupper():
                            data_format = None
                        break

                sf = getSaveframe(None, sf_framecode, list_id, self.__entry_id, original_file_name,
                                  constraintType=details)

                file_id += 1
                sf.add_tag('Constraint_file_ID', file_id)

                block_id += 1
                _block_id = str(block_id)
                sf.add_tag('Block_ID', _block_id)

                row = [None] * len(tags)
                row[0], row[1], row[5] = file_id, original_file_name, _block_id

                if data_format is not None and data_format != 'UNKNOWN':
                    if data_format in software_dict:
                        row[2], row[3], row[4] =\
                            software_dict[data_format][0], \
                            f'${software_dict[data_format][1]}' if data_format in defined_software else None, \
                            data_format
                    else:
                        software_id += 1
                        _code = f'software_{software_id}'
                        row[2], row[3], row[4] =\
                            software_id, \
                            f'${_code}' if data_format in defined_software else None, \
                            data_format
                        software_dict[data_format] = (software_id, _code)

                sel_res_file = os.path.join(dir_path, file_path + '-selected-as-res-cif')

                if os.path.exists(sel_res_file):
                    data_format = 'mmCIF'

                sf.add_tag('Text_data_format', data_format)

                with open(file_path, 'r', encoding='ascii', errors='ignore') as ifh:
                    sf.add_tag('Text_data', ifh.read())

                row[10], row[11] = 1, self.__entry_id

                # cf_loop.add_data(row)

                ext_mr_sf_holder.append(sf)

                if not os.path.exists(sel_res_file) and self.__internal_mode:

                    err = f"Uninterpreted NMR restraints are stored in {sf_framecode} saveframe as raw text format. "\
                        "@todo: It needs to be reviewed."

                    self.report.error.appendDescription('internal_error', f"+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - {err}")
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - {err}\n")

        cst_sf.add_loop(cf_loop)

        if len(cf_loop) > 0:
            master_entry.add_saveframe(cst_sf)

        for content_subtype in self.mr_content_subtypes:
            if self.__mr_sf_dict_holder is not None and content_subtype in self.__mr_sf_dict_holder:
                if content_subtype != 'other_restraint':
                    lp_category = self.lp_categories[file_type][content_subtype]
                    for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                        sf = sf_item['saveframe']
                        sf_framecode = get_first_sf_tag(sf, 'Sf_framecode')
                        if content_subtype == 'fchiral_restraint':
                            set_sf_tag(sf, 'Stereo_assigned_count', sf_item['id'])
                        # if __pynmrstar_v3_2__:
                        #     lp = sf.get_loop(lp_category)
                        # else:
                        #     lp = sf.get_loop_by_category(lp_category)
                        if self.__bmrb_only:
                            if any(_sf for _sf in master_entry.frame_list if _sf.name == sf_framecode):
                                continue
                            if self.__internal_mode and self.__nmr_cif_sf_category_list is not None and sf.category in self.__nmr_cif_sf_category_list:
                                continue
                        else:
                            if any(_sf for _sf in master_entry.frame_list if _sf.name == sf_framecode):
                                err = f"Couldn't add a saveframe with name {sf_framecode!r} since a saveframe with that name already exists in {original_file_name!r} file. "\
                                      f"Please remove {sf_framecode!r} saveframe and re-upload the {self.readable_file_type[file_type]} file."

                                self.report.error.appendDescription('format_issue',
                                                                    {'file_name': file_name, 'description': err})
                                self.report.setError()

                                self.__lfh.write("+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - "
                                                 f"{file_name} {err}\n")
                                continue

                        master_entry.add_saveframe(sf)
                else:
                    for sf_item in self.__mr_sf_dict_holder[content_subtype]:
                        sf = sf_item['saveframe']
                        sf_framecode = sf.get_tag('Sf_framecode')[0]

                        other_data = {'entry_id': self.__entry_id,
                                      'saveframes': [{'name': sf_framecode,
                                                      'category': 'undefined',
                                                      'tag_prefix': '?',
                                                      'tags': [['Sf_category', 'undefined'],
                                                               ['Sf_framecode', sf_framecode],
                                                               ['Definition', sf.get_tag('Definition')[0]],
                                                               ['Data_file_name', sf.get_tag('Data_file_name')[0]],
                                                               ['ID', sf.get_tag('ID')[0]],
                                                               ['Entry_ID', self.__entry_id]
                                                               ],
                                                      'loops': [{'category': 'unknown',
                                                                 'tags': sf_item['loop']['tags'],
                                                                 'data': sf_item['loop']['data']
                                                                 }]
                                                      }]
                                      }

                        sf.add_tag('Text_data_format', 'json')
                        sf.add_tag('Text_data', getPrettyJson(other_data))

                        if self.__bmrb_only:
                            if any(_sf for _sf in master_entry.frame_list if _sf.name == sf_framecode):
                                continue
                            if self.__internal_mode and self.__nmr_cif_sf_category_list is not None and sf.category in self.__nmr_cif_sf_category_list:
                                continue
                        else:
                            if any(_sf for _sf in master_entry.frame_list if _sf.name == sf_framecode):
                                err = f"Couldn't add a saveframe with name {sf_framecode!r} since a saveframe with that name already exists in {original_file_name!r} file. "\
                                      f"Please remove {sf_framecode!r} saveframe and re-upload the {self.readable_file_type[file_type]} file."

                                self.report.error.appendDescription('format_issue',
                                                                    {'file_name': file_name, 'description': err})
                                self.report.setError()

                                self.__lfh.write("+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - "
                                                 f"{file_name} {err}\n")
                                continue

                        master_entry.add_saveframe(sf)

        for sf in ext_mr_sf_holder:
            master_entry.add_saveframe(sf)

        self.__mergeStrPk()

        if self.__merge_any_pk_as_is:  # DAOTHER-7407 enabled until Phase 2 release
            self.__mergeAnyPkAsIs()

        try:

            content_subtype = 'entry_info'

            # Update _Data_set/Datum loop

            sf_category = self.sf_categories[file_type][content_subtype]

            self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(master_entry)

            if sf_category in self.__sf_category_list:

                sf = master_entry.get_saveframes_by_category(sf_category)[0]

                # Update _Data_set loop

                lp_category = '_Data_set'

                loop = next((loop for loop in sf.loops if loop.category == lp_category), None)

                if loop is not None:
                    del sf[loop]

                lp = pynmrstar.Loop.from_scratch(lp_category)

                items = ['Type', 'Count', 'Entry_ID']

                tags = [lp_category + '.' + item for item in items]

                for tag in tags:
                    lp.add_tag(tag)

                for content_subtype in self.nmr_rep_content_subtypes:
                    sf_category = self.sf_categories[file_type][content_subtype]

                    if sf_category.endswith('constraints'):  # ignore non-quantitative data set
                        continue

                    count = sum(1 for sf in master_entry.frame_list if sf.category == sf_category)

                    if count > 0:
                        row = [sf_category, count, self.__entry_id]
                        lp.add_data(row)

                lp.sort_rows('Type')

                sf.add_loop(lp)

                # Update _Datum loopa

                lp_category = '_Datum'

                loop = next((loop for loop in sf.loops if loop.category == lp_category), None)

                if loop is not None:
                    del sf[loop]

                lp = pynmrstar.Loop.from_scratch(lp_category)

                tags = [lp_category + '.' + item for item in items]

                for tag in tags:
                    lp.add_tag(tag)

                datum_counter = self.__getDatumCounter(master_entry)

                for k, v in datum_counter.items():
                    row = [k, v, self.__entry_id]
                    lp.add_data(row)

                sf.add_loop(lp)

        except IndexError as e:
            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - " + str(e))
            self.report.setError()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - {str(e)}\n")

        if self.__bmrb_only and self.__internal_mode:
            self.__performBmrbOnlyAnnotation()

        master_entry = self.__c2S.normalize_str(master_entry)

        if __pynmrstar_v3__:
            master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
        else:
            master_entry.write_to_file(self.__dstPath)

        self.__list_id_counter = None
        self.__mr_sf_dict_holder = None
        self.__pk_sf_holder = None

        # check inventory again

        self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(master_entry)

        lp_counts = {t: 0 for t in self.nmr_content_subtypes}

        for lp_category in self.__lp_category_list:
            if lp_category in self.lp_categories[file_type].values():
                lp_counts[[k for k, v in self.lp_categories[file_type].items() if v == lp_category][0]] += 1

        mr_loops = 0

        for content_subtype in self.mr_content_subtypes:
            if content_subtype in lp_counts:
                mr_loops += lp_counts[content_subtype]

        if mr_loops == 0 and not self.__validation_server:

            if 'other_data_types' not in self.__sf_category_list:

                mr_file_names = []

                for fileListId in range(self.__cs_file_path_list_len, self.__file_path_list_len):

                    input_source = self.report.input_sources[fileListId]
                    input_source_dic = input_source.get()

                    file_type = input_source_dic['file_type']

                    if file_type != 'nmr-star':
                        continue

                    mr_file_names.append(input_source_dic['file_name'])

                ar_file_path_list = 'atypical_restraint_file_path_list'

                if ar_file_path_list in self.__inputParamDict:

                    fileListId = self.__file_path_list_len

                    for ar in self.__inputParamDict[ar_file_path_list]:

                        input_source = self.report.input_sources[fileListId]
                        input_source_dic = input_source.get()

                        file_type = input_source_dic['file_type']

                        fileListId += 1

                        if file_type == 'nm-res-mr':
                            continue

                        mr_file_names.append(input_source_dic['file_name'])

                if len(mr_file_names) > 0:

                    desc = 'uploaded restraint file'\
                        + (f's, {mr_file_names}, are' if len(mr_file_names) > 1 else f', {mr_file_names[0]!r}, is')\
                        + ' consistent with the coordinates'

                    err = "Deposition of NMR restraints used for the structure determination is mandatory. "\
                        f"Please verify {desc} and re-upload valid restraint file(s)."

                    self.report.error.appendDescription('missing_mandatory_content',
                                                        {'file_name': os.path.basename(self.__dstPath), 'description': err})
                    self.report.setError()

                    if self.__verbose:
                        self.__lfh.write(f"+NmrDpUtility.__mergeLegacyCsAndMr() ++ Error  - {err}\n")

        return True

    def __getDatumCounter(self, master_entry):
        """ Return Datum counter dictionary.
        """

        file_type = 'nmr-star'

        datum_counter = {'1H chemical shifts': 0,
                         '2H chemical shifts': 0,
                         '3H chemical shifts': 0,
                         '13C chemical shifts': 0,
                         '15N chemical shifts': 0,
                         '31P chemical shifts': 0,
                         '111Cd chemical shifts': 0,
                         '113Cd chemical shifts': 0,
                         '19F chemical shifts': 0,
                         '6Li chemical shifts': 0,
                         '10B chemical shifts': 0,
                         '11B chemical shifts': 0,
                         '17O chemical shifts': 0,
                         '23Na chemical shifts': 0,
                         '29Si chemical shifts': 0,
                         '35Cl chemical shifts': 0,
                         '129Xe chemical shifts': 0,
                         '195Pt chemical shifts': 0,
                         'theoretical 1H chemical shifts': 0,
                         'theoretical 13C chemical shifts': 0,
                         'theoretical 15N chemical shifts': 0,
                         'chemical rates': 0,
                         'coupling constants': 0,
                         'theoretical coupling constants': 0,
                         'chemical shift isotope effects': 0,
                         'chemical shift perturbation values': 0,
                         'T1 relaxation values': 0,
                         'theoretical T1 relaxation values': 0,
                         'T1rho relaxation values': 0,
                         'T2 relaxation values': 0,
                         'theoretical T2 relaxation values': 0,
                         'dipole-dipole relaxation values': 0,
                         'dipole-dipole cross correlation relaxation values': 0,
                         'theoretical dipole-dipole cross-correlation values': 0,
                         'chemical shift anisotropy values': 0,
                         'chemical shift anisotropy tensor values': 0,
                         'quadrupolar couplings': 0,
                         'theoretical chemical shifts': 0,
                         'chemical shift tensors': 0,
                         'residual dipolar couplings': 0,
                         'dipolar coupling values': 0,
                         'dipolar coupling tensor values': 0,
                         'heteronuclear NOE values': 0,
                         'theoretical heteronuclear NOE values': 0,
                         'homonuclear NOE values': 0,
                         'order parameters': 0,
                         'spectral density values': 0,
                         'H exchange rates': 0,
                         'H exchange protection factors': 0,
                         'pKa values': 0,
                         'pH NMR parameter values': 0,
                         'binding constants': 0,
                         'D/H fractionation factors': 0,
                         'bond orientation values': 0,
                         'deduced secondary structure values': 0,
                         'deduced hydrogen bonds': 0,
                         'distance constraints': 0,
                         'ambiguous distance constraints': 0,
                         'hydrogen bond distance constraints': 0,
                         'torsion angle constraints': 0,
                         'chemical shift constraints': 0,
                         'symmetry constraints': 0
                         }

        def get_loop_size(content_subtype):
            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]
            size = 0
            for sf in master_entry.get_saveframes_by_category(sf_category):
                try:
                    if __pynmrstar_v3_2__:
                        lp = sf.get_loop(lp_category)
                    else:
                        lp = sf.get_loop_by_category(lp_category)
                except KeyError:
                    continue
                size += len(lp)
            return size

        for content_subtype in self.nmr_rep_content_subtypes:

            if content_subtype == 'chem_shift':
                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    try:
                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        dat = get_lp_tag(lp, ['Atom_isotope_number', 'Atom_type'])
                        for row in dat:
                            if row[0] not in emptyValue and row[1] not in emptyValue:
                                t = f'{row[0]}{row[1].title()} chemical shifts'
                                if t in datum_counter:
                                    datum_counter[t] += 1
                    except KeyError:
                        continue
            elif content_subtype == 'dist_restraint':
                sf_category = self.sf_categories[file_type][content_subtype]
                lp_category = self.lp_categories[file_type][content_subtype]
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    try:
                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        if constraint_type == 'hydrogen bond':
                            datum_counter['hydrogen bond distance constraints'] += len(lp)
                        elif constraint_type == 'symmetry':
                            datum_counter['symmetry constraints'] += len(lp)
                        else:
                            if 'Combination_ID' in lp.tags and 'Member_ID' in lp.tags:
                                dat = get_lp_tag(lp, ['Combination_ID', 'Member_ID'])
                                for row in dat:
                                    if row[0] in emptyValue and row[1] in emptyValue:
                                        datum_counter['distance constraints'] += 1
                                    else:
                                        datum_counter['ambiguous distance constraints'] += 1
                            else:
                                datum_counter['distance constraints'] += len(lp)
                    except KeyError:
                        continue
            elif content_subtype == 'dihed_restraint':
                datum_counter['torsion angle constraints'] += get_loop_size(content_subtype)
            elif content_subtype in ('rdc_restraint' 'rdc_raw_data'):
                datum_counter['residual dipolar couplings'] += get_loop_size(content_subtype)
            elif content_subtype == 'noepk_restraint':
                datum_counter['homonuclear NOE values'] += get_loop_size(content_subtype)
            elif content_subtype == 'jcoup_restraint':
                datum_counter['coupling constants'] += get_loop_size(content_subtype)
            elif content_subtype == 'csa_restraint':
                datum_counter['chemical shift anisotropy values'] += get_loop_size(content_subtype)
            elif content_subtype == 'ddc_restraint':
                datum_counter['dipolar coupling values'] += get_loop_size(content_subtype)
            elif content_subtype in ('hvycs_restraint', 'procs_restraint'):
                datum_counter['chemical shift constraints'] += get_loop_size(content_subtype)
            elif content_subtype == 'csp_restraint':
                datum_counter['chemical shift perturbation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_noe_data':
                datum_counter['heteronuclear NOE values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t1_data':
                datum_counter['T1 relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t2_data':
                datum_counter['T2 relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t1r_data':
                datum_counter['T1rho relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'order_param_data':
                datum_counter['order parameters'] += get_loop_size(content_subtype)
            elif content_subtype == 'ccrd_dd_restraint':
                datum_counter['dipole-dipole cross correlation relaxation values'] += get_loop_size(content_subtype)

        return {k: v for k, v in datum_counter.items() if v > 0}

    def __performBmrbOnlyAnnotation(self):
        """ Perform a series of BMRB annotation.
        """

        if self.__combined_mode or not self.__remediation_mode or self.__dstPath is None:
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        try:
            from wwpdb.utils.nmr.ann.BMRBjAnnTasks import BMRBjAnnTasks  # pylint: disable=import-outside-toplevel
        except ImportError:
            try:
                from nmr.ann.BMRBjAnnTasks import BMRBjAnnTasks  # pylint: disable=import-outside-toplevel
            except ImportError:
                return False

        ann = BMRBjAnnTasks(self.__verbose, self.__lfh,
                            self.__inputParamDict, self.__outputParamDict,
                            self.__sf_category_list, self.__entry_id, self.__bmrb_id,
                            self.__sail_flag, self.__recvd_nmr_data, self.report,
                            ccU=self.__ccU, csStat=self.__csStat)

        return ann.perform(self.__star_data[0])

    def __updateConstraintStats(self):
        """ Update _Constraint_stat_list saveframe.
        """

        if (not self.__combined_mode and not self.__remediation_mode)\
           or self.__dstPath is None\
           or self.__release_mode\
           or self.report.getInputSourceIdOfCoord() < 0:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type == 'nef':
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        master_entry = self.__star_data[0]

        if not isinstance(master_entry, pynmrstar.Entry):
            return False

        if 'constraint_statistics' in self.__sf_category_list and self.__list_id_counter is not None:
            return False

        if self.__bmrb_only and self.__internal_mode and self.__bmrb_id is not None:
            master_entry.entry_id = self.__bmrb_id
        else:
            master_entry.entry_id = f'nef_{self.__entry_id.lower()}'

        self.__c2S.set_entry_id(master_entry, self.__entry_id)

        # Refresh _Constraint_stat_list saveframe

        sf_framecode = 'constraint_statistics'

        cst_sfs = master_entry.get_saveframes_by_category(sf_framecode)

        if len(cst_sfs) > 0:

            if self.__list_id_counter is None:
                master_entry.remove_saveframe(sf_framecode)

            else:

                lp_category = '_Constraint_file'

                key_items = [{'name': 'ID', 'type': 'int'},
                             {'name': 'Constraint_filename', 'type': 'str'},
                             {'name': 'Block_ID', 'type': 'int'},
                             ]
                data_items = [{'name': 'Constraint_type', 'type': 'str', 'mandatory': True},
                              {'name': 'Constraint_subtype', 'type': 'str'},
                              {'name': 'Constraint_subsubtype', 'type': 'str',
                               'enum': ('ambi', 'simple')},
                              {'name': 'Constraint_number', 'type': 'int'},
                              {'name': 'Constraint_stat_list_ID', 'type': 'int', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                              {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                              ]

                allowed_tags = ['ID', 'Constraint_filename', 'Software_ID', 'Software_label', 'Software_name',
                                'Block_ID', 'Constraint_type', 'Constraint_subtype', 'Constraint_subsubtype', 'Constraint_number',
                                'Sf_ID', 'Entry_ID', 'Constraint_stat_list_ID']

                try:

                    for parent_pointer, cst_sf in enumerate(cst_sfs, start=1):

                        self.__nefT.check_data(cst_sf, lp_category, key_items, data_items,
                                               allowed_tags, None, parent_pointer=parent_pointer,
                                               enforce_allowed_tags=(file_type == 'nmr-star'),
                                               excl_missing_data=self.__excl_missing_data)

                    return True

                except Exception:
                    for cst_sf in reversed(cst_sfs):
                        del master_entry[cst_sf]

        self.__sf_category_list, self.__lp_category_list = self.__nefT.get_inventory_list(master_entry)

        # initialize loop counter
        lp_counts = {t: 0 for t in self.nmr_content_subtypes}

        # increment loop counter of each content subtype
        for lp_category in self.__lp_category_list:
            if lp_category in self.lp_categories[file_type].values():
                lp_counts[[k for k, v in self.lp_categories[file_type].items() if v == lp_category][0]] += 1

        content_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

        input_source.setItemValue('content_subtype', content_subtypes)

        sf_item = {}

        cst_sf = pynmrstar.Saveframe.from_scratch(sf_framecode)
        cst_sf.set_tag_prefix('_Constraint_stat_list')
        cst_sf.add_tag('Sf_category', sf_framecode)
        cst_sf.add_tag('Sf_framecode', sf_framecode)
        cst_sf.add_tag('Entry_ID', self.__entry_id)
        cst_sf.add_tag('ID', 1)
        if self.__srcName is not None:
            cst_sf.add_tag('Data_file_name', self.__srcName)

        if has_key_value(input_source_dic, 'content_subtype'):

            for content_subtype in input_source_dic['content_subtype']:

                if content_subtype == 'dist_restraint':

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        avr_method = get_first_sf_tag(sf, 'NOE_dist_averaging_method')
                        if len(avr_method) > 0 and avr_method not in emptyValue:
                            cst_sf.add_tag('NOE_dist_averaging_method', avr_method)
                            break

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        if sf_framecode not in sf_item:
                            sf_item[sf_framecode] = {'constraint_type': 'distance', 'constraint_subsubtype': 'simple'}
                            constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                            if len(constraint_type) > 0 and constraint_type not in emptyValue:
                                sf_item[sf_framecode]['constraint_subtype'] = constraint_type

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        item_names = self.item_names_in_ds_loop[file_type]
                        id_col = lp.tags.index('ID')
                        member_logic_code_col = lp.tags.index('Member_logic_code') if 'Member_logic_code' in lp.tags else -1
                        auth_asym_id_1_col = lp.tags.index('Auth_asym_ID_1')
                        auth_seq_id_1_col = lp.tags.index('Auth_seq_ID_1')
                        auth_asym_id_2_col = lp.tags.index('Auth_asym_ID_2')
                        auth_seq_id_2_col = lp.tags.index('Auth_seq_ID_2')
                        comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                        comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                        atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                        atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                        try:
                            target_value_col = lp.tags.index(item_names['target_value'])
                        except ValueError:
                            target_value_col = -1
                        try:
                            lower_limit_col = lp.tags.index(item_names['lower_limit'])
                        except ValueError:
                            lower_limit_col = -1
                        try:
                            upper_limit_col = lp.tags.index(item_names['upper_limit'])
                        except ValueError:
                            upper_limit_col = -1
                        try:
                            lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                        except ValueError:
                            lower_linear_limit_col = -1
                        try:
                            upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                        except ValueError:
                            upper_linear_limit_col = -1

                        has_or_code = False

                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                        _potential_type = None
                        count = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                                    has_or_code = True
                                continue
                            prev_id = _id
                            count += 1
                            if not has_potential_type:
                                dst_func = {}
                                if target_value_col != -1 and row[target_value_col] not in emptyValue:
                                    dst_func['target_value'] = float(row[target_value_col])
                                if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                                    dst_func['lower_limit'] = float(row[lower_limit_col])
                                if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                                    dst_func['upper_limit'] = float(row[upper_limit_col])
                                if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                                    dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                                if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                                    dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                                if _potential_type is None:
                                    _potential_type = getPotentialType(file_type, 'dist', dst_func)
                                else:
                                    if getPotentialType(file_type, 'dist', dst_func) != _potential_type:
                                        has_potential_type = True

                        if not has_potential_type and _potential_type is not None:
                            set_sf_tag(sf, 'Potential_type', _potential_type)

                        sf_item[sf_framecode]['id'] = count

                        if has_or_code:

                            prev_id = -1
                            for row in lp:
                                if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                                    _id = int(row[id_col])
                                    if _id != prev_id:
                                        _atom1 = {'chain_id': row[auth_asym_id_1_col],
                                                  'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                                  'comp_id': row[comp_id_1_col],
                                                  'atom_id': row[atom_id_1_col]}
                                        _atom2 = {'chain_id': row[auth_asym_id_2_col],
                                                  'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                                  'comp_id': row[comp_id_2_col],
                                                  'atom_id': row[atom_id_2_col]}
                                        prev_id = _id
                                        continue
                                    atom1 = {'chain_id': row[auth_asym_id_1_col],
                                             'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                             'comp_id': row[comp_id_1_col],
                                             'atom_id': row[atom_id_1_col]}
                                    atom2 = {'chain_id': row[auth_asym_id_2_col],
                                             'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                             'comp_id': row[comp_id_2_col],
                                             'atom_id': row[atom_id_2_col]}
                                    if isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                                       or isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                                        sf_item[sf_framecode]['constraint_subsubtype'] = 'ambi'
                                        break
                                    _atom1, _atom2 = atom1, atom2

                            if sf_item[sf_framecode]['constraint_subsubtype'] == 'ambi':

                                if 'pre' in sf_framecode or 'paramag' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'paramagnetic relaxation'
                                if 'cidnp' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'photo cidnp'
                                if 'csp' in sf_framecode or 'perturb' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'chemical shift perturbation'
                                if 'mutat' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'mutation'
                                if 'protect' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'hydrogen exchange protection'
                                if 'symm' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'symmetry'

                        if sf_item[sf_framecode]['constraint_subsubtype'] == 'simple':

                            metal_coord = False
                            disele_bond = False
                            disulf_bond = False
                            hydrog_bond = False

                            for row in lp:
                                comp_id_1 = row[comp_id_1_col]
                                comp_id_2 = row[comp_id_2_col]
                                atom_id_1 = row[atom_id_1_col]
                                atom_id_2 = row[atom_id_2_col]

                                if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                                    continue

                                atom_id_1_ = atom_id_1[0]
                                atom_id_2_ = atom_id_2[0]
                                if comp_id_1 == atom_id_1 or comp_id_2 == atom_id_2:
                                    metal_coord = True
                                elif 'SE' in (atom_id_1, atom_id_2):
                                    disele_bond = True
                                elif 'SG' in (atom_id_1, atom_id_2):
                                    disulf_bond = True
                                elif (atom_id_1_ == 'F' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'F' and atom_id_1_ in protonBeginCode):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'O' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'O' and atom_id_1_ in protonBeginCode):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'N' and atom_id_2_ in protonBeginCode) or (atom_id_2_ == 'N' and atom_id_1_ in protonBeginCode):
                                    hydrog_bond = True
                                elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):
                                    hydrog_bond = True

                            if not metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                                if 'build' in sf_framecode and 'up' in sf_framecode:
                                    if 'roe' in sf_framecode:
                                        sf_item[sf_framecode]['constraint_subtype'] = 'ROE build-up'
                                    else:
                                        sf_item[sf_framecode]['constraint_subtype'] = 'NOE build-up'

                                elif 'not' in sf_framecode and 'seen' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'NOE not seen'

                                elif 'roe' in sf_framecode:
                                    sf_item[sf_framecode]['constraint_subtype'] = 'ROE'

                                sf_item[sf_framecode]['constraint_subtype'] = 'NOE'

                            elif metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                                sf_item[sf_framecode]['constraint_subtype'] = 'metal coordination'

                            elif not metal_coord and disele_bond and not disulf_bond and not hydrog_bond:
                                sf_item[sf_framecode]['constraint_subtype'] = 'diselenide bond'

                            elif not metal_coord and not disele_bond and disulf_bond and not hydrog_bond:
                                sf_item[sf_framecode]['constraint_subtype'] = 'disulfide bond'

                            elif not metal_coord and not disele_bond and not disulf_bond and hydrog_bond:
                                sf_item[sf_framecode]['constraint_subtype'] = 'hydrogen bond'

                    NOE_tot_num = 0

                    NOE_intraresidue_tot_num = 0
                    NOE_sequential_tot_num = 0
                    NOE_medium_range_tot_num = 0
                    NOE_long_range_tot_num = 0
                    NOE_unique_tot_num = 0
                    NOE_intraresidue_unique_tot_num = 0
                    NOE_sequential_unique_tot_num = 0
                    NOE_medium_range_unique_tot_num = 0
                    NOE_long_range_unique_tot_num = 0
                    NOE_unamb_intramol_tot_num = 0
                    NOE_unamb_intermol_tot_num = 0
                    NOE_ambig_intramol_tot_num = 0
                    NOE_ambig_intermol_tot_num = 0
                    NOE_interentity_tot_num = 0
                    NOE_other_tot_num = 0

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        if 'lower' in potential_type:
                            continue
                        if 'constraint_subtype' in sf_item[sf_framecode] and 'NOE' in sf_item[sf_framecode]['constraint_subtype']:
                            # NOE_tot_num += sf_item[sf_framecode]['id']

                            if __pynmrstar_v3_2__:
                                lp = sf.get_loop(lp_category)
                            else:
                                lp = sf.get_loop_by_category(lp_category)

                            item_names = self.item_names_in_ds_loop[file_type]
                            id_col = lp.tags.index('ID')
                            chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                            chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                            seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                            seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                            comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                            comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                            atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                            atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                            # try:
                            #     member_logic_code_col = lp.tags.index(item_names['member_logic_code'])
                            # except ValueError:
                            #     member_logic_code_col = -1
                            try:
                                combination_id_col = lp.tags.index(item_names['combination_id'])
                            except ValueError:
                                combination_id_col = -1
                            try:
                                upper_limit_col = lp.tags.index(item_names['upper_limit'])
                            except ValueError:
                                upper_limit_col = -1

                            prev_id = -1
                            # _atom1 = _atom2 = None

                            for row in lp:
                                _id = int(row[id_col])
                                # member_logic_code = row[member_logic_code_col] if member_logic_code_col != -1 else None
                                try:
                                    chain_id_1 = int(row[chain_id_1_col])
                                    chain_id_2 = int(row[chain_id_2_col])
                                    seq_id_1 = int(row[seq_id_1_col])
                                    seq_id_2 = int(row[seq_id_2_col])
                                except (ValueError, TypeError):
                                    continue
                                comp_id_1 = row[comp_id_1_col]
                                comp_id_2 = row[comp_id_2_col]
                                atom_id_1 = row[atom_id_1_col]
                                atom_id_2 = row[atom_id_2_col]

                                if atom_id_1 in emptyValue or atom_id_2 in emptyValue or _id == prev_id:
                                    continue

                                prev_id = _id

                                combination_id = row[combination_id_col] if combination_id_col != -1 else None
                                upper_limit = float(row[upper_limit_col]) if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue else None

                                offset = abs(seq_id_1 - seq_id_2)
                                ambig = upper_limit is not None and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP)
                                uniq = combination_id in emptyValue and not ambig

                                NOE_tot_num += 1

                                if uniq:
                                    NOE_unique_tot_num += 1

                                if chain_id_1 == chain_id_2:
                                    if uniq:
                                        NOE_unamb_intramol_tot_num += 1
                                    else:
                                        NOE_ambig_intramol_tot_num += 1
                                    if offset == 0:
                                        NOE_intraresidue_tot_num += 1
                                        if uniq:
                                            NOE_intraresidue_unique_tot_num += 1
                                    elif offset == 1:
                                        NOE_sequential_tot_num += 1
                                        if uniq:
                                            NOE_sequential_unique_tot_num += 1
                                    elif offset < 5:
                                        NOE_medium_range_tot_num += 1
                                        if uniq:
                                            NOE_medium_range_unique_tot_num += 1
                                    else:
                                        NOE_long_range_tot_num += 1
                                        if uniq:
                                            NOE_long_range_unique_tot_num += 1
                                else:
                                    NOE_interentity_tot_num += 1
                                    if uniq:
                                        NOE_unamb_intermol_tot_num += 1
                                    else:
                                        NOE_ambig_intermol_tot_num += 1

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        if 'lower' in potential_type:
                            continue
                        constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                        if constraint_type in ('paramagnetic relaxation',
                                               'photo cidnp',
                                               'chemical shift perturbation',
                                               'mutation',
                                               'symmetry'):
                            NOE_other_tot_num += sf_item[sf_framecode]['id']

                    if NOE_tot_num > 0:
                        cst_sf.add_tag('NOE_tot_num', NOE_tot_num)
                        cst_sf.add_tag('NOE_intraresidue_tot_num', NOE_intraresidue_tot_num)
                        cst_sf.add_tag('NOE_sequential_tot_num', NOE_sequential_tot_num)
                        cst_sf.add_tag('NOE_medium_range_tot_num', NOE_medium_range_tot_num)
                        cst_sf.add_tag('NOE_long_range_tot_num', NOE_long_range_tot_num)
                        cst_sf.add_tag('NOE_unique_tot_num', NOE_unique_tot_num)
                        cst_sf.add_tag('NOE_intraresidue_unique_tot_num', NOE_intraresidue_unique_tot_num)
                        cst_sf.add_tag('NOE_sequential_unique_tot_num', NOE_sequential_unique_tot_num)
                        cst_sf.add_tag('NOE_medium_range_unique_tot_num', NOE_medium_range_unique_tot_num)
                        cst_sf.add_tag('NOE_long_range_unique_tot_num', NOE_long_range_unique_tot_num)
                        cst_sf.add_tag('NOE_unamb_intramol_tot_num', NOE_unamb_intramol_tot_num)
                        cst_sf.add_tag('NOE_unamb_intermol_tot_num', NOE_unamb_intermol_tot_num)
                        cst_sf.add_tag('NOE_ambig_intramol_tot_num', NOE_ambig_intramol_tot_num)
                        cst_sf.add_tag('NOE_ambig_intermol_tot_num', NOE_ambig_intermol_tot_num)
                        cst_sf.add_tag('NOE_interentity_tot_num', NOE_interentity_tot_num)
                        cst_sf.add_tag('NOE_other_tot_num', NOE_other_tot_num)

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        avr_method = get_first_sf_tag(sf, 'ROE_dist_averaging_method')
                        if len(avr_method) > 0 or avr_method not in emptyValue:
                            cst_sf.add_tag('ROE_dist_averaging_method', avr_method)
                            break

                    ROE_tot_num = 0

                    ROE_intraresidue_tot_num = 0
                    ROE_sequential_tot_num = 0
                    ROE_medium_range_tot_num = 0
                    ROE_long_range_tot_num = 0
                    ROE_unambig_intramol_tot_num = 0
                    ROE_unambig_intermol_tot_num = 0
                    ROE_ambig_intramol_tot_num = 0
                    ROE_ambig_intermol_tot_num = 0
                    ROE_other_tot_num = 0

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        if 'lower' in potential_type:
                            continue
                        if 'constraint_subtype' in sf_item[sf_framecode] and 'ROE' in sf_item[sf_framecode]['constraint_subtype']:
                            # ROE_tot_num += sf_item[sf_framecode]['id']

                            if __pynmrstar_v3_2__:
                                lp = sf.get_loop(lp_category)
                            else:
                                lp = sf.get_loop_by_category(lp_category)

                            item_names = self.item_names_in_ds_loop[file_type]
                            id_col = lp.tags.index('ID')
                            chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                            chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                            seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                            seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                            comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                            comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                            atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                            atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                            # try:
                            #     member_logic_code_col = lp.tags.index(item_names['member_logic_code'])
                            # except ValueError:
                            #     member_logic_code_col = -1
                            try:
                                combination_id_col = lp.tags.index(item_names['combination_id'])
                            except ValueError:
                                combination_id_col = -1
                            try:
                                upper_limit_col = lp.tags.index(item_names['upper_limit'])
                            except ValueError:
                                upper_limit_col = -1

                            prev_id = -1
                            # _atom1 = _atom2 = None

                            for row in lp:
                                _id = int(row[id_col])
                                # member_logic_code = row[member_logic_code_col] if member_logic_code_col != -1 else None
                                try:
                                    chain_id_1 = int(row[chain_id_1_col])
                                    chain_id_2 = int(row[chain_id_2_col])
                                    seq_id_1 = int(row[seq_id_1_col])
                                    seq_id_2 = int(row[seq_id_2_col])
                                except (ValueError, TypeError):
                                    continue
                                comp_id_1 = row[comp_id_1_col]
                                comp_id_2 = row[comp_id_2_col]
                                atom_id_1 = row[atom_id_1_col]
                                atom_id_2 = row[atom_id_2_col]

                                if atom_id_1 in emptyValue or atom_id_2 in emptyValue or _id == prev_id:
                                    continue

                                prev_id = _id

                                combination_id = row[combination_id_col] if combination_id_col != -1 else None
                                upper_limit = float(row[upper_limit_col]) if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue else None

                                offset = abs(seq_id_1 - seq_id_2)
                                ambig = upper_limit is not None and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP)
                                uniq = combination_id in emptyValue and not ambig

                                ROE_tot_num += 1

                                if chain_id_1 == chain_id_2:
                                    if uniq:
                                        ROE_unambig_intramol_tot_num += 1
                                    else:
                                        ROE_ambig_intramol_tot_num += 1
                                    if offset == 0:
                                        ROE_intraresidue_tot_num += 1
                                    elif offset == 1:
                                        ROE_sequential_tot_num += 1
                                    elif offset < 5:
                                        ROE_medium_range_tot_num += 1
                                    else:
                                        ROE_long_range_tot_num += 1
                                else:
                                    ROE_other_tot_num += 1
                                    if uniq:
                                        ROE_unambig_intermol_tot_num += 1
                                    else:
                                        ROE_ambig_intermol_tot_num += 1

                    if ROE_tot_num > 0:
                        cst_sf.add_tag('ROE_tot_num', ROE_tot_num)
                        cst_sf.add_tag('ROE_intraresidue_tot_num', ROE_intraresidue_tot_num)
                        cst_sf.add_tag('ROE_sequential_tot_num', ROE_sequential_tot_num)
                        cst_sf.add_tag('ROE_medium_range_tot_num', ROE_medium_range_tot_num)
                        cst_sf.add_tag('ROE_long_range_tot_num', ROE_long_range_tot_num)
                        cst_sf.add_tag('ROE_unambig_intramol_tot_num', ROE_unambig_intramol_tot_num)
                        cst_sf.add_tag('ROE_unambig_intermol_tot_num', ROE_unambig_intermol_tot_num)
                        cst_sf.add_tag('ROE_ambig_intramol_tot_num', ROE_ambig_intramol_tot_num)
                        cst_sf.add_tag('ROE_ambig_intermol_tot_num', ROE_ambig_intermol_tot_num)
                        cst_sf.add_tag('ROE_other_tot_num', ROE_other_tot_num)

                elif content_subtype == 'dihed_restraint':

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    auth_to_entity_type = self.__caC['auth_to_entity_type'] if self.__caC is not None else {}

                    Dihedral_angle_tot_num = 0
                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        if sf_framecode not in sf_item:
                            sf_item[sf_framecode] = {'constraint_type': 'dihedral angle'}

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        item_names = self.item_names_in_dh_loop[file_type]
                        id_col = lp.tags.index('ID')
                        try:
                            target_value_col = lp.tags.index(item_names['target_value'])
                        except ValueError:
                            target_value_col = -1
                        try:
                            lower_limit_col = lp.tags.index(item_names['lower_limit'])
                        except ValueError:
                            lower_limit_col = -1
                        try:
                            upper_limit_col = lp.tags.index(item_names['upper_limit'])
                        except ValueError:
                            upper_limit_col = -1
                        try:
                            lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                        except ValueError:
                            lower_linear_limit_col = -1
                        try:
                            upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                        except ValueError:
                            upper_linear_limit_col = -1

                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                        _potential_type = None
                        count = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            count += 1
                            if not has_potential_type:
                                dst_func = {}
                                if target_value_col != -1 and row[target_value_col] not in emptyValue:
                                    dst_func['target_value'] = float(row[target_value_col])
                                if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                                    dst_func['lower_limit'] = float(row[lower_limit_col])
                                if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                                    dst_func['upper_limit'] = float(row[upper_limit_col])
                                if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                                    dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                                if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                                    dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                                if _potential_type is None:
                                    _potential_type = getPotentialType(file_type, 'dihed', dst_func)
                                else:
                                    if getPotentialType(file_type, 'dihed', dst_func) != _potential_type:
                                        has_potential_type = True

                        if not has_potential_type and _potential_type is not None:
                            set_sf_tag(sf, 'Potential_type', _potential_type)

                        sf_item[sf_framecode]['id'] = count
                        Dihedral_angle_tot_num += count

                    if Dihedral_angle_tot_num > 0:
                        cst_sf.add_tag('Dihedral_angle_tot_num', Dihedral_angle_tot_num)

                    Protein_dihedral_angle_tot_num = 0

                    Protein_phi_angle_tot_num = 0
                    Protein_psi_angle_tot_num = 0
                    Protein_chi_one_angle_tot_num = 0
                    Protein_other_angle_tot_num = 0

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        id_col = lp.tags.index('ID')
                        auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                        auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                        auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                        angle_name_col = lp.tags.index('Torsion_angle_name')

                        _protein_angles = _other_angles = 0
                        _protein_bb_angles = _protein_oth_angles = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            auth_asym_id = row[auth_asym_id_col]
                            try:
                                auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                            except (ValueError, TypeError):
                                continue
                            auth_comp_id = row[auth_comp_id_col]
                            angle_name = row[angle_name_col]
                            if angle_name is None:
                                continue

                            seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                            if seq_key in auth_to_entity_type:
                                entity_type = auth_to_entity_type[seq_key]

                                if 'peptide' in entity_type:
                                    Protein_dihedral_angle_tot_num += 1
                                    _protein_angles += 1
                                    if angle_name == 'PHI':
                                        Protein_phi_angle_tot_num += 1
                                        _protein_bb_angles += 1
                                    elif angle_name == 'PSI':
                                        Protein_psi_angle_tot_num += 1
                                        _protein_bb_angles += 1
                                    elif angle_name == 'CHI1':
                                        Protein_chi_one_angle_tot_num += 1
                                        _protein_oth_angles += 1
                                    else:
                                        Protein_other_angle_tot_num += 1
                                        _protein_oth_angles += 1
                                else:
                                    _other_angles += 1

                        if _protein_angles > _other_angles == 0:
                            sf_item[sf_framecode]['constraint_type'] = 'protein dihedral angle'

                            tagNames = [t[0] for t in sf.tags]

                            if 'Constraint_type' not in tagNames:
                                sf_item[sf_framecode]['constraint_subtype'] = 'backbone chemical shifts'
                                sf.add_tag('Constraint_subtype', 'backbone chemical shifts')

                    if Protein_dihedral_angle_tot_num > 0:
                        cst_sf.add_tag('Protein_dihedral_angle_tot_num', Protein_dihedral_angle_tot_num)
                        cst_sf.add_tag('Protein_phi_angle_tot_num', Protein_phi_angle_tot_num)
                        cst_sf.add_tag('Protein_psi_angle_tot_num', Protein_psi_angle_tot_num)
                        cst_sf.add_tag('Protein_chi_one_angle_tot_num', Protein_chi_one_angle_tot_num)
                        cst_sf.add_tag('Protein_other_angle_tot_num', Protein_other_angle_tot_num)

                    NA_dihedral_angle_tot_num = 0

                    NA_alpha_angle_tot_num = 0
                    NA_beta_angle_tot_num = 0
                    NA_gamma_angle_tot_num = 0
                    NA_delta_angle_tot_num = 0
                    NA_epsilon_angle_tot_num = 0
                    NA_chi_angle_tot_num = 0
                    NA_other_angle_tot_num = 0
                    NA_amb_dihedral_angle_tot_num = 0

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        id_col = lp.tags.index('ID')
                        auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                        auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                        auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                        angle_name_col = lp.tags.index('Torsion_angle_name')

                        _na_angles = _other_angles = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            auth_asym_id = row[auth_asym_id_col]
                            try:
                                auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                            except (ValueError, TypeError):
                                continue
                            auth_comp_id = row[auth_comp_id_col]
                            angle_name = row[angle_name_col]
                            if angle_name is None:
                                continue

                            seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                            if seq_key in auth_to_entity_type:
                                entity_type = auth_to_entity_type[seq_key]

                                if 'nucleotide' in entity_type:
                                    NA_dihedral_angle_tot_num += 1
                                    _na_angles += 1
                                    if angle_name == 'ALPHA':
                                        NA_alpha_angle_tot_num += 1
                                    elif angle_name == 'BETA':
                                        NA_beta_angle_tot_num += 1
                                    elif angle_name == 'GAMMA':
                                        NA_gamma_angle_tot_num += 1
                                    elif angle_name == 'DELTA':
                                        NA_delta_angle_tot_num += 1
                                    elif angle_name == 'EPSILON':
                                        NA_epsilon_angle_tot_num += 1
                                    elif angle_name == 'CHI':
                                        NA_chi_angle_tot_num += 1
                                    elif angle_name == 'PPA':
                                        NA_amb_dihedral_angle_tot_num += 1
                                    else:
                                        NA_other_angle_tot_num += 1
                                else:
                                    _other_angles += 1

                        if _na_angles > _other_angles:
                            sf_item[sf_framecode]['constraint_type'] = 'nucleic acid dihedral angle'

                            tagNames = [t[0] for t in sf.tags]

                            if 'Constraint_type' not in tagNames:
                                sf_item[sf_framecode]['constraint_subtype'] = 'unknown'
                                sf.add_tag('Constraint_type', 'unknown')

                    if NA_dihedral_angle_tot_num > 0:
                        cst_sf.add_tag('NA_dihedral_angle_tot_num', NA_dihedral_angle_tot_num)
                        cst_sf.add_tag('NA_alpha_angle_tot_num', NA_alpha_angle_tot_num)
                        cst_sf.add_tag('NA_beta_angle_tot_num', NA_beta_angle_tot_num)
                        cst_sf.add_tag('NA_gamma_angle_tot_num', NA_gamma_angle_tot_num)
                        cst_sf.add_tag('NA_delta_angle_tot_num', NA_delta_angle_tot_num)
                        cst_sf.add_tag('NA_epsilon_angle_tot_num', NA_epsilon_angle_tot_num)
                        cst_sf.add_tag('NA_chi_angle_tot_num', NA_chi_angle_tot_num)
                        cst_sf.add_tag('NA_other_angle_tot_num', NA_other_angle_tot_num)
                        cst_sf.add_tag('NA_amb_dihedral_angle_tot_num', NA_amb_dihedral_angle_tot_num)

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        id_col = lp.tags.index('ID')
                        auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                        auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                        auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')
                        angle_name_col = lp.tags.index('Torsion_angle_name')

                        _br_angles = _other_angles = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            auth_asym_id = row[auth_asym_id_col]
                            try:
                                auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in emptyValue else None
                            except (ValueError, TypeError):
                                continue
                            auth_comp_id = row[auth_comp_id_col]
                            angle_name = row[angle_name_col]
                            if angle_name is None:
                                continue

                            seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                            if seq_key in auth_to_entity_type:
                                entity_type = auth_to_entity_type[seq_key]

                                if 'saccharide' in entity_type:
                                    _br_angles += 1
                                else:
                                    _other_angles += 1

                        if _br_angles > _other_angles:
                            sf_item[sf_framecode]['constraint_type'] = 'carbohydrate dihedral angle'  # DAOTHER-9471

                            tagNames = [t[0] for t in sf.tags]

                            if 'Constraint_type' not in tagNames:
                                sf_item[sf_framecode]['constraint_subtype'] = 'unknown'
                                sf.add_tag('Constraint_type', 'unknown')

                elif content_subtype == 'rdc_restraint':

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        if sf_framecode not in sf_item:
                            sf_item[sf_framecode] = {'constraint_type': 'dipolar coupling', 'constraint_subtype': 'RDC'}  # DAOTHER-9471

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        item_names = self.item_names_in_rdc_loop[file_type]
                        id_col = lp.tags.index('ID')
                        try:
                            target_value_col = lp.tags.index(item_names['target_value'])
                        except ValueError:
                            target_value_col = -1
                        try:
                            lower_limit_col = lp.tags.index(item_names['lower_limit'])
                        except ValueError:
                            lower_limit_col = -1
                        try:
                            upper_limit_col = lp.tags.index(item_names['upper_limit'])
                        except ValueError:
                            upper_limit_col = -1
                        try:
                            lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                        except ValueError:
                            lower_linear_limit_col = -1
                        try:
                            upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                        except ValueError:
                            upper_linear_limit_col = -1

                        potential_type = get_first_sf_tag(sf, 'Potential_type')
                        has_potential_type = len(potential_type) > 0 and potential_type not in emptyValue and potential_type != 'unknown'

                        _potential_type = None
                        count = 0

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            count += 1
                            if not has_potential_type:
                                dst_func = {}
                                if target_value_col != -1 and row[target_value_col] not in emptyValue:
                                    dst_func['target_value'] = float(row[target_value_col])
                                if lower_limit_col != -1 and row[lower_limit_col] not in emptyValue:
                                    dst_func['lower_limit'] = float(row[lower_limit_col])
                                if upper_limit_col != -1 and row[upper_limit_col] not in emptyValue:
                                    dst_func['upper_limit'] = float(row[upper_limit_col])
                                if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in emptyValue:
                                    dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                                if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in emptyValue:
                                    dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                                if _potential_type is None:
                                    _potential_type = getPotentialType(file_type, 'rdc', dst_func)
                                else:
                                    if getPotentialType(file_type, 'rdc', dst_func) != _potential_type:
                                        has_potential_type = True

                        if not has_potential_type and _potential_type is not None:
                            set_sf_tag(sf, 'Potential_type', _potential_type)

                        sf_item[sf_framecode]['id'] = count

                    RDC_tot_num = 0

                    RDC_HH_tot_num = 0
                    RDC_HNC_tot_num = 0
                    RDC_NH_tot_num = 0
                    RDC_CC_tot_num = 0
                    RDC_CN_i_1_tot_num = 0
                    RDC_CAHA_tot_num = 0
                    RDC_HNHA_tot_num = 0
                    RDC_HNHA_i_1_tot_num = 0
                    RDC_CAC_tot_num = 0
                    RDC_CAN_tot_num = 0
                    RDC_other_tot_num = 0

                    RDC_intraresidue_tot_num = 0
                    RDC_sequential_tot_num = 0
                    RDC_medium_range_tot_num = 0
                    RDC_long_range_tot_num = 0

                    RDC_unambig_intramol_tot_num = 0
                    RDC_unambig_intermol_tot_num = 0
                    RDC_ambig_intramol_tot_num = 0
                    RDC_ambig_intermol_tot_num = 0
                    RDC_intermol_tot_num = 0

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        if __pynmrstar_v3_2__:
                            lp = sf.get_loop(lp_category)
                        else:
                            lp = sf.get_loop_by_category(lp_category)

                        # RDC_tot_num += sf_item[sf_framecode]['id']

                        item_names = self.item_names_in_rdc_loop[file_type]
                        id_col = lp.tags.index('ID')
                        chain_id_1_col = lp.tags.index(item_names['chain_id_1'])
                        chain_id_2_col = lp.tags.index(item_names['chain_id_2'])
                        seq_id_1_col = lp.tags.index(item_names['seq_id_1'])
                        seq_id_2_col = lp.tags.index(item_names['seq_id_2'])
                        comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                        atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                        atom_id_2_col = lp.tags.index(item_names['atom_id_2'])
                        try:
                            combination_id_col = lp.tags.index(item_names['combination_id'])
                        except ValueError:
                            combination_id_col = -1

                        prev_id = -1
                        for row in lp:
                            _id = int(row[id_col])
                            if _id == prev_id:
                                continue
                            prev_id = _id
                            chain_id_1 = row[chain_id_1_col]
                            chain_id_2 = row[chain_id_2_col]
                            try:
                                seq_id_1 = int(row[seq_id_1_col]) if row[seq_id_1_col] not in emptyValue else None
                                seq_id_2 = int(row[seq_id_2_col]) if row[seq_id_2_col] not in emptyValue else None
                            except (ValueError, TypeError):
                                continue
                            comp_id_1 = row[comp_id_1_col]
                            atom_id_1 = row[atom_id_1_col]
                            atom_id_2 = row[atom_id_2_col]

                            if atom_id_1 in emptyValue or atom_id_2 in emptyValue:
                                continue

                            combination_id = row[combination_id_col] if combination_id_col != -1 else None

                            vector = {atom_id_1, atom_id_2}
                            offset = abs(seq_id_1 - seq_id_2)

                            RDC_tot_num += 1

                            if chain_id_1 == chain_id_2:
                                if vector == {'H', 'C'} and offset == 1:
                                    RDC_HNC_tot_num += 1
                                elif vector == {'H', 'N'} and offset == 0:
                                    RDC_NH_tot_num += 1
                                elif vector == {'C', 'N'} and offset == 1:
                                    RDC_CN_i_1_tot_num += 1
                                elif vector == {'CA', 'HA'} and offset == 0:
                                    RDC_CAHA_tot_num += 1
                                elif vector == {'H', 'HA'} and offset == 0:
                                    RDC_HNHA_tot_num += 1
                                elif vector == {'H', 'HA'} and offset == 1:
                                    RDC_HNHA_i_1_tot_num += 1
                                elif vector == {'CA', 'C'} and offset == 0:
                                    RDC_CAC_tot_num += 1
                                elif vector == {'CA', 'N'} and offset == 0:
                                    RDC_CAN_tot_num += 1
                                elif atom_id_1[0] == atom_id_2[0]:
                                    if atom_id_1[0] in protonBeginCode:
                                        RDC_HH_tot_num += 1
                                    elif atom_id_1[0] == 'C':
                                        RDC_CC_tot_num += 1
                                    else:
                                        RDC_other_tot_num += 1
                                elif offset == 0 and comp_id_1 == 'TRP' and vector == {'HE1', 'NE1'}:
                                    RDC_NH_tot_num += 1
                                elif offset == 0 and comp_id_1 == 'ARG' and vector == {'HE', 'NE'}:
                                    RDC_NH_tot_num += 1
                                else:
                                    RDC_other_tot_num += 1

                            if chain_id_1 == chain_id_2:
                                if offset == 0:
                                    RDC_intraresidue_tot_num += 1
                                elif offset == 1:
                                    RDC_sequential_tot_num += 1
                                elif offset < 5:
                                    RDC_medium_range_tot_num += 1
                                else:
                                    RDC_long_range_tot_num += 1
                                if combination_id in emptyValue:
                                    RDC_unambig_intramol_tot_num += 1
                                else:
                                    RDC_ambig_intramol_tot_num += 1

                            else:
                                RDC_intermol_tot_num += 1
                                if combination_id in emptyValue:
                                    RDC_unambig_intermol_tot_num += 1
                                else:
                                    RDC_ambig_intermol_tot_num += 1

                    if RDC_tot_num > 0:
                        cst_sf.add_tag('RDC_tot_num', RDC_tot_num)
                        cst_sf.add_tag('RDC_HH_tot_num', RDC_HH_tot_num)
                        cst_sf.add_tag('RDC_HNC_tot_num', RDC_HNC_tot_num)
                        cst_sf.add_tag('RDC_NH_tot_num', RDC_NH_tot_num)
                        cst_sf.add_tag('RDC_CC_tot_num', RDC_CC_tot_num)
                        cst_sf.add_tag('RDC_CN_i_1_tot_num', RDC_CN_i_1_tot_num)
                        cst_sf.add_tag('RDC_CAHA_tot_num', RDC_CAHA_tot_num)
                        cst_sf.add_tag('RDC_HNHA_tot_num', RDC_HNHA_tot_num)
                        cst_sf.add_tag('RDC_HNHA_i_1_tot_num', RDC_HNHA_i_1_tot_num)
                        cst_sf.add_tag('RDC_CAC_tot_num', RDC_CAC_tot_num)
                        cst_sf.add_tag('RDC_CAN_tot_num', RDC_CAN_tot_num)
                        cst_sf.add_tag('RDC_other_tot_num', RDC_other_tot_num)
                        cst_sf.add_tag('RDC_intraresidue_tot_num', RDC_intraresidue_tot_num)
                        cst_sf.add_tag('RDC_sequential_tot_num', RDC_sequential_tot_num)
                        cst_sf.add_tag('RDC_medium_range_tot_num', RDC_medium_range_tot_num)
                        cst_sf.add_tag('RDC_long_range_tot_num', RDC_long_range_tot_num)
                        cst_sf.add_tag('RDC_unambig_intramol_tot_num', RDC_unambig_intramol_tot_num)
                        cst_sf.add_tag('RDC_unambig_intermol_tot_num', RDC_unambig_intermol_tot_num)
                        cst_sf.add_tag('RDC_ambig_intramol_tot_num', RDC_ambig_intramol_tot_num)
                        cst_sf.add_tag('RDC_ambig_intermol_tot_num', RDC_ambig_intermol_tot_num)
                        cst_sf.add_tag('RDC_intermol_tot_num', RDC_intermol_tot_num)

                elif content_subtype in self.mr_content_subtypes:

                    sf_category = self.sf_categories[file_type][content_subtype]
                    lp_category = self.lp_categories[file_type][content_subtype]

                    restraint_name = getRestraintName(content_subtype)
                    _restraint_name = restraint_name.split()

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
                        if sf_framecode not in sf_item:
                            sf_item[sf_framecode] = {'constraint_type': ' '.join(_restraint_name[:-1])}
                            id_col = lp.tags.index('ID')

                            count = 0

                            prev_id = -1
                            for row in lp:
                                _id = int(row[id_col])
                                if _id == prev_id:
                                    continue
                                prev_id = _id
                                count += 1

                            sf_item[sf_framecode]['id'] = count

        content_subtype = 'dist_restraint'

        sf_category = self.sf_categories[file_type][content_subtype]

        H_bonds_constrained_tot_num = 0
        for sf in master_entry.get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
            if 'constraint_subtype' in sf_item[sf_framecode] and sf_item[sf_framecode]['constraint_subtype'] == 'hydrogen bond':
                H_bonds_constrained_tot_num += sf_item[sf_framecode]['id']

        if H_bonds_constrained_tot_num > 0:
            cst_sf.add_tag('H_bonds_constrained_tot_num', H_bonds_constrained_tot_num)

        SS_bonds_constrained_tot_num = 0
        for sf in master_entry.get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
            if 'constraint_subtype' in sf_item[sf_framecode] and sf_item[sf_framecode]['constraint_subtype'] == 'disulfide bond':
                SS_bonds_constrained_tot_num += sf_item[sf_framecode]['id']

        if SS_bonds_constrained_tot_num > 0:
            cst_sf.add_tag('SS_bonds_constrained_tot_num', SS_bonds_constrained_tot_num)

        Derived_photo_cidnps_tot_num = 0
        for sf in master_entry.get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
            if 'constraint_subtype' in sf_item[sf_framecode] and sf_item[sf_framecode]['constraint_subtype'] == 'photo cidnp':
                Derived_photo_cidnps_tot_num += sf_item[sf_framecode]['id']

        if Derived_photo_cidnps_tot_num > 0:
            cst_sf.add_tag('Derived_photo_cidnps_tot_num', Derived_photo_cidnps_tot_num)

        Derived_paramag_relax_tot_num = 0
        for sf in master_entry.get_saveframes_by_category(sf_category):
            sf_framecode = get_first_sf_tag(sf, 'sf_framecode')
            if 'constraint_subtype' in sf_item[sf_framecode] and sf_item[sf_framecode]['constraint_subtype'] == 'paramagnetic relaxation':
                Derived_paramag_relax_tot_num += sf_item[sf_framecode]['id']

        if Derived_paramag_relax_tot_num > 0:
            cst_sf.add_tag('Derived_paramag_relax_tot_num', Derived_paramag_relax_tot_num)

        lp_category = '_Constraint_file'
        cf_loop = pynmrstar.Loop.from_scratch(lp_category)

        cf_key_items = [{'name': 'ID', 'type': 'int'},
                        {'name': 'Constraint_filename', 'type': 'str'},
                        # {'name': 'Software_ID', 'type': 'int'},
                        # {'name': 'Software_label', 'type': 'str'},
                        # {'name': 'Software_name', 'type': 'str'},
                        {'name': 'Block_ID', 'type': 'int'},
                        {'name': 'Constraint_type', 'type': 'enum',
                         'enum': ('distance', 'dipolar coupling', 'protein dihedral angle', 'nucleic acid dihedral angle',
                                  'coupling constant', 'chemical shift', 'other angle', 'chemical shift anisotropy',
                                  'hydrogen exchange', 'line broadening', 'pseudocontact shift', 'intervector projection angle',
                                  'protein peptide planarity', 'protein other kinds of constraints',
                                  'nucleic acid base planarity', 'nucleic acid other kinds of constraints',
                                  'carbohydrate dihedral angle')},
                        {'name': 'Constraint_subtype', 'type': 'enum',
                         'enum': ('Not applicable', 'NOE', 'NOE buildup', 'NOE not seen', 'general distance',
                                  'alignment tensor', 'chirality', 'prochirality', 'disulfide bond', 'hydrogen bond',
                                  'symmetry', 'ROE', 'peptide', 'ring', 'PRE')},
                        {'name': 'Constraint_subsubtype', 'type': 'enum',
                         'enum': ('ambi', 'simple')}
                        ]
        cf_data_items = [{'name': 'Constraint_number', 'type': 'int'},
                         {'name': 'Constraint_stat_list_ID', 'type': 'int', 'mandatory': True, 'default': '1', 'default-from': 'parent'},
                         {'name': 'Entry_ID', 'type': 'str', 'mandatory': False}
                         ]

        tags = [lp_category + '.' + _item['name'] for _item in cf_key_items]
        tags.extend([lp_category + '.' + _item['name'] for _item in cf_data_items])

        for tag in tags:
            cf_loop.add_tag(tag)

        if has_key_value(input_source_dic, 'content_subtype'):

            block_id = 0

            for content_subtype in self.mr_content_subtypes:
                if content_subtype in input_source_dic['content_subtype']:
                    sf_category = self.sf_categories[file_type][content_subtype]

                    for sf in master_entry.get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        row = [None] * len(tags)

                        row[0], row[1] = 1, self.__srcName
                        sf_allowed_tags = self.sf_allowed_tags[file_type][content_subtype]
                        if 'Constraint_file_ID' in sf_allowed_tags:
                            set_sf_tag(sf, 'Constraint_file_ID', 1)
                        if 'Block_ID' in sf_allowed_tags:
                            block_id += 1
                            _block_id = str(block_id)
                            set_sf_tag(sf, 'Block_ID', _block_id)
                            row[2] = _block_id
                        constraint_type = sf_item[sf_framecode]['constraint_type']
                        constraint_subtype = get_first_sf_tag(sf, 'Constraint_type') if content_subtype != 'other_restraint' else get_first_sf_tag(sf, 'Definition')
                        if len(constraint_subtype) == 0 or constraint_subtype in emptyValue:
                            constraint_subtype = sf_item[sf_framecode]['constraint_subtype']\
                                if 'constraint_subtype' in sf_item[sf_framecode] else None
                        if constraint_subtype is not None and constraint_subtype == 'RDC':  # DAOTHER-9471
                            constraint_type = 'dipolar coupling'

                        constraint_subsubtype = sf_item[sf_framecode]['constraint_subsubtype']\
                            if 'constraint_subsubtype' in sf_item[sf_framecode] else None
                        row[3], row[4], row[5], row[6] =\
                            constraint_type, constraint_subtype, constraint_subsubtype, sf_item[sf_framecode]['id']
                        row[7], row[8] = 1, self.__entry_id

                        cf_loop.add_data(row)

            cst_sf.add_loop(cf_loop)

            if len(cf_loop) > 0:
                master_entry.add_saveframe(cst_sf)

        # Update _Data_set/Datum loop

        try:

            content_subtype = 'entry_info'

            sf_category = self.sf_categories[file_type][content_subtype]

            sf = master_entry.get_saveframes_by_category(sf_category)[0]

            # Update _Data_set loop

            lp_category = '_Data_set'

            loop = next((loop for loop in sf.loops if loop.category == lp_category), None)

            if loop is not None:
                del sf[loop]

            lp = pynmrstar.Loop.from_scratch(lp_category)

            items = ['Type', 'Count', 'Entry_ID']

            tags = [lp_category + '.' + item for item in items]

            for tag in tags:
                lp.add_tag(tag)

            for content_subtype in self.nmr_rep_content_subtypes:
                sf_category = self.sf_categories[file_type][content_subtype]

                if sf_category.endswith('constraints'):  # ignore non-quantitative data set
                    continue

                count = sum(1 for sf in master_entry.frame_list if sf.category == sf_category)

                if count > 0:
                    row = [sf_category, count, self.__entry_id]
                    lp.add_data(row)
                    lp.data.sort()

            lp.sort_rows('Type')

            sf.add_loop(lp)

            # Update _Datum loopa

            lp_category = '_Datum'

            loop = next((loop for loop in sf.loops if loop.category == lp_category), None)

            if loop is not None:
                del sf[loop]

            lp = pynmrstar.Loop.from_scratch(lp_category)

            tags = [lp_category + '.' + item for item in items]

            for tag in tags:
                lp.add_tag(tag)

            datum_counter = self.__getDatumCounter(master_entry)

            for k, v in datum_counter.items():
                row = [k, v, self.__entry_id]
                lp.add_data(row)

            sf.add_loop(lp)

        except IndexError:
            pass

        master_entry = self.__c2S.normalize_str(master_entry)

        if __pynmrstar_v3__:
            master_entry.write_to_file(self.__dstPath, show_comments=(self.__bmrb_only and self.__internal_mode), skip_empty_loops=True, skip_empty_tags=False)
        else:
            master_entry.write_to_file(self.__dstPath)

        return True

    def __detectSimpleDistanceRestraint(self):
        """ Detect simple distance restraints.
        """

        if self.__dstPath is None:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        file_type = input_source_dic['file_type']

        if file_type == 'nef':
            return True

        if len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        master_entry = self.__star_data[0]

        if not isinstance(master_entry, pynmrstar.Entry):
            return False

        sf_category = 'constraint_statistics'
        lp_category = '_Constraint_file'

        try:

            sf = master_entry.get_saveframes_by_category(sf_category)[0]

            data_file_name = get_first_sf_tag(sf, 'Data_file_name')
            if len(data_file_name) == 0:
                data_file_name = self.__srcName

            try:
                if __pynmrstar_v3_2__:
                    lp = sf.get_loop(lp_category)
                else:
                    lp = sf.get_loop_by_category(lp_category)
            except KeyError:
                return False

            try:
                block_id_col = lp.tags.index('Block_ID')
            except ValueError:
                return False
            try:
                file_name_col = lp.tags.index('Constraint_filename')
            except ValueError:
                return False

            constraint_type_col = lp.tags.index('Constraint_type')
            constraint_subtype_col = lp.tags.index('Constraint_subtype')
            constraint_subsubtype_col = lp.tags.index('Constraint_subsubtype')

            dist_rows = [row for row in lp if row[constraint_type_col] == 'distance']

            subtypes_not_derived_from_noes = ('paramagnetic relaxation',
                                              'photo cidnp',
                                              'chemical shift perturbation',
                                              'mutation',
                                              'symmetry',
                                              'metal coordination',
                                              'diselenide bond',
                                              'disulfide bond',
                                              'hydrogen bond')

            if len(dist_rows) == 0\
               or any(row for row in dist_rows
                      if row[constraint_subtype_col] not in subtypes_not_derived_from_noes
                      and row[constraint_subsubtype_col] == 'simple'):
                return True

            content_subtype = 'dist_restraint'

            sf_category = self.sf_categories[file_type][content_subtype]
            lp_category = self.lp_categories[file_type][content_subtype]

            if not any(row for row in dist_rows
                       if row[constraint_subtype_col] not in subtypes_not_derived_from_noes):

                subtypes = ','.join([row[constraint_subtype_col] for row in dist_rows])

                warn = f"There is no unique distance restraints derived from NOE/ROE experiment, except for {subtypes}. "\
                       "The wwPDB NMR Validation Task Force highly recommends the submission of unambiguous distance restraints "\
                       "used for the structure determination."

                self.report.warning.appendDescription('missing_content',
                                                      {'file_name': data_file_name, 'category': lp_category,
                                                       'description': warn})

                self.report.setWarning()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__detectSimpleDistanceRestraint() ++ Warning  - {warn}\n")

                return False

            block_ids = {row[block_id_col]: row[file_name_col] for row in dist_rows
                         if row[constraint_subtype_col] not in subtypes_not_derived_from_noes}

            for block_id in block_ids:
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    if get_first_sf_tag(sf, 'Block_ID') == block_id:

                        try:
                            if __pynmrstar_v3_2__:
                                lp = sf.get_loop(lp_category)
                            else:
                                lp = sf.get_loop_by_category(lp_category)
                        except KeyError:
                            continue

                        item_names = self.item_names_in_ds_loop[file_type]
                        id_col = lp.tags.index('ID')
                        member_logic_code_col = lp.tags.index('Member_logic_code') if 'Member_logic_code' in lp.tags else -1
                        auth_asym_id_1_col = lp.tags.index('Auth_asym_ID_1')
                        auth_seq_id_1_col = lp.tags.index('Auth_seq_ID_1')
                        auth_asym_id_2_col = lp.tags.index('Auth_asym_ID_2')
                        auth_seq_id_2_col = lp.tags.index('Auth_seq_ID_2')
                        comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                        comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                        atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                        atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                        for row in lp:
                            if member_logic_code_col != -1 and row[member_logic_code_col] != 'OR':
                                return True

                        prev_id = -1
                        for row in lp:
                            if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                                _id = int(row[id_col])
                                if _id != prev_id:
                                    _atom1 = {'chain_id': row[auth_asym_id_1_col],
                                              'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                              'comp_id': row[comp_id_1_col],
                                              'atom_id': row[atom_id_1_col]}
                                    _atom2 = {'chain_id': row[auth_asym_id_2_col],
                                              'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                              'comp_id': row[comp_id_2_col],
                                              'atom_id': row[atom_id_2_col]}
                                    prev_id = _id
                                    continue
                                atom1 = {'chain_id': row[auth_asym_id_1_col],
                                         'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in emptyValue else None,
                                         'comp_id': row[comp_id_1_col],
                                         'atom_id': row[atom_id_1_col]}
                                atom2 = {'chain_id': row[auth_asym_id_2_col],
                                         'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in emptyValue else None,
                                         'comp_id': row[comp_id_2_col],
                                         'atom_id': row[atom_id_2_col]}
                                if not isAmbigAtomSelection([_atom1, atom1], self.__csStat)\
                                   and not isAmbigAtomSelection([_atom2, atom2], self.__csStat):
                                    return True
                                _atom1, _atom2 = atom1, atom2

            for block_id, file_name in block_ids.items():
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    if block_id == get_first_sf_tag(sf, 'Block_ID'):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                        warn = "There is no unique distance restraints derived from NOE/ROE experiment in the set of uploaded restraint file(s). "\
                               "The wwPDB NMR Validation Task Force highly recommends the submission of unambiguous distance restraints "\
                               "used for the structure determination."

                        self.report.warning.appendDescription('encouragement',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode, 'category': lp_category,
                                                               'description': warn})

                        self.report.setWarning()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__detectSimpleDistanceRestraint() ++ Warning  - {warn}\n")

            return False

        except IndexError:
            return True

    def __initializeDpReportForNext(self):
        """ Initialize NMR data processing report using the next version of NMR unified data.
        """

        return self.__initializeDpReport(srcPath=self.__dstPath)

    def __validateInputSourceForNext(self):
        """ Validate the next version of NMR unified data as primary input source.
        """

        return self.__validateInputSource(srcPath=self.__dstPath)

    def __translateNef2Str(self):
        """ Translate NEF to NMR-STAR V3.2 file.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        if self.__dstPath is None:
            raise KeyError("+NmrDpUtility.__translateNef2Str() ++ Error  - Could not find destination path as input NEF file for NEFTranslator.")

        file_name = os.path.basename(self.__dstPath)
        file_type = input_source_dic['file_type']

        if 'nmr-star_file_path' not in self.__outputParamDict:
            raise KeyError("+NmrDpUtility.__translateNef2Str() ++ Error  - Could not find 'nmr-star_file_path' output parameter.")

        fPath = self.__outputParamDict['nmr-star_file_path']

        try:

            is_valid, message = self.__nefT.nef_to_nmrstar(self.__dstPath, fPath,
                                                           report=self.report,
                                                           leave_unmatched=self.__leave_intl_note)  # (None if self.__alt_chain else self.report))

            if self.__release_mode and self.__tmpPath is not None:
                os.remove(self.__tmpPath)
                self.__tmpPath = None

        except Exception as e:

            err = f"{file_name} is not compliant with the {self.readable_file_type[file_type]} dictionary."

            if 'No such file or directory' not in str(e):
                err += ' ' + re.sub('not in list', 'unknown item.', str(e))

            if not self.report.isError():
                self.report.error.appendDescription('format_issue',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

            self.__lfh.write("+NmrDpUtility.__translateNef2Str() ++ Error  - "
                             f"{file_name} {err}\n")

            if os.path.exists(fPath):
                os.remove(fPath)

            return False

        if is_valid:

            if 'deposit' in self.__op and 'nmr_cif_file_path' in self.__outputParamDict:

                # if self.__remediation_mode:

                try:

                    myIo = IoAdapterPy(False, sys.stderr)
                    containerList = myIo.readFile(fPath)

                    if containerList is not None and len(containerList) > 1:

                        if self.__verbose:
                            self.__lfh.write(f"Input container list is {[(c.getName(), c.getType()) for c in containerList]!r}\n")

                        for c in containerList:
                            c.setType('data')

                        myIo.writeFile(self.__outputParamDict['nmr_cif_file_path'], containerList=containerList[1:])

                except Exception as e:
                    self.__lfh.write(f"+NmrDpUtility.__translateNef2Str() ++ Error  - {str(e)}\n")

            return True

        err = f"{file_name} is not compliant with the {self.readable_file_type[file_type]} dictionary."

        if len(message['error']) > 0:
            for err_message in message['error']:
                if 'No such file or directory' not in err_message:
                    err += ' ' + re.sub('not in list', 'unknown item.', err_message)

        if not self.report.isError():
            self.report.error.appendDescription('format_issue',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

        self.__lfh.write("+NmrDpUtility.__translateNef2Str() ++ Error  - "
                         f"{file_name} {err}\n")

        if os.path.exists(fPath):
            os.remove(fPath)

        return False

    def __initResourceForNef2Str(self):
        """ Initialize resources for the translated NMR-STAR V3.2 file.
        """

        self.__rescue_mode = False

        self.report_prev = None

        try:

            self.__srcPath = self.__outputParamDict['nmr-star_file_path']
            self.__dstPath = self.__srcPath
            self.__logPath = self.__outputParamDict.get('report_file_path')
            if self.__logPath is not None:
                self.addInput('report_file_path', self.__logPath, type='file')
            self.__op = 'nmr-str-consistency-check'

            # reset cache dictionaries

            for content_subtype in self.__lp_data:
                self.__lp_data[content_subtype] = []

            for content_subtype in self.__aux_data:
                self.__aux_data[content_subtype] = []

            for content_subtype in self.__sf_tag_data:
                self.__sf_tag_data[content_subtype] = []

            return True

        except Exception:
            raise KeyError("+NmrDpUtility.__initReousrceForNef2Str() ++ Error  - Could not find 'nmr-star_file_path' or 'report_file_path' output parameter.")

        return False

    def __translateStr2Nef(self):
        """ Translate NMR-STAR V3.2 to NEF file.
        """

        if not self.__combined_mode:
            return True

        input_source = self.report.input_sources[0]
        input_source_dic = input_source.get()

        if self.__dstPath is None:
            raise KeyError("+NmrDpUtility.__translateStr2Nef() ++ Error  - Could not find destination path as input NMR-STAR file for NEFTranslator.")

        file_name = os.path.basename(self.__dstPath)
        file_type = input_source_dic['file_type']

        if 'nef_file_path' not in self.__outputParamDict:
            raise KeyError("+NmrDpUtility.__translateStr2Nef() ++ Error  - Could not find 'nef_file_path' output parameter.")

        fPath = self.__outputParamDict['nef_file_path']

        try:

            is_valid, message = self.__nefT.nmrstar_to_nef(self.__dstPath, fPath,
                                                           report=self.report)  # (None if self.__alt_chain else self.report))

            if self.__release_mode and self.__tmpPath is not None:
                os.remove(self.__tmpPath)
                self.__tmpPath = None

        except Exception as e:

            err = f"{file_name} is not compliant with the {self.readable_file_type[file_type]} dictionary."

            if 'No such file or directory' not in str(e):
                err += ' ' + re.sub('not in list', 'unknown item.', str(e))

            if not self.report.isError():
                self.report.error.appendDescription('format_issue',
                                                    {'file_name': file_name, 'description': err})
                self.report.setError()

            self.__lfh.write("+NmrDpUtility.__translateStr2Nef() ++ Error  - "
                             f"{file_name} {err}\n")

            if os.path.exists(fPath):
                os.remove(fPath)

            return False

        if is_valid:
            return True

        err = f"{file_name} is not compliant with the {self.readable_file_type[file_type]} dictionary."

        if len(message['error']) > 0:
            for err_message in message['error']:
                if 'No such file or directory' not in err_message:
                    err += ' ' + re.sub('not in list', 'unknown item.', err_message)

        if not self.report.isError():
            self.report.error.appendDescription('format_issue',
                                                {'file_name': file_name, 'description': err})
            self.report.setError()

        self.__lfh.write("+NmrDpUtility.__translateStr2Nef() ++ Error  - "
                         f"{file_name} {err}\n")

        if os.path.exists(fPath):
            os.remove(fPath)

        return False

    def __initResourceForStr2Nef(self):
        """ Initialize resources for the translated NEF file.
        """

        self.__rescue_mode = False

        self.report_prev = None

        try:

            self.__srcPath = self.__outputParamDict['nef_file_path']
            self.__dstPath = self.__srcPath
            self.__logPath = self.__outputParamDict.get('report_file_path')
            if self.__logPath is not None:
                self.addInput('report_file_path', self.__logPath, type='file')
            self.__op = 'nmr-nef-consistency-check'

            # reset cache dictionaries

            for content_subtype in self.__lp_data:
                self.__lp_data[content_subtype] = []

            for content_subtype in self.__aux_data:
                self.__aux_data[content_subtype] = []

            for content_subtype in self.__sf_tag_data:
                self.__sf_tag_data[content_subtype] = []

            return True

        except Exception:
            raise KeyError("+NmrDpUtility.__initReousrceForStr2Nef() ++ Error  - Could not find 'nef_file_path' or 'report_file_path' output parameter.")

        return False


if __name__ == '__main__':
    dp = NmrDpUtility()
