##
# 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 separated 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 separated deposition (DAOTHER-5594)
# 19-Apr-2020  M. Yokochi - support concatenated CS data in NMR separated 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 separated 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, i.e. 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,
#                           i.e. 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)
##
""" Wrapper class for NMR data processing.
    @author: Masashi Yokochi
"""
import sys
import os
import os.path
import itertools
import copy
import collections
import re
import math
import codecs
import shutil
import time
import hashlib
import pynmrstar

from packaging import version
from munkres import Munkres
import numpy as np

from wwpdb.utils.nmr.NEFTranslator.NEFTranslator import NEFTranslator
from wwpdb.utils.nmr.NmrDpReport import NmrDpReport
from wwpdb.utils.align.alignlib import PairwiseAlign  # pylint: disable=no-name-in-module
from wwpdb.utils.nmr.BMRBChemShiftStat import BMRBChemShiftStat
from wwpdb.utils.config.ConfigInfo import getSiteId
from wwpdb.utils.config.ConfigInfoApp import ConfigInfoAppCommon
from wwpdb.utils.nmr.io.ChemCompIo import ChemCompReader
from wwpdb.utils.nmr.io.CifReader import CifReader
from wwpdb.utils.nmr.rci.RCI import RCI
from wwpdb.utils.nmr.CifToNmrStar import CifToNmrStar
from wwpdb.utils.nmr.NmrStarToCif import NmrStarToCif

__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")


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

    with open(in_file, 'rb') as f:
        raw = f.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(in_file, out_file, in_codec='utf-8', out_codec='utf-8'):
    """ Convert codec of input file.
    """

    with open(in_file, 'rb') as ifp:
        with open(out_file, 'w+b') as ofp:
            contents = ifp.read()
            ofp.write(contents.decode(in_codec).encode(out_codec))

            ofp.close()

        ifp.close()


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 not d[key] is None

    return False


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

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


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

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

    array = sf_data.get_tag(tag)

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

    return array[0]


def has_large_seq_gap(s1, s2):
    """ Return whether large gap in sequence ID.
    """

    seq_ids = sorted(set(s1['seq_id']) | set(s2['seq_id']))

    for lp, i in enumerate(seq_ids):
        if lp > 0 and i - seq_ids[lp - 1] > 20:
            return True

    return False


def fill_blank_comp_id(s1, s2):
    """ Fill blanked comp ID in s2 against s1.
    """

    seq_ids = sorted(set(s1['seq_id']) | set(s2['seq_id']))
    comp_ids = []

    for i in seq_ids:
        if i in s2['seq_id']:
            j = s2['seq_id'].index(i)
            if j < len(s2['comp_id']):
                comp_ids.append(s2['comp_id'][j])
            else:
                comp_ids.append('.')
        else:
            comp_ids.append('.')

    return {'chain_id': s2['chain_id'], 'seq_id': seq_ids, 'comp_id': comp_ids}


def fill_blank_comp_id_with_offset(s, offset):
    """ Fill blanked comp ID with offset.
    """

    seq_ids = list(range(s['seq_id'][0] - offset, s['seq_id'][-1] + 1))
    comp_ids = []

    for i in seq_ids:
        if i in s['seq_id']:
            j = s['seq_id'].index(i)
            if j < len(s['comp_id']):
                comp_ids.append(s['comp_id'][j])
            else:
                comp_ids.append('.')
        else:
            comp_ids.append('.')

    return {'chain_id': s['chain_id'], 'seq_id': seq_ids, 'comp_id': comp_ids}


def beautify_seq_id(s1, s2):
    """ Truncate negative sequence IDs of s1 and s2 and insert spacing between the large gap.
    """

    _s1 = fill_blank_comp_id(s2, s1)  # pylint: disable=arguments-out-of-order
    _s2 = fill_blank_comp_id(s1, s2)  # pylint: disable=arguments-out-of-order

    if _s1['seq_id'] != _s2['seq_id']:
        return _s1, _s2

    _seq_id = [seq_id for seq_id in _s1['seq_id'] if seq_id > 0]
    _comp_id_1 = [comp_id for seq_id, comp_id in zip(_s1['seq_id'], _s1['comp_id']) if seq_id > 0]
    _comp_id_2 = [comp_id for seq_id, comp_id in zip(_s1['seq_id'], _s2['comp_id']) if seq_id > 0]

    gap_seq_id = []
    gap_index = []

    len_spacer = 5  # DAOTHER-7465, issue #2

    for lp, i in enumerate(_seq_id):
        if lp > 0 and i - _seq_id[lp - 1] > 20:
            j = _seq_id[lp - 1]
            for sp in range(1, len_spacer + 1):
                gap_seq_id.append(j + sp)
                gap_seq_id.append(i - sp)
            gap_index.append(lp)

    if len(gap_seq_id) == 0:
        return {'chain_id': _s1['chain_id'], 'seq_id': _seq_id, 'comp_id': _comp_id_1}, {'chain_id': _s2['chain_id'], 'seq_id': _seq_id, 'comp_id': _comp_id_2}

    _seq_id.extend(gap_seq_id)
    _seq_id.sort()

    for lp in reversed(gap_index):
        for sp in range(1, len_spacer + 1):
            _comp_id_1.insert(lp, '.')
            _comp_id_2.insert(lp, '.')

    return {'chain_id': _s1['chain_id'], 'seq_id': _seq_id, 'comp_id': _comp_id_1}, {'chain_id': _s2['chain_id'], 'seq_id': _seq_id, 'comp_id': _comp_id_2}


def get_middle_code(ref_seq, test_seq):
    """ Return array of middle code of sequence alignment.
    """

    array = ''

    for i, rs in enumerate(ref_seq):
        if i < len(test_seq):
            array += '|' if rs == test_seq[i] and rs != '.' else ' '
        else:
            array += ' '

    return array


def get_gauge_code(seq_id, offset=0):
    """ Return gauge code for seq ID.
    """

    if offset > 0:
        _seq_id = list(range(1, offset + 1))
        for lp, sid in enumerate(seq_id):
            if lp < offset:
                continue
            _seq_id.append(sid)
        seq_id = _seq_id

    sid_len = len([sid for sid in seq_id if sid is not None])
    code_len = 0

    chars = []

    for sid in seq_id:

        if sid is None:
            chars.append('-')
            continue

        if sid >= 0 and sid % 10 == 0 and code_len == 0:

            code = str(sid)
            code_len = len(code)

            for j in range(code_len):
                chars.append(code[j])

        if code_len > 0:
            code_len -= 1
        else:
            chars.append('-')

    for lp, sid in enumerate(seq_id):

        if sid is None or sid % 10 != 0:
            continue

        code = str(sid)
        code_len = len(code)

        if lp - code_len > 0:
            for j in range(code_len):
                chars[lp + j - code_len + 1] = chars[lp + j]
                chars[lp + j] = '-'

    if offset > 0:
        for lp in range(offset):
            chars[lp] = '-'

    array = ''.join(chars)

    if sid_len == len(seq_id):
        return array[:sid_len]

    _sid_len = len(seq_id)

    offset = 0
    for lp, sid in enumerate(seq_id):

        if sid is None:
            p = lp + offset
            array = array[0:p] + ' ' + array[p:]
            offset += 1

    return array[:_sid_len]


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 romermeric 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 romermeric 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 romermeric 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 to_np_array(a):
    """ Return Numpy array of a given Cartesian coordinate in {'x': float, 'y': float, 'z': float} format.
    """

    return np.asarray([a['x'], a['y'], a['z']])


def to_unit_vector(a):
    """ Return unit vector of a given vector.
    """

    return a / np.linalg.norm(a)


def dihedral_angle(p0, p1, p2, p3):
    """ Return dihedral angle from a series of four points.
    """

    b0 = -1.0 * (p1 - p0)
    b1 = p2 - p1
    b2 = p3 - p2

    # normalize b1 so that it does not influence magnitude of vector
    # rejections that come next
    b1 = to_unit_vector(b1)

    # vector rejections
    # v = projection of b0 onto plane perpendicular to b1
    #   = b0 minus component that aligns with b1
    # w = projection of b2 onto plane perpendicular to b1
    #   = b2 minus component that aligns with b1
    v = b0 - np.dot(b0, b1) * b1
    w = b2 - np.dot(b2, b1) * b1

    # angle between v and w in a plane is the torsion angle
    # v and w may not be normalized but that's fine since tan is y/x
    x = np.dot(v, w)
    y = np.dot(np.cross(b1, v), w)

    return np.degrees(np.arctan2(y, x))


def score_of_seq_align(my_align):
    """ Return score of sequence alignment.
    """

    length = len(my_align)

    aligned = [True] * length

    for i in range(length):
        myPr = my_align[i]
        myPr0 = str(myPr[0])
        myPr1 = str(myPr[1])
        if myPr0 == '.' or myPr1 == '.':
            aligned[i] = False
        elif myPr0 != myPr1:
            pass
        else:
            break

    not_aligned = True
    offset_1 = 0
    offset_2 = 0

    unmapped = 0
    conflict = 0
    _matched = 0
    for i in range(length):
        myPr = my_align[i]
        myPr0 = str(myPr[0])
        myPr1 = str(myPr[1])
        if myPr0 == '.' or myPr1 == '.':
            if not_aligned and not aligned[i]:
                if myPr0 == '.' and myPr1 != '.' and offset_2 == 0:  # DAOTHER-7421
                    offset_1 += 1
                if myPr0 != '.' and myPr1 == '.' and offset_1 == 0:  # DAOTHER-7421
                    offset_2 += 1
                if myPr0 == '.' and myPr1 == '.':  # DAOTHER-7465
                    if offset_2 == 0:
                        offset_1 += 1
                    if offset_1 == 0:
                        offset_2 += 1
            unmapped += 1
        elif myPr0 != myPr1:
            conflict += 1
        else:
            not_aligned = False
            _matched += 1

    return _matched, unmapped, conflict, offset_1, offset_2


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

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

        self.__lfh = log

        # current workflow operation
        self.__op = None

        # whether to run initial rescue routine
        self.__rescue_mode = True
        # whether NMR combined deposition or not (NMR separated deposition)
        self.__combined_mode = True
        # whether to use datablock name of public release
        self.__release_mode = 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 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 separated deposition or release mode)
        self.__fix_format_issue = False
        # whether to exclude missing mandatory data (enabled if NMR separated deposition)
        self.__excl_missing_data = False
        # whether to detect empty row in a loop # NEFTranslator.validate_file() already prompts the empty low error
        # self.__check_empty_loop = 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'
        # whether to insert entry_id (nmr-star specific)
        self.__insert_entry_id_to_loops = True

        # 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 legacy distance restraint has been uploaded
        self.__legacy_dist_restraint_uploaded = False

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

        self.__cifPath = None

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

        # auxiliary input resource
        self.__inputParamDict = {}

        # auxiliary output resource
        self.__outputParamDict = {}

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

        # validation tasks for NMR data only
        __nmrCheckTasks = [self.__detectContentSubType,
                           self.__detectContentSubTypeOfLegacyMR,
                           self.__extractPolymerSequence,
                           self.__extractPolymerSequenceInLoop,
                           # self.__testSequenceConsistency,
                           self.__extractCommonPolymerSequence,
                           self.__extractNonStandardResidue,
                           self.__appendPolymerSequenceAlignment,
                           self.__testSequenceConsistency,
                           self.__validateAtomNomenclature,
                           self.__appendElemAndIsoNumOfNefCSLoop,
                           self.__validateAtomTypeOfCSLoop,
                           self.__validateAmbigCodeOfCSLoop,
                           self.__appendIndexTag,
                           self.__testIndexConsistency,
                           self.__appendWeightInLoop,
                           self.__appendDihedAngleType,
                           self.__testDataConsistencyInLoop,
                           self.__detectConflictDataInLoop,
                           self.__testDataConsistencyInAuxLoop,
                           self.__appendSfTagItem,
                           self.__testSfTagConsistency,
                           # self.__validateCSValue,
                           self.__testCSValueConsistencyInPkLoop,
                           self.__testCSValueConsistencyInPkAltLoop,
                           self.__testRDCVector
                           ]

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

        # cross validation tasks
        __crossCheckTasks = [self.__assignCoordPolymerSequence,
                             self.__testCoordAtomIdConsistency,
                             self.__testCovalentBond,
                             self.__testResidueVariant,
                             self.__validateCSValue,
                             self.__extractCoordDisulfideBond,
                             self.__extractCoordOtherBond,
                             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,
                          self.__parseCoordinate,
                          # 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.__fixBadAmbiguityCode,
                          self.__resetCapitalStringInLoop,
                          self.__resetBoolValueInLoop,
                          self.__resetBoolValueInAuxLoop,
                          self.__appendParentSfTag,
                          self.__addUnnamedEntryId,
                          self.__depositNmrData,
                          # re-setup for next
                          self.__initializeDpReportForNext,
                          self.__validateInputSourceForNext
                          ]

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

        # additional nmr-nef2str 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)

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

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

        # NEFTranslator
        self.__nefT = NEFTranslator()

        if self.__nefT is None:
            raise IOError("+NmrDpUtility.__init__() ++ Error  - NEFTranslator is not available.")

        # BMRB chemical shift statistics
        self.__csStat = BMRBChemShiftStat()

        if not self.__csStat.isOk():
            raise IOError("+NmrDpUtility.__init__() ++ Error  - BMRBChemShiftStat is not available.")

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

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

        self.__original_error_message = []

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

        self.__alt_chain = False
        self.__valid_seq = False

        # empty value
        self.empty_value = (None, '', '.', '?')

        # true value
        self.true_value = ('true', 't', 'yes', 'y', '1')

        # 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')

        # CIF content types
        self.cif_content_subtypes = ('poly_seq', 'non_poly', '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
                             }

        # paramagnetic elements, except for Oxygen
        self.paramag_elems = ('LI', 'NA', 'MG', 'AL', 'K', 'CA', 'SC', 'TI', 'V', 'MN', 'RB', 'SR',
                              'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'SN', 'CS', 'BA', 'LA',
                              'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM',
                              'YB', 'LU', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'FR', 'RA', 'AC')

        # ferromagnetic elements
        self.ferromag_elems = ('CR', 'FE', 'CO', 'NI')

        # non-metal elements
        self.non_metal_elems = ('H', 'C', 'N', 'O', 'P', 'S', 'SE')

        # isotope numbers of NMR observable atoms
        self.atom_isotopes = {'H': [1, 2, 3],
                              'C': [13],
                              'N': [15, 14],
                              'O': [17],
                              'P': [31],
                              'S': [33],
                              'F': [19],
                              'CD': [113, 111],
                              'CA': [43]
                              }

        # nucleus with half spin
        self.half_spin_nucleus = ('H', 'C', 'N', 'P', 'F', 'CD')

        # ambiguity codes
        self.bmrb_ambiguity_codes = (1, 2, 3, 4, 5, 6, 9)

        isotope_nums = []
        for i in self.atom_isotopes.values():
            isotope_nums.extend(list(i))

        # 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
                                      },
                              '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'
                                           }
                              }

        # 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
                                      },
                              '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'
                                           },
                              'pdbx': {'poly_seq': 'pdbx_poly_seq_scheme',
                                       'non_poly': 'pdbx_nonpoly_scheme',
                                       'coordinate': 'atom_site',
                                       'poly_seq_alias': 'ndb_poly_seq_scheme',
                                       'non_poly_alias': 'ndb_nonpoly_scheme'
                                       }
                              }

        # allowed chem shift range
        self.chem_shift_range = {'min_exclusive': -300.0, 'max_exclusive': 300.0}
        self.chem_shift_error = {'min_inclusive': 0.0, 'max_inclusive': 3.0}

        # allowed distance range
        self.dist_restraint_range = {'min_inclusive': 0.5, 'max_inclusive': 50.0}
        self.dist_restraint_error = {'min_inclusive': 0.0, 'max_inclusive': 5.0}

        # allowed dihed range
        self.dihed_restraint_range = {'min_inclusive': -360.0, 'max_inclusive': 360.0}
        self.dihed_restraint_error = {'min_inclusive': 0.0, 'max_inclusive': 90.0}

        # allowed rdc range
        self.rdc_restraint_range = {'min_exclusive': -100.0, 'max_exclusive': 100.0}
        self.rdc_restraint_error = {'min_inclusive': 0.0, 'max_inclusive': 5.0}

        # allowed weight range
        self.weight_range = {'min_inclusive': 0.0, 'max_inclusive': 100.0}
        # allowed scale range
        self.scale_range = self.weight_range

        # criterion for low sequence coverage
        self.low_seq_coverage = 0.3

        # criterion for minimum sequence coverage when conflict occurs (NMR separated deposition)
        self.min_seq_coverage_w_conflict = 0.95

        # 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 = 8.0

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

        # 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
                                   },
                           '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
                                        },
                           'pdbx': {'poly_seq': None,
                                    'non_poly': 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
                                    },
                            '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
                                         },
                            'pdbx': {'poly_seq': None,
                                     'non_poly': 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
                                        },
                                'nmr-star': {'dist_restraint': 'ID',
                                             'dihed_restraint': 'ID',
                                             'rdc_restraint': 'ID',
                                             'spectral_peak': 'ID',
                                             'spectral_peak_alt': 'ID'
                                             }
                                }

        # key items of loop
        self.key_items = {'nef': {'poly_seq': [{'name': 'chain_code', 'type': 'str', 'default': 'A'},
                                               {'name': 'sequence_code', 'type': 'int'},
                                               {'name': 'residue_name', 'type': 'str', 'uppercase': 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'},
                                                     {'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': 'restraint_id', 'type': 'positive-int'},
                                                      {'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': 'restraint_id', 'type': 'positive-int'},
                                                    {'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
                                  },
                          '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'},
                                                    {'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',
                                                       'remove-bad-pattern': True},
                                                      {'name': 'Comp_ID', 'type': 'str',
                                                       'uppercase': True,
                                                       'remove-bad-pattern': True},
                                                      {'name': 'Atom_ID', 'type': 'str',
                                                       'remove-bad-pattern': True}
                                                      ],
                                       'chem_shift_ref': [{'name': 'Atom_type', 'type': 'enum', 'enum': set(self.atom_isotopes.keys()),
                                                           'enforce-enum': True},
                                                          {'name': 'Atom_isotope_number', 'type': 'enum-int', 'enum': set(isotope_nums),
                                                           'enforce-enum': True},
                                                          {'name': 'Mol_common_name', 'type': 'str'}],
                                       'dist_restraint': [{'name': 'ID', 'type': 'positive-int'},
                                                          {'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'}
                                                          ],
                                       'dihed_restraint': [{'name': 'ID', 'type': 'positive-int'},
                                                           {'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'},
                                                           {'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'},
                                                           {'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'},
                                                           {'name': 'Comp_ID_4', 'type': 'str', 'uppercase': True},
                                                           {'name': 'Atom_ID_4', 'type': 'str'}
                                                           ],
                                       'rdc_restraint': [{'name': 'ID', 'type': 'positive-int'},
                                                         {'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'}
                                                         ],
                                       'spectral_peak': None,
                                       'spectral_peak_alt': [{'name': 'ID', 'type': 'positive-int'},
                                                             {'name': 'Spectral_peak_list_ID', 'type': 'positive-int', 'default': '1', 'default-from': 'self'}
                                                             ]
                                       },
                          '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': 'str', 'alt_name': 'comp_id'},
                                                {'name': 'pdb_strand_id', 'type': 'str', 'alt_name': 'auth_chain_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': 'str', 'alt_name': 'comp_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': 'str', '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': 'str', 'alt_name': 'comp_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', 'alt_name': 'label_seq_id', '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', 'alt_name': 'label_seq_id', '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
                                          },
                                  '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'},
                                                                  {'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'}
                                                                  ],
                                               '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'},
                                                                   {'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'},
                                                                   {'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'},
                                                                   {'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'},
                                                                   {'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'},
                                                                 {'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'}
                                                                 ],
                                               'spectral_peak': None,
                                               'spectral_peak_alt': None
                                               }
                                  }

        # limit number of dimensions
        self.lim_num_dim = 16

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

        # 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,
                                                   'range': self.chem_shift_range},
                                                  {'name': 'value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                   'range': self.chem_shift_error},
                                                  {'name': 'element', 'type': 'enum', 'mandatory': True, 'default-from': 'atom_name',
                                                   'enum': set(self.atom_isotopes.keys()),
                                                   'enforce-enum': True},
                                                  {'name': 'isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'atom_name',
                                                   'enum': set(isotope_nums),
                                                   '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': self.weight_range},
                                                      # 'enforce-non-zero': True},
                                                      {'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'range': self.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,
                                                       'range': self.dist_restraint_error},
                                                      {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                       'range': self.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,
                                                       'range': self.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,
                                                       'range': self.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,
                                                       'range': self.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': self.weight_range},
                                                       # 'enforce-non-zero': True},
                                                       {'name': 'target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'range': self.dihed_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'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'target_value_uncertainty', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                        'range': self.dihed_restraint_error},
                                                       {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'range': self.dihed_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'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'lower_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'range': self.dihed_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'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'upper_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'range': self.dihed_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'],
                                                                  'circular-shift': 360.0}},
                                                       {'name': 'upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                        'range': self.dihed_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,
                                                                  '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,
                                                      'range': self.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,
                                                      'range': self.rdc_restraint_error},
                                                     {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                      'range': self.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,
                                                      'range': self.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,
                                                      'range': self.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,
                                                      'range': self.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': self.scale_range,
                                                      'enforce-non-zero': True},
                                                     {'name': 'distance_dependent', 'type': 'bool', 'mandatory': False}
                                                     ],
                                   'spectral_peak': [{'name': 'index', 'type': 'index-int', 'mandatory': True},
                                                     {'name': 'peak_id', 'type': 'positive-int', 'mandatory': True,
                                                      'enforce-non-zero': True},
                                                     {'name': 'volume', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                      'group': {'member-with': ['height'],
                                                                'coexist-with': None}},
                                                     {'name': 'volume_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True},
                                                     {'name': 'height', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                      'group': {'member-with': ['volume'],
                                                                'coexist-with': None}},
                                                     {'name': 'height_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True}
                                                     ],
                                   'spectral_peak_alt': None
                                   },
                           'nmr-star': {'poly_seq': [{'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(self.atom_isotopes.keys()),
                                                        'enforce-enum': True},
                                                       {'name': 'Atom_isotope_number', 'type': 'enum-int', 'mandatory': True, 'default-from': 'Atom_ID',
                                                        'enum': set(isotope_nums),
                                                        'enforce-enum': True},
                                                       {'name': 'Val', 'type': 'range-float', 'mandatory': True,
                                                        'range': self.chem_shift_range},
                                                       {'name': 'Val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                        'range': self.chem_shift_error},
                                                       {'name': 'Ambiguity_code', 'type': 'enum-int', 'mandatory': False,
                                                        'enum': self.bmrb_ambiguity_codes,
                                                        'enforce-enum': True},
                                                       {'name': 'Ambiguity_set_ID', 'type': 'positive-int', 'mandatory': False,
                                                        'enforce-non-zero': True},
                                                       {'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': 'ID', 'type': 'positive-int', 'mandatory': True,
                                                           # 'enforce-non-zero': True},
                                                           {'name': 'Combination_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,
                                                            'range': self.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,
                                                            'range': self.dist_restraint_error},
                                                           {'name': 'Lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True, 'void-zero': True,
                                                            'range': self.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, 'void-zero': True,
                                                            'range': self.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, 'void-zero': True,
                                                            'range': self.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,
                                                            'range': self.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': self.dist_restraint_range},
                                                           {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                            'range': self.weight_range},
                                                           # 'enforce-non-zero': True},
                                                           {'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': 'ID', 'type': 'index-int', 'mandatory': True,
                                                            # 'enforce-non-zero': True},
                                                            {'name': 'Combination_ID', 'type': 'positive-int', 'mandatory': False,
                                                             'enforce-non-zero': True},
                                                            {'name': 'Torsion_angle_name', 'type': 'str', 'mandatory': False},
                                                            {'name': 'Angle_lower_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'range': self.dihed_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'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_upper_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'range': self.dihed_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_bound_val', 'Angle_upper_linear_limit'],
                                                                       'larger-than': ['Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'range': self.dihed_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'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_target_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                             'range': self.dihed_restraint_error},
                                                            {'name': 'Angle_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'range': self.dihed_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', 'Angle_upper_linear_limit'],
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Angle_upper_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                             'range': self.dihed_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,
                                                                       'circular-shift': 360.0}},
                                                            {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                             'range': self.weight_range},
                                                            # 'enforce-non-zero': True},
                                                            {'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': 'ID', 'type': 'index-int', 'mandatory': True,
                                                          # 'enforce-non-zero': True},
                                                          {'name': 'Combination_ID', 'type': 'positive-int', 'mandatory': False,
                                                           'enforce-non-zero': True},
                                                          {'name': 'Weight', 'type': 'range-float', 'mandatory': False,
                                                           'range': self.weight_range},
                                                          # 'enforce-non-zero': True},
                                                          {'name': 'Target_value', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'range': self.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,
                                                           'range': self.rdc_restraint_error},
                                                          {'name': 'RDC_lower_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                           'range': self.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,
                                                           'range': self.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,
                                                           'range': self.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,
                                                           'range': self.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': 'RDC_val', 'type': 'range-float', 'mandatory': False,
                                                           'range': self.rdc_restraint_range},
                                                          {'name': 'RDC_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                           'range': self.rdc_restraint_error},
                                                          {'name': 'RDC_val_scale_factor', 'type': 'range-float', 'mandatory': False,
                                                           'range': self.scale_range,
                                                           'enforce-non-zero': True},
                                                          {'name': 'RDC_distant_dependent', 'type': 'bool', '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': 'ID', 'type': 'positive-int', 'mandatory': True,
                                                          # 'enforce-non-zero': True},
                                                          {'name': 'Volume', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'group': {'member-with': ['Height'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Volume_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': True},
                                                          {'name': 'Height', 'type': 'float', 'mandatory': False, 'group-mandatory': True,
                                                           'group': {'member-with': ['Volume'],
                                                                     'coexist-with': None}},
                                                          {'name': 'Height_uncertainty', 'type': 'positive-float', 'mandatory': False, 'void-zero': 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': self.weight_range},
                                                              {'name': 'Restraint', 'type': 'enum', 'mandatory': False,
                                                               'enum': ('no', 'yes')}
                                                              ]
                                        }
                           }

        # 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,
                                                               'range': self.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,
                                                               'range': self.dist_restraint_error},
                                                              {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                               'range': self.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,
                                                               'range': self.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,
                                                               'range': self.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,
                                                               'range': self.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,
                                                               'range': self.dihed_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,
                                                                'range': self.dihed_restraint_error},
                                                               {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                'range': self.dihed_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,
                                                                'range': self.dihed_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,
                                                                'range': self.dihed_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,
                                                                'range': self.dihed_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,
                                                              'range': self.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,
                                                              'range': self.rdc_restraint_error},
                                                             {'name': 'lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                              'range': self.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,
                                                              'range': self.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,
                                                              'range': self.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,
                                                              'range': self.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
                                           },
                                   'nmr-star': {'dist_restraint': [{'name': 'Target_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'range': self.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,
                                                                    'range': self.dist_restraint_error},
                                                                   {'name': 'Lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                    'range': self.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,
                                                                    'range': self.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,
                                                                    'range': self.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,
                                                                    'range': self.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': self.dist_restraint_range}
                                                                   ],
                                                'dihed_restraint': [{'name': 'Angle_lower_bound_val', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'range': self.dihed_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,
                                                                     'range': self.dihed_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,
                                                                     'range': self.dihed_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,
                                                                     'range': self.dihed_restraint_error},
                                                                    {'name': 'Angle_lower_linear_limit', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                     'range': self.dihed_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,
                                                                     'range': self.dihed_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,
                                                                   'range': self.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,
                                                                   'range': self.rdc_restraint_error},
                                                                  {'name': 'RDC_lower_bound', 'type': 'range-float', 'mandatory': False, 'group-mandatory': True,
                                                                   'range': self.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,
                                                                   'range': self.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,
                                                                   'range': self.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,
                                                                   'range': self.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,
                                                                   'range': self.rdc_restraint_range},
                                                                  {'name': 'RDC_val_err', 'type': 'range-float', 'mandatory': False,
                                                                   'range': self.rdc_restraint_error}
                                                                  ],
                                                'spectral_peak': None,
                                                'spectral_peak_alt': 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,
                                       'range': self.chem_shift_error},
                                      {'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,
                                            'range': self.chem_shift_error},
                                           {'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,
                                            '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': '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
                                     },
                             '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',
                                                             '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']
                                          }
                             }

        # 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,
                                        },
                                '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'
                                             }
                                }

        self.alt_potential_type = {'?': 'undefined'}

        # alternative dictionary of constraint type
        self.dist_alt_constraint_type = {'nef': {'NOE': 'noe',
                                                 'NOE build-up': 'noe_build_up',
                                                 'noe build-up': 'noe_build_up',
                                                 'NOE buildup': 'noe_build_up',
                                                 'noe buildup': 'noe_build_up',
                                                 'NOE build up': 'noe_build_up',
                                                 'noe build up': 'noe_build_up',
                                                 'noe not seen': 'noe_not_seen',
                                                 'ROE': 'roe',
                                                 'roe build-up': 'roe_build_up',
                                                 'ROE buildup': 'roe_build_up',
                                                 'roe buildup': 'roe_build_up',
                                                 'ROE build up': 'roe_build_up',
                                                 'roe build up': 'roe_build_up',
                                                 'hydrogen bond': 'hbond',
                                                 'Hbond': 'hbond',
                                                 'HBond': 'hbond',
                                                 'H-bond': 'hbond',
                                                 'h-bond': 'hbond',
                                                 'H-Bond': 'hbond',
                                                 'Hydrogen bond': 'hbond',
                                                 'disulfide bond': 'disulfide_bond',
                                                 'Disulfide bond': 'disulfide_bond',
                                                 'S-S bond': 'disulfide_bond',
                                                 'SS bond': 'disulfide_bond',
                                                 'SS-bond': 'disulfide_bond',
                                                 'disulfide bridge': 'disulfide_bond',
                                                 'Disulfide bridge': 'disulfide_bond',
                                                 'paramagnetic relaxation': 'pre',
                                                 'PRE': 'pre',
                                                 'Paramagnetic relaxation': 'pre',
                                                 'paramagnetic relaxation enhancement': 'pre',
                                                 'Paramagnetic relaxation enhancement': 'pre',
                                                 'general distance': 'undefined',
                                                 'distance': 'undefined',
                                                 'Mutation': 'mutation',
                                                 'chemical shift perturbation': 'shift_perturbation',
                                                 'shift perturbation': 'shift_perturbation',
                                                 'chem shift perturbation': 'shift_perturbation',
                                                 'CS perturbation': 'shift_perturbation',
                                                 'csp': 'shift_perturbation',
                                                 'CSP': 'shift_perturbation',
                                                 '?': 'undefined'
                                                 },
                                         'nmr-star': {'noe': 'NOE',
                                                      'noe_build_up': 'NOE build-up',
                                                      'noe build-up': 'NOE build-up',
                                                      'NOE buildup': 'NOE build-up',
                                                      'noe buildup': 'NOE build-up',
                                                      'NOE build up': 'NOE build-up',
                                                      'noe build up': 'NOE build-up',
                                                      'noe_not_seen': 'NOE not seen',
                                                      'noe not seen': 'NOE not seen',
                                                      'roe': 'ROE',
                                                      'roe_build_up': 'ROE build-up',
                                                      'roe build-up': 'ROE build-up',
                                                      'ROE buildup': 'ROE build-up',
                                                      'roe buildup': 'ROE build-up',
                                                      'ROE build up': 'ROE build-up',
                                                      'roe build up': 'ROE build-up',
                                                      'hbond': 'hydrogen bond',
                                                      'Hbond': 'hydrogen bond',
                                                      'HBond': 'hydrogen bond',
                                                      'H-bond': 'hydrogen bond',
                                                      'h-bond': 'hydrogen bond',
                                                      'H-Bond': 'hydrogen bond',
                                                      'Hydrogen bond': 'hydrogen bond',
                                                      'disulfide_bond': 'disulfide bond',
                                                      'Disulfide bond': 'disulfide bond',
                                                      'S-S bond': 'disulfide bond',
                                                      'SS bond': 'disulfide bond',
                                                      'SS-bond': 'disulfide bond',
                                                      'disulfide bridge': 'disulfide bond',
                                                      'Disulfide bridge': 'disulfide bond',
                                                      'PRE': 'paramagnetic relaxation',
                                                      'pre': 'paramagnetic relaxation',
                                                      'Paramagnetic relaxation': 'paramagnetic relaxation',
                                                      'paramagnetic relaxation enhancement': 'paramagnetic relaxation',
                                                      'Paramagnetic relaxation enhancement': 'paramagnetic relaxation',
                                                      'Mutation': 'mutation',
                                                      'unknown': 'general distance',
                                                      'undefined': 'general distance',
                                                      'shift_perturbation': 'chemical shift perturbation',
                                                      'shift perturbation': 'chemical shift perturbation',
                                                      'chem shift perturbation': 'chemical shift perturbation',
                                                      'CS perturbation': 'chemical shift perturbation',
                                                      'csp': 'chemical shift perturbation',
                                                      'CSP': 'chemical shift perturbation',
                                                      '?': 'undefined'
                                                      }
                                         }

        self.dihed_alt_constraint_type = {'nef': {'J-couplings': 'jcoupling',
                                                  'j-couplings': 'jcoupling',
                                                  'J couplings': 'jcoupling',
                                                  'j couplings': 'jcoupling',
                                                  'Jcouplings': 'jcoupling',
                                                  'jcouplings': 'jcoupling',
                                                  'J-coupling': 'jcoupling',
                                                  'j-coupling': 'jcoupling',
                                                  'J coupling': 'jcoupling',
                                                  'j coupling': 'jcoupling',
                                                  'Jcoupling': 'jcoupling',
                                                  'chemical shift': 'chemical_shift',
                                                  'Chemical shift': 'chemical_shift',
                                                  'Chemical_shift': 'chemical_shift',
                                                  'chemical shifts': 'chemical_shift',
                                                  'Chemical shifts': 'chemical_shift',
                                                  'Chemical_shifts': 'chemical_shift',
                                                  'backbone chemical shifts': 'chemical_shift',
                                                  'Backbone chemical shifts': 'chemical_shift',
                                                  'Mainchain chemical shifts': 'chemical_shift',
                                                  'mainchain chemical shifts': 'chemical_shift',
                                                  'Main chain chemical shifts': 'chemical_shift',
                                                  'main chain chemical shifts': 'chemical_shift',
                                                  'bb chemical shifts': 'chemical_shift',
                                                  'BB chemical shifts': 'chemical_shift',
                                                  'backbone chemical shift': 'chemical_shift',
                                                  'Backbone chemical shift': 'chemical_shift',
                                                  'Mainchain chemical shift': 'chemical_shift',
                                                  'mainchain chemical shift': 'chemical_shift',
                                                  'Main chain chemical shift': 'chemical_shift',
                                                  'main chain chemical shift': 'chemical_shift',
                                                  'bb chemical shift': 'chemical_shift',
                                                  'BB chemical shift': 'chemical_shift',
                                                  'backbone chem shifts': 'chemical_shift',
                                                  'Backbone chem shifts': 'chemical_shift',
                                                  'Mainchain chem shifts': 'chemical_shift',
                                                  'mainchain chem shifts': 'chemical_shift',
                                                  'Main chain chem shifts': 'chemical_shift',
                                                  'main chain chem shifts': 'chemical_shift',
                                                  'bb chem shifts': 'chemical_shift',
                                                  'BB chem shifts': 'chemical_shift',
                                                  'backbone chem shift': 'chemical_shift',
                                                  'Backbone chem shift': 'chemical_shift',
                                                  'Mainchain chem shift': 'chemical_shift',
                                                  'mainchain chem shift': 'chemical_shift',
                                                  'Main chain chem shift': 'chemical_shift',
                                                  'main chain chem shift': 'chemical_shift',
                                                  'bb chem shift': 'chemical_shift',
                                                  'BB chem shift': 'chemical_shift',
                                                  'backbone cs': 'chemical_shift',
                                                  'Backbone cs': 'chemical_shift',
                                                  'Mainchain cs': 'chemical_shift',
                                                  'mainchain cs': 'chemical_shift',
                                                  'Main chain cs': 'chemical_shift',
                                                  'main chain cs': 'chemical_shift',
                                                  'bb cs': 'chemical_shift',
                                                  'BB cs': 'chemical_shift',
                                                  'backbone CS': 'chemical_shift',
                                                  'Backbone CS': 'chemical_shift',
                                                  'Mainchain CS': 'chemical_shift',
                                                  'mainchain CS': 'chemical_shift',
                                                  'Main chain CS': 'chemical_shift',
                                                  'main chain CS': 'chemical_shift',
                                                  'bb CS': 'chemical_shift',
                                                  'BB CS': 'chemical_shift',
                                                  'TALOS': 'chemical_shift',
                                                  'talos': 'chemical_shift',
                                                  'TALOS+': 'chemical_shift',
                                                  'talos+': 'chemical_shift',
                                                  'TALOS-N': 'chemical_shift',
                                                  'talos-n': 'chemical_shift',
                                                  '?': 'undefined'
                                                  },
                                          'nmr-star': {'jcoupling': 'J-couplings',
                                                       'Jcoupling': 'J-couplings',
                                                       'jcouplings': 'J-couplings',
                                                       'Jcouplings': 'J-couplings',
                                                       'j-couplings': 'J-couplings',
                                                       'J couplings': 'J-couplings',
                                                       'j couplings': 'J-couplings',
                                                       'J-coupling': 'J-couplings',
                                                       'j-coupling': 'J-couplings',
                                                       'J coupling': 'J-couplings',
                                                       'j coupling': 'J-couplings',
                                                       'chemical_shift': 'backbone chemical shifts',
                                                       'Chemical_shift': 'backbone chemical shifts',
                                                       'chemical_shifts': 'backbone chemical shifts',
                                                       'Chemical_shifts': 'backbone chemical shifts',
                                                       'chemical shift': 'backbone chemical shifts',
                                                       'Chemical shift': 'backbone chemical shifts',
                                                       'chemical shifts': 'backbone chemical shifts',
                                                       'Chemical shifts': 'backbone chemical shifts',
                                                       'Backbone chemical shifts': 'backbone chemical shifts',
                                                       'Mainchain chemical shifts': 'backbone chemical shifts',
                                                       'mainchain chemical shifts': 'backbone chemical shifts',
                                                       'Main chain chemical shifts': 'backbone chemical shifts',
                                                       'main chain chemical shifts': 'backbone chemical shifts',
                                                       'bb chemical shifts': 'backbone chemical shifts',
                                                       'BB chemical shifts': 'backbone chemical shifts',
                                                       'backbone chemical shift': 'backbone chemical shifts',
                                                       'Backbone chemical shift': 'backbone chemical shifts',
                                                       'Mainchain chemical shift': 'backbone chemical shifts',
                                                       'mainchain chemical shift': 'backbone chemical shifts',
                                                       'Main chain chemical shift': 'backbone chemical shifts',
                                                       'main chain chemical shift': 'backbone chemical shifts',
                                                       'bb chemical shift': 'backbone chemical shifts',
                                                       'BB chemical shift': 'backbone chemical shifts',
                                                       'backbone chem shifts': 'backbone chemical shifts',
                                                       'Backbone chem shifts': 'backbone chemical shifts',
                                                       'Mainchain chem shifts': 'backbone chemical shifts',
                                                       'mainchain chem shifts': 'backbone chemical shifts',
                                                       'Main chain chem shifts': 'backbone chemical shifts',
                                                       'main chain chem shifts': 'backbone chemical shifts',
                                                       'bb chem shifts': 'backbone chemical shifts',
                                                       'BB chem shifts': 'backbone chemical shifts',
                                                       'backbone chem shift': 'backbone chemical shifts',
                                                       'Backbone chem shift': 'backbone chemical shifts',
                                                       'Mainchain chem shift': 'backbone chemical shifts',
                                                       'mainchain chem shift': 'backbone chemical shifts',
                                                       'Main chain chem shift': 'backbone chemical shifts',
                                                       'main chain chem shift': 'backbone chemical shifts',
                                                       'bb chem shift': 'backbone chemical shifts',
                                                       'BB chem shift': 'backbone chemical shifts',
                                                       'backbone cs': 'backbone chemical shifts',
                                                       'Backbone cs': 'backbone chemical shifts',
                                                       'Mainchain cs': 'backbone chemical shifts',
                                                       'mainchain cs': 'backbone chemical shifts',
                                                       'Main chain cs': 'backbone chemical shifts',
                                                       'main chain cs': 'backbone chemical shifts',
                                                       'bb cs': 'backbone chemical shifts',
                                                       'BB cs': 'backbone chemical shifts',
                                                       'backbone CS': 'backbone chemical shifts',
                                                       'Backbone CS': 'backbone chemical shifts',
                                                       'Mainchain CS': 'backbone chemical shifts',
                                                       'mainchain CS': 'backbone chemical shifts',
                                                       'Main chain CS': 'backbone chemical shifts',
                                                       'main chain CS': 'backbone chemical shifts',
                                                       'bb CS': 'backbone chemical shifts',
                                                       'BB CS': 'backbone chemical shifts',
                                                       'TALOS': 'backbone chemical shifts',
                                                       'talos': 'backbone chemical shifts',
                                                       'TALOS+': 'backbone chemical shifts',
                                                       'talos+': 'backbone chemical shifts',
                                                       'TALOS-N': 'backbone chemical shifts',
                                                       'talos-n': 'backbone chemical shifts',
                                                       '?': 'undefined'
                                                       }
                                          }

        self.rdc_alt_constraint_type = {'nef': {'RDC': 'measured',
                                                'rdc': 'measured',
                                                '?': 'undefined'
                                                },
                                        'nmr-star': {'rdc': 'RDC',
                                                     'measured': 'RDC',
                                                     '?': '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': self.alt_potential_type},
                                                        {'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': self.dist_alt_constraint_type['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': self.alt_potential_type},
                                                         {'name': 'restraint_origin', 'type': 'enum', 'mandatory': False,
                                                          'enum': ('jcoupling', 'chemical_shift', 'undefined', 'unknown'),
                                                          'enum-alt': self.dihed_alt_constraint_type['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': self.alt_potential_type},
                                                       {'name': 'restraint_origin', 'type': 'enum', 'mandatory': False,
                                                        'enum': ('measured', 'undefined', 'unknown'),
                                                        'enum-alt': self.rdc_alt_constraint_type['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, self.lim_num_dim)),
                                                        '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
                                     },
                             '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': 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': [{'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': self.dist_alt_constraint_type['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': self.alt_potential_type}
                                                             ],
                                          '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': self.dihed_alt_constraint_type['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': self.alt_potential_type}
                                                              ],
                                          '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': self.rdc_alt_constraint_type['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': self.alt_potential_type},
                                                            {'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, self.lim_num_dim)),
                                                             '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, self.lim_num_dim)),
                                                                 'enforce-enum': 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
                                      },
                              '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']
                                           }
                              }

        # 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
                                        },
                                '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': None,
                                             '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']
                                             }
                                }

        # 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': [],
                                          'poly_seq': ['_nef_covalent_links', '_nef_sequence'],
                                          'entity': [],
                                          'chem_shift': [],
                                          'chem_shift_ref': [],
                                          'dist_restraint': [],
                                          'dihed_restraint': [],
                                          'rdc_restraint': [],
                                          'spectral_peak': ['_nef_spectrum_dimension', '_nef_spectrum_dimension_transfer'],
                                          'spectral_peak_alt': []
                                          },
                                  'nmr-star': {'entry_info': [],
                                               'poly_seq': ['_Bond', '_Entity_deleted_atom'],
                                               'entity': ['_Entity_poly_seq'],
                                               'chem_shift': [],
                                               'chem_shift_ref': [],
                                               'dist_restraint': [],
                                               'dihed_restraint': [],
                                               'rdc_restraint': [],
                                               'spectral_peak': ['_Spectral_dim', '_Spectral_dim_transfer'],
                                               'spectral_peak_alt': ['_Spectral_dim', '_Spectral_dim_transfer', '_Peak_general_char', '_Peak_char', '_Assigned_peak_chem_shift']
                                               }
                                  }

        # linked loop categories
        self.linked_lp_categories = {'nef': {'entry_info': ['_nef_related_entries', '_nef_program_script', '_nef_run_history', '_audit'],
                                             'poly_seq': ['_nef_sequence', '_nef_covalent_links'],
                                             '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': []
                                             },
                                     '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': [],
                                                  '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']
                                                  }
                                     }

        # 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
                                      },
                              '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': None,
                                           'chem_shift': None,
                                           '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': []
                                           }
                                           }
                              }

        # 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
                                       },
                               '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': None,
                                            'chem_shift': None,
                                            '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, self.lim_num_dim)),
                                                                'enforce-enum': True},
                                                               {'name': 'Chem_shift_val', 'type': 'range-float', 'mandatory': True,
                                                                'range': self.chem_shift_range},
                                                               {'name': 'Chem_shift_val_err', 'type': 'range-float', 'mandatory': False, 'void-zero': True,
                                                                'range': self.chem_shift_error},
                                                               {'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, self.lim_num_dim)),
                                                                               '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': self.chem_shift_range},
                                                                              {'name': 'Contribution_fractional_val', 'type': 'range-float', 'mandatory': False,
                                                                               'range': self.weight_range},
                                                                              {'name': 'Figure_of_merit', 'type': 'range-float', 'mandatory': False,
                                                                               'range': self.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': self.bmrb_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'}
                                                                              ]
                                            }
                                            }
                               }

        # 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
                                         },
                                 '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': None,
                                              'chem_shift': None,
                                              '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']
                                              }
                                              }
                                 }

        # 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'
                                                   }
                                      }

        # 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'
                                                   }
                                      }

        # 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'
                                                   }
                                      }

        # item name in dihedral 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'
                                              },
                                      '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',
                                                   }
                                      }

        # 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'
                                               },
                                       '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'
                                                    }
                                       }

        # 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'
                                    }

        # taken from wwpdb.utils.align.SequenceReferenceData.py
        self.monDict3 = {'ALA': 'A',
                         'ARG': 'R',
                         'ASN': 'N',
                         'ASP': 'D',
                         'ASX': 'B',
                         'CYS': 'C',
                         'GLN': 'Q',
                         'GLU': 'E',
                         'GLX': 'Z',
                         'GLY': 'G',
                         'HIS': 'H',
                         'ILE': 'I',
                         'LEU': 'L',
                         'LYS': 'K',
                         'MET': 'M',
                         'PHE': 'F',
                         'PRO': 'P',
                         'SER': 'S',
                         'THR': 'T',
                         'TRP': 'W',
                         'TYR': 'Y',
                         'VAL': 'V',
                         'DA': 'A',
                         'DC': 'C',
                         'DG': 'G',
                         'DT': 'T',
                         'DU': 'U',
                         'DI': 'I',
                         'A': 'A',
                         'C': 'C',
                         'G': 'G',
                         'I': 'I',
                         'T': 'T',
                         'U': 'U'
                         }

        # conventional dihedral angle names in standard residues
        self.dihed_ang_names = ('PHI', 'PSI', 'OMEGA', 'CHI1', 'CHI2', 'CHI3', 'CHI4', 'CHI5',
                                'ALPHA', 'BETA', 'GAMMA', 'DELTA', 'EPSILON', 'ZETA',
                                'NU0', 'NU1', 'NU2', 'NU3', 'NU4',
                                'TAU0', 'TAU1', 'TAU2', 'TAU3', 'TAU4',
                                'CHI21', 'CHI22', 'CHI31', 'CHI32', 'CHI42')

        # patterns for detection of dihedral angle type
        self.dihed_atom_ids = ['N', 'CA', 'C']

        self.chi1_atom_id_4_pat = re.compile(r'^[COS]G1?$')
        self.chi2_atom_id_3_pat = re.compile(r'^CG1?$')
        self.chi2_atom_id_4_pat = re.compile(r'^[CNOS]D1?$')
        self.chi3_atom_id_3_pat = re.compile(r'^[CS]D$')
        self.chi3_atom_id_4_pat = re.compile(r'^[CNO]E1?$')
        self.chi4_atom_id_3_pat = re.compile(r'^[CN]E$')
        self.chi4_atom_id_4_pat = re.compile(r'^[CN]Z$')

        # 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': []
                          }

        # 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': []
                           }

        # 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': []
                              }

        # self.__remapped_def_chain_id = {}

        self.authSeqMap = None

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

        # CCD accessing utility
        self.__cICommon = ConfigInfoAppCommon(getSiteId())
        self.__ccCvsPath = self.__cICommon.get_site_cc_cvs_path()

        self.__ccR = ChemCompReader(self.__verbose, self.__lfh)
        self.__ccR.setCachePath(self.__ccCvsPath)

        # representative model id
        self.__representative_model_id = 1
        # total number of models
        self.__total_models = 0
        # atom id list in model
        self.__coord_atom_id = None
        # tautomer 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 = {}

        self.__last_comp_id = None
        self.__last_comp_id_test = False
        self.__last_chem_comp_dict = None
        self.__last_chem_comp_atoms = None
        self.__last_chem_comp_bonds = None

        # taken from wwpdb.apps.ccmodule.io.ChemCompIo
        self.__chem_comp_atom_dict = [
            ('_chem_comp_atom.comp_id', '%s', 'str', ''),
            ('_chem_comp_atom.atom_id', '%s', 'str', ''),
            ('_chem_comp_atom.alt_atom_id', '%s', 'str', ''),
            ('_chem_comp_atom.type_symbol', '%s', 'str', ''),
            ('_chem_comp_atom.charge', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_align', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_aromatic_flag', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_leaving_atom_flag', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_stereo_config', '%s', 'str', ''),
            ('_chem_comp_atom.model_Cartn_x', '%s', 'str', ''),
            ('_chem_comp_atom.model_Cartn_y', '%s', 'str', ''),
            ('_chem_comp_atom.model_Cartn_z', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_model_Cartn_x_ideal', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_model_Cartn_y_ideal', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_model_Cartn_z_ideal', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_component_atom_id', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_component_comp_id', '%s', 'str', ''),
            ('_chem_comp_atom.pdbx_ordinal', '%s', 'str', '')
        ]

        atom_id = next(d for d in self.__chem_comp_atom_dict if d[0] == '_chem_comp_atom.atom_id')
        self.__cca_atom_id = self.__chem_comp_atom_dict.index(atom_id)

        # aromatic_flag = next(d for d in self.__chem_comp_atom_dict if d[0] == '_chem_comp_atom.pdbx_aromatic_flag')
        # self.__cca_aromatic_flag = self.__chem_comp_atom_dict.index(aromatic_flag)

        leaving_atom_flag = next(d for d in self.__chem_comp_atom_dict if d[0] == '_chem_comp_atom.pdbx_leaving_atom_flag')
        self.__cca_leaving_atom_flag = self.__chem_comp_atom_dict.index(leaving_atom_flag)

        type_symbol = next(d for d in self.__chem_comp_atom_dict if d[0] == '_chem_comp_atom.type_symbol')
        self.__cca_type_symbol = self.__chem_comp_atom_dict.index(type_symbol)

        # taken from wwpdb.apps.ccmodule.io.ChemCompIo
        self.__chem_comp_bond_dict = [
            ('_chem_comp_bond.comp_id', '%s', 'str', ''),
            ('_chem_comp_bond.atom_id_1', '%s', 'str', ''),
            ('_chem_comp_bond.atom_id_2', '%s', 'str', ''),
            ('_chem_comp_bond.value_order', '%s', 'str', ''),
            ('_chem_comp_bond.pdbx_aromatic_flag', '%s', 'str', ''),
            ('_chem_comp_bond.pdbx_stereo_config', '%s', 'str', ''),
            ('_chem_comp_bond.pdbx_ordinal', '%s', 'str', '')
        ]

        atom_id_1 = next(d for d in self.__chem_comp_bond_dict if d[0] == '_chem_comp_bond.atom_id_1')
        self.__ccb_atom_id_1 = self.__chem_comp_bond_dict.index(atom_id_1)

        atom_id_2 = next(d for d in self.__chem_comp_bond_dict if d[0] == '_chem_comp_bond.atom_id_2')
        self.__ccb_atom_id_2 = self.__chem_comp_bond_dict.index(atom_id_2)

        aromatic_flag = next(d for d in self.__chem_comp_bond_dict if d[0] == '_chem_comp_bond.pdbx_aromatic_flag')
        self.__ccb_aromatic_flag = self.__chem_comp_bond_dict.index(aromatic_flag)

        # CIF reader
        self.__cR = CifReader(self.__verbose, self.__lfh)

        # extracted conformational annotation of coordinate file
        self.__nmr_struct_conf = {}

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

        # suspended error items for polypeptide
        self.__suspended_errors_for_polypeptide = []

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

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

        self.__verbose = flag
        self.__debug = flag

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

        if os.access(fPath, os.F_OK):
            self.__srcPath = os.path.abspath(fPath)

        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)

    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] = [{'file_name': os.path.abspath(f['file_name']), 'file_type': f['file_type'],
                                                    'original_file_name': f['original_file_name']} for f in value]
                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}.")
        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.__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 'bmrb_only' in self.__inputParamDict and self.__inputParamDict['bmrb_only'] is not None:
            if isinstance(self.__inputParamDict['bmrb_only'], bool):
                self.__bmrb_only = self.__inputParamDict['bmrb_only']
            else:
                self.__bmrb_only = self.__inputParamDict['bmrb_only'] in self.true_value

        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 = 4.0

        if 'nonblk_anomalous_cs' in self.__inputParamDict and self.__inputParamDict['nonblk_anomalous_cs'] is not None:
            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 self.true_value

        if 'nonblk_bad_nterm' in self.__inputParamDict and self.__inputParamDict['nonblk_bad_nterm'] is not None:
            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 self.true_value

        if 'update_poly_seq' in self.__inputParamDict and self.__inputParamDict['update_poly_seq'] is not None:
            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 self.true_value

        if 'resolve_conflict' in self.__inputParamDict and self.__inputParamDict['resolve_conflict'] is not None:
            if isinstance(self.__inputParamDict['resolve_conflict'], bool):
                self.__resolve_conflict = self.__inputParamDict['resolve_conflict']
            else:
                self.__resolve_conflict = self.__inputParamDict['resolve_conflict'] in self.true_value

        if 'check_mandatory_tag' in self.__inputParamDict and self.__inputParamDict['check_mandatory_tag'] is not None:
            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 self.true_value

        if 'check_auth_seq' in self.__inputParamDict and self.__inputParamDict['check_auth_seq'] is not None:
            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 self.true_value

        if 'transl_pseudo_name' in self.__inputParamDict and self.__inputParamDict['transl_pseudo_name'] is not None:
            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 self.true_value
        elif op in ('nmr-str-consistency-check', 'nmr-str2str-deposit', 'nmr-str2nef-release'):
            self.__transl_pseudo_name = True

        if 'tolerant_seq_align' in self.__inputParamDict and self.__inputParamDict['tolerant_seq_align'] is not None:
            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 self.true_value

        if 'fix_format_issue' in self.__inputParamDict and self.__inputParamDict['fix_format_issue'] is not None:
            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 self.true_value
        elif not self.__combined_mode or self.__release_mode:
            self.__fix_format_issue = True

        if 'excl_missing_data' in self.__inputParamDict and self.__inputParamDict['excl_missing_data'] is not None:
            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 self.true_value
        elif not self.__combined_mode:
            self.__excl_missing_data = True

        if 'trust_pdbx_nmr_ens' in self.__inputParamDict and self.__inputParamDict['trust_pdbx_nmr_ens'] is not None:
            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 self.true_value
        elif self.__release_mode:
            self.__trust_pdbx_nmr_ens = True

        if 'rmsd_not_superimposed' in self.__inputParamDict and self.__inputParamDict['rmsd_not_superimposed'] is not None:
            if isinstance(self.__inputParamDict['rmsd_not_superimposed'], float):
                self.rmsd_not_superimposed = self.__inputParamDict['rmsd_not_superimposed']

        if 'rmsd_overlaid_exactly' in self.__inputParamDict and self.__inputParamDict['rmsd_overlaid_exactly'] is not None:
            if isinstance(self.__inputParamDict['rmsd_overlaid_exactly'], float):
                self.rmsd_overlaid_exactly = self.__inputParamDict['rmsd_overlaid_exactly']

        if 'entry_id' in self.__outputParamDict and self.__outputParamDict['entry_id'] is not None:
            self.__entry_id = self.__outputParamDict['entry_id']

        if 'insert_entry_id_to_loops' in self.__outputParamDict and self.__outputParamDict['insert_entry_id_to_loops'] is not None:
            if isinstance(self.__outputParamDict['insert_entry_id_to_loops'], bool):
                self.__insert_entry_id_to_loops = self.__outputParamDict['insert_entry_id_to_loops']
            else:
                self.__insert_entry_id_to_loops = self.__outputParamDict['insert_entry_id_to_loops'] in self.true_value

        if 'retain_original' in self.__outputParamDict and self.__outputParamDict['retain_original'] is not None:
            if isinstance(self.__outputParamDict['retain_original'], bool):
                self.__retain_original = self.__outputParamDict['retain_original']
            else:
                self.__retain_original = self.__outputParamDict['retain_original'] in self.true_value

        if 'leave_intl_note' in self.__outputParamDict and self.__outputParamDict['leave_intl_note'] is not None:
            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 self.true_value

        if 'reduced_atom_notation' in self.__outputParamDict and self.__outputParamDict['reduced_atom_notation'] is not None:
            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 self.true_value

        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()

        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.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.
        """

        if srcPath is None:
            srcPath = self.__srcPath

        self.report = NmrDpReport()

        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)

        else:

            cs_file_path_list = 'chem_shift_file_path_list'

            for csListId, csPath 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

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

            mr_file_path_list = 'restraint_file_path_list'

            if mr_file_path_list in self.__inputParamDict:

                file_path_list_len = self.__cs_file_path_list_len

                for mrPath in self.__inputParamDict[mr_file_path_list]:

                    self.report.appendInputSource()

                    input_source = self.report.input_sources[file_path_list_len]

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

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

                    file_path_list_len += 1

            ar_file_path_list = 'atypical_restraint_file_path_list'

            if ar_file_path_list in self.__inputParamDict:

                file_path_list_len = self.__cs_file_path_list_len

                for ar in self.__inputParamDict[ar_file_path_list]:

                    self.report.appendInputSource()

                    input_source = self.report.input_sources[file_path_list_len]

                    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', ar['original_file_name'])

                    file_path_list_len += 1

        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')

            non_std_comp_ids = [i['id'] for i in chem_comp if i['mon_nstd_flag'] != 'y']

            if len(non_std_comp_ids) == 0:
                return

            for comp_id in non_std_comp_ids:

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

                    ref_elems = set(a[self.__cca_type_symbol] for a in self.__last_chem_comp_atoms if a[self.__cca_leaving_atom_flag] != 'Y')

                    for elem in ref_elems:
                        if elem in self.paramag_elems or elem in self.ferromag_elems:
                            self.report.setDiamagnetic(False)
                            break

        except:  # noqa: E722 pylint: disable=bare-except
            pass

    def __updateChemCompDict(self, comp_id):
        """ Update CCD information for a given comp_id.
            @return: True for successfully update CCD information or False for the case a given comp_id does not exist in CCD
        """

        comp_id = comp_id.upper()

        if comp_id != self.__last_comp_id:
            self.__last_comp_id_test = False if '_' in comp_id else self.__ccR.setCompId(comp_id)
            self.__last_comp_id = comp_id

            if self.__last_comp_id_test:
                self.__last_chem_comp_dict = self.__ccR.getChemCompDict()
                self.__last_chem_comp_atoms = self.__ccR.getAtomList()
                self.__last_chem_comp_bonds = self.__ccR.getBonds()

        return self.__last_comp_id_test

    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:

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

            srcPath_ = None

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

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

            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:
                try:
                    os.remove(srcPath_)
                except:  # noqa: E722 pylint: disable=bare-except
                    pass

        else:

            cs_file_path_list = 'chem_shift_file_path_list'

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

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

                csPath_ = None

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

                is_valid, message = self.__nefT.validate_file(csPath, 'S')  # '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."

                        if _file_type == 'nef':  # DAOTHER-5673
                            err += " Please re-upload the NEF file as an NMR combined data file."
                        else:
                            err += " 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)
                            _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) == 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(csListId)
                            self.__rescueImmatureStr(csListId)

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

                if csPath_ is not None:
                    try:
                        os.remove(csPath_)
                    except:  # noqa: E722 pylint: disable=bare-except
                        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 mrPath in self.__inputParamDict[mr_file_path_list]:

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

                    mrPath_ = None

                    if codec != 'utf-8':
                        mrPath_ = mrPath + '~'
                        convert_codec(mrPath, mrPath_, codec, 'utf-8')
                        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:  # noqa: E722 pylint: disable=bare-except
                            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 mrPath in self.__inputParamDict[mr_file_path_list]:

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

                    mrPath_ = None

                    if codec != 'utf-8':
                        mrPath_ = mrPath + '~'
                        convert_codec(mrPath, mrPath_, codec, 'utf-8')
                        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."

                            if _file_type == 'nef':  # DAOTHER-5673
                                err += " Please re-upload the NEF file as an NMR combined data file."
                            else:
                                err += " 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)

                            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(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:  # noqa: E722 pylint: disable=bare-except
                            pass

            if ar_file_path_list in self.__inputParamDict:

                for ar in self.__inputParamDict[ar_file_path_list]:

                    arPath = ar['file_name']

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

                    arPath_ = None

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

                    if arPath_ is not None:
                        try:
                            os.remove(arPath_)
                        except:  # noqa: E722 pylint: disable=bare-except
                            pass

        return is_done

    def __fixFormatIssueOfInputSource(self, file_list_id, file_name, file_type, srcPath=None, fileSubType='S', message=None, tmpPaths=None):
        """ 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)

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

                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

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

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

        len_tmp_paths = len(tmpPaths)

        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*')
        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*')

        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 ifp:
                with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                    for line in ifp:
                        ofp.write(line)

                    ofp.write('save_\n')

                    ofp.close()

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

        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 ifp:
                with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                    for line in ifp:
                        ofp.write(line)

                    ofp.write('save_\n')

                    ofp.close()

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

#        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 ifp:
                lines = ifp.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

                ifp.close()

            j += 1
            i = 1

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

                    ofp.close()

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

        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 ifp:
                with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                    for line in ifp:
                        if pass_datablock:
                            ofp.write(line)
                        elif datablock_pattern.match(line):
                            pass_datablock = True
                        else:
                            ofp.write(line)

                    ofp.close()

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

        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. 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 ifp:
                    with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                        for line in ifp:
                            if i == line_num:
                                ofp.write('stop_\n')
                            ofp.write(line)
                            i += 1

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

            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 ifp:
                    with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                        for line in ifp:
                            if i == line_num - 1:
                                ofp.write('loop_\n')
                            ofp.write(line)
                            i += 1

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

            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':

                cif_stop_pattern = re.compile(r'^#\s*')
                # """
                # cs_cif_pattern = re.compile(r'D_[0-9]+_cs_P[0-9]+.cif.V[0-9]+$')

                # if cs_cif_pattern.match(file_name):
                # """
                is_cs_cif = True

                try:

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

                        ifp.close()

                    if is_cs_cif:

                        loop_count = 0
                        has_sf_category = False
                        has_sf_framecode = False

                        with open(_srcPath, 'r', encoding='UTF-8') as ifp:
                            for line in ifp:
                                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

                            ifp.close()

                        if not has_sf_category and not has_sf_framecode:

                            in_loop = False

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

                                _srcPath = ofp.name
                                tmpPaths.append(_srcPath)
                                ofp.close()

                        else:

                            cif_to_star = CifToNmrStar()
                            cif_to_star.convert(_srcPath, _srcPath + '~')

                            _srcPath += '~'
                            tmpPaths.append(_srcPath)

                        ifp.close()

                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 ifp:
                        with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                            for line in ifp:
                                if tag_name_pattern.match(line) is None:
                                    ofp.write(line)
                                else:
                                    ofp.write('loop_\n')

                        _srcPath = ofp.name
                        tmpPaths.append(_srcPath)
                        ofp.close()

                    ifp.close()

                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 ifp:
                    with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                        for line in ifp:
                            if i != line_num:
                                ofp.write(line)
                            else:
                                ofp.write(f"save_{os.path.basename(srcPath)}\n")
                            i += 1

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

            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_framecode_pattern = re.compile(r'\s*save_' + sf_framecode + r'\s*')

                    with open(_srcPath, 'r', encoding='UTF-8') as ifp:
                        for line in ifp:
                            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_framecode_pattern.match(line):
                                pass_sf_framecode = True

                        ifp.close()

                    targets.append(target)

                except AttributeError:
                    pass

            for target in targets:

                sf_framecode = target['sf_framecode']

                pass_sf_framecode = False
                pass_sf_loop = False

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

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

                        _srcPath = ofp.name
                        tmpPaths.append(_srcPath)
                        ofp.close()

                    ifp.close()

        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" \[(.*)\]$")
            lp_obj_pattern = re.compile(r"\<pynmrstar\.Loop '(.*)'\>")

            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(lp_obj_pattern.search(lp_obj).groups()[0])

                        target = {'lp_category': lp_category}

                        pass_loop = False

                        lp_loc = -1
                        i = 1

                        with open(_srcPath, 'r', encoding='UTF-8') as ifp:
                            for line in ifp:
                                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

                        ifp.close()

                        targets.append(target)

                except AttributeError:
                    pass

            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 ifp:
                with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                    ofp.write('data_' + os.path.basename(srcPath) + '\n\n')
                    for line in ifp:
                        if i in target_loop_locations:
                            target = next(target for target in targets if target['loop_location'] == i)
                            if 'sf_category' in target:
                                ofp.write('save_' + target['sf_framecode'] + '\n')
                                ofp.write(target['sf_tag_prefix'] + '.' + ('sf_framecode' if file_type == 'nef' else 'Sf_framecode') + '   ' + target['sf_framecode'] + '\n')
                                ofp.write(target['sf_tag_prefix'] + '.' + ('sf_category' if file_type == 'nef' else 'Sf_category') + '    ' + target['sf_category'] + '\n')
                                ofp.write('#\n')
                        if i not in ignored_loop_locations:
                            ofp.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:
                                ofp.write('save_\n')

                        i += 1

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

        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 ifp:
                        for line in ifp:
                            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

                        ifp.close()

                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 ifp:
                    with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                        for line in ifp:
                            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']:
                                    ofp.write('save_\n')
                                    if target['category_type_2'] == 'saveframe':
                                        ofp.write('save_' + target['sf_framecode_2'] + '\n')
                                    else:
                                        ofp.write('save_' + target['sf_framecode_2'] + '\n')
                                        ofp.write(target['sf_tag_prefix_2'] + '.' + ('sf_framecode' if file_type == 'nef' else 'Sf_framecode')
                                                  + '   ' + target['sf_framecode_2'] + '\n')
                                        ofp.write(target['sf_tag_prefix_2'] + '.' + ('sf_category' if file_type == 'nef' else 'Sf_category')
                                                  + '    ' + target['category_2'] + '\n')
                                        ofp.write('loop_\n')
                                        lp_tags = ''
                                        lp_values = ''
                                elif target['category_type_2'] == 'loop':
                                    ofp.write('loop_\n')
                                    lp_tags = ''
                                    lp_values = ''
                            if i not in loop_category_locations:
                                ofp.write(line)
                            else:
                                g = tagvalue_pattern.search(line).groups()
                                try:
                                    lp_tags += f"_{g[0]}.{g[1]}\n"
                                    lp_values += ' ' + 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:
                                        ofp.write(lp_tags)
                                        ofp.write(lp_values.rstrip(' ') + '\n')
                                        ofp.write('stop_\n')
                                elif target['category_type_2'] == 'loop':
                                    ofp.write(lp_tags)
                                    ofp.write(lp_values.rstrip(' ') + '\n')
                                    ofp.write('stop_\n')

                            i += 1

                        _srcPath = ofp.name
                        tmpPaths.append(_srcPath)
                        ofp.close()

                    ifp.close()

        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 ifp:
                    with open(_srcPath + '~', 'w', encoding='UTF-8') as ofp:
                        for line in ifp:
                            if i == line_num:
                                ofp.write(re.sub(sf_framecode + r'\s$', saveframe_name + r'\n', line))
                            else:
                                ofp.write(line)
                            i += 1

                    _srcPath = ofp.name
                    tmpPaths.append(_srcPath)
                    ofp.close()

                ifp.close()

            except AttributeError:
                pass

        except StopIteration:
            pass

        if len(tmpPaths) > len_tmp_paths:

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

            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)

        is_done = True

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

        _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:
                    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(file_list_id)
                self.__rescueImmatureStr(file_list_id)

                if rescued:
                    onedep_file_pattern = re.compile(r'(.*)\-upload_(.*)\.V(.*)$')
                    if onedep_file_pattern.match(srcPath):
                        g = onedep_file_pattern.search(srcPath).groups()
                        srcPath = g[0] + '-upload-convert_' + g[1] + '.V' + g[2]
                    else:
                        onedep_file_pattern = re.compile(r'(.*)\.V(.*)$')
                        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, 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)

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

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

            is_done = False

        try:

            if self.__release_mode:
                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:  # noqa: E722 pylint: disable=bare-except
            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 False

        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_data in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, '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_data.name!r}. {itName} will be overwritten."

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

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

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

                            sf_framecode = sf_data.name

                            sf_data.tags[tagNames.index('sf_framecode')][1] = sf_framecode

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

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

                            if self.__verbose:
                                self.__lfh.write(f"+NmrDpUtility.__rescueFormerNef() ++ Error  - {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_data in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                format_version = sf_data.get_tag('format_version')[0]

                if not format_version.startswith('0.'):
                    sf_data.format_version = '1.1'

        else:

            self.__sf_category_list, self.__lp_category_list = self.__nefT.get_data_content(self.__star_data[file_list_id], self.__star_data_type[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_data = self.__star_data[file_list_id]
                sf_framecode = ''

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

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

                if content_subtype not in content_subtypes:
                    continue

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

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

            else:

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

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

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

        return True

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

        try:
            if __pynmrstar_v3_2__:
                loop = sf_data.get_loop(lp_category)
            else:
                loop = sf_data.get_loop_by_category(lp_category)
        except:  # noqa: E722 pylint: disable=bare-except
            loop = sf_data

        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(f"+NmrDpUtility.__rescueFormerNef() ++ LookupError  - {err}\n")

                    for l, i in enumerate(loop, start=1):  # noqa: E741
                        i.append(l)

                    loop.add_tag(lp_category + '.index')

            elif content_subtype == 'chem_shift':

                if any(tag for tag in sf_data.tags if tag[0] == 'atom_chemical_shift_units'):
                    if __pynmrstar_v3_2__:
                        sf_data.remove_tag('atom_chemical_shift_units')
                    else:
                        sf_data.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(f"+NmrDpUtility.__rescueFormerNef() ++ LookupError  - {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 self.empty_value:
                            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(f"+NmrDpUtility.__rescueFormerNef() ++ LookupError  - {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(self.atom_isotopes[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 self.empty_value:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[iso_num_col] = str(self.atom_isotopes[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(f"+NmrDpUtility.__rescueFormerNef() ++ LookupError  - {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_data.tags if tag[0] == 'tensor_residue_type')
                    sf_data.add_tag(sf_category + '.tensor_residue_name', tag[1])
                    if __pynmrstar_v3_2__:
                        sf_data.remove_tag('tensor_residue_type')
                    else:
                        sf_data.delete_tag('tensor_residue_type')
                except StopIteration:
                    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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        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 False

        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_data in self.__star_data[file_list_id].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, '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_data.name!r}. {itName} will be overwritten."

                            self.report.warning.appendDescription('missing_saveframe',
                                                                  {'file_name': file_name, 'sf_framecode': sf_data.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_data.tags]

                            sf_framecode = sf_data.name

                            sf_data.tags[tagNames.index('Sf_framecode')][1] = sf_data.name

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

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

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

        if not self.__rescue_mode:
            return True

        self.__sf_category_list, self.__lp_category_list = self.__nefT.get_data_content(self.__star_data[file_list_id], self.__star_data_type[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_data = self.__star_data[file_list_id]
                sf_framecode = ''

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

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

                if content_subtype not in content_subtypes:
                    continue

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

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

            else:

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

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

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

        return True

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

        try:
            if __pynmrstar_v3_2__:
                loop = sf_data.get_loop(lp_category)
            else:
                loop = sf_data.get_loop_by_category(lp_category)
        except:  # noqa: E722 pylint: disable=bare-except
            loop = sf_data

        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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {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 self.empty_value:
                            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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {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(self.atom_isotopes[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 self.empty_value:
                            atom_type = row[atom_name_col][0]
                            if atom_type in ('Q', 'M'):
                                atom_type = 'H'
                            row[iso_num_col] = str(self.atom_isotopes[atom_type][0])

            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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {err}\n")

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

                        for row in loop:
                            atom_type = re.sub(r'[0-9]+', '', 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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {err}\n")

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

                        for row in loop:
                            atom_type = re.sub(r'[0-9]+', '', row[axis_code_name_col])
                            row.append(str(self.atom_isotopes[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(f"+NmrDpUtility.__rescueImmatureStr() ++ LookupError  - {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 self.report.isError():
        #    return False

        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]
            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']

            self.__sf_category_list, self.__lp_category_list = self.__nefT.get_data_content(self.__star_data[fileListId], self.__star_data_type[fileListId])

            is_valid, messages, corrections = self.__nefT.resolve_sf_names_for_cif(self.__star_data[fileListId], self.__star_data_type[fileListId])  # 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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    for tag in sf_data.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:
                            warn = f"Ignored saveframe category {sf_category!r}%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")

                    else:
                        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()

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

            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 mandatory 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 lp_counts[content_subtype] > 0 and content_type == 'nmr-restraints' and not self.__bmrb_only:

                err = "NMR restraint file includes assigned chemical shifts. "\
                    f"Please re-upload the {file_type.upper()} file as an NMR combined 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]

                err = f"The mandatory 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 combined 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.__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 combined 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_subtypes = {k: lp_counts[k] for k in lp_counts if lp_counts[k] > 0}

            input_source.setItemValue('content_subtype', content_subtypes)

        return not self.report.isError()

    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

        first_atom_pattern = re.compile(r'^ATOM +1 .*')
        hbond_da_atom_types = ('O', 'N', 'F')
        rdc_origins = ('OO', 'X', 'Y', 'Z')

        cs_range_min = self.chem_shift_range['min_exclusive']
        cs_range_max = self.chem_shift_range['max_exclusive']
        dist_range_min = self.dist_restraint_range['min_inclusive']
        dist_range_max = self.dist_restraint_range['max_inclusive']
        dihed_range_min = self.dihed_restraint_range['min_inclusive']
        dihed_range_max = self.dihed_restraint_range['max_inclusive']
        rdc_range_min = self.rdc_restraint_range['min_exclusive']
        rdc_range_max = self.rdc_restraint_range['max_exclusive']

        fileListId = self.__file_path_list_len

        md5_list = []

        for ar in self.__inputParamDict[ar_file_path_list]:

            file_path = ar['file_name']

            with open(file_path, 'r', encoding='UTF-8') as ifp:

                md5_list.append(hashlib.md5(ifp.read().encode('utf-8')).hexdigest())

                ifp.close()

            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 'original_file_name' in input_source_dic:
                original_file_name = 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})"

            is_aux_amb = file_type == 'nm-aux-amb'

            if file_type == 'nm-res-cns':
                mr_format_name = 'CNS'
            elif file_type == 'nm-res-xpl':
                mr_format_name = 'XPLOR-NIH'
            elif file_type == 'nm-res-amb':
                mr_format_name = 'AMBER'
            elif is_aux_amb:
                mr_format_name = 'AMBER'
            elif file_type == 'nm-res-cya':
                mr_format_name = 'CYANA'
            else:
                mr_format_name = 'other format'

            atom_like_names = self.__csStat.getAtomLikeNameSet(minimum_len=(2 if file_type == 'nm-res-oth' or is_aux_amb else 1))
            cs_atom_like_names = list(filter(self.__isHalfSpin, 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_rdc_origins = 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-cns', 'nm-res-xpl'):

                with open(file_path, 'r', encoding='UTF-8') as ifp:

                    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 ifp:

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if 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

                        l = " ".join(line.split())  # noqa: E741

                        s = re.split('[ ()]', l)

                        _t_lower = ""

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] == '#' or t[0] == '!':
                                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] == 'H' 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 dihed_range_min <= v <= dihed_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

                    ifp.close()

                with open(file_path, 'r', encoding='UTF-8') as ifp:

                    atom_likes = 0
                    names = []
                    has_rest = False
                    has_plan = False
                    has_grou = False
                    has_sele = False
                    has_resi = False

                    for line in ifp:

                        l = " ".join(line.split())  # noqa: E741

                        s = re.split('[ ()=]', l)

                        _t_lower = ""

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] == '#' or t[0] == '!':
                                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 self.weight_range['min_inclusive'] <= v <= self.weight_range['max_inclusive']:
                                                has_plane_restraint = True
                                        except ValueError:
                                            pass

                                elif t_lower == 'end':
                                    has_grou = False
                                    has_sele = False
                                    has_resi = False

                            _t_lower = t_lower

                    ifp.close()

            elif file_type == 'nm-res-amb':

                ws_pattern = re.compile(r'\s+')
                r_pattern = re.compile(r'r(\d+)=(.*)')

                with open(file_path, 'r', encoding='UTF-8') as ifp:

                    in_rst = False
                    in_iat = False
                    in_igr1 = False
                    in_igr2 = False

                    names = []
                    values = []

                    pos = 0

                    for line in ifp:

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if 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 line.startswith('defa'):
                            has_amb_inpcrd = True

                        elif pos == 2 and has_amb_inpcrd:
                            try:
                                int(line.lstrip().split()[0])
                            except ValueError:
                                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)

                        l = " ".join(line.split())  # noqa: E741

                        if len(l) == 0 or l.startswith('#') or l.startswith('!'):
                            continue

                        s = re.split(',', ws_pattern.sub('', l).lower())

                        for t in s:

                            if len(t) == 0:
                                continue

                            if t[0] == '#' or t[0] == '!':
                                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 int != -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 dihed_range_min <= v <= dihed_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 r_pattern.match(t):
                                    len_values = len(values)
                                    g = 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

                    ifp.close()

            elif file_type == 'nm-res-cya' or file_type == 'nm-res-oth' or is_aux_amb:

                if is_aux_amb:

                    has_atom_name = False
                    has_residue_label = False
                    has_residue_pointer = False

                    chk_atom_name_format = False
                    chk_residue_label_format = False
                    chk_residue_pointer_format = False

                    in_atom_name = False
                    in_residue_label = False
                    in_residue_pointer = False

                    a_format_pattern = re.compile(r'^%FORMAT\((\d+)a(\d+)\)\s*')
                    i_format_pattern = re.compile(r'^%FORMAT\((\d+)I(\d+)\)\s*')

                    atom_names = []
                    residue_labels = []
                    residue_pointers = []

                atom_like_names_oth = self.__csStat.getAtomLikeNameSet(1)
                cs_atom_like_names_oth = list(filter(self.__isHalfSpin, atom_like_names_oth))  # DAOTHER-7491

                one_letter_codes = self.monDict3.values()
                three_letter_codes = self.monDict3.keys()

                prohibited_col = set()

                with open(file_path, 'r', encoding='UTF-8') as ifp:

                    pos = 0

                    for line in ifp:
                        pos += 1

                        if line.startswith('ATOM ') and line.count('.') >= 3:
                            has_coordinate = True
                            if 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 line.startswith('defa'):
                                has_amb_inpcrd = True

                            elif pos == 2 and has_amb_inpcrd:
                                try:
                                    int(line.lstrip().split()[0])
                                except ValueError:
                                    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 = False
                                in_residue_label = False
                                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 chk_atom_name_format:
                                chk_atom_name_format = a_format_pattern.match(line)
                                if chk_atom_name_format:
                                    in_atom_name = True
                                    g = 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 = a_format_pattern.match(line)
                                if chk_residue_label_format:
                                    in_residue_label = True
                                    g = 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 = i_format_pattern.match(line)
                                if chk_residue_pointer_format:
                                    in_residue_pointer = True
                                    g = 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 in_atom_name:
                                len_line = len(line)
                                begin = 0
                                end = max_char
                                col = 0
                                while col < max_cols and end < len_line:
                                    atom_names.append(line[begin:end].rstrip())
                                    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:
                                    residue_labels.append(line[begin:end].rstrip())
                                    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_pointers.append(int(line[begin:end].lstrip()))
                                    except ValueError:
                                        pass
                                    begin = end
                                    end += max_char
                                    col += 1

                        l = " ".join(line.split())  # noqa: E741

                        if len(l) == 0 or l.startswith('#') or l.startswith('!'):
                            continue

                        s = re.split('[ ()]', l)

                        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] == '#' or t[0] == '!':
                                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 dihed_range_min <= v <= dihed_range_max:
                                        dihed_range_like = True
                                except ValueError:
                                    pass

                            elif name in three_letter_codes:
                                res_like = True

                            elif name in self.dihed_ang_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

                    ifp.close()

                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 ifp:
                        for line in ifp:
                            l = " ".join(line.split())  # noqa: E741

                            if len(l) == 0 or l.startswith('#') or l.startswith('!'):
                                continue

                            s = re.split('[ ()]', l)

                            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] == '#' or t[0] == '!':
                                    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 dihed_range_min <= v <= dihed_range_max:
                                            dihed_range_like = True
                                    except ValueError:
                                        pass

                                elif name in three_letter_codes:
                                    res_like = True

                                elif name in self.dihed_ang_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

                        ifp.close()

                if is_aux_amb:

                    if has_atom_name and has_residue_label and has_residue_pointer and\
                       len(atom_names) > 0 and len(residue_labels) > 0 and len(residue_pointers) > 0:
                        has_topology = True

                    if has_amb_coord and (not has_first_atom or has_ens_coord):
                        has_amb_coord = False

            if file_type in ('nm-res-cya', 'nm-res-oth') and not has_dist_restraint:  # DAOTHER-7491

                with open(file_path, 'r', encoding='UTF-8') as ifp:

                    for line in ifp:

                        l = " ".join(line.split())  # noqa: E741

                        if len(l) == 0 or l.startswith('#') or l.startswith('!'):
                            continue

                        s = re.split('[ ()]', l)

                        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.__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.__updateChemCompDict(comp_id):
                                continue

                        has_dist_restraint = True

                        break

                    ifp.close()

            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:

                if not is_aux_amb:

                    err = f"NMR restraint file ({mr_format_name}) 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:

                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"NMR restraint file ({mr_format_name}) seems to 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 not is_aux_amb:

                    err = f"NMR restraint file ({mr_format_name}) 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

            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,
                               'coordinate': 1 if has_coordinate else 0,
                               'topology': 1 if has_topology else 0}

            if not is_aux_amb 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:

                hint = ""
                if file_type in ('nm-res-cns', 'nm-res-xpl') 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_amb_inpcrd:
                    subtype_name += "AMBER restart coordinates (.rst), "

                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', "\
                    "and '%FLAG RESIDUE_POINTER' 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")

            self.__legacy_dist_restraint_uploaded |= has_dist_restraint

            input_source.setItemValue('content_subtype', content_subtype)

            fileListId += 1

        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 (content_subtype is not None and 'dist_restraint' in content_subtype) or file_type == 'nm-aux-amb':
                    continue

                if content_subtype is None:

                    err = "NMR restraint file does not include mandatory distance restraints or is not recognized properly. "\
                        "Please re-upload the NMR restraint file."

                    self.__suspended_errors_for_polypeptide.append({'content_mismatch':
                                                                    {'file_name': file_name, 'description': err}})

                    # 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 'chem_shift' not in content_subtype:

                    subtype_name = ""
                    if 'dihed_restraint' in content_subtype:
                        subtype_name += "Dihedral angle restraints, "
                    if 'rdc_restraint' in content_subtype:
                        subtype_name += "RDC restraints, "
                    if 'plane_restraint' in content_subtype:
                        subtype_name += "Planarity restraints, "
                    if 'hbond_restraint' in content_subtype:
                        subtype_name += "Hydrogen bond restraints, "

                    err = f"NMR restraint file includes {subtype_name[:-2]}. "\
                        "However, deposition of distance restraints is mandatory. Please re-upload the NMR restraint file."

                    self.__suspended_errors_for_polypeptide.append({'content_mismatch':
                                                                    {'file_name': file_name, 'description': err}})

                    # 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")

        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] == 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'])

                    err = f"You have uploaded the same NMR restranit 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")

        return not self.report.isError()

    def __isHalfSpin(self, name):
        """ Check if nuclei of a given atom name has a spin 1/2.
            @return: True for spin 1/2 nuclei, False otherwise
        """

        return any(nucl for nucl in self.half_spin_nucleus if name.startswith(nucl))

    def __getPolymerSequence(self, file_list_id, sf_data, 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']

        if file_type == 'nef':  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
            return self.__nefT.get_nef_seq(sf_data, 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')))

        if content_subtype == 'spectral_peak_alt':
            return self.__nefT.get_star_seq(sf_data, lp_category='_Assigned_peak_chem_shift',
                                            allow_empty=True, allow_gap=True)

        # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
        return self.__nefT.get_star_seq(sf_data, 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')))

    def __extractPolymerSequence(self):
        """ Extract reference polymer sequence.
        """

        # if self.report.isError():
        #    return False

        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'].keys():
                continue

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

            for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                try:

                    poly_seq = self.__getPolymerSequence(fileListId, sf_data, 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_data, lp_category)[0]

                        for ps in poly_seq:
                            chain_id = ps['chain_id']
                            seq_ids = ps['seq_id']
                            comp_ids = ps['comp_id']

                            for aps in auth_poly_seq:

                                if aps['chain_id'] != chain_id:
                                    continue

                                _seq_ids = aps['seq_id']
                                auth_asym_ids = aps['auth_asym_id']
                                auth_seq_ids = aps['auth_seq_id']
                                auth_comp_ids = 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 self.empty_value:
                                            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 self.empty_value:
                                                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()

                    # if self.__verbose:
                    #     self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ LookupError  - {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')
                    proc_errs = set()

                    for err in errs:

                        if err == '' or err in proc_errs:
                            continue

                        proc_errs.add(err)

                        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.__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.
        """

        # if self.report.isError():
        #    return False

        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'

                list_id = 1

                has_poly_seq = False

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf_data,
                                                                          list_id, sf_framecode, lp_category, poly_seq_list_set)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf_data,
                                                                          list_id, sf_framecode, lp_category, poly_seq_list_set)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        has_poly_seq |= self.__extractPolymerSequenceInLoop__(fileListId, file_name, file_type, content_subtype, sf_data,
                                                                              list_id, sf_framecode, lp_category, poly_seq_list_set)

                        list_id += 1

                if not has_poly_seq:
                    poly_seq_list_set.pop(content_subtype)

            # if self.report.isError():
            #    is_done = False

            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_data,
                                         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_data, 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_data, lp_category)[0]

                    for ps in poly_seq:
                        chain_id = ps['chain_id']
                        seq_ids = ps['seq_id']
                        comp_ids = ps['comp_id']

                        for aps in auth_poly_seq:

                            if aps['chain_id'] != chain_id:
                                continue

                            _seq_ids = aps['seq_id']
                            auth_asym_ids = aps['auth_asym_id']
                            auth_seq_ids = aps['auth_seq_id']
                            auth_comp_ids = 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 self.empty_value:
                                        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 self.empty_value:
                                            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()

            # if self.__verbose:
            #     self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequenceInLoop() ++ LookupError  - {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')
            proc_errs = set()

            for err in errs:

                if err == '' or err in proc_errs:
                    continue

                proc_errs.add(err)

                if err.startswith('[Invalid data]'):

                    if 'Auth' not in err or self.__check_auth_seq:

                        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.__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
                # """
                # 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 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 self.empty_value and _comp_id not in self.empty_value 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 self.empty_value and _comp_id not in self.empty_value 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 self.empty_value and _comp_id not in self.empty_value and comp_id != _comp_id:
                                                return False

        return True

    def __testSequenceConsistency(self):
        """ Perform sequence consistency test among extracted polymer sequences.
        """

        # if self.report.isError():
        #    return False

        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)

            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 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}."

                                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 self.empty_value:
                                                    print(sf_framecode2)
                                                    print(s1['chain_id'])
                                                    print(s1['seq_id'])
                                                    print(s1['comp_id'])

                                                    print(s2['chain_id'])
                                                    print(s2['seq_id'])
                                                    print(s2['comp_id'])

                                                    print(f"{seq_id} {comp_id}")

                                                    sys.exit(1)

                                                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:
                                            i = s1['seq_id'].index(seq_id)
                                            _comp_id = s1['comp_id'][i]

                                            if comp_id not in self.empty_value and _comp_id not in self.empty_value and comp_id != _comp_id:

                                                if self.__target_framecode not in self.empty_value:
                                                    print(sf_framecode2)
                                                    print(s1['chain_id'])
                                                    print(s1['seq_id'])
                                                    print(s1['comp_id'])

                                                    print(s2['chain_id'])
                                                    print(s2['seq_id'])
                                                    print(s2['comp_id'])

                                                    print(f"{seq_id} {comp_id}")

                                                    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.__equalsRepresentativeCompId(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 self.__get1LetterCode(comp_id) == self.__get1LetterCode(_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 self.empty_value and _comp_id not in self.empty_value 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.__equalsRepresentativeCompId(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 self.empty_value and _comp_id not in self.empty_value 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.__equalsRepresentativeCompId(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 __equalsRepresentativeCompId(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 is self.empty_value or ref_comp_id in self.empty_value:
            return False

        if '_' in comp_id:
            comp_id = comp_id.split('_')[0]

        elif self.__get1LetterCode(comp_id) == 'X' and self.__updateChemCompDict(comp_id):
            if '_chem_comp.mon_nstd_parent_comp_id' in self.__last_chem_comp_dict:  # matches with comp_id in CCD
                if self.__last_chem_comp_dict['_chem_comp.mon_nstd_parent_comp_id'] not in self.empty_value:
                    comp_id = self.__last_chem_comp_dict['_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 self.__get1LetterCode(ref_comp_id) == 'X' and self.__updateChemCompDict(ref_comp_id):
            if '_chem_comp.mon_nstd_parent_comp_id' in self.__last_chem_comp_dict:  # matches with comp_id in CCD
                if self.__last_chem_comp_dict['_chem_comp.mon_nstd_parent_comp_id'] not in self.empty_value:
                    ref_comp_id = self.__last_chem_comp_dict['_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_data = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id, comp_id_conv_dict)

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

            sf_data = self.__star_data[file_list_id]

            if get_first_sf_tag(sf_data, 'sf_framecode') == sf_framecode:
                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id, comp_id_conv_dict)

        else:

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

                if get_first_sf_tag(sf_data, 'sf_framecode') != sf_framecode:
                    continue

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

                self.__fixCompIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id, comp_id_conv_dict)

    def __fixCompIdInLoop__(self, file_list_id, file_type, content_subtype, sf_data, 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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                num_dim = int(_num_dim)

                if num_dim not in range(1, self.lim_num_dim):
                    raise ValueError()

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

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
        else:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.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.data:

                if row[chain_id_col] != chain_id:
                    continue

                _seq_id = row[seq_id_col]

                if _seq_id in self.empty_value 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.data:

                    if row[chain_id_col] != chain_id:
                        continue

                    _seq_id = row[seq_id_col]

                    if _seq_id in self.empty_value 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.
        """

        # if self.report.isError():
        #    return False

        common_poly_seq = {}

        # primary_ps_list = []

        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):
                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 and 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]:
                ps = ps_in_loop['polymer_sequence']
                # """
                # if len(primary_ps_list) > 0:
                #     for primary_ps in primary_ps_list:
                #         for primary_s in primary_ps:
                #             last_chain_id = primary_s['chain_id']

                #     chain_id_offset = self.__nefT.letter_to_int(last_chain_id);

                #     for primary_ps in primary_ps_list:
                #         for primary_s in primary_ps:
                #             primary_chain_id = primary_s['chain_id']

                #             s = next((s for s in ps if s['chain_id'] == primary_chain_id), None)

                #             if s is not None:

                #                 _s1 = fill_blank_comp_id_with_offset(primary_s, 0)
                #                 _s2 = fill_blank_comp_id_with_offset(s, 0)

                #                 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 = score_of_seq_align(myAlign)

                #                 if length == unmapped + conflict or _matched <= conflict or (len(polymer_sequence) > 1 and _matched < 4 and offset_1 > 0):
                #                     chain_id_offset += 1
                #                     s['chain_id'] = self.__nefT.index_to_letter(chain_id_offset) if file_type == 'nef' else str(chain_id_offset)
                #                     if fileListId in self.__remapped_def_chain_id:
                #                         self.__remapped_def_chain_id[fileListId] = {}
                #                     self.__remapped_def_chain_id[fileListId] = {chain_id: s['chain_id']}

                # primary_ps_list.append(ps)
                # """
                for s in ps:
                    chain_id = s['chain_id']

                    if chain_id not in common_poly_seq:
                        common_poly_seq[chain_id] = set()

            chains = common_poly_seq.keys()
            offset_seq_ids = {c: 0 for c in chains}

            # for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                ps = ps_in_loop['polymer_sequence']

                for s in ps:
                    chain_id = s['chain_id']

                    min_seq_id = min(s['seq_id'])
                    if min_seq_id < 0:
                        offset_seq_ids[chain_id] = min_seq_id * -1

                    for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):
                        common_poly_seq[chain_id].add(f"{seq_id + offset_seq_ids[chain_id]:04d} {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_id_list = sorted(set(int(i.split(' ')[0]) - offset_seq_ids[chain_id] for i in common_poly_seq[chain_id]))
                comp_id_list = []

                for seq_id in seq_id_list:
                    _comp_id = [i.split(' ')[1] for i in common_poly_seq[chain_id] if int(i.split(' ')[0]) - offset_seq_ids[chain_id] == seq_id]
                    if len(_comp_id) == 1:
                        comp_id_list.append(_comp_id[0])
                    else:
                        comp_id_list.append(next(comp_id for comp_id in _comp_id if comp_id not in self.empty_value))

                if self.__combined_mode and self.__has_star_entity:
                    ent = self.__extractPolymerSequenceInEntityLoopOfChain(fileListId, chain_id)

                    if ent is not None:
                        asm.append(ent)
                        continue

                asm.append({'chain_id': chain_id, 'seq_id': seq_id_list, 'comp_id': comp_id_list})

        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 __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_data in self.__star_data[file_list_id].get_saveframes_by_category('assembly'):

            try:
                if __pynmrstar_v3_2__:
                    loop = sf_data.get_loop('_Entity_assembly')
                else:
                    loop = sf_data.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 self.empty_value or c[1] in self.empty_value or c[2] in self.empty_value:
                    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_data = self.__getSaveframeByName(file_list_id, entity_sf)

                if _sf_data is None:
                    return False

                content_subtype = 'entity'

                try:
                    if __pynmrstar_v3_2__:
                        _loop = _sf_data.get_loop(self.lp_categories[file_type][content_subtype])
                    else:
                        _loop = _sf_data.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 self.empty_value or s[1] in self.empty_value or s[2] in self.empty_value:
                        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(f"{seq_id:04d} {comp_id}")

                sorted_seq = sorted(seq)

                asm.append({'chain_id': chain_id,
                            'seq_id': [int(i.split(' ')[0]) for i in sorted_seq],
                            'comp_id': [i.split(' ')[-1] for i 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 separated 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'

        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

        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 i in dat:
                    if i[2] in self.empty_value:
                        i[2] = '1'
            elif set(tags_) & set(loop.tags) == set(tags_):  # No Entity_ID tag case
                dat = get_lp_tag(loop, tags_)
                for i in dat:
                    i.append('1')

            for i in dat:

                if i[0] in self.empty_value or i[1] in self.empty_value or i[2] in self.empty_value:
                    return False

                try:
                    c = str(i[2])

                    chain_ids.add(c)
                    if c not in seq:
                        seq[c] = set()
                    seq[c].add(f"{int(i[0]):04d} {i[1]}")
                except ValueError:
                    return False

        for chain_id in chain_ids:

            sorted_seq = sorted(seq[chain_id])

            asm.append({'chain_id': chain_id,
                        'seq_id': [int(i.split(' ')[0]) for i in sorted_seq],
                        'comp_id': [i.split(' ')[-1] for i 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 False

        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 False

        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 i in dat:
                    if i[2] in self.empty_value:
                        i[2] = '1'
            elif set(tags_) & set(loop.tags) == set(tags_):  # No Entity_ID tag case
                dat = get_lp_tag(loop, tags_)
                for i in dat:
                    i.append('1')

            for i in dat:

                if i[0] in self.empty_value or i[1] in self.empty_value or i[2] in self.empty_value:
                    return False

                try:
                    c = str(i[2])

                    chain_ids.add(c)
                    if c not in seq:
                        seq[c] = set()
                    seq[c].add(f"{int(i[0]):04d} {i[1]}")
                except ValueError:
                    return False

        if chain_id in chain_ids:

            sorted_seq = sorted(seq[chain_id])

            return {'chain_id': chain_id,
                    'seq_id': [int(i.split(' ')[0]) for i in sorted_seq],
                    'comp_id': [i.split(' ')[-1] for i in sorted_seq]}

        return None

    def __extractNonStandardResidue(self):
        """ Extract non-standard residue.
        """

        # if self.report.isError():
        #    return 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_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_data = self.__star_data[fileListId]
                sf_framecode = ''

                self.__extractNonStandardResidue__(file_name, sf_framecode, lp_category, input_source)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__extractNonStandardResidue__(file_name, sf_framecode, lp_category, input_source)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    if not any(loop for loop in sf_data.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 s in polymer_sequence:

            has_non_std_comp_id = False

            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 self.__get1LetterCode(comp_id) == 'X':
                    has_non_std_comp_id = True

                    ent['seq_id'].append(seq_id)
                    ent['comp_id'].append(comp_id)

                    if self.__updateChemCompDict(comp_id):  # matches with comp_id in CCD
                        cc_name = self.__last_chem_comp_dict['_chem_comp.name']
                        cc_rel_status = self.__last_chem_comp_dict['_chem_comp.pdbx_release_status']
                        if cc_rel_status == 'REL':
                            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(None)

                        warn = f"Non standard residue ({s['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")

                    ent['exptl_data'].append({'chem_shift': False, 'dist_restraint': False, 'dihed_restraint': False,
                                              'rdc_restraint': False, 'spectral_peak': False, 'coordinate': False})

            if has_non_std_comp_id:
                asm.append(ent)

        if len(asm) > 0:
            input_source.setItemValue('non_standard_residue', asm)

    def __appendPolymerSequenceAlignment(self):
        """ Append polymer sequence alignment of interesting loops.
        """

        # if self.report.isError():
        #    return False

        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 = fill_blank_comp_id_with_offset(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 = score_of_seq_align(myAlign)

                            alt_chain = False

                            if length == unmapped + conflict or _matched <= conflict or (len(polymer_sequence) > 1 and _matched < 4 and offset_1 > 0):

                                if self.__tolerant_seq_align and _matched <= conflict 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 = score_of_seq_align(myAlign)

                                        if length == unmapped + conflict or _matched <= conflict:
                                            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:
                                    print(f"#1 {chain_id} -> {__chain_id}, {__length} {__matched} {__unmapped} {__conflict} {__offset_1} {__offset_2}")

                                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:
                                    print(f"#2 {s2['chain_id']} -> {chain_id}, {length} {_matched} {unmapped} {conflict} {offset_1} {offset_2}")

                                alt_chain = True

                            _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(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 = self.__get1LetterCodeSequence(_s1['comp_id'])
                            test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                            mid_code = get_middle_code(ref_code, test_code)
                            ref_gauge_code = get_gauge_code(_s1['seq_id'])
                            test_gauge_code = get_gauge_code(_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,
                                                                            None if sf_framecode2 not in map_chain_ids else map_chain_ids[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:
                                            print(f"#a {chain_id2} {_matched} {offset_1} {offset_2} {seq_id_conv_dict}")

                                        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 = get_middle_code(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,
                                                                            None if sf_framecode2 not in map_chain_ids else map_chain_ids[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 = fill_blank_comp_id(_s1, _s2)
                                        if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                            continue
                                        test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                                        mid_code = get_middle_code(ref_code, test_code)
                                        test_gauge_code = ref_gauge_code

                                update_poly_seq = True
                            # """
                            # if 'identical_chain_id' in s2 and self.__tolerant_seq_align:

                            #     for chain_id2_ in s2['identical_chain_id']:

                            #         try:

                            #             s2_ = next(s2_ for s2_ in ps2 if s2_['chain_id'] == chain_id2_)

                            #             _s2_ = fill_blank_comp_id_with_offset(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_ = score_of_seq_align(myAlign)

                            #             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 seq_mismatch and comp_mismatch: # and not alt_chain:

                            #                 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 sf_framecode2 == self.__target_framecode:
                            #                         print(f"#d {chain_id2_} {_matched_} {offset_1_} {offset_2_} {seq_id_conv_dict}")

                            #                     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_)
                            #                     _s2_['seq_id'] = _s1['seq_id']

                            #                 update_poly_seq = True

                            #         except StopIteration:
                            #             pass
                            # """
                            matched = mid_code.count('|')

                            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_non_std_comp_id = False
                                for j, rc in enumerate(ref_code):
                                    if rc == 'X' and j < len(test_code) and test_code[j] == 'X':
                                        has_non_std_comp_id = True
                                        break

                                if not has_non_std_comp_id:
                                    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 self.__get1LetterCode(_comp_id) == 'X':

                                            ent['seq_id'].append(_seq_id)
                                            ent['comp_id'].append(_comp_id)

                                            if self.__updateChemCompDict(_comp_id):  # matches with comp_id in CCD
                                                cc_name = self.__last_chem_comp_dict['_chem_comp.name']
                                                cc_rel_status = self.__last_chem_comp_dict['_chem_comp.pdbx_release_status']
                                                if cc_rel_status == 'REL':
                                                    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(None)

                                            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 = fill_blank_comp_id_with_offset(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 = score_of_seq_align(myAlign)

                            alt_chain = False

                            if length == unmapped + conflict or _matched <= conflict or (len(polymer_sequence) > 1 and _matched < 4 and offset_1 > 0):

                                if self.__tolerant_seq_align and _matched <= conflict 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 = score_of_seq_align(myAlign)

                                        if length == unmapped + conflict or _matched <= conflict:
                                            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:
                                    print(f"#3 {chain_id} -> {__chain_id}, {__length} {__matched} {__unmapped} {__conflict} {__offset_1} {__offset_2}")

                                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:

                            #     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:
                            #         print(f"#4 {s2['chain_id'] -> {chain_id}, {length} {_matched} {unmapped} {conflict} {offset_1} {offset_2}")

                            #     alt_chain = True
                            # """
                            _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(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 = self.__get1LetterCodeSequence(_s1['comp_id'])
                            test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                            mid_code = get_middle_code(ref_code, test_code)
                            ref_gauge_code = get_gauge_code(_s1['seq_id'])
                            test_gauge_code = get_gauge_code(_s2['seq_id'])

                            self.__alt_chain |= not alt_chain
                            # """
                            # 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 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,
                            #                                                 None if sf_framecode2 not in map_chain_ids else map_chain_ids[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:
                            #                 print(f"#b {chain_id2} {_matched} {offset_1} {offset_2} {seq_id_conv_dict}")

                            #             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 = get_middle_code(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,
                            #                                                 None if sf_framecode2 not in map_chain_ids else map_chain_ids[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 = fill_blank_comp_id(_s1, _s2)
                            #             if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                            #                 continue
                            #             test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                            #             mid_code = get_middle_code(ref_code, test_code)
                            #             test_gauge_code = ref_gauge_code

                            #     update_poly_seq = True
                            # """
                            matched = mid_code.count('|')

                            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_non_std_comp_id = False
                                for j, rc in enumerate(ref_code):
                                    if rc == 'X' and j < len(test_code) and test_code[j] == 'X':
                                        has_non_std_comp_id = True
                                        break

                                if not has_non_std_comp_id:
                                    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 self.__get1LetterCode(_comp_id) == 'X':

                                            ent['seq_id'].append(_seq_id)
                                            ent['comp_id'].append(_comp_id)

                                            if self.__updateChemCompDict(_comp_id):  # matches with comp_id in CCD
                                                cc_name = self.__last_chem_comp_dict['_chem_comp.name']
                                                cc_rel_status = self.__last_chem_comp_dict['_chem_comp.pdbx_release_status']
                                                if cc_rel_status == 'REL':
                                                    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(None)

                                            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
                                    # """
                                    # for s1 in polymer_sequence:
                                    #     chain_id = s1['chain_id']

                                    #     if chain_id != dst_chain:
                                    #         continue

                                    #     for s2 in ps2:

                                    #         if chain_id != src_chain:
                                    #             continue

                                    #         _s2 = fill_blank_comp_id_with_offset(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 = score_of_seq_align(myAlign)

                                    #         if length == unmapped + conflict or _matched <= conflict:
                                    #             break

                                    #         mapping[src_chain] = dst_chain

                                    #         break
                                    # """

                                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 = fill_blank_comp_id_with_offset(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 = score_of_seq_align(myAlign)

                                            if length == unmapped + conflict or _matched <= conflict:
                                                break

                                            cross = True
                                            mapping[src_chain] = dst_chain

                                            break

                            if sf_framecode2 == self.__target_framecode:
                                print(f"chain_mapping {mapping} cross {cross} cicular {circular}")

                            for s1 in polymer_sequence:
                                chain_id = s1['chain_id']

                                for s2 in ps2:

                                    if chain_id != s2['chain_id']:
                                        continue

                                    _s2 = fill_blank_comp_id_with_offset(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 = score_of_seq_align(myAlign)

                                    if length == unmapped + conflict or _matched <= conflict:
                                        continue

                                    _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                                    _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(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 = self.__get1LetterCodeSequence(_s1['comp_id'])
                                    test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                                    mid_code = get_middle_code(ref_code, test_code)
                                    ref_gauge_code = get_gauge_code(_s1['seq_id'])
                                    test_gauge_code = get_gauge_code(_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:
                                                    print(f"#c {chain_id2} {_matched} {offset_1} {offset_2} {seq_id_conv_dict}")

                                                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 = get_middle_code(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 = fill_blank_comp_id(_s1, _s2)
                                                if _s1['seq_id'][0] < 0 and _s2['seq_id'][0] < 0:
                                                    continue
                                                test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                                                mid_code = get_middle_code(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.
        """

        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['seq_id']):
                    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['seq_id']):
                    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 = get_middle_code(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 = self.__get1LetterCodeSequence(comp_id1)
            test_code = self.__get1LetterCodeSequence(comp_id2)
            mid_code = get_middle_code(ref_code, test_code)
            ref_gauge_code = get_gauge_code(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_data = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, _chain_id)

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

            sf_data = self.__star_data[file_list_id]

            if get_first_sf_tag(sf_data, 'sf_framecode') == sf_framecode:
                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, _chain_id)

        else:

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

                if get_first_sf_tag(sf_data, 'sf_framecode') != sf_framecode:
                    continue

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

                self.__fixChainIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, _chain_id)

    def __fixChainIdInLoop__(self, file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, _chain_id):
        """ Fix sequence ID of interesting loop.
        """

        uniq_chains = 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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                num_dim = int(_num_dim)

                if num_dim not in range(1, self.lim_num_dim):
                    raise ValueError()

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

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
        else:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.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.data:

                if row[chain_id_col] != chain_id:
                    continue

                row[chain_id_col] = _chain_id

                if uniq_chains 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.data:

                    if row[chain_id_col] != chain_id:
                        continue

                    row[chain_id_col] = _chain_id

                    if uniq_chains 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_data = self.__star_data[file_list_id]

            if sf_framecode == '':
                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id_conv_dict)

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

            sf_data = self.__star_data[file_list_id]

            if get_first_sf_tag(sf_data, 'sf_framecode') == sf_framecode:
                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id_conv_dict)

        else:

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

                if get_first_sf_tag(sf_data, 'sf_framecode') != sf_framecode:
                    continue

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

                self.__fixSeqIdInLoop__(file_list_id, file_type, content_subtype, sf_data, lp_category, chain_id, seq_id_conv_dict)

    def __fixSeqIdInLoop__(self, file_list_id, file_type, content_subtype, sf_data, 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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                num_dim = int(_num_dim)

                if num_dim not in range(1, self.lim_num_dim):
                    raise ValueError()

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

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
        else:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.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.data:

                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.data:

                    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 __get1LetterCode(self, comp_id):
        """ Convert comp ID to 1-letter code.
        """

        comp_id = comp_id.upper()

        if comp_id in self.monDict3:
            return self.monDict3[comp_id]

        if comp_id in self.empty_value:
            return '.'

        return 'X'

    def __get1LetterCodeSequence(self, comp_ids):
        """ Convert array of comp ID to 1-letter code sequence.
        """

        array = ''

        for comp_id in comp_ids:
            array += self.__get1LetterCode(comp_id)

        return array

    def __validateAtomNomenclature(self):
        """ Validate atom nomenclature using NEFTranslator and CCD.
        """

        # if self.report.isError():
        #    return 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_in_loop = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if not has_poly_seq_in_loop:
                continue

            polymer_sequence = input_source_dic['polymer_sequence']

            first_comp_ids = set()

            if polymer_sequence is not None:
                for s in polymer_sequence:
                    first_comp_id = s['comp_id'][0]

                    if self.__csStat.getTypeOfCompId(first_comp_id)[0]:
                        first_comp_ids.add(first_comp_id)

            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_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, first_comp_ids)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, first_comp_ids)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        self.__validateAtomNomenclature__(file_name, file_type, content_subtype, sf_data, 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 == 'HN' and self.__csStat.getTypeOfCompId(comp_id)[0])
                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 __getRepresentativeAtomId(self, file_type, comp_id, atom_id):
        """ Return a representative atom ID in IUPAC atom nomenclature for a given atom_id.
        """

        _atom_id = self.__getAtomIdList(file_type, comp_id, atom_id)

        return atom_id if len(_atom_id) == 0 else _atom_id[0]

    def __getAtomIdList(self, file_type, comp_id, atom_id):
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id.
        """

        return self.__getAtomIdListWithAmbigCode(file_type, comp_id, atom_id, leave_unmatched=False)[0]

    def __getAtomIdListWithAmbigCode(self, file_type, 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.
        """

        if file_type == 'nef' or atom_id == 'HN' or atom_id.endswith('%') or atom_id.endswith('*'):
            return self.__nefT.get_star_atom(comp_id, atom_id, leave_unmatched=leave_unmatched)

        if atom_id.startswith('QQ'):
            return self.__nefT.get_star_atom(comp_id, 'H' + atom_id[2:] + '%', leave_unmatched=leave_unmatched)

        if atom_id.startswith('QR'):
            qr_atoms = sorted(set(atom_id[:-1] + '%' for atom_id in self.__csStat.getAromaticAtoms(comp_id)
                                  if atom_id[0] == 'H' and self.__csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id) == 3))
            if len(qr_atoms) == 0:
                return [], None, None
            atom_list = []
            for qr_atom in qr_atoms:
                _atom_list, ambiguity_code, details = self.__nefT.get_star_atom(comp_id, qr_atom, leave_unmatched=leave_unmatched)
                atom_list.extend(_atom_list)
            return atom_list, ambiguity_code, details

        if atom_id.startswith('Q') or atom_id.startswith('M'):
            return self.__nefT.get_star_atom(comp_id, 'H' + atom_id[1:] + '%', leave_unmatched=leave_unmatched)

        if atom_id + '2' in self.__csStat.getAllAtoms(comp_id):
            return self.__nefT.get_star_atom(comp_id, atom_id + '%', leave_unmatched=leave_unmatched)

        return self.__nefT.get_star_atom(comp_id, atom_id, leave_unmatched=leave_unmatched)

    def __validateAtomNomenclature__(self, file_name, file_type, content_subtype, sf_data, 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_data, 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_data, 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 self.__get1LetterCode(comp_id) != 'X':

                    if file_type == 'nef':

                        _atom_ids = []
                        for atom_id in atom_ids:

                            _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 atom_id == 'H1' and comp_id in first_comp_ids:
                                    continue

                                err = f"Invalid atom_id {atom_id!r} (comp_id {comp_id}) 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 == 'HN' and self.__csStat.getTypeOfCompId(comp_id)[0]:
                            self.__fixAtomNomenclature(comp_id, {'HN': 'H'})
                            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.__getRepresentativeAtomId(file_type, 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.getTypeOfCompId(comp_id)[0] 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_2}/{_atom_id_3} "\
                                    "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")

                                self.__fixAtomNomenclature(comp_id, {_atom_id_1: _atom_id_2, _atom_id_2: _atom_id_3})

                            elif self.__nonblk_bad_nterm and atom_id == 'H1' and comp_id in first_comp_ids:
                                pass

                            else:

                                err = f"Invalid atom_id {atom_id!r} (comp_id {comp_id}) 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.__updateChemCompDict(comp_id):  # matches with comp_id in CCD

                        ref_atom_ids = [a[self.__cca_atom_id] for a in self.__last_chem_comp_atoms]  # if a[self.__cca_leaving_atom_flag] != 'Y']
                        unk_atom_ids = []

                        for atom_id in atom_ids:

                            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:
                                unk_atom_ids.append(atom_id)

                        if len(unk_atom_ids) > 0:
                            cc_rel_status = self.__last_chem_comp_dict['_chem_comp.pdbx_release_status']
                            if cc_rel_status == 'REL':
                                cc_name = self.__last_chem_comp_dict['_chem_comp.name']
                            else:
                                cc_name = f"(Not available due to CCD status code {cc_rel_status})"

                            warn = f"Unknown atom_id {unk_atom_ids!r} (comp_id {comp_id}, chem_comp_name {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.__cca_type_symbol] for a in self.__last_chem_comp_atoms if a[self.__cca_leaving_atom_flag] != 'Y')

                        for elem in ref_elems:
                            if elem in self.paramag_elems or elem in self.ferromag_elems:
                                self.report.setDiamagnetic(False)
                                break

                        for atom_id in atom_ids:

                            if atom_id == 'HN' and self.__csStat.getTypeOfCompId(comp_id)[0]:
                                self.__fixAtomNomenclature(comp_id, {'HN': 'H'})
                                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.__getRepresentativeAtomId(file_type, 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:

                    auth_pairs = self.__nefT.get_star_auth_comp_atom_pair(sf_data, lp_category)[0]

                    for auth_pair in auth_pairs:
                        comp_id = auth_pair['comp_id']
                        auth_atom_ids = auth_pair['atom_id']

                        # standard residue
                        if self.__get1LetterCode(comp_id) != 'X':

                            _auth_atom_ids = []
                            for auth_atom_id in auth_atom_ids:

                                _auth_atom_id = self.__nefT.get_star_atom(comp_id, auth_atom_id, leave_unmatched=False)[0]

                                if len(_auth_atom_id) == 0:

                                    if self.__nonblk_bad_nterm and auth_atom_id == 'H1' and comp_id in first_comp_ids:
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {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")

                                else:
                                    _auth_atom_ids.extend(_auth_atom_id)

                            auth_atom_ids = sorted(set(_auth_atom_ids))

                            for auth_atom_id in auth_atom_ids:

                                if not self.__nefT.validate_comp_atom(comp_id, auth_atom_id):

                                    if self.__nonblk_bad_nterm and auth_atom_id == 'H1' and comp_id in first_comp_ids:
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {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 self.__nonblk_bad_nterm and auth_atom_id == 'H1' and comp_id in first_comp_ids:
                                            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 self.__nonblk_bad_nterm and auth_atom_id == 'H1' and comp_id in first_comp_ids:
                                        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()

                    # if self.__verbose:
                    #     self.__lfh.write(f"+NmrDpUtility.__extractPolymerSequence() ++ LookupError  - {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')
                    proc_errs = set()

                    for err in errs:

                        if err == '' or err in proc_errs:
                            continue

                        proc_errs.add(err)

                        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()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__validateAtomNomenclature() ++ LookupError  - {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')
            proc_errs = set()

            for err in errs:

                if err == '' or err in proc_errs:
                    continue

                proc_errs.add(err)

                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'].keys():

                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_data = self.__star_data[fileListId]
                    # sf_framecode = ''

                    self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf_data, lp_category, comp_id, atom_id_conv_dict)

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

                    sf_data = self.__star_data[fileListId]
                    # sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf_data, lp_category, comp_id, atom_id_conv_dict)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        # sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        self.__fixAtomNomenclature__(fileListId, file_type, content_subtype, sf_data, lp_category, comp_id, atom_id_conv_dict)

    def __fixAtomNomenclature__(self, file_list_id, file_type, content_subtype, sf_data, 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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                num_dim = int(_num_dim)

                if num_dim not in range(1, self.lim_num_dim):
                    raise ValueError()

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

            max_dim = num_dim + 1

        if __pynmrstar_v3_2__:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
        else:
            loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.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.
        """

        # if self.report.isError():
        #    return 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']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'chem_shift'

            if content_subtype not in input_source_dic['content_subtype'].keys():
                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_data = self.__star_data[fileListId]
                sf_framecode = ''

                self.__validateAtomTypeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__validateAtomTypeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    self.__validateAtomTypeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

        return not self.report.isError()

    def __validateAtomTypeOfCSLoop__(self, file_name, file_type, sf_data, 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_data, allow_empty=True)[0]
            else:
                a_types = self.__nefT.get_star_atom_type_from_cs_loop(sf_data, 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 self.atom_isotopes.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 self.atom_isotopes[atom_type]:

                            err = f"Invalid isotope number {str(isotope_num)!r} (atom_type {atom_type}, "\
                                f"allowed isotope number {self.atom_isotopes[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):

                            err = f"Invalid atom_id {atom_id!r} (atom_type {atom_type}) 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()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__validateAtomTypeOfCSLoop() ++ LookupError  - {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')
            proc_errs = set()

            for err in errs:

                if err == '' or err in proc_errs:
                    continue

                proc_errs.add(err)

                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.
        """

        # if self.report.isError():
        #    return 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']

            if input_source_dic['content_subtype'] is None:
                continue

            content_subtype = 'chem_shift'

            if content_subtype not in input_source_dic['content_subtype'].keys():
                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]

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = ''

                self.__validateAmbigCodeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__validateAmbigCodeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    self.__validateAmbigCodeOfCSLoop__(file_name, file_type, sf_data, sf_framecode, lp_category)

        return not self.report.isError()

    def __validateAmbigCodeOfCSLoop__(self, file_name, file_type, sf_data, sf_framecode, lp_category):
        """ Validate ambiguity code on assigned chemical shifts.
        """

        try:

            a_codes = self.__nefT.get_star_ambig_code_from_cs_loop(sf_data)[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:
                    pass

                # 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.__getRepresentativeAtomId(file_type, comp_id, atom_id)

                        allowed_ambig_code = self.__csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)

                        if ambig_code > allowed_ambig_code:

                            if allowed_ambig_code < 1:

                                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")

                            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")

            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")

        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()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testAmbigCodeOfCSLoop() ++ LookupError  - {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')
            proc_errs = set()

            for err in errs:

                if err == '' or err in proc_errs:
                    continue

                proc_errs.add(err)

                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")

    def __testIndexConsistency(self):
        """ Perform consistency test on index of interesting loops.
        """

        # if self.report.isError():
        #    return 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']

            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'].keys():

                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_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__testIndexConsistency__(file_name, sf_data, sf_framecode, lp_category, index_tag)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    self.__testIndexConsistency__(file_name, sf_data, sf_framecode, lp_category, index_tag)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        self.__testIndexConsistency__(file_name, sf_data, sf_framecode, lp_category, index_tag)

        return not self.report.isError()

    def __testIndexConsistency__(self, file_name, sf_data, sf_framecode, lp_category, index_tag):
        """ Perform consistency test on index of interesting loops.
        """

        try:

            indices = self.__nefT.get_index(sf_data, 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 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()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testIndexConsistency() ++ LookupError  - {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.__testIndexConsistency() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            errs = str(e).strip("'").split('\n')
            proc_errs = set()

            for err in errs:

                if err == '' or err in proc_errs:
                    continue

                proc_errs.add(err)

                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")

                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'].keys():

                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_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, 1)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, 1)

                else:

                    parent_pointer = 0

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        parent_pointer += 1

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        self.__testDataConsistencyInLoop__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, parent_pointer)

        return self.report.getTotalErrors() == __errors

    def __testDataConsistencyInLoop__(self, file_list_id, file_name, file_type, content_subtype, sf_data, 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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                num_dim = int(_num_dim)

                if num_dim not in range(1, self.lim_num_dim):
                    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]:
                    _k = copy.copy(k)
                    if '%s' in k['name']:
                        _k['name'] = k['name'] % dim
                    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 < self.lim_num_dim:
                disallowed_tags = []
                for dim in range(max_dim, self.lim_num_dim):
                    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]

        lp_data = None

        try:

            lp_data = self.__nefT.check_data(sf_data, 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 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 item' 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()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInLoop() ++ LookupError  - {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')
            proc_warns = set()

            has_multiple_data = False
            has_bad_pattern = False

            for warn in warns:

                if warn == '' or warn in proc_warns:
                    continue

                proc_warns.add(warn)

                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.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_data, lp_category, key_items)[0]

                if len(conflict_id) > 0:
                    if __pynmrstar_v3_2__:
                        loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop_by_category(lp_category)

                    for lcid in conflict_id:
                        del loop.data[lcid]

            # try to parse data without bad patterns

            if has_bad_pattern:
                conflict_id = self.__nefT.get_bad_pattern_id(sf_data, lp_category, key_items, data_items)[0]

                if len(conflict_id) > 0:
                    if __pynmrstar_v3_2__:
                        loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop_by_category(lp_category)

                    for lcid in conflict_id:
                        del loop.data[lcid]

            try:

                lp_data = self.__nefT.check_data(sf_data, lp_category, key_items, data_items, allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                                 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:  # noqa: E722 pylint: disable=bare-except
                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")
        # """
        # if (lp_data is not None) and len(lp_data) == 0 and self.__check_empty_loop:

        #     warn = "Unexpectedly, a loop has no rows."

        #     self.report.warning.appendDescription('missing_content',
        #                                           {'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")
        # """
    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'].keys():

                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_data = self.__star_data[fileListId]
                        sf_framecode = ''

                        self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

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

                        sf_data = self.__star_data[fileListId]
                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                        self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

                    else:

                        for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                            sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                            self.__detectConflictDataInLoop__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

        return self.report.getTotalErrors() == __errors

    def __detectConflictDataInLoop__(self, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category):
        """ Detect redundant/inconsistent data of interesting loops.
        """

        lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                        if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

        if lp_data is None or len(lp_data) == 0:
            return

        key_items = self.consist_key_items[file_type][content_subtype]

        conflict_id_set = self.__nefT.get_conflict_id_set(sf_data, 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]

        if content_subtype == 'dist_restraint':
            max_inclusive = self.dist_restraint_error['max_inclusive']

            data_unit_name = 'atom pair'

        elif content_subtype == 'dihed_restraint':
            max_inclusive = self.dihed_restraint_error['max_inclusive']

            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']
            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']

        elif content_subtype == 'rdc_restraint':
            max_inclusive = self.rdc_restraint_error['max_inclusive']

            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):
                    row_1 = lp_data[id_set[i]]
                    row_2 = lp_data[id_set[j]]

                    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]
                                comp_id_2 = row_1[comp_id_2_name]
                                comp_id_3 = row_1[comp_id_3_name]
                                comp_id_4 = row_1[comp_id_4_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]

                                data_type = self.__getTypeOfDihedralRestraint(data_type,
                                                                              chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                                                              chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_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.__getResucedAtomNotations(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.__getResucedAtomNotations(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:

                msg = '' if content_subtype != 'dihed_restraint' else angle_type_name + f" {row_1[angle_type_name]}, "
                msg += self.__getResucedAtomNotations(key_items, row_1)

                idx_msg = index_tag + ' '
                if index_tag in lp_data[0]:
                    for id in id_set:  # pylint: disable=redefined-builtin
                        idx_msg += f"{lp_data[id][index_tag]} vs "
                else:
                    for id in id_set:  # pylint: disable=redefined-builtin
                        idx_msg += f"{id + 1} vs "
                idx_msg = idx_msg[:-4] + ', '
                idx_msg += id_tag + ' '
                for id in id_set:
                    idx_msg += f"{lp_data[id][id_tag]} vs "

                warn = f"[Check rows of {idx_msg[:-4]}] Found redundant restraints for the same {data_unit_name} ({msg})."

                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 __testDataConsistencyInAuxLoop(self):
        """ Perform consistency test on data of auxiliary loops.
        """

        # if not self.__combined_mode:
        #    return True

        __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'].keys():

                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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    parent_pointer += 1

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    if content_subtype.startswith('spectral_peak'):

                        try:

                            _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                            num_dim = int(_num_dim)

                            if num_dim not in range(1, self.lim_num_dim):
                                raise ValueError()

                        except ValueError:

                            err = f"{self.num_dim_items[file_type]} {str(_num_dim)!r} must be in {set(range(1, self.lim_num_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.__testDataConsistencyInAuxLoop() ++ ValueError  - {err}\n")

                            continue

                    for loop in sf_data.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 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_data, 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_data, 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 item' 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()

                                if self.__verbose:
                                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ LookupError  - {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')
                                proc_warns = set()

                                has_multiple_data = False
                                has_bad_pattern = False

                                for warn in warns:

                                    if warn == '':
                                        continue

                                    if warn == '' or warn in proc_warns:
                                        continue

                                    proc_warns.add(warn)

                                    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_data, lp_category, key_items)[0]

                                    if len(conflict_id) > 0:
                                        if __pynmrstar_v3_2__:
                                            _loop = sf_data.get_loop(lp_category)
                                        else:
                                            _loop = sf_data.get_loop_by_category(lp_category)

                                        for lcid in conflict_id:
                                            del _loop.data[lcid]

                                # try to parse data without bad patterns

                                if has_bad_pattern:
                                    conflict_id = self.__nefT.get_bad_pattern_id(sf_data, lp_category, key_items, data_items)[0]

                                    if len(conflict_id) > 0:
                                        if __pynmrstar_v3_2__:
                                            _loop = sf_data.get_loop(lp_category)
                                        else:
                                            _loop = sf_data.get_loop_by_category(lp_category)

                                        for lcid in conflict_id:
                                            del _loop.data[lcid]

                                try:

                                    aux_data = self.__nefT.check_data(sf_data, 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_data, parent_pointer)

                                except:  # noqa: E722 pylint: disable=bare-except
                                    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")
                                # """
                                # err = f"{lp_category!r} loop exists unexpectedly."

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

                                # if self.__verbose:
                                #     self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoop() ++ Error  - {err}\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 = [None] * num_dim  # pylint: disable=redefined-builtin

                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 = None if 'value_first_point' not in sp_dim else sp_dim['value_first_point']
                            sp_width = None if 'spectral_width' not in sp_dim else sp_dim['spectral_width']
                            # acq = sp_dim['is_acquisition']
                            sp_freq = None if 'spectrometer_frequency' not in sp_dim else sp_dim['spectrometer_frequency']
                            abs[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 = None if 'Value_first_point' not in sp_dim else sp_dim['Value_first_point']
                            sp_width = None if 'Sweep_width' not in sp_dim else sp_dim['Sweep_width']
                            # acq = sp_dim['Acquisition']
                            sp_freq = None if 'Spectrometer_frequency' not in sp_dim else sp_dim['Spectrometer_frequency']
                            abs[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[i - 1] else 0.0)
                            max_point = first_point + (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs[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]:
                        _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((l['data'] for l in self.__lp_data[content_subtype]
                                if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

                if lp_data is not None:

                    for i in lp_data:
                        for j in range(num_dim):

                            if min_points[j] is None or max_points[j] is None:
                                continue

                            position = i[position_names[j]]

                            if position < min_points[j] or position > max_points[j]:

                                err = f"[Check row of {index_tag} {i[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[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} {i[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[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 i in aux_data:
                for name in [j['name'] for j in self.aux_key_items[file_type][content_subtype][lp_category]]:
                    if i[name] not in range(1, max_dim):

                        err = f"{name} {i[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_data, 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 = [None] * num_dim  # pylint: disable=redefined-builtin

                for i in range(1, max_dim):

                    for sp_dim in aux_data:

                        if sp_dim['ID'] != i:
                            continue

                        first_point = None if 'Value_first_point' not in sp_dim else sp_dim['Value_first_point']
                        sp_width = None if 'Sweep_width' not in sp_dim else sp_dim['Sweep_width']
                        sp_freq = None if 'Spectrometer_frequency' not in sp_dim else sp_dim['Spectrometer_frequency']
                        # acq = sp_dim['Acquisition']
                        abs[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[i - 1] else 0.0)
                            max_point = first_point + (sp_width * (1.0 if self.__bmrb_only else 3.0) if abs[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((l['data'] for l in self.__aux_data[content_subtype]
                                      if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode and l['category'] == _pk_char_category), None)  # noqa: E741

                if _pk_char_data is None and any(loop for loop in sf_data.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_data, _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 i in _pk_char_data:

                        j = i[dim_id_name] - 1

                        if j >= num_dim or min_points[j] is None or max_points[j] is None:
                            continue

                        position = i[position_name]

                        if position < min_points[j] or position > max_points[j]:

                            warn = f"[Check row of {pk_id_name} {i[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[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} {i[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[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 item' 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()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__testDataConsistencyInAuxLoopOfSpectralPeakAlt() ++ LookupError  - {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 i in aux_data:
                for name in [j['name'] for j in self.aux_key_items[file_type][content_subtype][lp_category]]:
                    if i[name] not in range(1, max_dim):

                        err = f"{name} {i[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'].keys():

                if content_subtype == 'entity':
                    continue

                sf_category = self.sf_categories[file_type][content_subtype]

                parent_keys = set()

                list_id = 1  # tentative parent key if not exists

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    if self.__combined_mode and sf_data.tag_prefix != self.sf_tag_prefixes[file_type][content_subtype]:

                        err = f"Saveframe tag prefix {sf_data.tag_prefix!r} did not match with {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_data, 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_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()

                        if self.__verbose:
                            self.__lfh.write(f"+NmrDpUtility.__testSfTagConsistency() ++ LookupError  - {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 warn == '':
                                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 self._sf_tag_items[file_type][content_subtype] is not None:

                                        if any(i for i in self._sf_tag_items[file_type][content_subtype] if i == 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_data, 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_tag_data)

                            self.__sf_tag_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode, 'data': sf_tag_data})

                        except:  # noqa: E722 pylint: disable=bare-except
                            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")

                    list_id += 1

        return self.report.getTotalErrors() == __errors

    def __testParentChildRelation(self, file_name, file_type, content_subtype, parent_keys, list_id, sf_framecode, 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} {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((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is not None:

                for i in lp_data:
                    if child_key_name in i and i[child_key_name] != parent_key:

                        if index_tag is None:
                            err = f"{child_key_name} {i[child_key_name]!r} must be {parent_key}."
                        else:
                            err = f"[Check row of {index_tag} {i[index_tag]}] {child_key_name} {i[child_key_name]!r} must be {parent_key}."

                        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")

            for lp_category in self.aux_lp_categories[file_type][content_subtype]:

                aux_data = next((l['data'] for l in self.__aux_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode and l['category'] == lp_category), None)  # noqa: E741

                if aux_data is not None:
                    for i in aux_data:
                        if child_key_name in i and i[child_key_name] != parent_key:

                            if index_tag is None:
                                err = f"{child_key_name} {str(i[child_key_name])!r} must be {parent_key}."
                            else:
                                err = f"[Check row of {index_tag} {i[index_tag]}] {child_key_name} {i[child_key_name]!r} must be {parent_key}."

                            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")

        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'].keys():
                continue

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

            add_details = False

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = ''

                add_details |= self.__validateCSValue__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                add_details |= self.__validateCSValue__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    add_details |= self.__validateCSValue__(fileListId, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category)

            if add_details:
                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __validateCSValue__(self, file_list_id, file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category):
        """ Validate assigned chemical shift value based on BMRB chemical shift statistics.
        """

        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

        full_value_name = lp_category + '.' + value_name

        # index_tag = self.index_tags[file_type][content_subtype]
        max_cs_err = self.chem_shift_error['max_inclusive']

        add_details = False

        try:

            if file_type == 'nmr-star':

                if __pynmrstar_v3_2__:
                    loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
                else:
                    loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop_by_category(lp_category)

                details_col = loop.tags.index('Details') if 'Details' in loop.tags and self.__leave_intl_note else -1

            if file_type == 'nef' or (not self.__nonblk_anomalous_cs):
                lp_data = next(l['data'] for l in self.__lp_data[content_subtype]
                               if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode)  # noqa: E741

            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_data, 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:  # noqa: E722 pylint: disable=bare-except
                    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 = {}

            for l, i in enumerate(lp_data):  # noqa: E741
                chain_id = i[chain_id_name]
                seq_id = i[seq_id_name]
                comp_id = i[comp_id_name]
                atom_id = i[atom_id_name]
                value = i[value_name]

                if value in self.empty_value:
                    continue

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id, ambig_code, details = self.__getAtomIdListWithAmbigCode(file_type, 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 += ' (' + details.rstrip('.') + ')'

                    else:
                        atom_name = atom_id + ' (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += atom_id_ + ' '

                        atom_name = 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 self.__get1LetterCode(comp_id) == 'X':

                    neighbor_comp_ids = set(j[comp_id_name] for j in lp_data if j[chain_id_name] == chain_id and abs(j[seq_id_name] - seq_id) < 4 and j[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__csStat.getTypeOfCompId(comp_id2)[0]

                    for cs_stat in self.__csStat.get(comp_id):

                        if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0:
                            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_.startswith('H') and 'methyl' in cs_stat['desc']:
                                _atom_id = self.__getRepresentativeAtomId(file_type, comp_id, atom_id)
                                _, methyl_h_list = self.__nefT.get_group(comp_id, _atom_id)

                                name_len = [len(n) for n in methyl_h_list]
                                max_len = max(name_len)
                                min_len = min(name_len)

                                if max_len == min_len or len(atom_id) == max_len:
                                    _atom_id = atom_id[:-1]
                                else:  # For example, HEM HM[A-D]
                                    _atom_id = atom_id

                                methyl_cs_key = f"{chain_id} {seq_id:04d} {_atom_id}"

                                if methyl_cs_key not in methyl_cs_vals:
                                    methyl_cs_vals[methyl_cs_key] = value

                                elif value != methyl_cs_vals[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."

                                    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")

                                    break

                            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")

                                break

                            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")

                                break

                            z_score = float(f"{(value - avg_value) / std_value:.2f}")
                            sigma = abs(z_score)

                            if self.__csStat.hasEnoughStat(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:

                                    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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                            "Please check for folded/aliased signals."

                                        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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                            "Please check for folded/aliased signals."

                                        if self.__nonblk_anomalous_cs:

                                            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 file_type == 'nmr-star' and details_col != -1:
                                                _details = loop.data[l][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}). "\
                                                    "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                                    "Please check for folded/aliased signals.\n"
                                                if _details in self.empty_value or (details not in _details):
                                                    if _details in self.empty_value:
                                                        loop.data[l][details_col] = details
                                                    else:
                                                        loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                    add_details = 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:

                                            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 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[l][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 self.empty_value or (details not in _details):
                                                    if _details in self.empty_value:
                                                        loop.data[l][details_col] = details
                                                    else:
                                                        loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                    add_details = 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 file_type == 'nmr-star' and details_col != -1 and pa['distance'] > self.vicinity_paramagnetic:
                                            _details = loop.data[l][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 self.empty_value or (details not in _details):
                                                if _details in self.empty_value:
                                                    loop.data[l][details_col] = details
                                                else:
                                                    loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                add_details = True

                                elif sigma > self.cs_anomalous_error_scaled_by_sigma:

                                    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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                        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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                        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:

                                    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

                                    else:
                                        warn += " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."
                                        warn_alt += " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                    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

                                if min_value < max_value and (value < min_value - tolerance or value > max_value + tolerance) and sigma > self.cs_anomalous_error_scaled_by_sigma:

                                    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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                            "Please check for folded/aliased signals."

                                        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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                            "Please check for folded/aliased signals."

                                        if self.__nonblk_anomalous_cs:

                                            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 file_type == 'nmr-star' and details_col != -1:
                                                _details = loop.data[l][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}). "\
                                                    "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                                    "Please check for folded/aliased signals.\n"
                                                if _details in self.empty_value or (details not in _details):
                                                    if _details in self.empty_value:
                                                        loop.data[l][details_col] = details
                                                    else:
                                                        loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                    add_details = 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:

                                            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 file_type == 'nmr-star' and details_col != -1:
                                                    _details = loop.data[l][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 self.empty_value or (details not in _details):
                                                        if _details in self.empty_value:
                                                            loop.data[l][details_col] = details
                                                        else:
                                                            loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                        add_details = 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 file_type == 'nmr-star' and details_col != -1:
                                                _details = loop.data[l][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 self.empty_value or (details not in _details):
                                                    if _details in self.empty_value:
                                                        loop.data[l][details_col] = details
                                                    else:
                                                        loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                    add_details = True

                                elif sigma > self.cs_anomalous_error_scaled_by_sigma:

                                    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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                        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}). "\
                                            "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                        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")

                            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:
                            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_.startswith('H') and 'methyl' in cs_stat['desc']:
                                methyl_cs_key = f"{chain_id} {seq_id:04d} {atom_id_[:-1]}"

                                if methyl_cs_key not in methyl_cs_vals:
                                    methyl_cs_vals[methyl_cs_key] = value

                                elif value != methyl_cs_vals[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."

                                    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")

                                    break

                            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")

                                break

                            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")

                                break

                            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:

                                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}). "\
                                        "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                        "Please check for folded/aliased signals."

                                    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}). "\
                                        "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                        "Please check for folded/aliased signals."

                                    if self.__nonblk_anomalous_cs:

                                        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 file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[l][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}). "\
                                                "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity. "\
                                                "Please check for folded/aliased signals.\n"
                                            if _details in self.empty_value or (details not in _details):
                                                if _details in self.empty_value:
                                                    loop.data[l][details_col] = details
                                                else:
                                                    loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                add_details = 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:

                                        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 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[l][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 self.empty_value or (details not in _details):
                                                if _details in self.empty_value:
                                                    loop.data[l][details_col] = details
                                                else:
                                                    loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                                add_details = 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 file_type == 'nmr-star' and details_col != -1 and pa['distance'] > self.vicinity_paramagnetic:
                                        _details = loop.data[l][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 self.empty_value or (details not in _details):
                                            if _details in self.empty_value:
                                                loop.data[l][details_col] = details
                                            else:
                                                loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                            add_details = True

                            elif sigma > self.cs_unusual_error_scaled_by_sigma:  # 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}). "\
                                        "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                    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}). "\
                                        "Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                                    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")
                            #     """ Can skip this to be consistent with validation report
                            # elif sigma > self.cs_unusual_error_scaled_by_sigma:

                            #     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

                            #     else:
                            #         warn += " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."
                            #         warn_alt += " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."

                            #     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")

                            break

                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 i:
                    ambig_code = i[ambig_code_name]

                    if ambig_code in self.empty_value or ambig_code == 1:
                        continue

                    _atom_id = atom_id

                    if self.__isNmrAtomName(comp_id, atom_id):
                        _atom_id = self.__getRepresentativeAtomId(file_type, 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:

                                try:

                                    j = next(j for j in lp_data
                                             if j[chain_id_name] == chain_id
                                             and j[seq_id_name] == seq_id
                                             and j[comp_id_name] == comp_id
                                             and j[atom_id_name] == _atom_id2)

                                    loop.data[lp_data.index(j)][loop.tags.index(ambig_code_name)] = 1

                                except StopIteration:
                                    pass

                            elif allowed_ambig_code > 0:

                                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:

                            j = next(j for j in lp_data
                                     if j[chain_id_name] == chain_id
                                     and j[seq_id_name] == seq_id
                                     and j[comp_id_name] == comp_id
                                     and j[atom_id_name] == _atom_id2)

                            ambig_code2 = j[ambig_code_name]

                            if ambig_code2 != ambig_code:
                                loop.data[lp_data.index(j)][loop.tags.index(ambig_code_name)] = ambig_code

                                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:
                            # """
                            # 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, row of {atom_id_name} {_atom_id2} of the same residue was not found."

                            # self.report.warning.appendDescription('bad_ambiguity_code',
                            #                                       {'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")
                            # """
                            pass

                    elif ambig_code in (4, 5, 6, 9):

                        ambig_set_id_name = 'Ambiguity_set_ID'

                        if ambig_set_id_name not in i:

                            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."

                            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(f"+NmrDpUtility.__validateCSValue() ++ LookupError  - {err}\n")

                        else:

                            ambig_set_id = i[ambig_set_id_name]

                            if ambig_set_id in self.empty_value:

                                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 = [j for j in lp_data if j[ambig_set_id_name] == ambig_set_id and j != i]

                                if len(ambig_set) == 0:

                                    err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} requires other rows sharing {ambig_set_id_name} {ambig_set_id}."

                                    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.__validateCSValue() ++ LookupError  - {err}\n")

                                # Intra-residue ambiguities
                                elif ambig_code == 4:

                                    for j in ambig_set:
                                        chain_id2 = j[chain_id_name]
                                        seq_id2 = j[seq_id_name]
                                        comp_id2 = j[comp_id_name]
                                        atom_id2 = j[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepresentativeAtomId(file_type, 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:

                                    for j in ambig_set:
                                        chain_id2 = j[chain_id_name]
                                        seq_id2 = j[seq_id_name]
                                        comp_id2 = j[comp_id_name]
                                        atom_id2 = j[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepresentativeAtomId(file_type, comp_id2, atom_id2)

                                        if ((chain_id2 != chain_id and chain_id < chain_id2) or (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 j in ambig_set:
                                        chain_id2 = j[chain_id_name]
                                        seq_id2 = j[seq_id_name]
                                        comp_id2 = j[comp_id_name]
                                        atom_id2 = j[atom_id_name]
                                        value2 = j[value_name]

                                        _atom_id2 = atom_id2

                                        if self.__isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.__getRepresentativeAtomId(file_type, comp_id2, atom_id2)

                                        if chain_id2 == chain_id and (seq_id < seq_id2 or (seq_id == seq_id2 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-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 j in ambig_set:
                                    chain_id2 = j[chain_id_name]
                                    seq_id2 = j[seq_id_name]
                                    comp_id2 = j[comp_id_name]
                                    atom_id2 = j[atom_id_name]
                                    value2 = j[value_name]

                                    _atom_id2 = atom_id2

                                    if self.__isNmrAtomName(comp_id2, atom_id2):
                                        _atom_id2 = self.__getRepresentativeAtomId(file_type, comp_id2, atom_id2)

                                    if _atom_id[0] != _atom_id2[0] 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}] "\
                                            "However, observation nucleus of "\
                                            + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2) + f" sharing the same {ambig_set_id_name} differs."

                                        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) > max_cs_err and value < value2:

                                        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)\
                                            + f"differs by {value2 - value} (tolerance {max_cs_err})."

                                        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 {self.bmrb_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 did 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 add_details

    def __testCSValueConsistencyInPkLoop(self):
        """ Perform consistency test on peak position and assignment of spectral peaks.
        """

        # if not self.__combined_mode:
        #    return False

        __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'].keys():
                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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                try:
                    cs_list = sf_data.get_tag(self.cs_list_sf_tag_name[file_type])[0]
                except:  # noqa: E722 pylint: disable=bare-except
                    continue

                try:

                    cs_data = next(l['data'] for l in self.__lp_data['chem_shift']
                                   if l['file_name'] == file_name and l['sf_framecode'] == cs_list)  # noqa: E741

                except StopIteration:

                    if cs_list not in self.empty_value:

                        err = f"Assigned chemical shifts are mandatory. Referred {cs_list!r} saveframe 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

                    try:
                        cs_data = next(l['data'] for l in self.__lp_data['chem_shift'] if l['file_name'] == file_name)  # noqa: E741
                    except StopIteration:
                        continue

                try:

                    _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        raise ValueError()

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

                max_dim = num_dim + 1

                aux_data = next((l['data'] for l in self.__aux_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                 and l['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)  # noqa: E741

                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 'spectral_width' not in sp_dim or 'axis_unit' not in sp_dim else sp_dim['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' not in sp_dim or 'Sweep_width_units' not in sp_dim else sp_dim['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((l['data'] for l in self.__aux_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                 and l['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)  # noqa: E741

                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]
                max_cs_err = self.chem_shift_error['max_inclusive']

                try:

                    lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                                    if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

                    if lp_data is not None:

                        for i in lp_data:
                            for d in range(num_dim):

                                if __pynmrstar_v3__\
                                   and not (chain_id_names[d] in i and seq_id_names[d] in i and comp_id_names[d] in i and atom_id_names[d] in i):
                                    continue

                                chain_id = i[chain_id_names[d]]
                                if chain_id in self.empty_value:
                                    continue

                                seq_id = i[seq_id_names[d]]
                                if seq_id in self.empty_value:
                                    continue

                                comp_id = i[comp_id_names[d]]
                                if comp_id in self.empty_value:
                                    continue

                                atom_id = i[atom_id_names[d]]
                                if atom_id in self.empty_value:
                                    continue

                                position = i[position_names[d]]

                                try:

                                    if file_type == 'nmr-star' and self.__isNmrAtomName(comp_id, atom_id):
                                        _atom_id = self.__getAtomIdListWithAmbigCode(file_type, comp_id, atom_id)[0]

                                        len_atom_id = len(_atom_id)

                                        if len_atom_id == 0:
                                            atom_id_ = atom_id

                                        elif 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

                                    cs = next(j for j in cs_data
                                              if j[cs_chain_id_name] == chain_id
                                              and j[cs_seq_id_name] == seq_id
                                              and j[cs_comp_id_name] == comp_id
                                              and j[cs_atom_id_name] == atom_id_)

                                    value = cs[cs_value_name]
                                    error = cs[cs_error_name]

                                    if value in self.empty_value:
                                        continue

                                    if error is None or error < 1.0e-3 or error * self.cs_diff_error_scaled_by_sigma > max_cs_err:
                                        error = max_cs_err
                                    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:

                                            err = f"[Check row of {index_tag} {i[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 >= max_cs_err:

                                                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} {i[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")

                                except StopIteration:

                                    err = f"[Check row of {index_tag} {i[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 in {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.__testCSValueConsistencyInPkLoop() ++ ValueError  - {err}\n")

                                if True in onebond[d]:
                                    for d2 in range(num_dim):
                                        if onebond[d][d2]:
                                            chain_id2 = i[chain_id_names[d2]]
                                            seq_id2 = i[seq_id_names[d2]]
                                            comp_id2 = i[comp_id_names[d2]]
                                            atom_id2 = i[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 self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value or\
                                               (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id or _atom_id2 != _atom_id)):

                                                err = f"[Check row of {index_tag} {i[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 = i[chain_id_names[d2]]
                                            seq_id2 = i[seq_id_names[d2]]
                                            comp_id2 = i[comp_id_names[d2]]
                                            atom_id2 = i[atom_id_names[d2]]

                                            if chain_id2 in self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value 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} {i[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 = i[chain_id_names[d2]]
                                            seq_id2 = i[seq_id_names[d2]]
                                            comp_id2 = i[comp_id_names[d2]]
                                            atom_id2 = i[atom_id_names[d2]]

                                            if chain_id2 in self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value 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} {i[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.
        """

        # if not self.__combined_mode:
        #    return False

        __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'].keys():
                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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                cs_data = None

                try:

                    cs_list = sf_data.get_tag(self.cs_list_sf_tag_name[file_type])[0]
                    _cs_list_id = sf_data.get_tag('ID')[0]

                    try:

                        cs_data = next(l['data'] for l in self.__lp_data['chem_shift']
                                       if l['file_name'] == file_name and l['sf_framecode'] == cs_list)  # noqa: E741

                    except StopIteration:

                        if cs_list not in self.empty_value:

                            err = f"Assigned chemical shifts are mandatory. Referred {cs_list!r} saveframe 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

                except:  # noqa: E722 pylint: disable=bare-except
                    pass

                if cs_data is None:

                    try:

                        cs = next(l for l in self.__lp_data['chem_shift'] if l['file_name'] == file_name)  # noqa: E741

                    except StopIteration:
                        continue

                    cs_data = cs['data']
                    cs_list = cs['sf_framecode']

                    cs_sf_data = self.__getSaveframeByName(fileListId, cs_list)

                    if cs_sf_data is None:
                        continue

                    _cs_list_id = cs_sf_data.get_tag('ID')[0]

                try:

                    _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        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((l['data'] for l in self.__aux_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                 and l['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)  # noqa: E741

                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' not in sp_dim or 'Sweep_width_units' not in sp_dim else sp_dim['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((l['data'] for l in self.__aux_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                 and l['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)  # noqa: E741

                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'

                max_cs_err = self.chem_shift_error['max_inclusive']

                try:

                    lp_data = next((l['data'] for l in self.__aux_data[content_subtype]
                                    if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                    and l['category'] == lp_category), None)  # noqa: E741

                    if lp_data is not None:

                        for i in lp_data:

                            if __pynmrstar_v3__\
                               and not (cs_chain_id_name in i and cs_seq_id_name in i and cs_comp_id_name in i and cs_atom_id_name in i):
                                continue

                            chain_id = i[cs_chain_id_name]
                            if chain_id in self.empty_value:
                                continue

                            seq_id = i[cs_seq_id_name]
                            if seq_id in self.empty_value:
                                continue

                            comp_id = i[cs_comp_id_name]
                            if comp_id in self.empty_value:
                                continue

                            atom_id = i[cs_atom_id_name]
                            if atom_id in self.empty_value:
                                continue

                            cs_list_id = i['Assigned_chem_shift_list_ID']

                            if cs_list_id != _cs_list_id:

                                for l in self.__lp_data['chem_shift']:  # noqa: E741

                                    if l['file_name'] == file_name:

                                        cs_data = l['data']
                                        cs_list = l['sf_framecode']

                                        cs_sf_data = self.__getSaveframeByName(fileListId, cs_list)

                                        if cs_sf_data is None:
                                            continue

                                        _cs_list_id = cs_sf_data.get_tag('ID')[0]

                                        if cs_list_id == _cs_list_id:
                                            break

                            pk_id = i[pk_id_name]
                            d = i[dim_id_name] - 1
                            set_id = i[set_id_name]

                            position = i[cs_value_name]

                            try:

                                if file_type == 'nmr-star' and self.__isNmrAtomName(comp_id, atom_id):
                                    _atom_id = self.__getAtomIdListWithAmbigCode(file_type, comp_id, atom_id)[0]

                                    len_atom_id = len(_atom_id)

                                    if len_atom_id == 0:
                                        atom_id_ = atom_id

                                    elif 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

                                cs = next(j for j in cs_data
                                          if j[cs_chain_id_name] == chain_id
                                          and j[cs_seq_id_name] == seq_id
                                          and j[cs_comp_id_name] == comp_id
                                          and j[cs_atom_id_name] == atom_id_)

                                value = cs[cs_value_name]
                                error = cs[cs_error_name]

                                if value in self.empty_value:
                                    continue

                                if error is None or error < 1.0e-3 or error * self.cs_diff_error_scaled_by_sigma > max_cs_err:
                                    error = max_cs_err
                                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:

                                        err = f"[Check row of {pk_id_name} {i[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 >= max_cs_err:

                                            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} {i[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")

                            except StopIteration:

                                err = f"[Check row of {pk_id_name} {i[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" was not found in assigned chemical shifts in {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.__testCSValueConsistencyInPkAltLoop() ++ ValueError  - {err}\n")

                            if d < num_dim and True in onebond[d]:
                                for d2 in range(num_dim):
                                    if onebond[d][d2]:

                                        try:
                                            j = next(j for j in lp_data
                                                     if j[pk_id_name] == pk_id
                                                     and j[dim_id_name] - 1 == d2
                                                     and j[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = j[cs_chain_id_name]
                                        seq_id2 = j[cs_seq_id_name]
                                        comp_id2 = j[cs_comp_id_name]
                                        atom_id2 = j[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 self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value or\
                                           (d < d2 and (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id or _atom_id2 != _atom_id)):

                                            err = f"[Check row of {pk_id_name} {i[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:
                                            j = next(j for j in lp_data
                                                     if j[pk_id_name] == pk_id
                                                     and j[dim_id_name] - 1 == d2
                                                     and j[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = j[cs_chain_id_name]
                                        seq_id2 = j[cs_seq_id_name]
                                        comp_id2 = j[cs_comp_id_name]
                                        atom_id2 = j[cs_atom_id_name]

                                        if chain_id2 in self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value 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} {i[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:
                                            j = next(j for j in lp_data
                                                     if j[pk_id_name] == pk_id
                                                     and j[dim_id_name] - 1 == d2
                                                     and j[set_id_name] is set_id)
                                        except StopIteration:
                                            continue

                                        chain_id2 = j[cs_chain_id_name]
                                        seq_id2 = j[cs_seq_id_name]
                                        comp_id2 = j[cs_comp_id_name]
                                        atom_id2 = j[cs_atom_id_name]

                                        if chain_id2 in self.empty_value or seq_id2 in self.empty_value or comp_id2 in self.empty_value or atom_id2 in self.empty_value 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} {i[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'].keys():
                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_data = 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_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__testRDCVector__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    if not any(loop for loop in sf_data.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((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is not None:

                for i in lp_data:
                    chain_id_1 = i[chain_id_1_name]
                    seq_id_1 = i[seq_id_1_name]
                    comp_id_1 = i[comp_id_1_name]
                    atom_id_1 = i[atom_id_1_name]
                    chain_id_2 = i[chain_id_2_name]
                    seq_id_2 = i[seq_id_2_name]
                    comp_id_2 = i[comp_id_2_name]
                    atom_id_2 = i[atom_id_2_name]

                    if (atom_id_1[0] not in self.atom_isotopes) or (atom_id_2[0] not in self.atom_isotopes):

                        idx_msg = f"[Check row of {index_tag} {i[index_tag]}] "

                        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:

                        idx_msg = f"[Check row of {index_tag} {i[index_tag]}] "

                        err = idx_msg + "Invalid 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")

                    elif abs(seq_id_1 - seq_id_2) > 1:

                        idx_msg = f"[Check row of {index_tag} {i[index_tag]}] "

                        err = idx_msg + "Invalid 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.getTypeOfCompId(comp_id_1)[0] and self.__csStat.getTypeOfCompId(comp_id_2)[0] and\
                           ((seq_id_1 < seq_id_2 and atom_id_1 == 'C' and atom_id_2 in ('N', 'H')) or (seq_id_1 > seq_id_2 and atom_id_1 in ('N', 'H') and atom_id_2 == 'C')):
                            pass

                        else:

                            idx_msg = f"[Check row of {index_tag} {i[index_tag]}] "

                            err = idx_msg + "Invalid 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} {i[index_tag]}] "

                        err = idx_msg + "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.__updateChemCompDict(comp_id_1):  # matches with comp_id in CCD

                            if not any(b for b in self.__last_chem_comp_bonds
                                       if ((b[self.__ccb_atom_id_1] == atom_id_1 and b[self.__ccb_atom_id_2] == atom_id_2)
                                           or (b[self.__ccb_atom_id_1] == atom_id_2 and b[self.__ccb_atom_id_2] == atom_id_1))):

                                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} {i[index_tag]}] "

                                    warn = idx_msg + "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 __testCovalentBond(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'].keys():
                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_data = self.__star_data[fileListId]
                sf_framecode = ''

                self.__testCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__testCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    self.__testCovalentBond__(file_name, file_type, content_subtype, sf_framecode, lp_category)

        return self.report.getTotalErrors() == __errors

    def __testCovalentBond__(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((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == lp_category), None)  # noqa: E741

            if aux_data is not None:

                for l, i in enumerate(aux_data):  # noqa: E741
                    chain_id_1 = i[chain_id_1_name]
                    seq_id_1 = i[seq_id_1_name]
                    comp_id_1 = i[comp_id_1_name]
                    atom_id_1 = i[atom_id_1_name]
                    chain_id_2 = i[chain_id_2_name]
                    seq_id_2 = i[seq_id_2_name]
                    comp_id_2 = i[comp_id_2_name]
                    atom_id_2 = i[atom_id_2_name]

                    bond = self.__getBondLength(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.__testCovalentBond() ++ Warning  - {warn}\n")

        except Exception as e:

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

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

    def __getBondLength(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 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

            try:

                model_num_name = 'pdbx_PDB_model_num' if self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                atom_site_1 = 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': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                               ],
                                                              [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id_1},
                                                               {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id_1},
                                                               {'name': 'label_atom_id', 'type': 'str', 'value': nmr_atom_id_1}])

                atom_site_2 = 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': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                               ],
                                                              [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id_2},
                                                               {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id_2},
                                                               {'name': 'label_atom_id', 'type': 'str', 'value': nmr_atom_id_2}])

            except Exception as e:

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

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__getBondLength() ++ 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"{np.linalg.norm(to_np_array(a_1) - to_np_array(a_2)):.3f}")})

            if len(bond) > 0:
                self.__coord_bond_length[seq_key] = bond
                return bond

        self.__coord_bond_length[seq_key] = None
        return None

    def __testResidueVariant(self):
        """ Perform consistency test on residue variants.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        cif_input_source = self.report.input_sources[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']

            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'].keys():
                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 chain_assign in chain_assign_dic['nmr_poly_seq_vs_model_poly_seq']:

                ref_chain_id = chain_assign['ref_chain_id']
                test_chain_id = chain_assign['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] = []

                complex = {'seq_align': result}  # DAOTHER-7465  # pylint: disable=redefined-builtin

                if 'unmapped_sequence' in chain_assign:
                    complex['seq_unmap'] = [unmapped['ref_seq_id'] for unmapped in chain_assign['unmapped_sequence']]

                nmr2ca[ref_chain_id].append(complex)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = ''

                self.__testResidueVariant__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

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

                sf_data = self.__star_data[fileListId]
                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                self.__testResidueVariant__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

            else:

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    self.__testResidueVariant__(file_name, file_type, content_subtype, sf_data, sf_framecode, lp_category, cif_polymer_sequence, nmr2ca)

        return self.report.getTotalErrors() == __errors

    def __testResidueVariant__(self, file_name, file_type, content_subtype, sf_data, 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_data, 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 i in aux_data:
                    chain_id = i[chain_id_name]
                    seq_id = i[seq_id_name]
                    comp_id = i[comp_id_name]
                    variant = i[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)

                    coord_atom_id_ = None if seq_key not in self.__coord_atom_id else self.__coord_atom_id[seq_key]

                    self.__updateChemCompDict(comp_id)

                    if file_type == 'nef':

                        if variant in self.empty_value:
                            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(file_type, 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 += ' (' + details.rstrip('.') + ')'

                                else:
                                    atom_name = atom_id + ' (e.g. '

                                    for atom_id_ in _atom_id:
                                        atom_name += atom_id_ + ' '

                                    atom_name = 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.__last_comp_id_test:  # matches with comp_id in CCD

                                    if not any(a for a in self.__last_chem_comp_atoms if a[self.__cca_atom_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_id_ is not None and coord_atom_id_['comp_id'] == cif_comp_id and atom_id_ in coord_atom_id_['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 coordinate."

                                    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_id_ is not None and coord_atom_id_['comp_id'] == cif_comp_id and atom_id_ not in coord_atom_id_['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") which is a {variant_name} {_variant_!r} is not present in the coordinate."

                                    self.report.error.appendDescription('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(file_type, 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 += ' (' + details.rstrip('.') + ')'

                            else:
                                atom_name = atom_id + ' (e.g. '

                                for atom_id_ in _atom_id:
                                    atom_name += atom_id_ + ' '

                                atom_name = atom_name.rstrip() + ')'

                                # representative atom id
                                atom_id_ = _atom_id[0]

                        else:
                            atom_id_ = atom_id
                            atom_name = atom_id

                            if self.__last_comp_id_test:  # matches with comp_id in CCD

                                if not any(a for a in self.__last_chem_comp_atoms if a[self.__cca_atom_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_id_ is not None and coord_atom_id_['comp_id'] == cif_comp_id and atom_id_ in coord_atom_id_['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)\
                                    + ") is unexpectedly incorporated in the coordinate."

                                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 item' 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()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__testResidueVariant() ++ LookupError  - {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 __getResucedAtomNotations(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 __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'].keys():

                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_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf_data, list_id, sf_framecode, lp_category, seq_align_dic, asm)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf_data, list_id, sf_framecode, lp_category, seq_align_dic, asm)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        self.__calculateStatsOfExptlData__(fileListId, file_name, file_type, content_subtype, sf_data, list_id, sf_framecode, lp_category, seq_align_dic, asm)

                        list_id += 1

                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_data, 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 = sf_data.get_tag('ID')
            if len(val) > 0:
                try:
                    _list_id = int(val[0])
                except ValueError:
                    return

        if content_subtype != 'poly_seq':
            lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741
        else:
            lp_data = next((l['data'] for l in self.__aux_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode and l['category'] == lp_category), None)  # noqa: E741

        if lp_data is None or len(lp_data) == 0:
            return

        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:
                type = sf_data.get_tag('restraint_origin' if file_type == 'nef' else 'Constraint_type')  # pylint: disable=redefined-builtin
                if len(type) > 0 and type[0] not in self.empty_value:
                    ent['exp_type'] = type[0]
                else:
                    ent['exp_type'] = 'Unknown'

        elif content_subtype.startswith('spectral_peak'):

            if len(sf_framecode) == 0:
                ent['exp_type'] = 'Unknown'
            else:
                type = sf_data.get_tag('experiment_type' if file_type == 'nef' else 'Experiment_type')
                if len(type) > 0 and type[0] not in self.empty_value:
                    ent['exp_type'] = type[0]
                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 sa_name in seq_align_dic and seq_align_dic[sa_name] is not None:

                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'] < self.low_seq_coverage and seq_align['length'] > 1:
                            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:

                        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.getTypeOfCompId(comp_id)[0]

                                if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                    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.getTypeOfCompId(comp_id)[0]

                                if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                    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.getTypeOfCompId(comp_id)[0]

                                if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                    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'):

                    conflict_id_set = self.__nefT.get_conflict_id_set(sf_data, 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 c_warn in conflict_warns:
                            for index in c_warn['row_locations'][index_tag]:
                                inconsistent.add(int(index))

                    if inconsist_warns is not None:

                        for i_warn in inconsist_warns:
                            for index in i_warn['row_locations'][index_tag]:
                                inconsistent.add(int(index))

                    if redundant_warns is not None:

                        for d_warn in redundant_warns:
                            for index in d_warn['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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        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

        else:

            err = f"Not found a module for calculation of statistics on content subtype {content_subtype}."

            self.report.error.appendDescription('internal_error', "+NmrDpUtility.__calculateStatsOfExptlData() ++ Error  - " + err)
            self.report.setError()

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

            return

        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 i in lp_data:

                if i[atom_type] in self.empty_value or i[iso_number] in self.empty_value or value_name in self.empty_value:
                    continue

                data_type = str(i[iso_number]) + i[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' else str(self.__nefT.letter_to_int(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

                    l = 0  # noqa: E741

                    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 = l

                        elif data_type.startswith('13c'):
                            c13_col = l

                        elif data_type.startswith('15n'):
                            n15_col = l

                        elif data_type.startswith('31p'):
                            p31_col = l

                        all_c.append(atom_group)

                        l += 1  # noqa: E741

                    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.getTypeOfCompId(comp_id)[0]

                            if self.__csStat.hasEnoughStat(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.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                for a in all_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a.startswith('H'):
                                        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 j in lp_data:

                                    if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                       or j[value_name] in self.empty_value:
                                        continue

                                    atom_id = j[atom_id_name]
                                    data_type = str(j[iso_number]) + j[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(file_type, 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.startswith('H'):
                                                    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': j[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.startswith('H'):
                                                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': j[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

                    l = 0  # noqa: E741

                    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 = l

                        elif data_type.startswith('13c'):
                            c13_col = l

                        elif data_type.startswith('15n'):
                            n15_col = l

                        elif data_type.startswith('31p'):
                            p31_col = l

                        bb_c.append(atom_group)

                        l += 1  # noqa: E741

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                            if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):

                                bb_atoms = self.__csStat.getBackBoneAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                for a in bb_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a.startswith('H'):
                                        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 j in lp_data:

                                    if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                       or j[value_name] in self.empty_value:
                                        continue

                                    atom_id = j[atom_id_name]
                                    data_type = str(j[iso_number]) + j[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(file_type, 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.startswith('H'):
                                                    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.startswith('H'):
                                            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

                    l = 0  # noqa: E741

                    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 = l

                        elif data_type.startswith('13c'):
                            c13_col = l

                        elif data_type.startswith('15n'):
                            n15_col = l

                        elif data_type.startswith('31p'):
                            p31_col = l

                        sc_c.append(atom_group)

                        l += 1  # noqa: E741

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                            if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):

                                sc_atoms = self.__csStat.getSideChainAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                for a in sc_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a.startswith('H'):
                                        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 j in lp_data:

                                    if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                       or j[value_name] in self.empty_value:
                                        continue

                                    atom_id = j[atom_id_name]
                                    data_type = str(j[iso_number]) + j[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(file_type, 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.startswith('H'):
                                                    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.startswith('H'):
                                            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

                    l = 0  # noqa: E741

                    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 = l

                        elif data_type.startswith('13c'):
                            c13_col = l

                        else:
                            continue

                        ch3_c.append(atom_group)

                        l += 1  # noqa: E741

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                            if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):

                                ch3_atoms = self.__csStat.getMethylAtoms(comp_id, excl_minor_atom=True, primary=polypeptide_like)
                                non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                for a in ch3_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a.startswith('H'):
                                        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 j in lp_data:

                                    if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                       or j[value_name] in self.empty_value:
                                        continue

                                    atom_id = j[atom_id_name]
                                    data_type = str(j[iso_number]) + j[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(file_type, 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.startswith('H'):
                                                    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.startswith('H'):
                                            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

                    l = 0  # noqa: E741

                    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 = l

                        elif data_type.startswith('13c'):
                            c13_col = l

                        elif data_type.startswith('15n'):
                            n15_col = l

                        aro_c.append(atom_group)

                        l += 1  # noqa: E741

                    if s is not None:

                        for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):

                            polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                            if self.__csStat.hasEnoughStat(comp_id, polypeptide_like):

                                aro_atoms = self.__csStat.getAromaticAtoms(comp_id, excl_minor_atom=True, primary=polypeptide_like)
                                non_rep_methyl_pros = self.__csStat.getNonRepresentativeMethylProtons(comp_id, excl_minor_atom=True, primary=polypeptide_like)

                                for a in aro_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a.startswith('H'):
                                        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 j in lp_data:

                                    if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                       or j[value_name] in self.empty_value:
                                        continue

                                    atom_id = j[atom_id_name]
                                    data_type = str(j[iso_number]) + j[atom_type]

                                    if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.__getAtomIdList(file_type, 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.startswith('H'):
                                                    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.startswith('H'):
                                            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 i in lp_data:

                if i[atom_type] in self.empty_value or i[iso_number] in self.empty_value or value_name in self.empty_value:
                    continue

                data_type = str(i[iso_number]) + i[atom_type].lower() + '_chemical_shifts'

                chain_id = i[chain_id_name]
                seq_id = i[seq_id_name]
                comp_id = i[comp_id_name]
                atom_id = i[atom_id_name]
                value = i[value_name]

                _chain_id = chain_id if file_type == 'nef' else str(self.__nefT.letter_to_int(chain_id))

                if value in self.empty_value:
                    continue

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id = self.__getAtomIdListWithAmbigCode(file_type, comp_id, atom_id)[0]

                    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 self.__get1LetterCode(comp_id) == 'X':

                    neighbor_comp_ids = set(j[comp_id_name] for j in lp_data if j[chain_id_name] == _chain_id
                                            and abs(j[seq_id_name] - seq_id) < 4 and j[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__csStat.getTypeOfCompId(comp_id2)[0]

                    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 = copy.copy(count)

                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:
                # """
                # has_value = False
                # for j in range(1, len(range_of_vals) - 1):
                #     for k in count.keys():
                #         if transposed[k][j] > 0:
                #             has_value = True
                #             break
                #     if has_value:
                #         break

                # if has_value:
                # """
                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' else str(self.__nefT.letter_to_int(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 != 'CYS':
                                continue

                            cys = {'chain_id': chain_id, 'seq_id': seq_id}

                            ca_chem_shift = None
                            cb_chem_shift = None

                            for j in lp_data:

                                atom_id = j[atom_id_name]

                                if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id:
                                    if atom_id == 'CA':
                                        ca_chem_shift = j[value_name]
                                    elif atom_id == 'CB':
                                        cb_chem_shift = j[value_name]

                                if ca_chem_shift is None or cb_chem_shift is None:
                                    if j[chain_id_name] == _chain_id and j[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%}"

                            cys['in_disulfide_bond'] = False
                            if input_source_dic['disulfide_bond'] is not None:
                                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 input_source_dic['other_bond'] is not None:
                                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' else str(self.__nefT.letter_to_int(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 j in lp_data:

                                atom_id = j[atom_id_name]

                                if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id:
                                    if atom_id == 'CB':
                                        cb_chem_shift = j[value_name]
                                    elif atom_id == 'CG':
                                        cg_chem_shift = j[value_name]

                                if cb_chem_shift is None or cg_chem_shift is None:
                                    if j[chain_id_name] == _chain_id and j[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%}"

                            pro['in_cis_peptide_bond'] = self.__isProtCis(chain_id, seq_id)

                            if pro['cis_trans_pred'] != 'unknown':

                                if (pro['in_cis_peptide_bond'] and pro['cis_trans_pred'] != 'cis')\
                                   or (not pro['in_cis_peptide_bond'] and pro['cis_trans_pred'] != 'trans'):
                                    item = None
                                    if ',' in pro['cis_trans_pred']:
                                        if (pro['in_cis_peptide_bond'] and cis > trs) or\
                                           (not pro['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 pro['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' else str(self.__nefT.letter_to_int(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 j in lp_data:

                                atom_id = j[atom_id_name]

                                if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id:
                                    if atom_id == 'CG':
                                        cg_chem_shift = j[value_name]
                                    elif atom_id == 'CD2':
                                        cd2_chem_shift = j[value_name]
                                    elif atom_id == 'ND1':
                                        nd1_chem_shift = j[value_name]
                                    elif atom_id == 'NE2':
                                        ne2_chem_shift = j[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 j[chain_id_name] == _chain_id and j[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' else str(self.__nefT.letter_to_int(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 j in lp_data:

                                    atom_id = j[atom_id_name]

                                    if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id\
                                       and atom_id.startswith('CG'):

                                        _atom_id = atom_id

                                        if self.__isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.__getRepresentativeAtomId(file_type, comp_id, atom_id)

                                        if _atom_id == 'CG1':
                                            cg1_chem_shift = j[value_name]
                                        elif _atom_id == 'CG2':
                                            cg2_chem_shift = j[value_name]

                                    if cg1_chem_shift is None or cg2_chem_shift is None:
                                        if j[chain_id_name] == _chain_id and j[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 j in lp_data:

                                    atom_id = j[atom_id_name]

                                    if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id\
                                       and atom_id.startswith('CD'):

                                        _atom_id = atom_id

                                        if self.__isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.__getRepresentativeAtomId(file_type, comp_id, atom_id)

                                        if _atom_id == 'CD1':
                                            cd1_chem_shift = j[value_name]
                                        elif _atom_id == 'CD2':
                                            cd2_chem_shift = j[value_name]

                                    if cd1_chem_shift is None or cd2_chem_shift is None:
                                        if j[chain_id_name] == _chain_id and j[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 j in lp_data:

                                    atom_id = j[atom_id_name]

                                    if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id:
                                        if atom_id == 'CD1':
                                            cd1_chem_shift = j[value_name]

                                    if cd1_chem_shift is None:
                                        if j[chain_id_name] == _chain_id and j[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' else str(self.__nefT.letter_to_int(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 self.empty_value:
                                if comp_id not in self.monDict3.keys():
                                    continue
                                if not self.__csStat.getTypeOfCompId(comp_id)[0]:
                                    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 self.monDict3.keys():
                                        continue
                                    if not self.__csStat.getTypeOfCompId(_comp_id)[0]:
                                        continue
                                    rci_residues.append([_comp_id, seq_id])
                                else:
                                    continue

                            has_bb_atoms = False

                            for j in lp_data:

                                if j[chain_id_name] != _chain_id or j[seq_id_name] != seq_id or j[comp_id_name] != comp_id\
                                   or j[value_name] in self.empty_value:
                                    continue

                                atom_id = j[atom_id_name]

                                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                                    _atom_id = self.__getAtomIdListWithAmbigCode(file_type, comp_id, atom_id)[0]

                                    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, j[atom_type], j[value_name]])

                                has_bb_atoms = True

                            if has_bb_atoms:

                                if comp_id == 'CYS':

                                    ca_chem_shift = None
                                    cb_chem_shift = None

                                    for j in lp_data:

                                        atom_id = j[atom_id_name]

                                        if j[chain_id_name] == _chain_id and j[seq_id_name] == seq_id and j[comp_id_name] == comp_id:
                                            if atom_id == 'CA':
                                                ca_chem_shift = j[value_name]
                                            elif atom_id == 'CB':
                                                cb_chem_shift = j[value_name]

                                        if ca_chem_shift is None or cb_chem_shift is None:
                                            if j[chain_id_name] == _chain_id and j[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'])
                                result['struct_conf'] = []
                                for seq_id in result['seq_id']:
                                    pos = s['seq_id'].index(seq_id)
                                    result['struct_conf'].append(struct_conf[pos])

                                cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(chain_id)

                                if cif_ps is not None and 'ca_rmsd' in cif_ps:

                                    if len(cif_ps['ca_rmsd']) > 0 and 'rmsd_in_well_defined_region' in cif_ps['ca_rmsd'][0]:
                                        rmsd = cif_ps['ca_rmsd'][0]['rmsd_in_well_defined_region']
                                        result['rmsd_in_well_defined_region'] = 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]
        comb_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']
        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']

        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'])})

            for l, i in enumerate(lp_data):  # noqa: E741
                index = i[index_tag] if index_tag in i else None
                comb_id = i[comb_id_name] if comb_id_name in i else None

                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]
                comp_id_1 = i[comp_id_1_name]
                comp_id_2 = i[comp_id_2_name]
                atom_id_1 = i[atom_id_1_name]
                atom_id_2 = i[atom_id_2_name]
                weight = None if weight_name not in i else i[weight_name]
                set_id.add(i[id_tag])

                target_value = i[target_value_name] if target_value_name in i else None

                upper_limit_value = None
                lower_limit_value = None

                if target_value is None:

                    if lower_limit_name in i and (i[lower_limit_name] is not None)\
                            and upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                            and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                        target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                    elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                        target_value = i[upper_linear_limit_name]
                        upper_limit_value = target_value

                    elif upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = i[upper_limit_name]
                        upper_limit_value = target_value

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                        target_value = i[lower_linear_limit_name]
                        lower_limit_value = target_value

                    elif lower_limit_name in i and (i[lower_limit_name] is not None):
                        target_value = i[lower_limit_name]
                        lower_limit_value = target_value

                    else:
                        continue

                if target_value > max_val:
                    max_val = target_value

                if target_value < min_val:
                    min_val = target_value

                data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, l, target_value, upper_limit_value, lower_limit_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)

                if 'hydrogen_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if target_value_name in i and i[target_value_name] is not None:
                        values += f"{target_value_name} {i[target_value_name]}, "
                    if lower_limit_name in i and i[lower_limit_name] is not None:
                        values += f"{lower_limit_name} {i[lower_limit_name]}, "
                    if upper_limit_name in i and i[upper_limit_name] is not None:
                        values += f"{upper_limit_name} {i[upper_limit_name]}, "
                    if lower_linear_limit_name in i and i[lower_linear_limit_name] is not None:
                        values += f"{lower_linear_limit_name} {i[lower_linear_limit_name]}, "
                    if upper_linear_limit_name in i and i[upper_linear_limit_name] is not None:
                        values += f"{upper_linear_limit_name} {i[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' if 'close' in data_type else 'far'} ({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 target_value_name in i and i[target_value_name] is not None:
                        values += f"{target_value_name} {i[target_value_name]}, "
                    if lower_limit_name in i and i[lower_limit_name] is not None:
                        values += f"{lower_limit_name} {i[lower_limit_name]}, "
                    if upper_limit_name in i and i[upper_limit_name] is not None:
                        values += f"{upper_limit_name} {i[upper_limit_name]}, "
                    if lower_linear_limit_name in i and i[lower_linear_limit_name] is not None:
                        values += f"{lower_linear_limit_name} {i[lower_linear_limit_name]}, "
                    if upper_linear_limit_name in i and i[upper_linear_limit_name] is not None:
                        values += f"{upper_linear_limit_name} {i[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' if 'close' in data_type else 'far'} ({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 target_value_name in i and i[target_value_name] is not None:
                        values += f"{target_value_name} {i[target_value_name]}, "
                    if lower_limit_name in i and i[lower_limit_name] is not None:
                        values += f"{lower_limit_name} {i[lower_limit_name]}, "
                    if upper_limit_name in i and i[upper_limit_name] is not None:
                        values += f"{upper_limit_name} {i[upper_limit_name]}, "
                    if lower_linear_limit_name in i and i[lower_linear_limit_name] is not None:
                        values += f"{lower_linear_limit_name} {i[lower_linear_limit_name]}, "
                    if upper_linear_limit_name in i and i[upper_linear_limit_name] is not None:
                        values += f"{upper_linear_limit_name} {i[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' if 'close' in data_type else 'far'} ({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 target_value_name in i and i[target_value_name] is not None:
                        values += f"{target_value_name} {i[target_value_name]}, "
                    if lower_limit_name in i and i[lower_limit_name] is not None:
                        values += f"{lower_limit_name} {i[lower_limit_name]}, "
                    if upper_limit_name in i and i[upper_limit_name] is not None:
                        values += f"{upper_limit_name} {i[upper_limit_name]}, "
                    if lower_linear_limit_name in i and i[lower_linear_limit_name] is not None:
                        values += f"{lower_linear_limit_name} {i[lower_linear_limit_name]}, "
                    if upper_linear_limit_name in i and i[upper_linear_limit_name] is not None:
                        values += f"{upper_linear_limit_name} {i[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' if 'close' in data_type else 'far'} ({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 (comb_id is not None) and (comb_id not in self.empty_value):
                    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 = i[target_value_name] if target_value_name in i else None
                lower_limit_value = i[lower_limit_name] if lower_limit_name in i else None
                upper_limit_value = i[upper_limit_name] if upper_limit_name in i else None
                lower_linear_limit_value = i[lower_linear_limit_name] if lower_linear_limit_name in i else None
                upper_linear_limit_value = i[upper_linear_limit_name] if upper_linear_limit_name in i else None

                if (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and (lower_linear_limit_value is not None) and (upper_linear_limit_value is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and (upper_linear_limit_value is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and (lower_linear_limit_value is not None) and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit_value is None and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value 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': max_val, 'min_value': min_val}
            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 = copy.copy(count)

                for k in count:
                    _count[k] = 0

                for l, i in enumerate(lp_data):  # noqa: E741
                    chain_id_1 = i[chain_id_1_name]
                    chain_id_2 = i[chain_id_2_name]
                    seq_id_1 = i[seq_id_1_name]
                    seq_id_2 = i[seq_id_2_name]
                    comp_id_1 = i[comp_id_1_name]
                    comp_id_2 = i[comp_id_2_name]
                    atom_id_1 = i[atom_id_1_name]
                    atom_id_2 = i[atom_id_2_name]

                    target_value = i[target_value_name] if target_value_name in i else None

                    upper_limit_value = None
                    lower_limit_value = None

                    if target_value is None:

                        if lower_limit_name in i and (i[lower_limit_name] is not None)\
                                and upper_limit_name in i and (i[upper_limit_name] is not None):
                            target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                        elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                                and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                            target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                        elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                            target_value = i[upper_linear_limit_name]
                            upper_limit_value = target_value

                        elif upper_limit_name in i and (i[upper_limit_name] is not None):
                            target_value = i[upper_limit_name]
                            upper_limit_value = target_value

                        elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                            target_value = i[lower_linear_limit_name]
                            lower_limit_value = target_value

                        elif lower_limit_name in i and (i[lower_limit_name] is not None):
                            target_value = i[lower_limit_name]
                            lower_limit_value = target_value

                        else:
                            continue

                    if target_value < v or target_value >= v + scale:
                        continue

                    data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, l, target_value, upper_limit_value, lower_limit_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)

                    _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:
                # """
                # has_value = False
                # for j in range(1, len(range_of_vals) - 1):
                #     for k in count.keys():
                #         if transposed[k][j] > 0:
                #             has_value = True
                #             break
                #     if has_value:
                #         break

                # if has_value:
                # """
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                # max_inclusive = self.dist_restraint_error['max_inclusive']

                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):
                            row_1 = lp_data[id_set[i]]
                            row_2 = lp_data[id_set[j]]

                            target_value_1 = row_1[target_value_name] if target_value_name in row_1 else None

                            if target_value_1 is None:

                                if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                        and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = row_1[upper_limit_name]

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                            if target_value_2 is None:

                                if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                        and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = row_2[upper_limit_name]

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                    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

                            if discrepancy > max_val:
                                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 = copy.copy(count)

                        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_1 = lp_data[row_id_1]
                                    row_2 = lp_data[id_set[j]]

                                    target_value_1 = row_1[target_value_name] if target_value_name in row_1 else None

                                    if target_value_1 is None:

                                        if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                                and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                                    if target_value_2 is None:

                                        if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                                and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                            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[target_value_name] if target_value_name in row_1 else None

                                    upper_limit_value = None
                                    lower_limit_value = None

                                    if target_value is None:

                                        if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                                and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                            target_value = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                            target_value = row_1[upper_linear_limit_name]
                                            upper_limit_value = target_value

                                        elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value = row_1[upper_limit_name]
                                            upper_limit_value = target_value

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                            target_value = row_1[lower_linear_limit_name]
                                            lower_limit_value = target_value

                                        elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                            target_value = row_1[lower_limit_name]
                                            lower_limit_value = target_value

                                        else:
                                            continue

                                    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_value, lower_limit_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)

                                    _count[data_type] += 1

                            if 0.0 <= v < scale and redundant:

                                target_value = row_1[target_value_name] if target_value_name in row_1 else None

                                upper_limit_value = None
                                lower_limit_value = None

                                if target_value is None:

                                    if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                            and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                        target_value = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                    elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                            and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                        target_value = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                    elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                        target_value = row_1[upper_linear_limit_name]
                                        upper_limit_value = target_value

                                    elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                        target_value = row_1[upper_limit_name]
                                        upper_limit_value = target_value

                                    elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                        target_value = row_1[lower_linear_limit_name]
                                        lower_limit_value = target_value

                                    elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                        target_value = row_1[lower_limit_name]
                                        lower_limit_value = target_value

                                    else:
                                        continue

                                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_value, lower_limit_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)

                                _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:
                        # """
                        # has_value = False
                        # for j in range(1, len(range_of_vals) - 1):
                        #     for k in count.keys():
                        #         if transposed[k][j] > 0:
                        #             has_value = True
                        #             break
                        #     if has_value:
                        #         break

                        # if has_value:
                        # """
                        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 l, i in enumerate(lp_data):  # noqa: E741
                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]
                comp_id_1 = i[comp_id_1_name]
                comp_id_2 = i[comp_id_2_name]
                atom_id_1 = i[atom_id_1_name]
                atom_id_2 = i[atom_id_2_name]

                bond = self.__getBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)

                if bond is None:
                    continue

                distance = next((b['distance'] for b in bond if b['model_id'] == self.__representative_model_id), None)

                if distance is None:
                    distance = bond[0]['distance']

                data_type = self.__getTypeOfCovalentBond(file_type, lp_data, l, distance,
                                                         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' if 'close' in data_type else 'far'} ({distance}Å)."

                    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' if 'close' in data_type else 'far'} ({distance}Å)."

                    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' if 'close' in data_type else 'far'} ({distance}Å)."

                    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' if 'close' in data_type else 'far'} ({distance}Å)."

                    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_value, lower_limit_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 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_value is not None:
                target_value -= 0.4
            elif lower_limit_value is not None:
                target_value += 0.4

            if (atom_id_1_ == 'F' and atom_id_2_ == 'H') or (atom_id_2_ == 'F' and atom_id_1_ == 'H'):

                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_ == 'H') or (atom_id_2_ == 'O' and atom_id_1_ == 'H'):

                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_ == 'H') or (atom_id_2_ == 'N' and atom_id_1_ == 'H'):

                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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'N' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'O' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'P' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2)) or\
                 (atom_id_2_ == 'S' and not atom_id_2.startswith('SE') and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2)) or\
                 (atom_id_2.startswith('SE') and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 l, j in enumerate(lp_data):  # noqa: E741

                    if l == row_id:  # noqa: E741
                        continue

                    _chain_id_1 = j[chain_id_1_name]
                    _chain_id_2 = j[chain_id_2_name]
                    _seq_id_1 = j[seq_id_1_name]
                    _seq_id_2 = j[seq_id_2_name]
                    _comp_id_1 = j[comp_id_1_name]
                    _comp_id_2 = j[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 += '_' + 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 += '_' + 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 += '_' + 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 += '_' + 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(file_type, comp_id_1, atom_id_1)
                _atom_id_2 = self.__getAtomIdList(file_type, 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_ == 'H') or (atom_id_2_ == 'F' and atom_id_1_ == 'H'):

                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_ == 'H') or (atom_id_2_ == 'O' and atom_id_1_ == 'H'):

                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_ == 'H') or (atom_id_2_ == 'N' and atom_id_1_ == 'H'):

                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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'N' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'O' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2))\
                    or (atom_id_2_ == 'P' and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2)) or\
                 (atom_id_2_ == 'S' and not atom_id_2.startswith('SE') and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 self.__isNonMetalElement(atom_id_2)) or\
                 (atom_id_2.startswith('SE') and not self.__isNonMetalElement(atom_id_1)):

                metal = atom_id_2 if self.__isNonMetalElement(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 l, j in enumerate(lp_data):  # noqa: E741

                    if l == row_id:  # noqa: E741
                        continue

                    _chain_id_1 = j[chain_id_1_name]
                    _chain_id_2 = j[chain_id_2_name]
                    _seq_id_1 = j[seq_id_1_name]
                    _seq_id_2 = j[seq_id_2_name]
                    _comp_id_1 = j[comp_id_1_name]
                    _comp_id_2 = j[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 += '_' + 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 += '_' + 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 += '_' + 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 += '_' + 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(file_type, comp_id_1, atom_id_1)
                _atom_id_2 = self.__getAtomIdList(file_type, 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 __isNonMetalElement(self, atom_id):
        """ Return whether a given atom_id is non metal element.
            @return: True for non metal element, False otherwise
        """

        for elem in self.non_metal_elems:
            if atom_id.startswith(elem):
                return True

        return False

    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]
        comb_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 i in lp_data:
                index = i[index_tag] if index_tag in i else None
                comb_id = i[comb_id_name] if comb_id_name in i else None

                target_value = i[target_value_name] if target_value_name in i else None

                if target_value is None:

                    if lower_limit_name in i and (i[lower_limit_name] is not None)\
                            and upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                            and upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                        target_value = (i[lower_linear_limit_name] + i[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 lower_limit_name in i and (i[lower_limit_name] is not None)\
                        and upper_limit_name in i and (i[upper_limit_name] is not None):
                    lower_limit = i[lower_limit_name]
                    upper_limit = i[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 lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                        and upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                    lower_limit = i[lower_linear_limit_name]
                    upper_limit = i[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

                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                chain_id_3 = i[chain_id_3_name]
                chain_id_4 = i[chain_id_4_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]
                seq_id_3 = i[seq_id_3_name]
                seq_id_4 = i[seq_id_4_name]
                comp_id_1 = i[comp_id_1_name]
                comp_id_2 = i[comp_id_2_name]
                comp_id_3 = i[comp_id_3_name]
                comp_id_4 = i[comp_id_4_name]
                atom_id_1 = i[atom_id_1_name]
                atom_id_2 = i[atom_id_2_name]
                atom_id_3 = i[atom_id_3_name]
                atom_id_4 = i[atom_id_4_name]
                data_type = i[angle_type_name]
                weight = None if weight_name not in i else i[weight_name]
                set_id.add(i[id_tag])

                data_type, seq_id_common, comp_id_common =\
                    self.__getTypeOfDihedralRestraint(data_type,
                                                      chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                                      chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_id_4, atom_id_4)

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (comb_id is not None) and (comb_id not in self.empty_value):
                    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

                peptide, nucleotide, carbohydrate = self.__csStat.getTypeOfCompId(comp_id_common[0][0])

                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

                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_common[0][0]
                    chi1['comp_id'] = comp_id_common[0][0]
                    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_common[0][0]
                    chi2['comp_id'] = comp_id_common[0][0]
                    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 = i[target_value_name] if target_value_name in i else None
                lower_limit_value = i[lower_limit_name] if lower_limit_name in i else None
                upper_limit_value = i[upper_limit_name] if upper_limit_name in i else None
                lower_linear_limit_value = i[lower_linear_limit_name] if lower_linear_limit_name in i else None
                upper_linear_limit_value = i[upper_linear_limit_name] if upper_linear_limit_name in i else None

                if (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and (lower_linear_limit_value is not None) and (upper_linear_limit_value is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and (upper_linear_limit_value is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and (lower_linear_limit_value is not None) and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit_value is None and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value 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 = self.dihed_restraint_error['max_inclusive']

                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[target_value_name] if target_value_name in row_1 else None

                            if target_value_1 is None:

                                if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                        and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = row_1[upper_limit_name]

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                            if target_value_2 is None:

                                if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                        and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = row_2[upper_limit_name]

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                    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]
                            comp_id_2 = row_1[comp_id_2_name]
                            comp_id_3 = row_1[comp_id_3_name]
                            comp_id_4 = row_1[comp_id_4_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]

                            data_type = self.__getTypeOfDihedralRestraint(data_type,
                                                                          chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                                                          chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_id_4, atom_id_4)[0]

                            if data_type.startswith('phi') or data_type.startswith('psi') or data_type.startswith('omega'):

                                if discrepancy > max_val:
                                    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 = copy.copy(count)

                        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[target_value_name] if target_value_name in row_1 else None

                                    if target_value_1 is None:

                                        if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                                and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                                    if target_value_2 is None:

                                        if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                                and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                            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]
                                    comp_id_2 = row_1[comp_id_2_name]
                                    comp_id_3 = row_1[comp_id_3_name]
                                    comp_id_4 = row_1[comp_id_4_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 = self.__getTypeOfDihedralRestraint(data_type,
                                                                                  chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                                                                  chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_id_4, atom_id_4)[0]

                                    _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]
                                comp_id_2 = row_1[comp_id_2_name]
                                comp_id_3 = row_1[comp_id_3_name]
                                comp_id_4 = row_1[comp_id_4_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 = self.__getTypeOfDihedralRestraint(data_type,
                                                                              chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                                                              chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_id_4, atom_id_4)[0]

                                _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:
                        # """
                        # has_value = False
                        # for j in range(1, len(range_of_vals) - 1):
                        #     for k in count.keys():
                        #         if transposed[k][j] > 0:
                        #             has_value = True
                        #             break
                        #     if has_value:
                        #         break

                        # if has_value:
                        # """
                        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,
                                     chain_id_1, seq_id_1, comp_id_1, atom_id_1, chain_id_2, seq_id_2, comp_id_2, atom_id_2,
                                     chain_id_3, seq_id_3, comp_id_3, atom_id_3, chain_id_4, seq_id_4, comp_id_4, atom_id_4):
        """ Return type of dihedral restraint.
        """

        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)
        atom_ids = []
        atom_ids.append(atom_id_1)
        atom_ids.append(atom_id_2)
        atom_ids.append(atom_id_3)
        atom_ids.append(atom_id_4)

        seq_id_common = collections.Counter(seq_ids).most_common()
        comp_id_common = collections.Counter(comp_ids).most_common()

        if data_type in self.empty_value:

            data_type = 'undefined'

            if chain_id_1 == chain_id_2 and chain_id_2 == chain_id_3 and chain_id_3 == chain_id_4:

                polypeptide_like = self.__csStat.getTypeOfCompId(comp_id_1)[0]

                if polypeptide_like:

                    if len(seq_id_common) == 2:

                        # phi or psi

                        if seq_id_common[0][1] == 3 and seq_id_common[1][1] == 1:

                            # phi

                            seq_id_prev = seq_id_common[1][0]

                            if seq_id_common[0][0] == seq_id_prev + 1:

                                j = 0
                                if seq_ids[j] == seq_id_prev and atom_ids[j] == 'C':
                                    atom_ids.pop(j)
                                    if atom_ids == self.dihed_atom_ids:
                                        data_type = 'phi'

                            # psi

                            seq_id_next = seq_id_common[1][0]

                            if seq_id_common[0][0] == seq_id_next - 1:

                                j = 3
                                if seq_ids[j] == seq_id_next and atom_ids[j] == 'N':
                                    atom_ids.pop(j)
                                    if atom_ids == self.dihed_atom_ids:
                                        data_type = 'psi'

                        # omega

                        if atom_ids[0] == 'O' and atom_ids[1] == 'C' and atom_ids[2] == 'N'\
                           and (atom_ids[3] == 'H' or atom_ids[3] == 'CA')\
                           and seq_ids[0] == seq_ids[1] and seq_ids[1] + 1 == seq_ids[2] and seq_ids[2] == seq_ids[3]:
                            data_type = 'omega'

                    elif len(seq_id_common) == 1:

                        # chi1

                        if atom_ids[0] == 'N' and atom_ids[1] == 'CA' and atom_ids[2] == 'CB'\
                           and self.chi1_atom_id_4_pat.match(atom_ids[3]):
                            # if (atom_ids[3] == 'CG' and comp_id in ('ARG', 'ASN', 'ASP', 'GLN', 'GLU', 'HIS', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'TRP', 'TYR')) or\
                            #   (atom_ids[3] == 'CG1' and comp_id in ('ILE', 'VAL')) or\
                            #   (atom_ids[3] == 'OG' and comp_id == 'SER') or\
                            #   (atom_ids[3] == 'OG1' and comp_id == 'THR') or\
                            #   (atom_ids[3] == 'SG' and comp_id == 'CYS'):
                            data_type = 'chi1'

                        # chi2

                        if atom_ids[0] == 'CA' and atom_ids[1] == 'CB'\
                           and self.chi2_atom_id_3_pat.match(atom_ids[2]) and self.chi2_atom_id_4_pat.match(atom_ids[3]):
                            # if (atom_ids[2] == 'CG' and atom_ids[3] == 'CD' and comp_id in ('ARG', 'GLN', 'GLU', 'LYS', 'PRO')) or\
                            #   (atom_ids[2] == 'CG' and atom_ids[3] == 'CD1' and comp_id in ('LEU', 'PHE', 'TRP', 'TYR')) or\
                            #   (atom_ids[2] == 'CG' and atom_ids[3] == 'ND1' and comp_id == 'HIS') or\
                            #   (atom_ids[2] == 'CG' and atom_ids[3] == 'OD1' and comp_id in ('ASN', 'ASP')) or\
                            #   (atom_ids[2] == 'CG' and atom_ids[3] == 'SD' and comp_id == 'MET') or\
                            #   (atom_ids[2] == 'CG1' and atom_ids[3] == 'CD' and comp_id == 'ILE'):
                            data_type = 'chi2'

                        # chi3

                        if atom_ids[0] == 'CB' and atom_ids[1] == 'CG'\
                           and self.chi3_atom_id_3_pat.match(atom_ids[2]) and self.chi3_atom_id_4_pat.match(atom_ids[3]):
                            # if (atom_ids[2] == 'CD' and atom_ids[3] == 'CE' and comp_id == 'LYS') or\
                            #   (atom_ids[2] == 'CD' and atom_ids[3] == 'NE' and comp_id == 'ARG') or\
                            #   (atom_ids[2] == 'CD' and atom_ids[3] == 'OE1' and comp_id in ('GLN', 'GLU')) or\
                            #   (atom_ids[2] == 'SD' and atom_ids[3] == 'CE' and comp_id == 'MET'):
                            data_type = 'chi3'

                        # chi4

                        if atom_ids[0] == 'CG' and atom_ids[1] == 'CD'\
                           and self.chi4_atom_id_3_pat.match(atom_ids[2]) and self.chi4_atom_id_4_pat.match(atom_ids[3]):
                            # if (atom_ids[2] == 'NE' and atom_ids[3] == 'CZ' and comp_id == 'ARG') or\
                            #  (atom_ids[2] == 'CE' and atom_ids[3] == 'NZ' and comp_id == 'LYS'):
                            data_type = 'chi4'

                        # chi5

                        if atom_ids == ['CD', 'NE', 'CZ', 'NH1']:  # and comp_id == 'ARG':
                            data_type = 'chi5'

        else:
            data_type = data_type.lower()

        if not data_type.endswith('_angle_constraints'):
            data_type += '_angle_constraints'

        return data_type, seq_id_common, comp_id_common

    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 i in lp_data:
                target_value = i[target_value_name] if target_value_name in i else None

                if target_value is None:

                    if lower_limit_name in i and (i[lower_limit_name] is not None)\
                            and upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                            and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                        target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                    elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                        target_value = i[upper_linear_limit_name]

                    elif upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = i[upper_limit_name]

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                        target_value = i[lower_linear_limit_name]

                    elif lower_limit_name in i and (i[lower_limit_name] is not None):
                        target_value = i[lower_limit_name]

                    else:
                        continue

                if target_value > max_val:
                    max_val = target_value

                elif target_value < min_val:
                    min_val = target_value

                if target_value > max_val_:
                    max_val_ = target_value

                if target_value < min_val_:
                    min_val_ = target_value

            item_names = self.item_names_in_rdc_loop[file_type]
            comb_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 i in lp_data:
                index = i[index_tag] if index_tag in i else None
                comb_id = i[comb_id_name] if comb_id_name in i else None

                chain_id_1 = i[chain_id_1_name]
                seq_id_1 = i[seq_id_1_name]
                atom_id_1 = i[atom_id_1_name]
                atom_id_2 = i[atom_id_2_name]
                weight = None if weight_name not in i else i[weight_name]
                set_id.add(i[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 (comb_id is not None) and (comb_id not in self.empty_value):
                    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 = i[target_value_name] if target_value_name in i else None
                lower_limit_value = i[lower_limit_name] if lower_limit_name in i else None
                upper_limit_value = i[upper_limit_name] if upper_limit_name in i else None
                lower_linear_limit_value = i[lower_linear_limit_name] if lower_linear_limit_name in i else None
                upper_linear_limit_value = i[upper_linear_limit_name] if upper_linear_limit_name in i else None

                if (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit_value is not None) and (upper_limit_value is not None)\
                        and (lower_linear_limit_value is not None) and (upper_linear_limit_value is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit_value is None and (upper_limit_value is not None)\
                        and lower_linear_limit_value is None and (upper_linear_limit_value is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit_value is not None) and upper_limit_value is None\
                        and (lower_linear_limit_value is not None) and upper_linear_limit_value is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit_value is None and upper_limit_value is None\
                        and lower_linear_limit_value is None and upper_linear_limit_value 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': max_val_, 'min_value': min_val_}
            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 = copy.copy(count)

                for k in count:
                    _count[k] = 0

                for i in lp_data:
                    target_value = i[target_value_name] if target_value_name in i else None

                    if target_value is None:

                        if lower_limit_name in i and (i[lower_limit_name] is not None)\
                                and upper_limit_name in i and (i[upper_limit_name] is not None):
                            target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                        elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                                and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                            target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                        elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                            target_value = i[upper_linear_limit_name]

                        elif upper_limit_name in i and (i[upper_limit_name] is not None):
                            target_value = i[upper_limit_name]

                        elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                            target_value = i[lower_linear_limit_name]

                        elif lower_limit_name in i and (i[lower_limit_name] is not None):
                            target_value = i[lower_limit_name]

                        else:
                            continue

                    if target_value < v or target_value >= v + scale:
                        continue

                    atom_id_1 = i[atom_id_1_name]
                    atom_id_2 = i[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:
                # """
                # has_value = False
                # for j in range(1, len(range_of_vals) - 1):
                #     for k in count.keys():
                #         if transposed[k][j] > 0:
                #             has_value = True
                #             break
                #     if has_value:
                #         break

                # if has_value:
                # """
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                max_inclusive = self.rdc_restraint_error['max_inclusive']

                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[target_value_name] if target_value_name in row_1 else None

                            if target_value_1 is None:

                                if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                        and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                    target_value_1 = row_1[upper_linear_limit_name]

                                elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                    target_value_1 = row_1[upper_limit_name]

                                elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                    target_value_1 = row_1[lower_linear_limit_name]

                                elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                    target_value_1 = row_1[lower_limit_name]

                            target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                            if target_value_2 is None:

                                if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                        and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                        and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                    target_value_2 = row_2[upper_linear_limit_name]

                                elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                    target_value_2 = row_2[upper_limit_name]

                                elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                    target_value_2 = row_2[lower_linear_limit_name]

                                elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                    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)

                            if discrepancy > max_val:
                                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 = copy.copy(count)

                        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[target_value_name] if target_value_name in row_1 else None

                                    if target_value_1 is None:

                                        if lower_limit_name in row_1 and (row_1[lower_limit_name] is not None)\
                                                and upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = (row_1[lower_limit_name] + row_1[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = (row_1[lower_linear_limit_name] + row_1[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_1 and (row_1[upper_linear_limit_name] is not None):
                                            target_value_1 = row_1[upper_linear_limit_name]

                                        elif upper_limit_name in row_1 and (row_1[upper_limit_name] is not None):
                                            target_value_1 = row_1[upper_limit_name]

                                        elif lower_linear_limit_name in row_1 and (row_1[lower_linear_limit_name] is not None):
                                            target_value_1 = row_1[lower_linear_limit_name]

                                        elif lower_limit_name in row_1 and (row_1[lower_limit_name] is not None):
                                            target_value_1 = row_1[lower_limit_name]

                                    target_value_2 = row_2[target_value_name] if target_value_name in row_2 else None

                                    if target_value_2 is None:

                                        if lower_limit_name in row_2 and (row_2[lower_limit_name] is not None)\
                                                and upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = (row_2[lower_limit_name] + row_2[upper_limit_name]) / 2.0

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None)\
                                                and upper_linear_limit_name is i and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = (row_2[lower_linear_limit_name] + row_2[upper_linear_limit_name]) / 2.0

                                        elif upper_linear_limit_name in row_2 and (row_2[upper_linear_limit_name] is not None):
                                            target_value_2 = row_2[upper_linear_limit_name]

                                        elif upper_limit_name in row_2 and (row_2[upper_limit_name] is not None):
                                            target_value_2 = row_2[upper_limit_name]

                                        elif lower_linear_limit_name in row_2 and (row_2[lower_linear_limit_name] is not None):
                                            target_value_2 = row_2[lower_linear_limit_name]

                                        elif lower_limit_name in row_2 and (row_2[lower_limit_name] is not None):
                                            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:
                        # """
                        # has_value = False
                        # for j in range(1, len(range_of_vals) - 1):
                        #     for k in count.keys():
                        #         if transposed[k][j] > 0:
                        #             has_value = True
                        #             break
                        #     if has_value:
                        #         break

                        # if has_value:
                        # """
                        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):
        """ Return type of RDC restraint.
        """

        try:
            iso_number_1 = self.atom_isotopes[atom_id_1[0]][0]
            iso_number_2 = self.atom_isotopes[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((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)  # noqa: E741

            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((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)  # noqa: E741

            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 = None if 'value_first_point' not in sp_dim else sp_dim['value_first_point']
                            sp_width = None if 'spectral_width' not in sp_dim else sp_dim['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 = None if 'Value_first_point' not in sp_dim else sp_dim['Value_first_point']
                            sp_width = None if 'Sweep_width' not in sp_dim else sp_dim['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 self.empty_value:
                                    center_point = None
                            if 'Encoding_code' in sp_dim:
                                encoding_code = sp_dim['Encoding_code']
                                if encoding_code in self.empty_value:
                                    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 self.empty_value:
                                    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 self.empty_value:
                                    mag_link_id = None

                        if sp_freq is not None and sp_freq in self.empty_value:
                            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 self.empty_value:
                            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 = 'Hz' if 'axis_unit' not in _sp_dim else _sp_dim['axis_unit']
                                            _first_point = None if 'value_first_point' not in _sp_dim else _sp_dim['value_first_point']
                                            _sp_width = None if 'spectral_width' not in _sp_dim or 'axis_unit' not in _sp_dim else _sp_dim['spectral_width']
                                            if 'spectrometer_frequency' in _sp_dim:
                                                _sp_freq = _sp_dim['spectrometer_frequency']
                                        else:
                                            _axis_unit = 'Hz' if 'Sweep_width_units' not in _sp_dim else _sp_dim['Sweep_width_units']
                                            _first_point = None if 'Value_first_point' not in _sp_dim else _sp_dim['Value_first_point']
                                            _sp_width = None if 'Sweep_width' not in _sp_dim or 'Sweep_width_units' not in _sp_dim else _sp_dim['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 self.empty_value:
                                                    _center_point = None

                                        if _sp_freq is not None and _sp_freq in self.empty_value:
                                            _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 i in lp_data:

                has_assignment = True

                for j in range(num_dim):

                    if __pynmrstar_v3__\
                       and not (chain_id_names[j] in i and seq_id_names[j] in i and comp_id_names[j] in i and atom_id_names[j] in i):
                        has_assignment = False
                        break

                    chain_id = i[chain_id_names[j]]
                    seq_id = i[seq_id_names[j]]
                    comp_id = i[comp_id_names[j]]
                    atom_id = i[atom_id_names[j]]

                    if chain_id in self.empty_value or seq_id in self.empty_value or comp_id in self.empty_value or atom_id in self.empty_value:
                        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']
        # value_name = item_names['value']

        try:

            ent['number_of_spectral_dimensions'] = num_dim
            ent['spectral_dim'] = []

            aux_data = next((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == self.aux_lp_categories[file_type][content_subtype][1]), None)  # noqa: E741

            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((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == self.aux_lp_categories[file_type][content_subtype][0]), None)  # noqa: E741

            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 = None if 'Value_first_point' not in sp_dim else sp_dim['Value_first_point']
                        sp_width = None if 'Sweep_width' not in sp_dim else sp_dim['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 self.empty_value:
                                center_point = None
                        if 'Encoding_code' in sp_dim:
                            encoding_code = sp_dim['Encoding_code']
                            if encoding_code in self.empty_value:
                                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 self.empty_value:
                                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 self.empty_value:
                                mag_link_id = None

                        if sp_freq is not None and sp_freq in self.empty_value:
                            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 self.empty_value:
                            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 = 'Hz' if 'Sweep_width_units' not in _sp_dim else _sp_dim['Sweep_width_units']
                                        _first_point = None if 'Value_first_point' not in _sp_dim else _sp_dim['Value_first_point']
                                        _sp_width = None if 'Sweep_width' not in _sp_dim or 'Sweep_width_units' not in _sp_dim else _sp_dim['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 self.empty_value:
                                                _center_point = None

                                        if _sp_freq is not None and _sp_freq in self.empty_value:
                                            _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((l['data'] for l in self.__aux_data[content_subtype]
                             if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                             and l['category'] == '_Assigned_peak_chem_shift'), None)  # noqa: E741

            pk_id_name = 'Peak_ID'
            dim_id_name = 'Spectral_dim_ID'

            pk_id_set = set()

            for i in lp_data:

                has_assignment = aux_data is not None

                pk_id = i['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 self.empty_value or seq_id in self.empty_value or comp_id in self.empty_value or atom_id in self.empty_value:
                            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():

            file_name = os.path.basename(self.__cifPath)

            self.report.appendInputSource()

            input_source = self.report.input_sources[-1]

            input_source.setItemValue('file_name', file_name)
            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.
        """

        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']

            self.__total_models = 0

            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 not self.__trust_pdbx_nmr_ens:
                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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') 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)

                    except Exception as e:

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

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

            if self.__total_models < 2:
                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:
                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")

            return True

        except:  # noqa: E722 pylint: disable=bare-except
            return False

    def __parseCoordFilePath(self):
        """ Parse effective coordinate file path.
        """

        if self.__cifPath is not None:
            return True

        if 'coordinate_file_path' in self.__inputParamDict:

            fPath = self.__inputParamDict['coordinate_file_path']

            try:

                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:  # noqa: E722 pylint: disable=bare-except
                pass

        return False

    def __detectCoordContentSubType(self):
        """ Detect content subtype of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

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

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

        # initialize loop counter
        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 != 'non_poly':

                if content_subtype == 'poly_seq' and self.__cR.hasCategory(self.lp_categories[file_type][content_subtype + '_alias']):
                    lp_counts[content_subtype] = 1
                # """ DAOTHER-5654
                # else:
                #     err = f"Category {lp_category} is mandatory."

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

                #     if self.__verbose:
                #         self.__lfh.write(f"+NmrDpUtility.__detectCoordContentSubType() ++ Error  - {err}\n")
                # """
            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}

        input_source.setItemValue('content_subtype', content_subtypes)

        return True

    def __extractCoordPolymerSequence(self):
        """ Extract reference polymer sequence of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        input_source = self.report.input_sources[id]
        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 = 'poly_seq'

        if content_subtype not in input_source_dic['content_subtype'].keys():
            return True

        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']

        try:

            try:
                poly_seq = self.__cR.getPolymerSequence(lp_category, key_items,
                                                        withStructConf=True, alias=alias, total_models=self.__total_models)
            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, alias=alias, total_models=self.__total_models)
                else:
                    poly_seq = []

            if len(poly_seq) == 0:
                return False

            input_source.setItemValue('polymer_sequence', poly_seq)

            not_superimposed_ensemble = {}
            exactly_overlaid_ensemble = {}
            exactly_overlaid_models = {}

            for ps in poly_seq:

                if 'type' in ps:

                    type = ps['type']  # pylint: disable=redefined-builtin

                    if 'polypeptide' in type:
                        rmsd_label = 'ca_rmsd'

                        if len(self.__suspended_errors_for_polypeptide) > 0:
                            for msg in self.__suspended_errors_for_polypeptide:
                                for k, v in msg.items():
                                    self.report.error.appendDescription(k, v)
                                    self.report.setError()
                            self.__suspended_errors_for_polypeptide = []

                    elif 'ribonucleotide' in 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:
                                    if chain_id not in exactly_overlaid_ensemble:
                                        exactly_overlaid_ensemble[chain_id] = []
                                    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 an 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 an 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 an 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()

            if self.__verbose:
                self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequence() ++ LookupError  - {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 __extractCoordNonPolymerScheme(self):
    #     "" Extract non-polymer scheme of coordinate file.
    #     ""

    #     id = self.report.getInputSourceIdOfCoord()

    #     if id < 0:
    #         return True

    #     input_source = self.report.input_sources[id]
    #     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 = 'non_poly'

    #     if content_subtype not in input_source_dic['content_subtype'].keys():
    #         return True

    #     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']

    #     try:

    #         try:
    #             non_poly = 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')]
    #                 non_poly = self.__cR.getPolymerSequence(lp_category, key_items)
    #             else:
    #                 non_poly = []

    #         if len(non_poly) > 0:

    #             poly_seq = input_source_dic['polymer_sequence']

    #             if poly_seq is None:
    #                 ""
    #                 err = "Polymer sequence does not exist, __extractCoordPolymerSequence() should be invoked."

    #                 self.report.error.appendDescription('internal_error', "+NmrDpUtility.__extractCoordNonPolymerScheme() ++ Error  - " + err)
    #                 self.report.setError()

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

    #             for np in non_poly:
    #                 poly_seq.append(np)

    #             input_source.setItemValue('polymer_sequence', poly_seq)

    #         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.__extractCoordNonPolymerScheme() ++ 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()

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

    #     except ValueError as e:

    #         if not 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.__extractCoordNonPolymerScheme() ++ ValueError  - {str(e)}\n")

    #     except Exception as e:

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

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

    #     return False
    # """
    def __extractCoordPolymerSequenceInLoop(self):
        """ Extract polymer sequence in interesting loops of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        __errors = self.report.getTotalErrors()

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

        file_name = input_source_dic['file_name']
        file_type = 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') or (not has_key_value(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 not self.__cR.hasItem(lp_category, 'pdbx_PDB_model_num'):
                alias = True
                key_items = self.key_items[file_type][content_subtype + '_alias']

            list_id = 1

            has_poly_seq = False

            try:

                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:

                    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()

                if self.__verbose:
                    self.__lfh.write(f"+NmrDpUtility.__extractCoordPolymerSequenceInLoop() ++ LookupError  - {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:
            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.
        """

        # if self.report.isError():
        #    return False

        common_poly_seq = {}

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        input_source = self.report.input_sources[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')

        # pass if poly_seq exists
        if has_poly_seq or (not has_poly_seq_in_loop):
            return True

        polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

        chains = set()

        for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                ps = ps_in_loop['polymer_sequence']

                for s in ps:
                    chain_id = s['chain_id']
                    chains.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 chains}

        for content_subtype in polymer_sequence_in_loop.keys():

            for ps_in_loop in polymer_sequence_in_loop[content_subtype]:
                ps = ps_in_loop['polymer_sequence']

                for s in ps:
                    chain_id = s['chain_id']

                    min_seq_id = min(s['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]:
                ps = ps_in_loop['polymer_sequence']

                for s in ps:
                    chain_id = s['chain_id']

                    for seq_id, comp_id in zip(s['seq_id'], s['comp_id']):
                        common_poly_seq[chain_id].add(f"{seq_id + offset_seq_ids[chain_id]:04d} {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_id_list = sorted(set(int(i.split(' ')[0]) - offset_seq_ids[chain_id] for i in common_poly_seq[chain_id]))
                comp_id_list = []

                for seq_id in seq_id_list:
                    _comp_id = [i.split(' ')[1] for i in common_poly_seq[chain_id]
                                if int(i.split(' ')[0]) - offset_seq_ids[chain_id] == seq_id]
                    if len(_comp_id) == 1:
                        comp_id_list.append(_comp_id[0])
                    else:
                        comp_id_list.append(next(comp_id for comp_id in _comp_id if comp_id not in self.empty_value))

                asm.append({'chain_id': chain_id, 'seq_id': seq_id_list, 'comp_id': comp_id_list})

        if len(asm) > 0:
            input_source.setItemValue('polymer_sequence', asm)

        return True

    def __extractCoordNonStandardResidue(self):
        """ Extract non-standard residue of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        input_source = self.report.input_sources[id]
        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 True

        polymer_sequence = input_source_dic['polymer_sequence']

        asm = []

        for s in polymer_sequence:

            has_non_std_comp_id = False

            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 self.__get1LetterCode(comp_id) == 'X':
                    has_non_std_comp_id = True

                    ent['seq_id'].append(seq_id)
                    ent['comp_id'].append(comp_id)

                    if self.__updateChemCompDict(comp_id):  # matches with comp_id in CCD
                        cc_name = self.__last_chem_comp_dict['_chem_comp.name']
                        cc_rel_status = self.__last_chem_comp_dict['_chem_comp.pdbx_release_status']
                        if cc_rel_status == 'REL':
                            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(None)

                    ent['exptl_data'].append({'coordinate': False})

            if has_non_std_comp_id:
                asm.append(ent)

        if len(asm) > 0:
            input_source.setItemValue('non_standard_residue', asm)

        return True

    def __appendCoordPolymerSequenceAlignment(self):
        """ Append polymer sequence alignment between coordinate and NMR data.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        # sequence alignment inside coordinate file

        input_source = self.report.input_sources[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:
            return False

        polymer_sequence = input_source_dic['polymer_sequence']

        if has_poly_seq_in_loop:

            polymer_sequence_in_loop = input_source_dic['polymer_sequence_in_loop']

            for content_subtype in polymer_sequence_in_loop.keys():

                if content_subtype == 'non_poly':
                    continue

                seq_align_set = []

                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']

                        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 = score_of_seq_align(myAlign)

                            if length == unmapped + conflict or _matched <= conflict:
                                continue

                            _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                            _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(s2, offset_2)

                            ref_length = len(s1['seq_id'])

                            ref_code = self.__get1LetterCodeSequence(_s1['comp_id'])
                            test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                            mid_code = get_middle_code(ref_code, test_code)
                            ref_gauge_code = get_gauge_code(_s1['seq_id'])
                            test_gauge_code = get_gauge_code(_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 s1 in polymer_sequence:
            chain_id = s1['chain_id']

            for s2 in nmr_polymer_sequence:
                chain_id2 = s2['chain_id']

                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 = score_of_seq_align(myAlign)

                if length == unmapped + conflict or _matched <= conflict:
                    continue

                _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(s2, offset_2)

                if conflict > 0 and has_large_seq_gap(_s1, _s2):  # DAOTHER-7465
                    __s1, __s2 = beautify_seq_id(_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 = score_of_seq_align(myAlign)

                    if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                        conflict = 0
                        offset_1 = _offset_1
                        offset_2 = _offset_2
                        _s1 = __s1
                        _s2 = __s2

                ref_length = len(s1['seq_id'])

                ref_code = self.__get1LetterCodeSequence(_s1['comp_id'])
                test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                mid_code = get_middle_code(ref_code, test_code)
                ref_gauge_code = get_gauge_code(_s1['seq_id'])
                test_gauge_code = get_gauge_code(_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):
                    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['seq_id']):
                                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['seq_id']):
                                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 = self.__get1LetterCodeSequence(comp_id1)
                    test_code = self.__get1LetterCodeSequence(comp_id2)
                    mid_code = get_middle_code(ref_code, test_code)
                    ref_gauge_code = get_gauge_code(seq_id1, offset_1)
                    test_gauge_code = get_gauge_code(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 s1 in nmr_polymer_sequence:
            chain_id = s1['chain_id']

            for s2 in polymer_sequence:
                chain_id2 = s2['chain_id']

                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 = score_of_seq_align(myAlign)

                if length == unmapped + conflict or _matched <= conflict:
                    continue

                _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(s2, offset_2)

                if conflict > 0 and has_large_seq_gap(_s1, _s2):  # DAOTHER-7465
                    __s1, __s2 = beautify_seq_id(_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 = score_of_seq_align(myAlign)

                    if _conflict == 0 and len(__s1['comp_id']) - len(s1['comp_id']) == conflict:
                        conflict = 0
                        offset_1 = _offset_1
                        offset_2 = _offset_2
                        _s1 = __s1
                        _s2 = __s2

                ref_length = len(s1['seq_id'])

                ref_code = self.__get1LetterCodeSequence(_s1['comp_id'])
                test_code = self.__get1LetterCodeSequence(_s2['comp_id'])
                mid_code = get_middle_code(ref_code, test_code)
                ref_gauge_code = get_gauge_code(_s1['seq_id'])
                test_gauge_code = get_gauge_code(_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):
                    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['seq_id']):
                                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['seq_id']):
                                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 = self.__get1LetterCodeSequence(comp_id1)
                    test_code = self.__get1LetterCodeSequence(comp_id2)
                    mid_code = get_middle_code(ref_code, test_code)
                    ref_gauge_code = get_gauge_code(seq_id1, offset_1)
                    test_gauge_code = get_gauge_code(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 __assignCoordPolymerSequence(self):
        """ Assign polymer sequences of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        cif_input_source = self.report.input_sources[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

                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.__combined_mode 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.__combined_mode:
                    indices = m.compute(mat)

                chain_assign_set = []

                concatenated_nmr_chain = {}

                for row, column in indices:

                    if mat[row][column] >= 0:
                        if self.__combined_mode:
                            continue

                        _cif_chains = []
                        for _row, _column in indices:
                            if column == _column:
                                _cif_chains.append(cif_polymer_sequence[_row]['chain_id'])

                        if len(_cif_chains) > 1:
                            chain_id2 = nmr_polymer_sequence[column]['chain_id']
                            concatenated_nmr_chain[chain_id2] = _cif_chains

                            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_chains} 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[column]['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)

                    chain_assign = {'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']}

                    auth_chain_id = chain_id
                    if 'auth_chain_id' in cif_polymer_sequence[row]:
                        auth_chain_id = cif_polymer_sequence[row]['auth_chain_id']
                        chain_assign['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 = score_of_seq_align(myAlign)

                    _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                    _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(s2, offset_2)

                    if conflict > 0 and has_large_seq_gap(_s1, _s2):  # DAOTHER-7465
                        __s1, __s2 = beautify_seq_id(_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, _, _ = score_of_seq_align(myAlign)

                        if _conflict == 0 and len(__s2['comp_id']) - len(s2['comp_id']) == conflict:
                            result['conflict'] = 0
                            s2 = __s2

                    ref_code = self.__get1LetterCodeSequence(s1['comp_id'])
                    test_code = self.__get1LetterCodeSequence(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 str(myAlign[i][0]) != '.':
                                seq_id1.append(s1['seq_id'][j])
                                j += 1
                            else:
                                seq_id1.append(None)

                        j = 0
                        for i in range(length):
                            if str(myAlign[i][1]) != '.':
                                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.__combined_mode:

                            _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 > chain_assign['unmapped'] and chain_assign['sequence_coverage'] < self.min_seq_coverage_w_conflict:
                                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})

                                if not aligned[i]:
                                    cif_seq_code = f"{auth_chain_id}:{seq_id1[i]}:{cif_comp_id}"

                                    auth_seq = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_coordinate']
                                                     if seq_align['chain_id'] == chain_id), None)

                                    if auth_seq is not None:
                                        try:
                                            auth_seq_id = auth_seq['test_seq_id'][auth_seq['ref_seq_id'].index(seq_id1[i])]
                                            if seq_id1[i] != auth_seq_id:
                                                cif_seq_code += f" ({auth_chain_id}:{auth_seq_id}:{cif_comp_id} in author numbering scheme)"
                                        except:  # noqa: E722 pylint: disable=bare-except
                                            pass

                                    warn = f"{cif_seq_code} is not present in the NMR data (chain_id {chain_id2})."

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

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

                            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})

                                cif_seq_code = f"{auth_chain_id}:{seq_id1[i]}:{cif_comp_id}"
                                if cif_comp_id == '.':
                                    cif_seq_code += ', insersion error'
                                nmr_seq_code = f"{chain_id2}:{seq_id2[i]}:{nmr_comp_id}"
                                if nmr_comp_id == '.':
                                    nmr_seq_code += ', insersion error'

                                auth_seq = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_coordinate']
                                                 if seq_align['chain_id'] == chain_id), None)

                                if auth_seq is not None and cif_comp_id != '.':
                                    try:
                                        auth_seq_id = auth_seq['test_seq_id'][auth_seq['ref_seq_id'].index(seq_id1[i])]
                                        if seq_id1[i] != auth_seq_id:
                                            cif_seq_code += f", or {auth_chain_id}:{auth_seq_id}:{cif_comp_id} in author numbering scheme"
                                    except:  # noqa: E722 pylint: disable=bare-except
                                        pass

                                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.__equalsRepresentativeCompId(nmr_comp_id, cif_comp_id):
                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                          {'file_name': cif_file_name, 'description': err})
                                    self.report.setWarning()

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

                                else:
                                    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")
                                # """
                                # ref_code = result['ref_code']
                                # test_code = result['test_code']
                                # ref_gauge_code = result['ref_gauge_code']
                                # test_gauge_code = result['test_gauge_code']

                                # _ref_code = _result['ref_code']
                                # _test_code = _result['test_code']
                                # _ref_gauge_code = _result['ref_gauge_code']
                                # _test_gauge_code = _result['test_gauge_code']

                                # if cif_comp_id == '.':

                                #     offset = 0
                                #     hit = False
                                #     while i + offset >= 0:
                                #         if seq_id1[i + offset] is not None:
                                #             hit = True
                                #             break
                                #         offset -= 1

                                #     if not hit:
                                #         offset = 0
                                #         while i + offset < length:
                                #             offset += 1

                                #     p = offset_1 + s1['seq_id'].index(seq_id1[i + offset]) - offset
                                #     ref_code = ref_code[0:p] + '-' + ref_code[p:]
                                #     ref_gauge_code = ref_gauge_code[0:p] + ' ' + ref_gauge_code[p:]

                                #     result['ref_code'] = ref_code
                                #     result['ref_gauge_code'] = ref_gauge_code
                                #     result['mid_code'] = get_middle_code(ref_code, test_code)

                                #     _test_code = _test_code[0:p] + '-' + _test_code[p:]
                                #     _test_gauge_code = _test_gauge_code[0:p] + ' ' + _test_gauge_code[p:]

                                #     _result['test_code'] = _test_code
                                #     _result['test_gauge_code'] = _test_gauge_code
                                #     _result['mid_code'] = get_middle_code(_ref_code, _test_code)

                                #     offset_1 += 1

                                # elif nmr_comp_id == '.':

                                #     offset = 0
                                #     hit = False
                                #     while i + offset >= 0:
                                #         if seq_id2[i + offset] is not None:
                                #             hit = True
                                #             break
                                #         offset -= 1

                                #     if not hit:
                                #         offset = 0
                                #         while i + offset < length:
                                #             offset += 1

                                #     p = offset_2 + s2['seq_id'].index(seq_id2[i + offset]) - offset
                                #     test_code = test_code[0:p] + '-' + test_code[p:]
                                #     test_gauge_code = test_gauge_code[0:p] + ' ' + test_gauge_code[p:]

                                #     result['test_code'] = test_code
                                #     result['test_gauge_code'] = test_gauge_code
                                #     result['mid_code'] = get_middle_code(ref_code, test_code)

                                #     _ref_code = _ref_code[0:p] + '-' + _ref_code[p:]
                                #     _ref_gauge_code = _ref_gauge_code[0:p] + ' ' + _ref_gauge_code[p:]

                                #     _result['ref_code'] = _ref_code
                                #     _result['ref_gauge_code'] = _ref_gauge_code
                                #     _result['mid_code'] = get_middle_code(_ref_code, _test_code)

                                #     offset_2 += 1
                                # """
                        if len(unmapped) > 0:
                            chain_assign['unmapped_sequence'] = unmapped

                        if len(conflict) > 0:
                            chain_assign['conflict_sequence'] = conflict
                            chain_assign['conflict'] = len(conflict)
                            chain_assign['unmapped'] = chain_assign['unmapped'] - len(conflict)
                            if chain_assign['unmapped'] < 0:
                                chain_assign['conflict'] -= chain_assign['unmapped']
                                chain_assign['unmapped'] = 0

                            result['conflict'] = chain_assign['conflict']
                            result['unmapped'] = chain_assign['unmapped']

                            if _result is not None:
                                _result['conflict'] = chain_assign['conflict']
                                _result['unmapped'] = chain_assign['unmapped']

                    chain_assign_set.append(chain_assign)

                if len(chain_assign_set) > 0 and fileListId == 0:

                    if len(cif_polymer_sequence) > 1:

                        if any(s for s in cif_polymer_sequence if 'identical_chain_id' in s):

                            for chain_assign in chain_assign_set:

                                if chain_assign['conflict'] > 0:
                                    continue

                                chain_id = chain_assign['ref_chain_id']
                                auth_chain_id = None if 'ref_auth_chain_id' not in chain_assign else chain_assign['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(_chain_assign for _chain_assign in chain_assign_set if _chain_assign['ref_chain_id'] == chain_id):
                                            _chain_assign = copy.copy(chain_assign)
                                            _chain_assign['ref_chain_id'] = chain_id
                                            if auth_chain_id is not None:
                                                _chain_assign['ref_auth_chain_id'] = auth_chain_id
                                            chain_assign_set.append(_chain_assign)

                                except StopIteration:
                                    pass

                    self.report.chain_assignment.setItemValue('model_poly_seq_vs_nmr_poly_seq', chain_assign_set)

                # from nmr to model

                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.__combined_mode 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.__combined_mode:
                    indices = m.compute(mat)

                chain_assign_set = []

                for row, column in indices:

                    if self.__combined_mode and mat[row][column] >= 0:
                        continue

                    chain_id = nmr_polymer_sequence[row]['chain_id']
                    chain_id2 = cif_polymer_sequence[column]['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)

                    chain_assign = {'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']}

                    auth_chain_id2 = chain_id2
                    if 'auth_chain_id' in cif_polymer_sequence[column]:
                        auth_chain_id2 = cif_polymer_sequence[column]['auth_chain_id']
                        chain_assign['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 = score_of_seq_align(myAlign)

                    _s1 = s1 if offset_1 == 0 else fill_blank_comp_id_with_offset(s1, offset_1)
                    _s2 = s2 if offset_2 == 0 else fill_blank_comp_id_with_offset(s2, offset_2)

                    if conflict > 0 and has_large_seq_gap(_s1, _s2):  # DAOTHER-7465
                        __s1, __s2 = beautify_seq_id(_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, _, _ = score_of_seq_align(myAlign)

                        if _conflict == 0 and len(__s1['comp_id']) - len(s1['comp_id']) == conflict:
                            result['conflict'] = 0
                            s1 = __s1

                    if result['unmapped'] > 0 or result['conflict'] > 0:

                        aligned = [True] * length
                        seq_id1 = []
                        seq_id2 = []

                        for i in range(length):
                            if str(myAlign[i][0]) != '.' and i < len(s1['seq_id']):  # DAOTHER-7421
                                seq_id1.append(s1['seq_id'][i])
                            else:
                                seq_id1.append(None)

                        for i in range(length):
                            if str(myAlign[i][1]) != '.' and i < len(s2['seq_id']):  # DAOTHER-7421
                                seq_id2.append(s2['seq_id'][i])
                            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.__combined_mode:

                            _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 > chain_assign['unmapped'] and chain_assign['sequence_coverage'] < self.min_seq_coverage_w_conflict:
                                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 != '.':

                                unmapped.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': nmr_comp_id})

                                if not aligned[i]:

                                    if self.__combined_mode or chain_id not in concatenated_nmr_chain or chain_id2 not in concatenated_nmr_chain[chain_id]:

                                        warn = f"{chain_id}:{seq_id1[i]}:{nmr_comp_id} is not present in the coordinate (chain_id {chain_id2}). "\
                                            "Please update the sequence in the Macromolecules page."

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

                                        if self.__verbose:
                                            self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Warning  - {warn}\n")
                                    # """
                                    # ref_code = result['ref_code']
                                    # test_code = result['test_code']
                                    # test_gauge_code = result['test_gauge_code']

                                    # offset = 0
                                    # hit = False
                                    # while i + offset >= 0:
                                    #     if seq_id2[i + offset] is not None:
                                    #         hit = True
                                    #         break
                                    #     offset -= 1

                                    # if not hit:
                                    #     offset = 0
                                    #     while i + offset < length:
                                    #         offset += 1

                                    # if i + offset >= 0 and i + offset < length:
                                    #     p = offset_2 + s2['seq_id'].index(seq_id2[i + offset]) - offset
                                    #     test_code = test_code[0:p] + '-' + test_code[p:]
                                    #     test_gauge_code = test_gauge_code[0:p] + ' ' + test_gauge_code[p:]

                                    #     result['test_code'] = test_code
                                    #     result['test_gauge_code'] = test_gauge_code
                                    #     result['mid_code'] = get_middle_code(ref_code, test_code)
                                    # """
                            elif cif_comp_id != nmr_comp_id and aligned[i]:

                                conflict.append({'ref_seq_id': seq_id1[i], 'ref_comp_id': nmr_comp_id,
                                                 'test_seq_id': seq_id2[i], 'test_comp_id': cif_comp_id})

                                cif_seq_code = f"{auth_chain_id2}:{seq_id2[i]}:{cif_comp_id}"
                                if cif_comp_id == '.':
                                    cif_seq_code += ', insersion error'
                                nmr_seq_code = f"{chain_id}:{seq_id1[i]}:{nmr_comp_id}"
                                if nmr_comp_id == '.':
                                    nmr_seq_code += ', insersion error'

                                auth_seq = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_coordinate']
                                                 if seq_align['chain_id'] == chain_id2), None)

                                if auth_seq is not None and cif_comp_id != '.':
                                    try:
                                        auth_seq_id = auth_seq['test_seq_id'][auth_seq['ref_seq_id'].index(seq_id2[i])]
                                        if seq_id2[i] != auth_seq_id:
                                            cif_seq_code += f", or {auth_chain_id2}:{auth_seq_id}:{cif_comp_id} in author numbering scheme"
                                    except:  # noqa: E722 pylint: disable=bare-except
                                        pass

                                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.__equalsRepresentativeCompId(cif_comp_id, nmr_comp_id):
                                    self.report.warning.appendDescription('sequence_mismatch',
                                                                          {'file_name': nmr_file_name, 'description': err})
                                    self.report.setWarning()

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

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

                                    if self.__verbose:
                                        self.__lfh.write(f"+NmrDpUtility.__assignCoordPolymerSequence() ++ Error  - {err}\n")
                                # """
                                # ref_code = result['ref_code']
                                # test_code = result['test_code']
                                # ref_gauge_code = result['ref_gauge_code']
                                # test_gauge_code = result['test_gauge_code']

                                # _ref_code = _result['ref_code']
                                # _test_code = _result['test_code']
                                # _ref_gauge_code = _result['ref_gauge_code']
                                # _test_gauge_code = _result['test_gauge_code']

                                # if nmr_comp_id == '.':

                                #     offset = 0
                                #     hit = False
                                #     while i + offset >= 0:
                                #         if seq_id1[i + offset] is not None:
                                #             hit = True
                                #             break
                                #         offset -= 1

                                #     if not hit:
                                #         offset = 0
                                #         while i + offset < length:
                                #             offset += 1

                                #     p = offset_1 + s1['seq_id'].index(seq_id1[i + offset]) - offset
                                #     ref_code = ref_code[0:p] + '-' + ref_code[p:]
                                #     ref_gauge_code = ref_gauge_code[0:p] + ' ' + ref_gauge_code[p:]

                                #     result['ref_code'] = ref_code
                                #     result['ref_gauge_code'] = ref_gauge_code
                                #     result['mid_code'] = get_middle_code(ref_code, test_code)

                                #     _test_code = _test_code[0:p] + '-' + _test_code[p:]
                                #     _test_gauge_code = _test_gauge_code[0:p] + ' ' + _test_gauge_code[p:]

                                #     _result['test_code'] = _test_code
                                #     _result['test_gauge_code'] = _test_gauge_code
                                #     _result['mid_code'] = get_middle_code(_ref_code, _test_code)

                                #     offset_1 += 1

                                # elif cif_comp_id == '.':

                                #     offset = 0
                                #     hit = False
                                #     while i + offset >= 0:
                                #         if seq_id2[i + offset] is not None:
                                #             hit = True
                                #             break
                                #         offset -= 1

                                #     if not hit:
                                #         offset = 0
                                #         while i + offset < length:
                                #             offset += 1

                                #     p = offset_2 + s2['seq_id'].index(seq_id2[i + offset]) - offset
                                #     test_code = test_code[0:p] + '-' + test_code[p:]
                                #     test_gauge_code = test_gauge_code[0:p] + ' ' + test_gauge_code[p:]

                                #     result['test_code'] = test_code
                                #     result['test_gauge_code'] = test_gauge_code
                                #     result['mid_code'] = get_middle_code(ref_code, test_code)

                                #     _ref_code = _ref_code[0:p] + '-' + _ref_code[p:]
                                #     _ref_gauge_code = _ref_gauge_code[0:p] + ' ' + _ref_gauge_code[p:]

                                #     _result['ref_code'] = _ref_code
                                #     _result['ref_gauge_code'] = _ref_gauge_code
                                #     _result['mid_code'] = get_middle_code(_ref_code, _test_code)

                                #     offset_2 += 1
                                # """
                        if len(unmapped) > 0:
                            chain_assign['unmapped_sequence'] = unmapped

                        if len(conflict) > 0:
                            chain_assign['conflict_sequence'] = conflict
                            chain_assign['conflict'] = len(conflict)
                            chain_assign['unmapped'] = chain_assign['unmapped'] - len(conflict)
                            if chain_assign['unmapped'] < 0:
                                chain_assign['conflict'] -= chain_assign['unmapped']
                                chain_assign['unmapped'] = 0

                            result['conflict'] = chain_assign['conflict']
                            result['unmapped'] = chain_assign['unmapped']

                            if _result is not None:
                                _result['conflict'] = chain_assign['conflict']
                                _result['unmapped'] = chain_assign['unmapped']

                    chain_assign_set.append(chain_assign)

                if len(chain_assign_set) > 0 and fileListId == 0:

                    if len(cif_polymer_sequence) > 1:

                        if any(s for s in cif_polymer_sequence if 'identical_chain_id' in s):

                            for chain_assign in chain_assign_set:

                                if chain_assign['conflict'] > 0:
                                    continue

                                _chain_id = chain_assign['test_chain_id']
                                _auth_chain_id = None if 'test_auth_chain_id' not in chain_assign else chain_assign['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(_chain_assign for _chain_assign in chain_assign_set if _chain_assign['test_chain_id'] == _chain_id):
                                            _chain_assign = copy.copy(chain_assign)
                                            _chain_assign['test_chain_id'] = _chain_id
                                            if _auth_chain_id is not None:
                                                _chain_assign['test_auth_chain_id'] = _auth_chain_id
                                            chain_assign_set.append(_chain_assign)

                                except StopIteration:
                                    pass

                    self.report.chain_assignment.setItemValue('nmr_poly_seq_vs_model_poly_seq', chain_assign_set)

            else:

                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 __testCoordAtomIdConsistency(self):
        """ Perform consistency test on atom names of coordinate file.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return True

        cif_input_source = self.report.input_sources[id]
        cif_input_source_dic = cif_input_source.get()

        cif_polymer_sequence = cif_input_source_dic['polymer_sequence']

        __errors = self.report.getTotalErrors()

        if self.__coord_atom_id is None:

            try:

                model_num_name = 'pdbx_PDB_model_num' if self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                coord = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                         {'name': 'label_seq_id', 'type': 'str', 'alt_name': 'seq_id'},
                                                         {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'auth_seq_id'},  # non-polymer
                                                         {'name': 'label_comp_id', 'type': 'str', 'alt_name': 'comp_id'},
                                                         {'name': 'label_atom_id', 'type': 'str', 'alt_name': 'atom_id'}
                                                         ],
                                                        [{'name': model_num_name, 'type': 'int', 'value': self.__representative_model_id}
                                                         ])

                self.__coord_atom_id = {}
                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_id[seq_key] = {'comp_id': comp_id, 'atom_id': atom_ids}

            except Exception as e:

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

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

                return False

        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 chain_assign in chain_assign_dic['nmr_poly_seq_vs_model_poly_seq']:

                ref_chain_id = chain_assign['ref_chain_id']
                test_chain_id = chain_assign['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] = []

                complex = {'seq_align': result}  # DAOTHER-7465  # pylint: disable=redefined-builtin

                if 'unmapped_sequence' in chain_assign:
                    complex['seq_unmap'] = [unmapped['ref_seq_id'] for unmapped in chain_assign['unmapped_sequence']]

                nmr2ca[ref_chain_id].append(complex)

            if nmr_input_source_dic['content_subtype'] is None:
                continue

            add_details = False

            for content_subtype in nmr_input_source_dic['content_subtype'].keys():

                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_data = self.__star_data[fileListId]
                    sf_framecode = ''

                    add_details |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype, sf_data,
                                                                       list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                       seq_align_dic, nmr2ca, ref_chain_id)

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

                    sf_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    add_details |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype, sf_data,
                                                                       list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                       seq_align_dic, nmr2ca, ref_chain_id)

                else:

                    for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                        add_details |= self.__testCoordAtomIdConsistency__(fileListId, file_name, file_type, content_subtype,
                                                                           sf_data, list_id, sf_framecode, lp_category, cif_polymer_sequence,
                                                                           seq_align_dic, nmr2ca, ref_chain_id)

                        list_id += 1

            if add_details:
                self.__depositNmrData()

        return self.report.getTotalErrors() == __errors

    def __testCoordAtomIdConsistency__(self, file_list_id, file_name, file_type, content_subtype,
                                       sf_data, 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.
        """

        add_details = 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((l['data'] for l in self.__lp_data[content_subtype]
                                if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741
            else:
                lp_data = next((l['data'] for l in self.__aux_data[content_subtype]
                                if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode
                                and l['category'] == lp_category), None)  # noqa: E741

        else:

            if content_subtype == 'spectral_peak':

                try:

                    _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        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]:
                        _k = copy.copy(k)
                        if '%s' in k['name']:
                            _k['name'] = k['name'] % dim
                        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_data, 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:  # noqa: E722 pylint: disable=bare-except
                return False

        if lp_data is None:
            return False

        has_seq_align = False

        sa_name = 'nmr_poly_seq_vs_' + content_subtype

        if sa_name in seq_align_dic and seq_align_dic[sa_name] is not None:

            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 = sf_data.get_tag(self.num_dim_items[file_type])[0]
                    num_dim = int(_num_dim)

                    if num_dim not in range(1, self.lim_num_dim):
                        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 = []

        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':

            if __pynmrstar_v3_2__:
                loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop(lp_category)
            else:
                loop = sf_data if self.__star_data_type[file_list_id] == 'Loop' else sf_data.get_loop_by_category(lp_category)

            details_col = loop.tags.index('Details') if 'Details' in loop.tags and self.__leave_intl_note else -1

        for l, i in enumerate(lp_data):  # noqa: E741

            for j in range(num_dim):
                chain_id = i[chain_id_names[j]]
                seq_id = i[seq_id_names[j]]
                comp_id = i[comp_id_names[j]]
                atom_id = i[atom_id_names[j]]

                if content_subtype.startswith('spectral_peak')\
                   and (chain_id in self.empty_value or seq_id in self.empty_value or comp_id in self.empty_value or atom_id in self.empty_value):
                    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:
                    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

                if file_type == 'nef' or self.__isNmrAtomName(comp_id, atom_id):
                    _atom_id, _, details = self.__getAtomIdListWithAmbigCode(file_type, 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 += ' (' + details.rstrip('.') + ')'

                    else:
                        atom_name = atom_id + ' (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += atom_id_ + ' '

                        atom_name = atom_name.rstrip() + ')'

                        # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id
                    atom_name = atom_id

                seq_key = (cif_chain_id, cif_seq_id)

                coord_atom_id_ = None if seq_key not in self.__coord_atom_id else self.__coord_atom_id[seq_key]

                if coord_atom_id_ is None or coord_atom_id_['comp_id'] != cif_comp_id or atom_id_ not in coord_atom_id_['atom_id']:

                    idx_msg = ''
                    if index_tag is not None:
                        idx_msg = f"[Check row of {index_tag} {i[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 coordinate."

                    cyclic = self.__isCyclicPolymer(ref_chain_id)

                    if self.__nonblk_bad_nterm and seq_id == 1 and atom_id_ == 'H' and (cyclic or comp_id == 'PRO'):

                        err += " However, it is acceptable if an appropriate atom name, H1, is given "

                        if cyclic:
                            err += "because of a cyclic-peptide."
                        else:
                            err += f"because sequence (chain_id {ref_chain_id}) starts with Proline residue."

                        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 file_type == 'nmr-star' and details_col != -1:
                            _details = loop.data[l][details_col]
                            details = f"{chain_id}:{seq_id}:{comp_id}:{atom_name} is not present in the coordinate. "\
                                "However, it is acceptable if an appropriate atom name, H1, is given because of a cyclic-peptide.\n"
                            if _details in self.empty_value or (details not in _details):
                                if _details in self.empty_value:
                                    loop.data[l][details_col] = details
                                else:
                                    loop.data[l][details_col] += ('' if '\n' in _details else '\n') + details
                                add_details = True

                    elif ca['conflict'] == 0:  # no conflict in sequenc alignment

                        if self.__get1LetterCode(comp_id) != 'X':

                            self.report.error.appendDescription('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:

                            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 add_details

    def __retrieveDpReport(self):
        """ Retrieve NMR data processing report from JSON file.
        """

        if not self.__combined_mode:
            return False

        # 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_data_content(star_data, star_data_type)

            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, star_data_type)  # 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:
            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.report.loadFile(fPath)

        self.report_prev = NmrDpReport()
        self.report_prev.loadFile(fPath)

        return True

    def __resolveConflictsInLoop(self):
        """ Resolve conflicted rows in loops.
        """

        if not self.__combined_mode:
            return False

        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'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

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

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, self.lim_num_dim):
                            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]:
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            key_items.append(_k)

                else:

                    key_items = self.key_items[file_type][content_subtype]

                    if len(key_items) == 0:
                        continue

                    if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

                        conflict_id = self.__nefT.get_conflict_atom_id(sf_data, file_type, lp_category, key_items)[0]

                        if len(conflict_id) > 0:
                            if __pynmrstar_v3_2__:
                                loop = sf_data.get_loop(lp_category)
                            else:
                                loop = sf_data.get_loop_by_category(lp_category)

                            for lid in conflict_id:
                                del loop.data[lid]

                try:

                    conflict_id = self.__nefT.get_conflict_id(sf_data, lp_category, key_items)[0]

                    if len(conflict_id) > 0:
                        if __pynmrstar_v3_2__:
                            loop = sf_data.get_loop(lp_category)
                        else:
                            loop = sf_data.get_loop_by_category(lp_category)

                        for lid in conflict_id:
                            del loop.data[lid]

                except:  # noqa: E722 pylint: disable=bare-except
                    pass

        return True

    def __resolveConflictsInAuxLoop(self):
        """ Resolve conflicted rows in auxiliary loops.
        """

        if not self.__combined_mode:
            return False

        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'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                if content_subtype.startswith('spectral_peak'):

                    try:

                        _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, self.lim_num_dim):
                            raise ValueError()

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

                for loop in sf_data.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 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_data, lp_category, key_items)[0]

                            if len(conflict_id) > 0:
                                if __pynmrstar_v3_2__:
                                    _loop = sf_data.get_loop(lp_category)
                                else:
                                    _loop = sf_data.get_loop_by_category(lp_category)

                                for lid in conflict_id:
                                    del _loop.data[lid]

                        except:  # noqa: E722 pylint: disable=bare-except
                            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'].keys():

                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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    try:
                        if __pynmrstar_v3_2__:
                            loop = sf_data.get_loop(lp_category)
                        else:
                            loop = sf_data.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(f"+NmrDpUtility.__appendIndexTag() ++ LookupError  - {err}\n")

                    new_loop = pynmrstar.Loop.from_scratch(lp_category)

                    new_loop.add_tag(lp_tag)

                    for tag in loop.tags:
                        new_loop.add_tag(lp_category + '.' + tag)

                    for l, i in enumerate(loop.data, start=1):  # noqa: E741
                        new_loop.add_data([str(l)] + i)

                    del sf_data[loop]

                    sf_data.add_loop(new_loop)

    def __deleteSkippedSf(self):
        """ Delete skipped saveframes.
        """

        if len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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('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_data in reversed(sf_list):
                            del self.__star_data[0][sf_data]

            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 len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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('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_data = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf_data 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_data[sf_data.get_loop(w['category'])]
                        else:
                            del sf_data[sf_data.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 len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                    loops = []

                    for loop in sf_data.loops:

                        if loop.category == lp_category:
                            continue

                        loops.append(loop)

                    for loop in reversed(loops):
                        del sf_data[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 __updatePolymerSequence(self):
        """ Update polymer sequence.
        """

        if len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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']

        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

        for sf_category in self.__sf_category_list:  # DAOTHER-5896

            for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                for tag in sf_data.tags:
                    if isinstance(tag[1], str) and len(tag[1]) == 0:
                        tag[1] = '.'

        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_auth_asym_id = False
        has_auth_seq_id = False
        has_auth_comp_id = False
        has_nef_index = False
        has_assembly_id = False
        has_entry_id = False

        for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

            sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

            orig_lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                                 if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if orig_lp_data is None:
                try:
                    orig_lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                    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(i for i in orig_lp_data if i['residue_variant'] not in self.empty_value):
                            has_res_var_dat = True

                else:
                    if 'Auth_variant_ID' in orig_lp_data[0]:
                        if any(i for i in orig_lp_data if i['Auth_variant_ID'] not in self.empty_value):
                            has_res_var_dat = True

                    if 'Auth_asym_ID' in orig_lp_data[0]:
                        if any(i for i in orig_lp_data if i['Auth_asym_ID'] not in self.empty_value):
                            has_auth_asym_id = True

                    if 'Auth_seq_ID' in orig_lp_data[0]:
                        if any(i for i in orig_lp_data if i['Auth_seq_ID'] not in self.empty_value):
                            has_auth_seq_id = True

                    if 'Auth_comp_ID' in orig_lp_data[0]:
                        if any(i for i in orig_lp_data if i['Auth_comp_ID'] not in self.empty_value):
                            has_auth_comp_id = True

                    if 'NEF_index' in orig_lp_data[0]:
                        if any(i for i in orig_lp_data if i['NEF_index'] not in self.empty_value):
                            has_nef_index = True

                    if 'Assembly_ID' in orig_lp_data[0]:
                        has_assembly_id = 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:  # DAOTHER-6694
                return False

        sf_cat_name = 'nef_molecular_system' if file_type == 'nef' else 'Assembly'
        lp_cat_name = '_nef_sequence' if file_type == 'nef' else '_Chem_comp_assembly'

        try:
            orig_poly_seq_sf_data = self.__star_data[0].get_saveframes_by_category(sf_category)[0] if self.__retain_original else None
        except IndexError:
            return False

        poly_seq_sf_data = pynmrstar.Saveframe.from_scratch(sf_framecode)  # sf_cat_name

        tagNames = None

        if orig_poly_seq_sf_data is not None:

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

            for tag in orig_poly_seq_sf_data.tags:
                poly_seq_sf_data.add_tag(sf_cat_name + '.' + tag[0], tag[1])

            for loop in orig_poly_seq_sf_data.loops:

                if loop.category == lp_cat_name:
                    continue

                if file_type == 'nef' or file_type == 'nmr-star' and loop.category != '_Entity_assembly':
                    continue

                poly_seq_sf_data.add_loop(loop)

        tag_names = ['sf_category'] if file_type == 'nef' else ['Sf_category']
        tag_value = 'nef_molecular_system' if file_type == 'nef' else 'assembly'

        for tag_name in tag_names:
            if tagNames is None or (tag_name not in tagNames):
                poly_seq_sf_data.add_tag(sf_cat_name + '.' + tag_name, tag_value)
            else:
                poly_seq_sf_data.tags[tagNames.index(tag_name)][1] = tag_value

        loop = pynmrstar.Loop.from_scratch(lp_cat_name)

        has_index_tag = self.index_tags[file_type][content_subtype] is not None

        if has_index_tag:
            loop.add_tag(lp_cat_name + '.' + self.index_tags[file_type][content_subtype])

        for key_item in key_items:
            loop.add_tag(lp_cat_name + '.' + key_item['name'])

        for data_item in data_items:
            data_name = data_item['name']
            if data_name != 'NEF_index':
                loop.add_tag(lp_cat_name + '.' + data_name)
            elif has_nef_index:
                loop.add_tag(lp_cat_name + '.' + data_name)

        if has_entry_id:
            loop.add_tag(lp_cat_name + '.Entry_ID')

        polymer_sequence = input_source_dic['polymer_sequence']

        if polymer_sequence is not None:

            cid_offset = 0

            chains = []

            for s in polymer_sequence:
                chains.append(s['chain_id'])

            row_id = 1

            for s in polymer_sequence:

                chain_id = s['chain_id']
                seq_id_offset = 1 - s['seq_id'][0]

                length = len(s['seq_id'])

                cyclic = self.__isCyclicPolymer(chain_id)

                if cyclic:
                    self.report.setCyclicPolymer(cyclic)

                for j in range(length):

                    row = []

                    if has_index_tag:
                        row.append(row_id)

                    auth_seq_id = s['seq_id'][j]
                    auth_comp_id = s['comp_id'][j]

                    seq_id = auth_seq_id  # + seq_id_offset
                    _seq_id_ = auth_seq_id + seq_id_offset
                    comp_id = auth_comp_id.upper()
                    # """
                    # if comp_id in self.empty_value and 'identical_chain_id' in s:
                    #     for s_ in polymer_sequence:
                    #         if s_['chain_id'] in s['identical_chain_id']:
                    #             auth_comp_id = s_['comp_id'][j]
                    #             comp_id = auth_comp_id.upper()
                    #         if comp_id not in self.empty_value:
                    #             break

                    # if comp_id in self.empty_value:
                    #     chain_assign_dic = self.report.chain_assignment.get()
                    #     if 'model_poly_seq_vs_nmr_poly_seq' in chain_assign_dic:
                    #         try:
                    #             chain_assign = next(chain_assign for chain_assign in chain_assign_dic['model_poly_seq_vs_nmr_poly_seq']
                    #                                 if chain_assign['test_chain_id'] == chain_id)
                    #             if 'unmapped_sequence' in chain_assign:
                    #                 ref_seq = next(ref_seq for ref_seq in chain_assign['unmapped_sequence'] if ref_seq['ref_seq_id'] == seq_id)
                    #                 auth_comp_id = ref_seq['ref_comp_id']
                    #                 comp_id = auth_comp_id.upper()
                    #         except StopIteration:
                    #             if 'identical_chain_id' in s:
                    #                 for chain_id_ in s['identical_chain_id']:
                    #                     try:
                    #                         chain_assign_ = next(chain_assign for chain_assign in chain_assign_dic['model_poly_seq_vs_nmr_poly_seq']
                    #                                              if chain_assign['test_chain_id'] == chain_id_)
                    #                         if 'unmapped_sequence' in chain_assign_:
                    #                             ref_seq = next(ref_seq for ref_seq in chain_assign_['unmapped_sequence'] if ref_seq['ref_seq_id'] == seq_id)
                    #                             auth_comp_id = ref_seq['ref_comp_id']
                    #                             comp_id = auth_comp_id.upper()
                    #                             break
                    #                     except StopIteration:
                    #                         pass
                    # """
                    if file_type == 'nef':

                        row.append(self.__nefT.index_to_letter(self.__nefT.letter_to_int(chain_id) - 1 + cid_offset))  # chain_code
                        row.append(seq_id)  # sequence_code
                        row.append(comp_id)  # residue_name

                        # linking

                        if cyclic and _seq_id_ in (1, length):
                            row.append('cyclic')
                        elif _seq_id_ == 1 and length == 1:
                            row.append('single')
                        elif _seq_id_ == 1:
                            row.append('start')
                        elif _seq_id_ == length:
                            row.append('end')
                        elif auth_seq_id - 1 == s['seq_id'][j - 1] and auth_seq_id + 1 == s['seq_id'][j + 1]:
                            row.append('middle')
                        else:
                            row.append('break')

                        # residue_variant

                        if has_res_var_dat:
                            orig_row = None if orig_lp_data is None else next((i for i in orig_lp_data
                                                                               if i['chain_code'] == chain_id and i['sequence_code'] == auth_seq_id
                                                                               and i['residue_name'] == auth_comp_id), None)
                            if orig_row is not None:
                                row.append(orig_row['residue_variant'])
                            else:
                                row.append('.')
                        else:
                            row.append('.')

                        # cis_peptide

                        if self.__isProtCis(chain_id, seq_id):
                            row.append('true')
                        elif comp_id in ('PRO', 'GLY'):
                            row.append('false')
                        else:
                            row.append('.')

                    else:

                        cid = chains.index(chain_id) + 1

                        row.append(cid + cid_offset)  # Entity_assembly_ID
                        row.append(seq_id)  # Comp_index_ID
                        row.append(comp_id)  # Comp_ID

                        orig_row = None if orig_lp_data is None else next((i for i in orig_lp_data
                                                                           if i['Entity_assembly_ID'] == str(cid) and i['Comp_index_ID'] == auth_seq_id
                                                                           and i['Comp_ID'] == auth_comp_id), None)

                        # Auth_asym_ID

                        if has_auth_asym_id:
                            if orig_row is not None:
                                row.append(orig_row['Auth_asym_ID'])
                            else:
                                row.append(chain_id)
                        else:
                            row.append(chain_id)

                        # Auth_seq_ID

                        if has_auth_seq_id:
                            if orig_row is not None:
                                row.append(orig_row['Auth_seq_ID'])
                            else:
                                row.append(auth_seq_id)
                        else:
                            row.append(auth_seq_id)

                        # Auth_comp_ID

                        if has_auth_comp_id:
                            if orig_row is not None:
                                row.append(orig_row['Auth_comp_ID'])
                            else:
                                row.append(auth_comp_id)
                        else:
                            row.append(auth_comp_id)

                        # Auth_variant_ID

                        if has_res_var_dat:
                            if orig_row is not None:
                                row.append(orig_row['Auth_variant_ID'])
                            else:
                                row.append('.')
                        else:
                            row.append('.')

                        # Sequence_linking

                        if cyclic and seq_id in (1, length):
                            row.append('cyclic')
                        elif seq_id == 1 and length == 1:
                            row.append('single')
                        elif seq_id == 1:
                            row.append('start')
                        elif _seq_id_ == length:
                            row.append('end')
                        elif auth_seq_id - 1 == s['seq_id'][j - 1] and auth_seq_id + 1 == s['seq_id'][j + 1]:
                            row.append('middle')
                        else:
                            row.append('break')

                        # Cis_residue

                        if self.__isProtCis(chain_id, seq_id):
                            row.append('yes')
                        elif comp_id in ('PRO', 'GLY'):
                            row.append('no')
                        else:
                            row.append('.')

                        # NEF_index

                        if has_nef_index:
                            if orig_row is not None:
                                row.append(orig_row['NEF_index'])
                            else:
                                row.append('.')

                        # Assembly_ID

                        if has_assembly_id:
                            if orig_row is not None:
                                row.append(orig_row['Assembly_ID'])
                            else:
                                row.append(1)

                        else:
                            row.append(1)

                        if has_entry_id:
                            if orig_row is not None:
                                row.append(orig_row['Entry_ID'])
                            else:
                                row.append('.')

                    loop.add_data(row)

                    row_id += 1

                asym_ids_for_same_entity = self.report.getAsymIdsForSameEntity()
                chain_ids_for_same_entity = self.report.getChainIdsForSameEntity()

                if asym_ids_for_same_entity is not None and chain_ids_for_same_entity is None:
                    for asym_id in asym_ids_for_same_entity:
                        nmr_ps = self.report.getNmrPolymerSequenceWithModelChainId(asym_id)
                        if nmr_ps is not None and nmr_ps['chain_id'] == chain_id:
                            cif_ps = self.report.getModelPolymerSequenceWithNmrChainId(chain_id)
                            if cif_ps is not None and cif_ps['chain_id'] != asym_id:

                                cid_offset += 1

                                for j in range(length):

                                    row = []

                                    if has_index_tag:
                                        row.append(row_id)

                                    auth_seq_id = s['seq_id'][j]
                                    auth_comp_id = s['comp_id'][j]

                                    seq_id = auth_seq_id  # + seq_id_offset
                                    _seq_id_ = auth_seq_id + seq_id_offset
                                    comp_id = auth_comp_id.upper()

                                    if file_type == 'nef':

                                        row.append(self.__nefT.index_to_letter(self.__nefT.letter_to_int(chain_id) - 1 + cid_offset))  # chain_code
                                        row.append(seq_id)  # sequence_code
                                        row.append(comp_id)  # residue_name

                                        # linking

                                        if cyclic and _seq_id_ in (1, length):
                                            row.append('cyclic')
                                        elif _seq_id_ == 1 and length == 1:
                                            row.append('single')
                                        elif _seq_id_ == 1:
                                            row.append('start')
                                        elif _seq_id_ == length:
                                            row.append('end')
                                        elif auth_seq_id - 1 == s['seq_id'][j - 1] and auth_seq_id + 1 == s['seq_id'][j + 1]:
                                            row.append('middle')
                                        else:
                                            row.append('break')

                                        # residue_variant

                                        if has_res_var_dat:
                                            orig_row = None if orig_lp_data is None else next((i for i in orig_lp_data
                                                                                               if i['chain_code'] == chain_id and i['sequence_code'] == auth_seq_id
                                                                                               and i['residue_name'] == auth_comp_id), None)
                                            if orig_row is not None:
                                                row.append(orig_row['residue_variant'])
                                            else:
                                                row.append('.')
                                        else:
                                            row.append('.')

                                        # cis_peptide

                                        if self.__isProtCis(chain_id, seq_id):
                                            row.append('true')
                                        elif comp_id in ('PRO', 'GLY'):
                                            row.append('false')
                                        else:
                                            row.append('.')

                                    else:

                                        cid = chains.index(chain_id) + 1

                                        row.append(cid + cid_offset)  # Entity_assembly_ID
                                        row.append(seq_id)  # Comp_index_ID
                                        row.append(comp_id)  # Comp_ID

                                        orig_row = None if orig_lp_data is None else next((i for i in orig_lp_data
                                                                                           if i['Entity_assembly_ID'] == str(cid) and i['Comp_index_ID'] == auth_seq_id
                                                                                           and i['Comp_ID'] == auth_comp_id), None)

                                        # Auth_asym_ID

                                        if has_auth_asym_id:
                                            if orig_row is not None:
                                                row.append(orig_row['Auth_asym_ID'])
                                            else:
                                                row.append(chain_id)
                                        else:
                                            row.append(chain_id)

                                        # Auth_seq_ID

                                        if has_auth_seq_id:
                                            if orig_row is not None:
                                                row.append(orig_row['Auth_seq_ID'])
                                            else:
                                                row.append(auth_seq_id)
                                        else:
                                            row.append(auth_seq_id)

                                        # Auth_comp_ID

                                        if has_auth_comp_id:
                                            if orig_row is not None:
                                                row.append(orig_row['Auth_comp_ID'])
                                            else:
                                                row.append(auth_comp_id)
                                        else:
                                            row.append(auth_comp_id)

                                        # Auth_variant_ID

                                        if has_res_var_dat:
                                            if orig_row is not None:
                                                row.append(orig_row['Auth_variant_ID'])
                                            else:
                                                row.append('.')
                                        else:
                                            row.append('.')

                                        # Sequence_linking

                                        if cyclic and seq_id in (1, length):
                                            row.append('cyclic')
                                        elif seq_id == 1 and length == 1:
                                            row.append('single')
                                        elif seq_id == 1:
                                            row.append('start')
                                        elif _seq_id_ == length:
                                            row.append('end')
                                        elif auth_seq_id - 1 == s['seq_id'][j - 1] and auth_seq_id + 1 == s['seq_id'][j + 1]:
                                            row.append('middle')
                                        else:
                                            row.append('break')

                                        # Cis_residue

                                        if self.__isProtCis(chain_id, seq_id):
                                            row.append('yes')
                                        elif comp_id in ('PRO', 'GLY'):
                                            row.append('no')
                                        else:
                                            row.append('.')

                                        # NEF_index

                                        if has_nef_index:
                                            if orig_row is not None:
                                                row.append(orig_row['NEF_index'])
                                            else:
                                                row.append('.')

                                        # Assembly_ID

                                        if has_assembly_id:
                                            if orig_row is not None:
                                                row.append(orig_row['Assembly_ID'])
                                            else:
                                                row.append(1)

                                        else:
                                            row.append(1)

                                        if has_entry_id:
                                            if orig_row is not None:
                                                row.append(orig_row['Entry_ID'])
                                            else:
                                                row.append('.')

                                    loop.add_data(row)

                                    row_id += 1

            poly_seq_sf_data.add_loop(loop)

            # add the other loops except for _Entity_assembly

            for loop in orig_poly_seq_sf_data.loops:

                if loop.category == lp_cat_name:
                    continue

                if file_type == 'nef' or file_type == 'nmr-star' and loop.category != '_Entity_assembly':
                    poly_seq_sf_data.add_loop(loop)

            # replace polymer sequence

            sf_list = self.__star_data[0].get_saveframes_by_category(sf_category)

            if sf_list is not None:

                for old_sf_data in reversed(sf_list):
                    del self.__star_data[0][old_sf_data]

        norm_star_data = copy.copy(self.__star_data[0])

        for content_subtype in self.nmr_content_subtypes:

            sf_category = self.sf_categories[file_type][content_subtype]

            if content_subtype == 'poly_seq':

                if polymer_sequence is not None:
                    norm_star_data.add_saveframe(poly_seq_sf_data)

            elif has_key_value(input_source_dic['content_subtype'], content_subtype):

                sf_list = self.__star_data[0].get_saveframes_by_category(sf_category)

                if sf_list is not None:

                    for old_sf_data in reversed(sf_list):
                        del norm_star_data[old_sf_data]

                    for sf_data in sf_list:
                        norm_star_data.add_saveframe(sf_data)

        self.__star_data[0] = norm_star_data

        return True

    def __updateAuthSequence(self):
        """ Update auth sequence in NMR-STAR.
        """

        if len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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 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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

            try:
                if __pynmrstar_v3_2__:
                    loop = sf_data.get_loop(lp_category)
                else:
                    loop = sf_data.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:  # noqa: E722 pylint: disable=bare-except
                        pass

        if len(self.authSeqMap) == 0:
            return False

        for content_subtype in input_source_dic['content_subtype'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                try:
                    if __pynmrstar_v3_2__:
                        loop = sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data.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, self.lim_num_dim):
                        _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 self.empty_value or star_seq in self.empty_value:
                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 __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 not self.__bmrb_only:
                struct_conn = self.__cR.getDictListWithFilter('struct_conn',
                                                              [{'name': 'conn_type_id', 'type': 'str'}
                                                               ],
                                                              [{'name': 'pdbx_leaving_atom_flag', 'type': 'str', 'value': 'both'},
                                                               {'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},
                                                               ])
            else:
                struct_conn = self.__cR.getDictListWithFilter('struct_conn',
                                                              [{'name': 'conn_type_id', 'type': 'str'}
                                                               ],
                                                              [{'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},
                                                               ])

        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:

            try:

                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': cif_chain_id},
                                                                 {'name': 'auth_seq_id_1', 'type': 'int', 'value': beg_cif_seq_id},
                                                                 {'name': 'auth_atom_id_1', 'type': 'str', 'value': 'N'},
                                                                 {'name': 'auth_asym_id_2', 'type': 'str', 'value': cif_chain_id},
                                                                 {'name': 'auth_seq_id_2', 'type': 'int', 'value': 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:
                return False

            if close_contact[0]['dist'] > 1.2 and close_contact[0]['dist'] < 1.4:
                return True

        elif struct_conn[0]['conn_type_id'] == 'covale':
            return True

        return False

    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:

                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}
                                                            ])

            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 __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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                protons = self.__cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'label_atom_id', 'type': 'str', '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}])

            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)

        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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'str', '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'}])

            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']

            for r in rot1:
                if r == 'name':
                    continue
                rot1[r] /= total_models
                rot1[r] = float(f"{rot1[r]:.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)

        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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'str', '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'}])

            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']

            for r in rot1:
                if r == 'name':
                    continue
                rot1[r] /= total_models
                rot1[r] = float(f"{rot1[r]:.3f}")

            for r in rot2:
                if r == 'name':
                    continue
                rot2[r] /= total_models
                rot2[r] = float(f"{rot2[r]:.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)

        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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') else 'ndb_model'

                atoms = self.__cR.getDictListWithFilter('atom_site',
                                                        [{'name': 'label_atom_id', 'type': 'str', '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'}])

            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']

            for r in rot1:
                if r == 'name':
                    continue
                rot1[r] /= total_models
                rot1[r] = float(f"{rot1[r]:.3f}")

            for r in rot2:
                if r == 'name':
                    continue
                rot2[r] /= total_models
                rot2[r] = float(f"{rot2[r]:.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.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return False

        input_source = self.report.input_sources[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:

            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'}
                                                           ])

        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:
                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_data = 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_data, 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_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, '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_data, 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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                        if not any(loop for loop in sf_data.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_data, 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_data, 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((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                    pass

            if lp_data is not None:

                for i in lp_data:
                    chain_id = i[chain_id_name]
                    seq_id = i[seq_id_name]
                    comp_id = i[comp_id_name]
                    atom_id = i[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 = i[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = i[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 = i[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = i[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.
        """

        id = self.report.getInputSourceIdOfCoord()  # pylint: disable=redefined-builtin

        if id < 0:
            return False

        input_source = self.report.input_sources[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:

            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'}
                                                           ])

        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'] == 'disulf' or sc['conn_type_id'] == 'covale' or sc['conn_type_id'] == 'hydrog':
                    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:
                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_data = 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_data, 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_data = self.__star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf_data, '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_data, 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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                        sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                        if not any(loop for loop in sf_data.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_data, 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_data, 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((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                    pass

            if lp_data is not None:

                for i in lp_data:
                    chain_id = i[chain_id_name]
                    seq_id = i[seq_id_name]
                    comp_id = i[comp_id_name]
                    atom_id = i[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 = i[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = i[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 = i[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = i[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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') 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}])

            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': 'str', 'alt_name': 'comp_id'},
                                                             {'name': 'label_atom_id', 'type': 'str', '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}])

            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'] != 'H'
                        and np.linalg.norm(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': np.linalg.norm(to_np_array(n) - o)})

            if len(atom_list) == 0:
                return None

            na = sorted(atom_list, key=lambda a: a['distance'])[0]

            na_atom_id = na['atom_id']

            if not self.__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.__last_chem_comp_bonds:

                if b1[self.__ccb_aromatic_flag] != 'Y':
                    continue

                if b1[self.__ccb_atom_id_1] == na_atom_id and b1[self.__ccb_atom_id_2][0] != 'H':
                    na_ = b1[self.__ccb_atom_id_2]

                elif b1[self.__ccb_atom_id_2] == na_atom_id and b1[self.__ccb_atom_id_1][0] != 'H':
                    na_ = b1[self.__ccb_atom_id_1]

                else:
                    continue

                for b2 in self.__last_chem_comp_bonds:

                    if b2[self.__ccb_aromatic_flag] != 'Y':
                        continue

                    if b2[self.__ccb_atom_id_1] == na_ and b2[self.__ccb_atom_id_2][0] != 'H' and b2[self.__ccb_atom_id_2] != na_atom_id:
                        na__ = b2[self.__ccb_atom_id_2]

                    elif b2[self.__ccb_atom_id_2] == na_ and b2[self.__ccb_atom_id_1][0] != 'H' and b2[self.__ccb_atom_id_1] != na_atom_id:
                        na__ = b2[self.__ccb_atom_id_1]

                    else:
                        continue

                    for b3 in self.__last_chem_comp_bonds:

                        if b3[self.__ccb_aromatic_flag] != 'Y':
                            continue

                        if b3[self.__ccb_atom_id_1] == na__ and b3[self.__ccb_atom_id_2][0] != 'H' and b3[self.__ccb_atom_id_2] != na_:
                            na___ = b3[self.__ccb_atom_id_2]

                        elif b3[self.__ccb_atom_id_2] == na__ and b3[self.__ccb_atom_id_1][0] != 'H' and b3[self.__ccb_atom_id_1] != na_:
                            na___ = b3[self.__ccb_atom_id_1]

                        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': 'str', '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},
                                                       ])

            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

            distance = 0.0
            ring_distance = 0.0
            ring_angle = 0.0

            for model_id in model_ids:

                rc = np.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:
                            distance += np.linalg.norm(_a - o)

                        rc = np.add(rc, _a)

                        total += 1

                if total == len(ring_atoms):

                    rc = rc / total

                    ring_distance += np.linalg.norm(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 = np.cross(na__ - na_, na___ - na_)

                    ring_angle += math.acos(abs(np.dot(to_unit_vector(o - rc), to_unit_vector(ring_vector))))

                    len_model_ids += 1

            na['ring_atoms'] = ring_atoms
            na['distance'] = float(f"{distance / len_model_ids:.1f}")
            na['ring_distance'] = float(f"{ring_distance / len_model_ids:.1f}")
            na['ring_angle'] = float(f"{np.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 self.__cR.hasItem('atom_site', 'pdbx_PDB_model_num') 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}])

            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': 'label_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': 'str', 'alt_name': 'comp_id'},
                                                             {'name': 'label_atom_id', 'type': 'str', '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}])

            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 np.linalg.norm(to_np_array(n) - o) < cutoff
                        and (n['type_symbol'] in self.paramag_elems
                             or n['type_symbol'] in self.ferromag_elems)]

            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': np.linalg.norm(to_np_array(n) - o)})

            if len(atom_list) == 0:
                return None

            p = sorted(atom_list, key=lambda a: a['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': 'label_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']},
                                                      ])

            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

            distance = 0.0

            for __p in _p:
                distance += np.linalg.norm(to_np_array(__p) - o)

            p['distance'] = float(f"{distance / 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 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']

            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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                try:
                    if __pynmrstar_v3_2__:
                        loop = sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data.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.data:

                        atom_id = row[atomIdCol]

                        if row[atomTypeCol] in self.empty_value:
                            row[atomTypeCol] = atom_id[0]

                        if row[isoNumCol] is self.empty_value:

                            try:
                                row[isoNumCol] = self.atom_isotopes[atom_id[0]][0]
                            except KeyError:
                                pass

                elif has_atom_type:

                    atomTypeCol = loop.tags.index(cs_atom_type)

                    for row in loop.data:

                        atom_id = row[atomIdCol]

                        if row[atomTypeCol] in self.empty_value:
                            row[atomTypeCol] = atom_id[0]

                        try:
                            iso_num = self.atom_isotopes[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.data:

                        atom_id = row[atomIdCol]

                        if row[isoNumCol] is self.empty_value:

                            try:
                                row[isoNumCol] = self.atom_isotopes[atom_id[0]][0]
                            except KeyError:
                                pass

                        row.append(atom_id[0] if atom_id[0] in self.atom_isotopes else '.')

                    loop.add_tag(cs_atom_type)

                else:

                    for row in loop.data:

                        atom_id = row[atomIdCol]

                        row.append(atom_id[0] if atom_id[0] in self.atom_isotopes else '.')

                        try:
                            iso_num = self.atom_isotopes[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

    def __appendWeightInLoop(self):
        """ Append weight column in interesting loops, if required.
        """

        if not self.__combined_mode:
            return False

        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'].keys():

                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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    try:
                        if __pynmrstar_v3_2__:
                            loop = sf_data.get_loop(lp_category)
                        else:
                            loop = sf_data.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(f"+NmrDpUtility.__appendWeightInLoop() ++ LookupError  - {err}\n")

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

                    loop.add_tag(weight_tag)

        return is_done

    def __appendDihedAngleType(self):
        """ Append dihedral angle type column, if required.
        """

        if not self.__combined_mode:
            return 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_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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                try:
                    if __pynmrstar_v3_2__:
                        loop = sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data.get_loop_by_category(lp_category)
                except KeyError:
                    continue

                if angle_type_tag in loop.tags:
                    continue

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

                loop.add_tag(angle_type_tag)

        return True

    def __appendSfTagItem(self):
        """ Append saveframe tag items, if required.
        """

        if not self.__combined_mode:
            return 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']

            if input_source_dic['content_subtype'] is None:
                continue

            for content_subtype in input_source_dic['content_subtype'].keys():

                if content_subtype in ('entry_info', 'poly_seq', 'entity', 'chem_shift'):
                    continue

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

                tag_items = self._sf_tag_items[file_type][content_subtype]

                if tag_items is None:
                    continue

                for sf_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

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

                    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_data.add_tag(tag_item, '.')

        return True

    def __updateDihedralAngleType(self):
        """ Update dihedral angle types (phi, psi, omega, chi[1-5] for polypeptide-like residue) if possible.
        """

        if not self.__combined_mode:
            return 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_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_data in self.__star_data[fileListId].get_saveframes_by_category(sf_category):

                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                                if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

                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_data, 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:  # noqa: E722 pylint: disable=bare-except
                        pass

                if lp_data is not None:

                    phi_index = []
                    psi_index = []
                    omega_index = []
                    chi1_index = []
                    chi2_index = []
                    chi3_index = []
                    chi4_index = []
                    chi5_index = []

                    try:

                        for i in lp_data:
                            index_id = i[index_id_name]
                            chain_id_1 = i[chain_id_1_name]
                            chain_id_2 = i[chain_id_2_name]
                            chain_id_3 = i[chain_id_3_name]
                            chain_id_4 = i[chain_id_4_name]
                            seq_ids = []
                            seq_ids.append(i[seq_id_1_name])
                            seq_ids.append(i[seq_id_2_name])
                            seq_ids.append(i[seq_id_3_name])
                            seq_ids.append(i[seq_id_4_name])
                            comp_id = i[comp_id_1_name]
                            atom_ids = []
                            atom_ids.append(i[atom_id_1_name])
                            atom_ids.append(i[atom_id_2_name])
                            atom_ids.append(i[atom_id_3_name])
                            atom_ids.append(i[atom_id_4_name])
                            angle_type = i[angle_type_name]

                            if angle_type not in self.empty_value:
                                continue

                            if chain_id_1 != chain_id_2 or chain_id_2 != chain_id_3 or chain_id_3 != chain_id_4:
                                continue

                            polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                            if not polypeptide_like:
                                continue

                            seq_id_common = collections.Counter(seq_ids).most_common()

                            if len(seq_id_common) == 2:

                                # phi or psi

                                if seq_id_common[0][1] == 3 and seq_id_common[1][1] == 1:

                                    # phi

                                    seq_id_prev = seq_id_common[1][0]

                                    if seq_id_common[0][0] == seq_id_prev + 1:

                                        j = 0
                                        if seq_ids[j] == seq_id_prev and atom_ids[j] == 'C':
                                            atom_ids.pop(j)
                                            if atom_ids == self.dihed_atom_ids:
                                                phi_index.append(index_id)

                                    # psi

                                    seq_id_next = seq_id_common[1][0]

                                    if seq_id_common[0][0] == seq_id_next - 1:

                                        j = 3
                                        if seq_ids[j] == seq_id_next and atom_ids[j] == 'N':
                                            atom_ids.pop(j)
                                            if atom_ids == self.dihed_atom_ids:
                                                psi_index.append(index_id)

                                # omega

                                if atom_ids[0] == 'O' and atom_ids[1] == 'C' and atom_ids[2] == 'N'\
                                   and (atom_ids[3] == 'H' or atom_ids[3] == 'CA')\
                                   and seq_ids[0] == seq_ids[1] and seq_ids[1] + 1 == seq_ids[2] and seq_ids[2] == seq_ids[3]:
                                    omega_index.append(index_id)

                            elif len(seq_id_common) == 1:

                                # chi1

                                if atom_ids[0] == 'N' and atom_ids[1] == 'CA' and atom_ids[2] == 'CB'\
                                   and self.chi1_atom_id_4_pat.match(atom_ids[3]):
                                    # if (atom_ids[3] == 'CG' and comp_id in ('ARG', 'ASN', 'ASP', 'GLN', 'GLU', 'HIS', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'TRP', 'TYR')) or\
                                    #   (atom_ids[3] == 'CG1' and comp_id in ('ILE', 'VAL')) or\
                                    #   (atom_ids[3] == 'OG' and comp_id == 'SER') or\
                                    #   (atom_ids[3] == 'OG1' and comp_id == 'THR') or\
                                    #   (atom_ids[3] == 'SG' and comp_id == 'CYS'):
                                    chi1_index.append(index_id)

                                # chi2

                                if atom_ids[0] == 'CA' and atom_ids[1] == 'CB'\
                                   and self.chi2_atom_id_3_pat.match(atom_ids[2]) and self.chi2_atom_id_4_pat.match(atom_ids[3]):
                                    # if (atom_ids[2] == 'CG' and atom_ids[3] == 'CD' and comp_id in ('ARG', 'GLN', 'GLU', 'LYS', 'PRO')) or\
                                    #   (atom_ids[2] == 'CG' and atom_ids[3] == 'CD1' and comp_id in ('LEU', 'PHE', 'TRP', 'TYR')) or\
                                    #   (atom_ids[2] == 'CG' and atom_ids[3] == 'ND1' and comp_id == 'HIS') or\
                                    #   (atom_ids[2] == 'CG' and atom_ids[3] == 'OD1' and comp_id in ('ASN', 'ASP')) or\
                                    #   (atom_ids[2] == 'CG' and atom_ids[3] == 'SD' and comp_id == 'MET') or\
                                    #   (atom_ids[2] == 'CG1' and atom_ids[3] == 'CD1' and comp_id == 'ILE'):
                                    chi2_index.append(index_id)

                                # chi3

                                if atom_ids[0] == 'CB' and atom_ids[1] == 'CG'\
                                   and self.chi3_atom_id_3_pat.match(atom_ids[2]) and self.chi3_atom_id_4_pat.match(atom_ids[3]):
                                    # if (atom_ids[2] == 'CD' and atom_ids[3] == 'CE' and comp_id == 'LYS') or\
                                    #   (atom_ids[2] == 'CD' and atom_ids[3] == 'NE' and comp_id == 'ARG') or\
                                    #   (atom_ids[2] == 'CD' and atom_ids[3] == 'OE1' and comp_id in ('GLN', 'GLU')) or\
                                    #   (atom_ids[2] == 'SD' and atom_ids[3] == 'CE' and comp_id == 'MET'):
                                    chi3_index.append(index_id)

                                # chi4

                                if atom_ids[0] == 'CG' and atom_ids[1] == 'CD'\
                                   and self.chi4_atom_id_3_pat.match(atom_ids[2]) and self.chi4_atom_id_4_pat.match(atom_ids[3]):
                                    # if (atom_ids[2] == 'NE' and atom_ids[3] == 'CZ' and comp_id == 'ARG') or\
                                    #  (atom_ids[2] == 'CE' and atom_ids[3] == 'NZ' and comp_id == 'LYS'):
                                    chi4_index.append(index_id)

                                # chi5

                                if atom_ids == ['CD', 'NE', 'CZ', 'NH1']:  # and comp_id == 'ARG':
                                    chi5_index.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 len(phi_index) + len(psi_index) + len(omega_index)\
                            + len(chi1_index) + len(chi2_index) + len(chi3_index)\
                            + len(chi4_index) + len(chi5_index) > 0:

                        try:
                            if __pynmrstar_v3_2__:
                                loop = sf_data.get_loop(lp_category)
                            else:
                                loop = sf_data.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.data:

                            index_id = int(row[idxCol])

                            if index_id in phi_index:
                                row[aglCol] = 'PHI'
                            elif index_id in psi_index:
                                row[aglCol] = 'PSI'
                            elif index_id in omega_index:
                                row[aglCol] = 'OMEGA'
                            elif index_id in chi1_index:
                                row[aglCol] = 'CHI1'
                            elif index_id in chi2_index:
                                row[aglCol] = 'CHI2'
                            elif index_id in chi3_index:
                                row[aglCol] = 'CHI3'
                            elif index_id in chi4_index:
                                row[aglCol] = 'CHI4'
                            elif index_id in chi5_index:
                                row[aglCol] = 'CHI5'

        return True

    def __fixDisorderedIndex(self):
        """ Fix disordered indices.
        """

        if len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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_data = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf_data 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'].keys()
                                                   if self.lp_categories[file_type][c] == category and self.index_tags[file_type][c] is not None)

                            if __pynmrstar_v3_2__:
                                loop = sf_data.get_loop(w['category'])
                            else:
                                loop = sf_data.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 len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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('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_data = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf_data 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_data.get_loop(w['category'])
                        else:
                            loop = sf_data.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.data:

                                val = row[itCol]

                                if val is self.empty_value:
                                    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 len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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('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_data = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf_data 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_data.get_loop(w['category'])
                        else:
                            loop = sf_data.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.data:

                                val = row[itCol]

                                if val is self.empty_value:
                                    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 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('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 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('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 len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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 self.empty_value 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_data = self.__getSaveframeByName(0, w['sf_framecode'])

                    if sf_data 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_data.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_data.tags[itCol][1]
                            if val in self.empty_value:
                                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_data.tags[itCol][1] = itEnum[0]

                                # specific remediation follows
                                else:

                                    sf_category = sf_data.get_tag('sf_category')[0]

                                    try:

                                        content_subtype = next(c for c in input_source_dic['content_subtype'].keys() 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((l['data'] for l in self.__lp_data[content_subtype]
                                                            if l['file_name'] == file_name and l['sf_framecode'] == w['sf_framecode']), None)  # noqa: E741

                                            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_data, 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:  # noqa: E722 pylint: disable=bare-except
                                                    pass

                                            if lp_data is not None:

                                                if content_subtype == 'dist_restraint':

                                                    if mandatory_tag:
                                                        sf_data.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_data.tags[itCol][1] = 'hbond' if file_type == 'nef' else 'hydrogen bond'

                                                    elif self.__testDistRestraintAsDisulfideBond(lp_data):
                                                        sf_data.tags[itCol][1] = 'disulfide_bond' if file_type == 'nef' else 'disulfide bond'

                                                    elif self.__testDistRestraintAsSymmetry(lp_data):
                                                        sf_data.tags[itCol][1] = 'symmetry'

                                                    else:
                                                        sf_data.tags[itCol][1] = 'undefined' if file_type == 'nef' else 'general distance'

                                                elif content_subtype == 'dihed_restraint':

                                                    if mandatory_tag:
                                                        sf_data.tags[itCol][1] = 'undefined'

                                                    # 'J-couplings', 'backbone chemical shifts'

                                                    elif self.__testDihedRestraintAsBackBoneChemShifts(lp_data):
                                                        sf_data.tags[itCol][1] = 'chemical_shift' if file_type == 'nef' else 'backbone chemical shifts'

                                                    # else:
                                                    #    sf_data.tags[itCol][1] = 'J-couplings'

                                                    else:
                                                        sf_data.tags[itCol][1] = 'undefined'

                                                elif content_subtype == 'rdc_restraint':

                                                    if mandatory_tag:
                                                        sf_data.tags[itCol][1] = 'undefined'
                                                    else:
                                                        sf_data.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((l['data'] for l in self.__lp_data[content_subtype]
                                                            if l['file_name'] == file_name and l['sf_framecode'] == w['sf_framecode']), None)  # noqa: E741

                                            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_data, 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:  # noqa: E722 pylint: disable=bare-except
                                                    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_data.tags[itCol][1] = 'undefined'
                                                elif self.__testRestraintPotentialSWP(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'square-well-parabolic'
                                                elif self.__testRestraintPotentialSWPL(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'square-well-parabolic-linear'
                                                elif self.__testRestraintPotentialUBP(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'upper-bound-parabolic'
                                                elif self.__testRestraintPotentialLBP(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'lower-bound-parabolic'
                                                elif self.__testRestraintPotentialUBPL(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'upper-bound-parabolic-linear'
                                                elif self.__testRestraintPotentialLBPL(content_subtype, lp_data):
                                                    sf_data.tags[itCol][1] = 'lower-bound-parabolic-linear'
                                                elif self.__testRestraintPonentialLHorP(content_subtype, lp_data):
                                                    if content_subtype == 'dist_restraint':
                                                        sf_data.tags[itCol][1] = 'log-harmonic'
                                                    else:
                                                        sf_data.tags[itCol][1] = 'parabolic'
                                                else:
                                                    sf_data.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_data.get_loop(w['category'])
                        else:
                            loop = sf_data.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.data:

                                val = row[itCol]

                                if val is self.empty_value:
                                    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 False

        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 i in lp_data:
                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]

                if chain_id_1 == chain_id_2 and seq_id_1 == seq_id_2:
                    return False

                target_value = i[target_value_name] if target_value_name in i else None

                upper_limit_value = None
                lower_limit_value = None

                if target_value is None:

                    if lower_limit_name in i and (i[lower_limit_name] is not None)\
                            and upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                            and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                        target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                    elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                        target_value = i[upper_linear_limit_name]
                        upper_limit_value = target_value

                    elif upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = i[upper_limit_name]
                        upper_limit_value = target_value

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                        target_value = i[lower_linear_limit_name]
                        lower_limit_value = target_value

                    elif lower_limit_name in i and (i[lower_limit_name] is not None):
                        target_value = i[lower_limit_name]
                        lower_limit_value = target_value

                    else:
                        return False

                atom_id_1_ = i[atom_id_1_name][0]
                atom_id_2_ = i[atom_id_2_name][0]

                if upper_limit_value is not None:
                    target_value -= 0.4
                elif lower_limit_value is not None:
                    target_value += 0.4

                if (atom_id_1_ == 'F' and atom_id_2_ == 'H') or (atom_id_2_ == 'F' and atom_id_1_ == 'H'):

                    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_ == 'H') or (atom_id_2_ == 'O' and atom_id_1_ == 'H'):

                    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_ == 'H') or (atom_id_2_ == 'N' and atom_id_1_ == 'H'):

                    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 False

        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 i in lp_data:
                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]

                if chain_id_1 == chain_id_2 and seq_id_1 == seq_id_2:
                    return False

                target_value = i[target_value_name] if target_value_name in i else None

                upper_limit_value = None
                lower_limit_value = None

                if target_value is None:

                    if lower_limit_name in i and (i[lower_limit_name] is not None)\
                            and upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = (i[lower_limit_name] + i[upper_limit_name]) / 2.0

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None)\
                            and upper_linear_limit_name is i and (i[upper_linear_limit_name] is not None):
                        target_value = (i[lower_linear_limit_name] + i[upper_linear_limit_name]) / 2.0

                    elif upper_linear_limit_name in i and (i[upper_linear_limit_name] is not None):
                        target_value = i[upper_linear_limit_name]
                        upper_limit_value = target_value

                    elif upper_limit_name in i and (i[upper_limit_name] is not None):
                        target_value = i[upper_limit_name]
                        upper_limit_value = target_value

                    elif lower_linear_limit_name in i and (i[lower_linear_limit_name] is not None):
                        target_value = i[lower_linear_limit_name]
                        lower_limit_value = target_value

                    elif lower_limit_name in i and (i[lower_limit_name] is not None):
                        target_value = i[lower_limit_name]
                        lower_limit_value = target_value

                    else:
                        return False

                atom_id_1_ = i[atom_id_1_name][0]
                atom_id_2_ = i[atom_id_2_name][0]

                if upper_limit_value is not None:
                    target_value -= 0.4
                elif lower_limit_value 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 False

        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 i in lp_data:
                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                seq_id_1 = i[seq_id_1_name]
                seq_id_2 = i[seq_id_2_name]
                comp_id_1 = i[comp_id_1_name]
                comp_id_2 = i[comp_id_2_name]

                if chain_id_1 == chain_id_2:
                    return False

                has_symmetry = False

                for j in lp_data:

                    if j is i:
                        continue

                    _chain_id_1 = j[chain_id_1_name]
                    _chain_id_2 = j[chain_id_2_name]
                    _seq_id_1 = j[seq_id_1_name]
                    _seq_id_2 = j[seq_id_2_name]
                    _comp_id_1 = j[comp_id_1_name]
                    _comp_id_2 = j[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 restraints are derived from backbone chemical shifts.
        """

        if not self.__combined_mode:
            return False

        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_chains = set()
        dh_seq_ids = {}
        cs_chains = set()
        cs_seq_ids = {}

        try:

            for i in lp_data:
                chain_id_1 = i[chain_id_1_name]
                chain_id_2 = i[chain_id_2_name]
                chain_id_3 = i[chain_id_3_name]
                chain_id_4 = i[chain_id_4_name]
                seq_ids = []
                seq_ids.append(i[seq_id_1_name])
                seq_ids.append(i[seq_id_2_name])
                seq_ids.append(i[seq_id_3_name])
                seq_ids.append(i[seq_id_4_name])
                comp_id = i[comp_id_1_name]
                atom_ids = []
                atom_ids.append(i[atom_id_1_name])
                atom_ids.append(i[atom_id_2_name])
                atom_ids.append(i[atom_id_3_name])
                atom_ids.append(i[atom_id_4_name])
                angle_type = i[angle_type_name]

                if angle_type in self.empty_value:
                    continue

                angle_type = angle_type.lower()

                if angle_type not in ('phi', 'psi'):
                    return False

                if chain_id_1 != chain_id_2 or chain_id_2 != chain_id_3 or chain_id_3 != chain_id_4:
                    return False

                polypeptide_like = self.__csStat.getTypeOfCompId(comp_id)[0]

                if not polypeptide_like:
                    return False

                dh_chains.add(chain_id_1)

                seq_id_common = collections.Counter(seq_ids).most_common()

                if len(seq_id_common) != 2 or seq_id_common[0][1] != 3 or seq_id_common[1][1] != 1:
                    return False

                # phi

                if angle_type == 'phi':

                    seq_id_prev = seq_id_common[1][0]

                    if seq_id_common[0][0] != seq_id_prev + 1:
                        return False

                    j = 0
                    if seq_ids[j] == seq_id_prev:
                        if atom_ids[j] != 'C':
                            return False
                        atom_ids.pop(j)
                        if atom_ids != self.dihed_atom_ids:
                            return False

                # psi

                else:

                    seq_id_next = seq_id_common[1][0]

                    if seq_id_common[0][0] != seq_id_next - 1:
                        return False

                    j = 3
                    if seq_ids[j] == seq_id_next:
                        if atom_ids[j] != 'N':
                            return False
                        atom_ids.pop(j)
                        if atom_ids != self.dihed_atom_ids:
                            return False

                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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                if self.report.error.exists(file_name, sf_framecode):
                    continue

                lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                                if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

                if lp_data is None:

                    try:

                        lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                        pass

                if lp_data is not None:

                    for i in lp_data:
                        chain_id = i[chain_id_name]
                        seq_id = i[seq_id_name]
                        atom_id = i[atom_id_name]

                        if chain_id in dh_chains and seq_id in dh_seq_ids[chain_id] and atom_id == 'CA':
                            cs_chains.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_chains != dh_chains:
                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 False

        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 i in lp_data:
                if (lower_limit_name in i and i[lower_limit_name] is not None) and\
                   (upper_limit_name in i and i[upper_limit_name] is not None) and\
                   ((lower_linear_limit_name not in i) or i[lower_linear_limit_name] is None) and\
                   ((upper_linear_limit_name not in i) or i[upper_linear_limit_name] is None):
                    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 False

        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 i in lp_data:
                if (lower_limit_name in i and i[lower_limit_name] is not None) and\
                   (upper_limit_name in i and i[upper_limit_name] is not None) and\
                   (lower_linear_limit_name in i and i[lower_linear_limit_name] is not None) and\
                   (upper_linear_limit_name in i and i[upper_linear_limit_name] is not None):
                    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 False

        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 i in lp_data:
                if ((lower_limit_name not in i) or i[lower_limit_name] is None) and\
                   (upper_limit_name in i and i[upper_limit_name] is not None) and\
                   ((lower_linear_limit_name not in i) or i[lower_linear_limit_name] is None) and\
                   ((upper_linear_limit_name not in i) or i[upper_linear_limit_name] is None):
                    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 False

        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 i in lp_data:
                if (lower_limit_name in i and i[lower_limit_name] is not None) and\
                   ((upper_limit_name not in i) or i[upper_limit_name] is None) and\
                   ((lower_linear_limit_name not in i) or i[lower_linear_limit_name] is None) and\
                   ((upper_linear_limit_name not in i) or i[upper_linear_limit_name] is None):
                    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 False

        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 i in lp_data:
                if ((lower_limit_name not in i) or i[lower_limit_name] is None) and\
                   (upper_limit_name in i and i[upper_limit_name] is not None) and\
                   ((lower_linear_limit_name not in i) or i[lower_linear_limit_name] is None) and\
                   (upper_linear_limit_name in i and i[upper_linear_limit_name] is not None):
                    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 False

        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 i in lp_data:
                if (lower_limit_name in i and i[lower_limit_name] is not None) and\
                   ((upper_limit_name not in i) or i[upper_limit_name] is None) and\
                   (lower_linear_limit_name in i and i[lower_linear_limit_name] is not None) and\
                   ((upper_linear_limit_name not in i) or i[upper_linear_limit_name] is None):
                    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 False

        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 i in lp_data:
                if (target_value_name in i and i[target_value_name] is not None) and\
                   ((lower_limit_name not in i) or i[lower_limit_name] is None) and\
                   ((upper_limit_name not in i) or i[upper_limit_name] is None) and\
                   ((lower_linear_limit_name not in i) or i[lower_linear_limit_name] is None) and\
                   ((upper_linear_limit_name not in i) or i[upper_linear_limit_name] is None):
                    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 __fixBadAmbiguityCode(self):
    #     "" Fix bad ambiguity code if possible.
    #     ""

    #     if len(self.__star_data) == 0:
    #         return False

    #     if not self.__combined_mode:
    #         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']

    #     # NEF file has no ambiguity code
    #     if file_type == 'nef':
    #         return True

    #     warnings = self.report.warning.getValueList('bad_ambiguity_code', file_name)

    #     if warnings is None:
    #         return True

    #     for w in warnings:

    #         if "the same residue was not found." 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.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                 self.report.setError()

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

    #             elif 'category' not in w:

    #                 err = "Could not specify 'category' in NMR data processing report."

    #                 self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                 self.report.setError()

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

    #             elif 'row_location' not in w:

    #                 err = "Could not specify 'row_location' in NMR data processing report."

    #                 self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                 self.report.setError()

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

    #             else:

    #                 sf_data = self.__getSaveframeByName(0, w['sf_framecode'])

    #                 if sf_data 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.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                     self.report.setError()

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

    #                     continue

    #                 description = w['description'].split(' ')

    #                 itName = description[0]
    #                 itVal = description[1]

    #                 if __pynmrstar_v3_2__:
    #                     loop = sf_data.get_loop(w['category'])
    #                 else:
    #                     loop = sf_data.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.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                     self.report.setError()

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

    #                 else:

    #                     itCol = loop.tags.index(itName)

    #                     itColVal = {str(itCol): itVal}

    #                     has_loop_tag = True

    #                     for k, v in w['row_location'].items():

    #                         if k in loop.tags:
    #                             itColVal[str(loop.tags.index(k))] = v

    #                         else:

    #                             err = f"Could not find loop tag {k} in {w['category']} category, {w['sf_framecode']!r} saveframe, {file_name!r} file."

    #                             self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixBadAmbiguityCode() ++ Error  - " + err)
    #                             self.report.setError()

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

    #                             has_loop_tag = False

    #                     if not has_loop_tag:
    #                         continue

    #                     for row in loop.data:

    #                         exist = True

    #                         for k, v in itColVal.items():

    #                             if row[int(k)] != v:
    #                                 exist = False
    #                                 break

    #                         if exist:
    #                             row[itCol] = 1
    #                             break

    #         else:

    #             err = "Unexpected PyNMRSTAR object type {self.__star_data_type[0]} found about {file_name!r} file."

    #             self.report.error.appendDescription('internal_error', "+NmrDpUtility.__fixBadAmbiguityCode() ++ Error  - " + err)
    #             self.report.setError()

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

    #     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 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 False

        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'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

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

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, self.lim_num_dim):
                            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]:
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            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 < self.lim_num_dim:
                        disallowed_tags = []
                        for dim in range(max_dim, self.lim_num_dim):
                            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_data.get_loop(lp_category)
                else:
                    loop = sf_data.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.data:

                            val = row[itCol]

                            if val is self.empty_value:
                                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.data:

                            val = row[itCol]

                            if val is self.empty_value:
                                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 False

        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'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

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

                if content_subtype == 'spectral_peak':

                    try:

                        _num_dim = sf_data.get_tag(self.num_dim_items[file_type])[0]
                        num_dim = int(_num_dim)

                        if num_dim not in range(1, self.lim_num_dim):
                            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]:
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            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 < self.lim_num_dim:
                        disallowed_tags = []
                        for dim in range(max_dim, self.lim_num_dim):
                            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_data.get_loop(lp_category)
                    else:
                        loop = sf_data.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.data:

                                    val = row[itCol]

                                    if val is self.empty_value:
                                        continue

                                    if val.lower() in self.true_value:
                                        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.data:

                                    val = row[itCol]

                                    if val is self.empty_value:
                                        continue

                                    if val.lower() in self.true_value:
                                        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 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']

        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'].keys():

            if content_subtype in ('entry_info', 'entity'):
                continue

            sf_category = self.sf_categories[file_type][content_subtype]

            for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                # sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                for loop in sf_data.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 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_data.get_loop(lp_category)
                            else:
                                _loop = sf_data.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.data:

                                            val = row[itCol]

                                            if val is self.empty_value:
                                                continue

                                            if val.lower() in self.true_value:
                                                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.data:

                                            val = row[itCol]

                                            if val is self.empty_value:
                                                continue

                                            if val.lower() in self.true_value:
                                                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 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 file_type == 'nef':
            return True

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

        for content_subtype in input_source_dic['content_subtype'].keys():

            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_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                    sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

                    if not any(loop for loop in sf_data.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_data.get_loop(lp_category)
                    else:
                        loop = sf_data.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.data:

                            val = row[itCol]

                            if val is self.empty_value:
                                continue

                            list_ids.append(val)

                        list_id = collections.Counter(list_ids).most_common()[0][0]

                        if len(sf_data.get_tag('ID')) > 0:
                            tagNames = [t[0] for t in sf_data.tags]
                            itCol = tagNames.index('ID')

                            sf_data.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_data.add_tag('ID', list_id)

        return True

    def __addUnnamedEntryId(self):
        """ Add UNNAMED entry id.
        """

        if len(self.__star_data) == 0:
            return False

        if not self.__combined_mode:
            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.__release_mode:
                self.__star_data[0].entry_id = self.__entry_id + ('' if self.release_type[file_type] in self.empty_value else ('_' + self.release_type[file_type]))
            else:
                self.__star_data[0].entry_id = self.__entry_id + '_' + self.content_type[file_type]

        if file_type == 'nef':
            return True

        self.__sortCSLoop()

        if self.__updateAtomChemShiftId():
            self.__updateAmbiguousAtomChemShift()

        proc_sf_categories = set()

        # supported sf_categories
        if input_source_dic['content_subtype'] is not None:

            for content_subtype in input_source_dic['content_subtype'].keys():

                if content_subtype == 'entity':
                    continue

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

                proc_sf_categories.add(sf_category)

                for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                    entryIdTag = 'ID' if content_subtype == 'entry_info' else 'Entry_ID'

                    if entryIdTag in self.sf_allowed_tags[file_type][content_subtype]:

                        if len(sf_data.get_tag(entryIdTag)) == 0:
                            sf_data.add_tag(entryIdTag, self.__entry_id)

                        else:
                            tagNames = [t[0] for t in sf_data.tags]
                            itCol = tagNames.index(entryIdTag)

                            sf_data.tags[itCol][1] = self.__entry_id

                    if self.__insert_entry_id_to_loops:

                        entryIdTag = 'Entry_ID'

                        try:
                            if __pynmrstar_v3_2__:
                                loop = sf_data.get_loop(lp_category)
                            else:
                                loop = sf_data.get_loop_by_category(lp_category)
                        except KeyError:
                            continue

                        if loop is not None:

                            if entryIdTag in self.allowed_tags[file_type][content_subtype]:

                                if entryIdTag in loop.tags:

                                    itCol = loop.tags.index(entryIdTag)

                                    for row in loop.data:
                                        row[itCol] = self.__entry_id

                                else:

                                    for row in loop.data:
                                        row.append(self.__entry_id)

                                    loop.add_tag(entryIdTag)

                        for loop in sf_data.loops:

                            lp_category = loop.category

                            if lp_category is None:
                                continue

                            if lp_category in self.lp_categories[file_type][content_subtype]:
                                continue

                            # elif lp_category in self.aux_lp_categories[file_type][content_subtype]:

                            if __pynmrstar_v3_2__:
                                _loop = sf_data.get_loop(lp_category)
                            else:
                                _loop = sf_data.get_loop_by_category(lp_category)

                                # if entryIdTag in self.aux_allowed_tags[file_type][content_subtype][lp_category]:

                            if entryIdTag in _loop.tags:

                                itCol = _loop.tags.index(entryIdTag)

                                for row in _loop.data:
                                    row[itCol] = self.__entry_id

                            else:

                                for row in _loop.data:
                                    row.append(self.__entry_id)

                                _loop.add_tag(entryIdTag)

        # skipped saveframe categories
        for sf_category in set(self.__sf_category_list):

            if sf_category in proc_sf_categories:
                continue

            for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

                entryIdTag = 'ID' if sf_category == 'entry_information' else 'Entry_ID'

                if len(sf_data.get_tag(entryIdTag)) == 0:
                    sf_data.add_tag(entryIdTag, self.__entry_id)

                else:
                    tagNames = [t[0] for t in sf_data.tags]
                    itCol = tagNames.index(entryIdTag)

                    sf_data.tags[itCol][1] = self.__entry_id

                if self.__insert_entry_id_to_loops:

                    entryIdTag = 'Entry_ID'

                    for loop in sf_data.loops:

                        lp_category = loop.category

                        if lp_category is None:
                            continue

                        if __pynmrstar_v3_2__:
                            _loop = sf_data.get_loop(lp_category)
                        else:
                            _loop = sf_data.get_loop_by_category(lp_category)

                        if entryIdTag in _loop.tags:

                            itCol = _loop.tags.index(entryIdTag)

                            for row in _loop.data:
                                row[itCol] = self.__entry_id

                        else:

                            for row in _loop.data:
                                row.append(self.__entry_id)

                            _loop.add_tag(entryIdTag)

        return True

    def __sortCSLoop(self):
        """ Sort assigned chemical shift loop if required.
        """

        if not self.__combined_mode:
            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 input_source_dic['content_subtype'] is None:
            return False

        content_subtype = 'chem_shift'

        if content_subtype not in input_source_dic['content_subtype'].keys():
            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']
        iso_number_name = item_names['isotope_number']
        atom_id_name = item_names['atom_id']

        for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

            sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

            if self.report.error.exists(file_name, sf_framecode):
                continue

            lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                    pass

            if lp_data is not None:

                atoms = []

                chains = set()

                for i in lp_data:
                    chains.add(i[chain_id_name])

                min_seq_ids = {c: 0 for c in chains}

                for i in lp_data:
                    chain_id = i[chain_id_name]
                    seq_id = i[seq_id_name]

                    if seq_id < min_seq_ids[chain_id]:
                        min_seq_ids[chain_id] = seq_id

                for l, i in enumerate(lp_data):  # noqa: E741
                    chain_id = i[chain_id_name]
                    seq_id = i[seq_id_name]
                    iso_number = i[iso_number_name]
                    atom_id = i[atom_id_name]

                    atoms.append(f"{chain_id:<4}:{seq_id - min_seq_ids[chain_id]:04d}:{iso_number:02d}:{atom_id:<8}:{l:06d}")

                sorted_atoms = sorted(atoms)

                sorted_id = []

                for j in sorted_atoms:
                    sorted_id.append(int(j.split(':')[4]))

                if sorted_id != list(range(len(lp_data))):

                    if __pynmrstar_v3_2__:
                        loop = sf_data.get_loop(lp_category)
                    else:
                        loop = sf_data.get_loop_by_category(lp_category)

                    new_loop = pynmrstar.Loop.from_scratch(lp_category)

                    for tag in loop.tags:
                        new_loop.add_tag(lp_category + '.' + tag)

                    for i in sorted_id:
                        new_loop.add_data(loop[i])

                    del sf_data[loop]

                    sf_data.add_loop(new_loop)

        return True

    def __updateAtomChemShiftId(self):
        """ Update _Atom_chem_shift.ID.
        """

        if not self.__combined_mode:
            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 file_type == 'nef':
            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'].keys():
            return False

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

        for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

            sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

            if self.report.error.exists(file_name, sf_framecode):
                continue

            if __pynmrstar_v3_2__:
                loop = sf_data.get_loop(lp_category)
            else:
                loop = sf_data.get_loop_by_category(lp_category)

            ambig_set_id_name = 'Ambiguity_set_ID'

            try:
                ambig_set_id_col = loop.tags.index(ambig_set_id_name)
            except ValueError:
                continue

            ambig_set_id_dic = {}

            if ambig_set_id_name in loop.tags:

                ambig_set_ids = []

                for row in loop:

                    ambig_set_id = row[ambig_set_id_col]

                    if ambig_set_id not in self.empty_value:
                        ambig_set_ids.append(str(ambig_set_id))

                if len(ambig_set_ids) > 0:

                    for l, ambig_set_id in enumerate(ambig_set_ids, start=1):  # noqa: E741

                        if ambig_set_id in ambig_set_id_dic:
                            continue

                        ambig_set_id_dic[ambig_set_id] = str(l)

            disordered_ambig_set_id = False

            for k, v in ambig_set_id_dic.items():
                if k != v:
                    disordered_ambig_set_id = True
                    break

            if disordered_ambig_set_id:

                for row in loop:
                    ambig_set_id = row[ambig_set_id_col]

                    if ambig_set_id not in self.empty_value:
                        row[ambig_set_id_col] = int(ambig_set_id_dic[str(ambig_set_id)])

            if 'ID' in loop.tags:
                loop.renumber_rows('ID')

            else:

                lp_tag = lp_category + '.ID'
                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(f"+NmrDpUtility.__updateAtomChemShiftId() ++ LookupError  - {err}\n")

                new_loop = pynmrstar.Loop.from_scratch(lp_category)

                new_loop.add_tag(lp_tag)

                for tag in loop.tags:
                    new_loop.add_tag(lp_category + '.' + tag)

                for j, i in enumerate(loop, start=1):
                    new_loop.add_data([str(j)] + i)

                del sf_data[loop]

                sf_data.add_loop(new_loop)

        return True

    def __updateAmbiguousAtomChemShift(self):
        """ Update _Ambiguous_atom_chem_shift loops.
        """

        if not self.__combined_mode:
            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 file_type == 'nef':
            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'].keys():
            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]

        for sf_data in self.__star_data[0].get_saveframes_by_category(sf_category):

            sf_framecode = get_first_sf_tag(sf_data, 'sf_framecode')

            if self.report.error.exists(file_name, sf_framecode):
                continue

            lp_data = next((l['data'] for l in self.__lp_data[content_subtype]
                            if l['file_name'] == file_name and l['sf_framecode'] == sf_framecode), None)  # noqa: E741

            if lp_data is None:

                try:

                    lp_data = self.__nefT.check_data(sf_data, 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:  # noqa: E722 pylint: disable=bare-except
                    pass

            if lp_data is not None:

                ambig_set_id_name = 'Ambiguity_set_ID'

                has_ambig_set_id = False

                for i in lp_data:

                    if ambig_set_id_name in i and i[ambig_set_id_name] not in self.empty_value:
                        has_ambig_set_id = True
                        break

                if has_ambig_set_id:

                    aux_lp_category = '_Ambiguous_atom_chem_shift'

                    for _loop in sf_data.loops:

                        if _loop.category == aux_lp_category:
                            del sf_data[_loop]
                            break

                    _loop = pynmrstar.Loop.from_scratch(aux_lp_category)
                    _loop.add_tag(aux_lp_category + '.Ambiguous_shift_set_ID')
                    _loop.add_tag(aux_lp_category + '.Assigned_chem_shift_list_ID')
                    _loop.add_tag(aux_lp_category + '.Atom_chem_shift_ID')

                    if self.__insert_entry_id_to_loops:
                        _loop.add_tag(aux_lp_category + '.Entry_ID')

                    for l, i in enumerate(lp_data, start=1):  # noqa: E741

                        if ambig_set_id_name in i and i[ambig_set_id_name] not in self.empty_value:

                            row = []

                            row.append(i[ambig_set_id_name])
                            row.append(i['Assigned_chem_shift_list_ID'])
                            row.append(l)

                            if self.__insert_entry_id_to_loops:
                                row.append(self.__entry_id)

                            _loop.add_data(row)

                    sf_data.add_loop(_loop)

        return True

    def __depositNmrData(self):
        """ Deposit next NMR unified data file.
        """

        if not self.__combined_mode:
            return False

        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 len(self.__star_data) == 0 or self.__star_data[0] is None:
            return False

        if self.__dstPath == self.__srcPath and self.__release_mode:
            return True

        if __pynmrstar_v3__:
            self.__star_data[0].write_to_file(self.__dstPath, skip_empty_loops=True, skip_empty_tags=False)
        else:
            self.__star_data[0].write_to_file(self.__dstPath)

        if 'nef' not in self.__op and 'deposit' in self.__op and 'nmr-cif_file_path' in self.__outputParamDict:
            star_to_cif = NmrStarToCif()

            original_file_name = ''
            if 'original_file_name' in self.__inputParamDict:
                original_file_name = self.__inputParamDict['original_file_name']

            star_to_cif.convert(self.__dstPath, self.__outputParamDict['nmr-cif_file_path'], original_file_name, 'nm-uni-str')

        return not self.report.isError()

    def __depositLegacyNmrData(self):
        """ Deposit next NMR legacy data files.
        """

        if self.__combined_mode:
            return False

        cs_file_path_list = 'chem_shift_file_path_list'

        if cs_file_path_list in self.__outputParamDict:

            for fileListId, dstPath in enumerate(self.__outputParamDict[cs_file_path_list]):

                if dstPath is None:

                    err = "Not found destination file path."

                    self.report.error.appendDescription('internal_error', "+NmrDpUtility.__depositLegacyNmrData() ++ Error  - " + err)
                    self.report.setError()

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

                    return False

                if fileListId >= len(self.__star_data) or self.__star_data[fileListId] is None:
                    return False
                # """ DAOTHER-7407: utilize NMR-STAR format normalizer of NEFTranslator v3
                # if self.__star_data_type[fileListId] == 'Loop':  # copied already
                #     continue
                # """
                if dstPath in self.__inputParamDict[cs_file_path_list]:
                    return False
                # """ DAOTHER-7407: utilize NMR-STAR format normalizer of NEFTranslator v3
                # if __pynmrstar_v3__:
                #     self.__star_data[fileListId].write_to_file(dstPath, skip_empty_loops=True, skip_empty_tags=False)
                # else:
                #     self.__star_data[fileListId].write_to_file(dstPath)
                # """
                if self.__nefT.star_data_to_nmrstar(self.__star_data_type[fileListId],
                                                    self.__star_data[fileListId],
                                                    dstPath, fileListId,
                                                    report=self.report,
                                                    leave_unmatched=self.__leave_intl_note)[0]:

                    if 'nmr-cif_file_path' in self.__outputParamDict:
                        star_to_cif = NmrStarToCif()

                        original_file_name = ''
                        if 'original_file_name' in self.__inputParamDict:
                            original_file_name = self.__inputParamDict['original_file_name']

                        star_to_cif.convert(dstPath, self.__outputParamDict['nmr-cif_file_path'], original_file_name, 'nm-shi')

            mr_file_path_list = 'restraint_file_path_list'

            if mr_file_path_list in self.__outputParamDict:

                fileListId = len(self.__outputParamDict[cs_file_path_list])

                for dstPath in self.__outputParamDict[mr_file_path_list]:

                    if dstPath is None:

                        err = "Not found destination file path."

                        self.report.error.appendDescription('internal_error', "+NmrDpUtility.__depositLegacyNmrData() ++ Error  - " + err)
                        self.report.setError()

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

                        return False

                    if fileListId >= len(self.__star_data) or self.__star_data[fileListId] is None:
                        return False
                    # """ DAOTHER-7407: utilize NMR-STAR format normalizer of NEFTranslator v3
                    # if self.__star_data_type[fileListId] == 'Loop':  # copied already
                    #     continue
                    # """
                    if dstPath in self.__inputParamDict[mr_file_path_list]:
                        return False
                    # """ DAOTHER-7407: utilize NMR-STAR format normalizer of NEFTranslator v3
                    # if __pynmrstar_v3__:
                    #     self.__star_data[fileListId].write_to_file(dstPath, skip_empty_loops=True, skip_empty_tags=False)
                    # else:
                    #     self.__star_data[fileListId].write_to_file(dstPath)
                    # """
                    self.__nefT.star_data_to_nmrstar(self.__star_data_type[fileListId], self.__star_data[fileListId], dstPath, fileListId, self.report)

                    fileListId += 1

        return not self.report.isError()

    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 False

        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.")

        out_file_path = self.__outputParamDict['nmr-star_file_path']

        try:

            is_valid, message = self.__nefT.nef_to_nmrstar(self.__dstPath, out_file_path,
                                                           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()

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

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

            return False

        if is_valid:

            if 'deposit' in self.__op and 'nmr-cif_file_path' in self.__outputParamDict:
                star_to_cif = NmrStarToCif()

                original_file_name = ''
                if 'original_file_name' in self.__inputParamDict:
                    original_file_name = self.__inputParamDict['original_file_name']

                star_to_cif.convert(out_file_path, self.__outputParamDict['nmr-cif_file_path'], original_file_name, 'nm-uni-nef')

            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()

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

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

        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 = None if 'report_file_path' not in self.__outputParamDict else self.__outputParamDict['report_file_path']
            if self.__logPath is not None:
                self.addInput('report_file_path', self.__logPath, type='file')
            self.__op = 'nmr-star-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:  # noqa: E722 pylint: disable=bare-except
            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 False

        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.")

        out_file_path = self.__outputParamDict['nef_file_path']

        try:

            is_valid, message = self.__nefT.nmrstar_to_nef(self.__dstPath, out_file_path, 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()

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

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

            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()

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

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

        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 = None if 'report_file_path' not in self.__outputParamDict else self.__outputParamDict['report_file_path']
            if self.__logPath is not None:
                self.addInput('report_file_path', self.__logPath, type='file')
            self.__op = '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:  # noqa: E722 pylint: disable=bare-except
            raise KeyError("+NmrDpUtility.__initReousrceForStr2Nef() ++ Error  - Could not find 'nef_file_path' or 'report_file_path' output parameter.")

        return False


if __name__ == '__main__':
    dp = NmrDpUtility()
