##
# File: NmrDpValidation.py
# Date: 07-Jan-2026
#
# Updates:
##
""" Wrapper class for NMR data validation.
    @author: Masashi Yokochi
"""
__docformat__ = "restructuredtext en"
__author__ = "Masashi Yokochi"
__email__ = "yokochi@protein.osaka-u.ac.jp"
__license__ = "Apache License 2.0"
__version__ = "5.0.0"

import collections
import copy
import functools
import itertools
import math
import os
import re
import shutil
from operator import itemgetter
from typing import List, Optional, Set, Tuple, Union

import numpy

import pynmrstar

try:
    from wwpdb.utils.nmr.NmrDpConstant import (CS_FILE_PATH_LIST_KEY,
                                               MR_FILE_PATH_LIST_KEY,
                                               AR_FILE_PATH_LIST_KEY,
                                               AC_FILE_PATH_LIST_KEY,
                                               NMR_CIF_FILE_PATH_KEY,
                                               NMR_CONTENT_SUBTYPES,
                                               READABLE_FILE_TYPE,
                                               SF_CATEGORIES,
                                               LP_CATEGORIES,
                                               HARD_PROBE_LIMIT,
                                               CUTOFF_AROMATIC,
                                               CUTOFF_PARAMAGNETIC,
                                               VICINITY_AROMATIC,
                                               VICINITY_PARAMAGNETIC,
                                               CUTOFF_BOND_LENGTH,
                                               MAGIC_ANGLE,
                                               INCONSIST_OVER_CONFLICTED,
                                               R_CONFLICTED_DIST_RESTRAINT,
                                               R_INCONSISTENT_DIST_RESTRAINT,
                                               INDEX_TAGS,
                                               WEIGHT_TAGS,
                                               CONSIST_ID_TAGS,
                                               PK_KEY_ITEMS,
                                               DATA_ITEMS,
                                               POTENTIAL_ITEMS,
                                               CONSIST_DATA_ITEMS,
                                               NUM_DIM_ITEMS,
                                               ALLOWED_TAGS,
                                               DISALLOWED_PK_TAGS,
                                               SF_TAG_PREFIXES,
                                               SF_ALLOWED_TAGS,
                                               AUX_LP_CATEGORIES,
                                               LINKED_LP_CATEGORIES,
                                               AUX_ALLOWED_TAGS,
                                               ITEM_NAMES_IN_CS_LOOP,
                                               ITEM_NAMES_IN_PK_LOOP,
                                               ITEM_NAMES_IN_DIST_LOOP,
                                               ITEM_NAMES_IN_DIHED_LOOP,
                                               ITEM_NAMES_IN_RDC_LOOP,
                                               LOW_SEQ_COVERAGE,
                                               LARGE_ASYM_ID,
                                               LEN_MAJOR_ASYM_ID,
                                               EMPTY_VALUE,
                                               STD_MON_DICT,
                                               PROTON_BEGIN_CODE,
                                               PSE_PRO_BEGIN_CODE,
                                               AMINO_PROTON_CODE,
                                               RDC_BB_PAIR_CODE,
                                               PARAMAGNETIC_ELEMENTS,
                                               FERROMAGNETIC_ELEMENTS,
                                               NON_METAL_ELEMENTS,
                                               MAX_DIM_NUM_OF_SPECTRA,
                                               MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK,
                                               ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS,
                                               ALLOWED_AMBIGUITY_CODES,
                                               PERIPH_OFFSET_ATTEMPT,
                                               PDB_ID_PAT,
                                               DEP_ID_PAT,
                                               BMRB_NMR_STAR_FILE_NAME_PAT,
                                               INTNL_ANY_MR_FILE_NAME_PAT,
                                               PDB_MR_FILE_NAME_PAT,
                                               WS_PAT,
                                               CONCAT_SEQ_ID_INS_CODE_PAT,
                                               CS_UNCERT_MAX,
                                               DIST_UNCERT_MAX,
                                               ANGLE_UNCERT_MAX,
                                               RDC_UNCERT_MAX,
                                               DIST_AMBIG_LOW,
                                               DIST_AMBIG_UP,
                                               REPRESENTATIVE_ASYM_ID,
                                               NMR_STAR_LP_KEY_ITEMS,
                                               NMR_STAR_LP_DATA_ITEMS,
                                               NMR_STAR_LP_DATA_ITEMS_INS_CODE,
                                               THRESHOLD_FOR_CIRCULAR_SHIFT,
                                               PLANE_LIKE_LOWER_LIMIT,
                                               PLANE_LIKE_UPPER_LIMIT,
                                               C_CARBONYL_CENTER_MAX,
                                               C_CARBONYL_CENTER_MIN,
                                               C_AROMATIC_CENTER_MAX,
                                               C_AROMATIC_CENTER_MIN_TOR,
                                               C_ALIPHATIC_CENTER_MAX,
                                               C_ALIPHATIC_CENTER_MIN,
                                               C_METHYL_CENTER_MAX,
                                               C_METHYL_CENTER_MIN,
                                               DEFAULT_DATUM_COUNTER)
    from wwpdb.utils.nmr.NmrDpRegistry import NmrDpRegistry
    from wwpdb.utils.nmr.NmrDpMrSplitter import (detect_bom,
                                                 convert_codec,
                                                 convert_rtf_to_ascii,
                                                 is_rtf_file)
    from wwpdb.utils.nmr.NmrDpReport import NmrDpReportInputSource
    from wwpdb.utils.nmr.AlignUtil import (letterToDigit,
                                           alignPolymerSequence,
                                           assignPolymerSequence)
    from wwpdb.utils.nmr.CifToNmrStar import (has_key_value,
                                              get_first_sf_tag,
                                              set_sf_tag)
    from wwpdb.utils.nmr.NmrVrptUtility import (uncompress_gzip_file,
                                                compress_as_gzip_file,
                                                write_as_pickle,
                                                to_np_array,
                                                distance,
                                                to_unit_vector,
                                                dihedral_angle)
    from wwpdb.utils.nmr.mr.ParserListenerUtil import (translateToStdResName,
                                                       translateToStdAtomName,
                                                       isIdenticalRestraint,
                                                       isAmbigAtomSelection,
                                                       getTypeOfDihedralRestraint,
                                                       isLikeHis,
                                                       getRestraintName,
                                                       incListIdCounter,
                                                       getSaveframe,
                                                       getLoop,
                                                       getRowForStrMr,
                                                       assignCoordPolymerSequenceWithChainId,
                                                       selectCoordAtoms,
                                                       getPotentialType)
    from wwpdb.utils.nmr.rci.RCI import RCI
except ImportError:
    from nmr.NmrDpConstant import (CS_FILE_PATH_LIST_KEY,
                                   MR_FILE_PATH_LIST_KEY,
                                   AR_FILE_PATH_LIST_KEY,
                                   AC_FILE_PATH_LIST_KEY,
                                   NMR_CIF_FILE_PATH_KEY,
                                   NMR_CONTENT_SUBTYPES,
                                   READABLE_FILE_TYPE,
                                   SF_CATEGORIES,
                                   LP_CATEGORIES,
                                   HARD_PROBE_LIMIT,
                                   CUTOFF_AROMATIC,
                                   CUTOFF_PARAMAGNETIC,
                                   VICINITY_AROMATIC,
                                   VICINITY_PARAMAGNETIC,
                                   CUTOFF_BOND_LENGTH,
                                   MAGIC_ANGLE,
                                   INCONSIST_OVER_CONFLICTED,
                                   R_CONFLICTED_DIST_RESTRAINT,
                                   R_INCONSISTENT_DIST_RESTRAINT,
                                   INDEX_TAGS,
                                   WEIGHT_TAGS,
                                   CONSIST_ID_TAGS,
                                   PK_KEY_ITEMS,
                                   DATA_ITEMS,
                                   POTENTIAL_ITEMS,
                                   CONSIST_DATA_ITEMS,
                                   NUM_DIM_ITEMS,
                                   ALLOWED_TAGS,
                                   DISALLOWED_PK_TAGS,
                                   SF_TAG_PREFIXES,
                                   SF_ALLOWED_TAGS,
                                   AUX_LP_CATEGORIES,
                                   LINKED_LP_CATEGORIES,
                                   AUX_ALLOWED_TAGS,
                                   ITEM_NAMES_IN_CS_LOOP,
                                   ITEM_NAMES_IN_PK_LOOP,
                                   ITEM_NAMES_IN_DIST_LOOP,
                                   ITEM_NAMES_IN_DIHED_LOOP,
                                   ITEM_NAMES_IN_RDC_LOOP,
                                   LOW_SEQ_COVERAGE,
                                   LARGE_ASYM_ID,
                                   LEN_MAJOR_ASYM_ID,
                                   EMPTY_VALUE,
                                   STD_MON_DICT,
                                   PROTON_BEGIN_CODE,
                                   PSE_PRO_BEGIN_CODE,
                                   AMINO_PROTON_CODE,
                                   RDC_BB_PAIR_CODE,
                                   PARAMAGNETIC_ELEMENTS,
                                   FERROMAGNETIC_ELEMENTS,
                                   NON_METAL_ELEMENTS,
                                   MAX_DIM_NUM_OF_SPECTRA,
                                   MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK,
                                   ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS,
                                   ALLOWED_AMBIGUITY_CODES,
                                   PERIPH_OFFSET_ATTEMPT,
                                   PDB_ID_PAT,
                                   DEP_ID_PAT,
                                   BMRB_NMR_STAR_FILE_NAME_PAT,
                                   INTNL_ANY_MR_FILE_NAME_PAT,
                                   PDB_MR_FILE_NAME_PAT,
                                   WS_PAT,
                                   CONCAT_SEQ_ID_INS_CODE_PAT,
                                   CS_UNCERT_MAX,
                                   DIST_UNCERT_MAX,
                                   ANGLE_UNCERT_MAX,
                                   RDC_UNCERT_MAX,
                                   DIST_AMBIG_LOW,
                                   DIST_AMBIG_UP,
                                   REPRESENTATIVE_ASYM_ID,
                                   NMR_STAR_LP_KEY_ITEMS,
                                   NMR_STAR_LP_DATA_ITEMS,
                                   NMR_STAR_LP_DATA_ITEMS_INS_CODE,
                                   THRESHOLD_FOR_CIRCULAR_SHIFT,
                                   PLANE_LIKE_LOWER_LIMIT,
                                   PLANE_LIKE_UPPER_LIMIT,
                                   C_CARBONYL_CENTER_MAX,
                                   C_CARBONYL_CENTER_MIN,
                                   C_AROMATIC_CENTER_MAX,
                                   C_AROMATIC_CENTER_MIN_TOR,
                                   C_ALIPHATIC_CENTER_MAX,
                                   C_ALIPHATIC_CENTER_MIN,
                                   C_METHYL_CENTER_MAX,
                                   C_METHYL_CENTER_MIN,
                                   DEFAULT_DATUM_COUNTER)
    from nmr.NmrDpRegistry import NmrDpRegistry
    from nmr.NmrDpMrSplitter import (detect_bom,
                                     convert_codec,
                                     convert_rtf_to_ascii,
                                     is_rtf_file)
    from nmr.NmrDpReport import NmrDpReportInputSource
    from nmr.AlignUtil import (letterToDigit,
                               alignPolymerSequence,
                               assignPolymerSequence)
    from nmr.CifToNmrStar import (has_key_value,
                                  get_first_sf_tag,
                                  set_sf_tag)
    from nmr.NmrVrptUtility import (uncompress_gzip_file,
                                    compress_as_gzip_file,
                                    write_as_pickle,
                                    to_np_array,
                                    distance,
                                    to_unit_vector,
                                    dihedral_angle)
    from nmr.mr.ParserListenerUtil import (translateToStdResName,
                                           translateToStdAtomName,
                                           isIdenticalRestraint,
                                           isAmbigAtomSelection,
                                           getTypeOfDihedralRestraint,
                                           isLikeHis,
                                           getRestraintName,
                                           incListIdCounter,
                                           getSaveframe,
                                           getLoop,
                                           getRowForStrMr,
                                           assignCoordPolymerSequenceWithChainId,
                                           selectCoordAtoms,
                                           getPotentialType)
    from nmr.rci.RCI import RCI


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

    if comp_id == atom_id:
        return False

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


def probability_density(value: float, mean: float, stddev: float) -> float:
    """ 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: Optional[float], cb_chem_shift: Optional[float]
                                   ) -> Tuple[float, float]:
    """ 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
                                         ) -> Tuple[float, float]:
    """ 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: Optional[float], cd2_chem_shift: Optional[float],
                                        nd1_chem_shift: Optional[float], ne2_chem_shift: Optional[float]
                                        ) -> Tuple[float, float]:
    """ 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: Optional[float], cd2_chem_shift: Optional[float]
                                     ) -> Tuple[float, float]:
    """ Return prediction of rotermeric state of Leucine using assigned CD1 and CD2 chemical shifts.
        @return: probability of gauche+, trans, gauche-
        Reference:
          Dependence of Amino Acid Side Chain 13C Shifts on Dihedral Angle: Application to Conformational Analysis.
          Robert E. London, Brett D. Wingad, and Geoffrey A. Mueller.
          Journal of the American Chemical Society 2008 130 (33), 11097-11105
          DOI: 10.1021/ja802729t
    """

    if None not in (cd1_chem_shift, cd2_chem_shift):

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

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

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

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

    gm = 1.0
    gp = 1.0
    t = 1.0

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

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

    total = gm + gp + t

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

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


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

    if cd1_chem_shift is None:
        return 0.0, 0.0, 0.0

    if cd1_chem_shift < 9.3:
        return 0.0, 0.0, 1.0

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

    pgm = (14.8 - cd1_chem_shift) / 5.5

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


def is_like_planality_boundary(row: dict, lower_limit_name: str, upper_limit_name: str) -> bool:
    """ Return whether boundary conditions like planality restraint.
    """

    try:

        upper_limit = float(row[upper_limit_name])
        lower_limit = float(row[lower_limit_name])

        _array = numpy.array([upper_limit, lower_limit], dtype=float)

        shift = None
        if numpy.nanmin(_array) >= THRESHOLD_FOR_CIRCULAR_SHIFT:
            shift = -(numpy.nanmax(_array) // 360) * 360
        elif numpy.nanmax(_array) <= -THRESHOLD_FOR_CIRCULAR_SHIFT:
            shift = -(numpy.nanmin(_array) // 360) * 360
        if shift is not None:
            upper_limit += shift
            lower_limit += shift

        return PLANE_LIKE_LOWER_LIMIT <= lower_limit < 0.0 < upper_limit <= PLANE_LIKE_UPPER_LIMIT\
            or PLANE_LIKE_LOWER_LIMIT <= lower_limit - 180.0 < 0.0 < upper_limit - 180.0 <= PLANE_LIKE_UPPER_LIMIT\
            or PLANE_LIKE_LOWER_LIMIT <= lower_limit - 360.0 < 0.0 < upper_limit - 360.0 <= PLANE_LIKE_UPPER_LIMIT

    except (ValueError, TypeError):
        return False


def get_atom_name_mapping(lp: pynmrstar.Loop, list_of_tags: List[List[str]]
                          ) -> Optional[List[dict]]:
    """ Return atom name mapping history for each comp_id.
        Each tags should be array of 'comp_id', 'atom_id', and 'atom_name'.
    """

    mapping, identity_mapping = [], []
    list_of_dat = [None] * len(list_of_tags)

    for idx, tags in enumerate(list_of_tags):
        if set(tags) & set(lp.tags) == set(tags):
            list_of_dat[idx] = lp.get_tag(tags)
            for row in list_of_dat[idx]:
                if row[0] in EMPTY_VALUE or row[1] in EMPTY_VALUE or row[2] in EMPTY_VALUE or row[1] != row[2]:
                    continue
                key = (row[0], row[2])
                if key not in identity_mapping:
                    identity_mapping.append(key)

    for dat in list_of_dat:
        if dat is None:
            continue
        for row in dat:
            if row[0] in EMPTY_VALUE or row[1] in EMPTY_VALUE or row[2] in EMPTY_VALUE or row[1] == row[2]:
                continue
            comp_id = row[0]
            atom_id = row[1]
            atom_name = row[2]

            if not any(m['comp_id'] == comp_id for m in mapping):
                mapping.append({'comp_id': comp_id, 'history': []})

            history = next(m['history'] for m in mapping if m['comp_id'] == comp_id)

            if not any(True for h in history if h['atom_name'] == atom_name):
                history.append({'atom_name': atom_name, 'atom_id': [atom_name] if (comp_id, atom_name) in identity_mapping else []})

            h = next(h for h in history if h['atom_name'] == atom_name)
            if atom_id not in h['atom_id']:
                h['atom_id'].append(atom_id)

    if len(mapping) == 0:
        mapping = None

    else:
        for m in mapping:
            for h in m['history']:
                h['atom_id'] = sorted(h['atom_id'])
            m['history'] = sorted(m['history'], key=itemgetter('atom_name'))
        mapping = sorted(mapping, key=lambda x: (len(x['comp_id']), x['comp_id']))

    return mapping


class NmrDpValidation:
    """ Wrapper class for NMR data validation.
    """
    __slots__ = ('__class_name__',
                 '__version__',
                 '__reg',
                 '__rci')

    def __init__(self, registry: NmrDpRegistry) -> None:
        self.__class_name__ = self.__class__.__name__
        self.__version__ = __version__

        self.__reg = registry

        # RCI
        self.__rci = RCI(False, self.__reg.log)

    def getChemCompNameAndStatusOf(self, comp_id: str
                                   ) -> Tuple[bool, Optional[str], Optional[str]]:
        """ Return _chem_comp.name and release status a given CCD ID, if possible.
        """

        cc_name = cc_rel_status = processing_site = None

        if len(self.__reg.star_data_type) > 0 and self.__reg.star_data_type[0] == 'Entry'\
           and 'chem_comp' in self.__reg.sf_category_list:
            chem_comp_sf = next((sf for sf in self.__reg.star_data[0].frame_list if sf.name == f'chem_comp_{comp_id}'), None)

            if chem_comp_sf is not None:
                cc_name = get_first_sf_tag(chem_comp_sf, 'Name')
                if cc_name in EMPTY_VALUE:
                    cc_name = None
                processing_site = get_first_sf_tag(chem_comp_sf, 'Processing_site')
                if processing_site in EMPTY_VALUE:
                    processing_site = None

        if self.__reg.ccU.updateChemCompDict(comp_id):  # matches with comp_id in CCD
            is_valid = True

            if cc_name is None:
                cc_name = self.__reg.ccU.lastChemCompDict['name']

            if processing_site is not None and processing_site.startswith('BMRB'):
                is_valid = False
                cc_name += f', processing site {processing_site}'
            else:
                cc_rel_status = self.__reg.ccU.lastChemCompDict['release_status']

        else:
            is_valid = False

        return is_valid, cc_name, cc_rel_status

    def isNmrAtomName(self, comp_id: str, atom_id: str) -> bool:
        """ Return whether a given atom_id uses NMR conventional atom name.
        """

        return ((atom_id in ('HN', 'CO') and self.__reg.csStat.peptideLike(comp_id))
                or atom_id.startswith('Q') or atom_id.startswith('M')
                or atom_id.endswith('%') or atom_id.endswith('#')
                or self.__reg.csStat.getMaxAmbigCodeWoSetId(comp_id, atom_id) == 0)

    def getAtomIdListInXplor(self, comp_id: str, atom_id: str
                             ) -> List[str]:
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id in XPLOR atom nomenclature.
        """

        atom_list, _, details = self.__reg.nefT.get_valid_star_atom_in_xplor(comp_id, atom_id)

        return atom_list if details is None else []

    def getAtomIdListInXplorForLigandRemap(self, comp_id: str, atom_id: str, coord_atom_site: dict
                                           ) -> List[str]:
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id in XPLOR atom nomenclature
            in reference to coordinates' alternative atom IDs. (DAOTHER-9286)
        """

        return self.__reg.nefT.get_valid_star_atom_in_xplor_for_ligand_remap(comp_id, atom_id, coord_atom_site)[0]

    def getRepAtomId(self, comp_id: str, atom_id: str) -> str:
        """ Return a representative atom ID in IUPAC atom nomenclature for a given atom_id.
        """

        _atom_id = self.__reg.nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

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

    def getAtomIdList(self, comp_id: str, atom_id: str
                      ) -> List[str]:
        """ Return atom ID list in IUPAC atom nomenclature for a given atom_id.
        """

        return self.__reg.nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

    def __getAtomIdListWithAmbigCode(self, comp_id: str, atom_id: str, leave_unmatched: bool = True
                                     ) -> Tuple[List[str], Optional[int], Optional[str]]:
        """ Return lists of atom ID, ambiguity_code, details in IUPAC atom nomenclature for a given conventional NMR atom name.
            @see: NefTranslator.get_valid_star_atom()
        """

        return self.__reg.nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=leave_unmatched)

    def getReducedAtomNotation(self, chain_id_name: str, chain_id: str, seq_id_name: str, seq_id: int,
                               comp_id_name: str, comp_id: str, atom_id_name: str, atom_id: str) -> str:
        """ Return reduced form of atom notation.
        """

        if self.__reg.reduced_atom_notation:
            return f"{chain_id}:{seq_id}:{comp_id}:{atom_id}"

        return f"{chain_id_name} {chain_id}, {seq_id_name} {seq_id}, {comp_id_name} {comp_id}, {atom_id_name} {atom_id}"

    def __getReducedAtomNotations(self, key_items: List[dict], row_data: dict) -> str:
        """ Return reduced from of series of atom notations.
        """

        msg = ''

        if self.__reg.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 validateInputSource(self, srcPath: str = None) -> bool:
        """ Validate NMR data as primary input source.
        """

        if srcPath is None:
            srcPath = self.__reg.srcPath

        is_done = True

        if self.__reg.combined_mode:

            self.__reg.dirPath = os.path.dirname(srcPath)

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

                _srcPath = None

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

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

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

            if not is_valid:

                _srcPath = srcPath + '.cif2str'
                if self.__reg.c2S.convert(srcPath, _srcPath):
                    is_valid, message = self.__reg.nefT.validate_file(_srcPath, 'A')  # 'A' for NMR unified data
                    self.__reg.srcPath = srcPath = _srcPath

            self.__reg.original_error_message.append(message)

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

            input_source = self.__reg.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 {READABLE_FILE_TYPE[file_type]} file, "\
                          f"but recognized as {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.__reg.report.error.appendDescription('content_mismatch',
                                                              {'file_name': file_name, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                             f"++ Error  - {err}\n")

                    is_done = False

                else:

                    is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(srcPath)

                    if len(self.__reg.star_data_type) > 0:
                        del self.__reg.star_data_type[-1]
                        del self.__reg.star_data[-1]

                    self.__reg.star_data_type.append(star_data_type)
                    self.__reg.star_data.append(star_data)

                    self.__reg.dpA.rescueFormerNef(0)
                    self.__reg.dpA.rescueImmatureStr(0)

            else:

                if not self.__reg.dpA.fixFormatIssueOfInputSource(0, file_name, file_type, srcPath, 'A', message):

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

                        _, star_data_type, star_data = self.__reg.nefT.read_input_file(srcPath)

                        if len(self.__reg.star_data_type) > 0:
                            del self.__reg.star_data_type[-1]
                            del self.__reg.star_data[-1]

                        self.__reg.star_data_type.append(star_data_type)
                        self.__reg.star_data.append(star_data)

                        self.__reg.dpA.rescueFormerNef(0)
                        self.__reg.dpA.rescueImmatureStr(0)

                    is_done = False

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

            if is_done and file_type == 'nmr-star':
                for sf in self.__reg.star_data[0].get_saveframes_by_category('assembly'):
                    self.__reg.assembly_name = get_first_sf_tag(sf, 'Name', '?')
                    details = get_first_sf_tag(sf, 'Details')
                    if details not in EMPTY_VALUE and WS_PAT.match(details):
                        set_sf_tag(sf, 'Details', None)
                    break

            if self.__reg.op == 'nmr-str-replace-cs':

                for csListId, cs in enumerate(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY], start=1):

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

                    if csPath.endswith('.gz'):

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

                        if not os.path.exists(_csPath):

                            try:

                                uncompress_gzip_file(csPath, _csPath)

                            except Exception as e:  # pylint: disable=broad-exception-caught

                                self.__reg.report.error.appendDescription('internal_error',
                                                                          f"+{self.__class_name__}.validateInputSource() "
                                                                          "++ Error  - " + str(e))

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                         "++ Error  - {str(e)}\n")

                                return False

                        csPath = _csPath

                    if not os.path.basename(csPath).startswith('bmr'):

                        _csPath = csPath + '.cif2str'
                        if not self.__reg.c2S.convert(csPath, _csPath,
                                                      originalFileName=cs.get('original_file_name') if isinstance(cs, dict) else None):  # noqa: E501, pylint: disable=line-too-long
                            _csPath = csPath

                        csPath = _csPath

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

                    _csPath = None

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

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

                    allow_empty = self.__reg.bmrb_only and self.__reg.internal_mode\
                        and (NMR_CIF_FILE_PATH_KEY in self.__reg.inputParamDict
                             or (csListId == 1 and len(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]) > 1))

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

                    self.__reg.original_error_message.append(message)

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

                    input_source = self.__reg.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 is_valid:

                        if _file_type != file_type:

                            if self.__reg.internal_mode and _file_type == 'nef':

                                _csPath = csPath + '.nef2str'

                                try:

                                    is_valid, message = self.__reg.nefT.nef_to_nmrstar(csPath, _csPath)

                                    if is_valid:
                                        csPath = _csPath

                                        _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

                                        self.__reg.has_legacy_sf_issue = False

                                        if star_data_type == 'Saveframe':
                                            self.__reg.has_legacy_sf_issue = True

                                            self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath,
                                                                                       'S', message,
                                                                                       allowEmpty=allow_empty,
                                                                                       hasLegacySfIssue=self.__reg.has_legacy_sf_issue)  # noqa: E501, pylint: disable=line-too-long

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

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

                                            self.__reg.dpA.rescueFormerNef(csListId)
                                            self.__reg.dpA.rescueImmatureStr(csListId)

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

                                except Exception as e:  # pylint: disable=broad-exception-caught

                                    err = f"{file_name!r} is not compliant with the {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))

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

                                    self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                         f"++ Error  - {file_name} {err}\n")

                            else:

                                err = f"{file_name!r} was selected as {READABLE_FILE_TYPE[file_type]} file, "\
                                      f"but recognized as {READABLE_FILE_TYPE[_file_type]} file."
                                # DAOTHER-5673
                                err += " Please re-upload the NEF file as an NMR unified data file." if _file_type == 'nef'\
                                    else " Please re-upload the file."
                                if len(message['error']) > 0:
                                    for err_message in message['error']:
                                        if 'No such file or directory' not in err_message:
                                            err += ' ' + re.sub('not in list', 'unknown item.', err_message)

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

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                         f"++ Error  - {err}\n")

                                is_done = False

                        else:

                            _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

                            self.__reg.has_legacy_sf_issue = False

                            if star_data_type == 'Saveframe':
                                self.__reg.has_legacy_sf_issue = True

                                self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message,
                                                                           allowEmpty=allow_empty,
                                                                           hasLegacySfIssue=self.__reg.has_legacy_sf_issue)

                                _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

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

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

                                self.__reg.dpA.rescueFormerNef(csListId)
                                self.__reg.dpA.rescueImmatureStr(csListId)

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

                    else:

                        if not self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message,
                                                                          allowEmpty=allow_empty,
                                                                          hasLegacySfIssue=self.__reg.has_legacy_sf_issue):
                            is_done = False

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

        else:

            for csListId, cs in enumerate(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]):

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

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

                if csPath.endswith('.gz'):

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

                    if not os.path.exists(_csPath):

                        try:

                            uncompress_gzip_file(csPath, _csPath)

                        except Exception as e:  # pylint: disable=broad-exception-caught

                            self.__reg.report.error.appendDescription('internal_error',
                                                                      f"+{self.__class_name__}.validateInputSource() "
                                                                      "++ Error  - " + str(e))

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                     f"++ Error  - {str(e)}\n")

                            return False

                    csPath = _csPath

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

                    _csPath = csPath + '.cif2str'
                    if not self.__reg.c2S.convert(csPath, _csPath,
                                                  originalFileName=cs.get('original_file_name') if isinstance(cs, dict) else None):
                        _csPath = csPath

                    csPath = _csPath

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

                _csPath = None

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

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

                if self.__reg.op == 'nmr-cs-mr-merge':

                    dir_path = os.path.dirname(csPath)

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

                    try:

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

                        cs_file_name = os.path.basename(csPath)

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

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

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

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

                            os.symlink(cs_file_path, cs_file_link)

                    except OSError:
                        pass

                allow_empty = self.__reg.bmrb_only and self.__reg.internal_mode\
                    and (NMR_CIF_FILE_PATH_KEY in self.__reg.inputParamDict
                         or (csListId == 0 and len(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]) > 1))

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

                self.__reg.original_error_message.append(message)

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

                input_source = self.__reg.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_KEY in self.__reg.outputParamDict:
                    if csListId < len(self.__reg.outputParamDict[CS_FILE_PATH_LIST_KEY]):
                        dstPath = self.__reg.outputParamDict[CS_FILE_PATH_LIST_KEY][csListId]
                        if dstPath is not None and dstPath not in self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]:
                            shutil.copyfile(csPath, dstPath)

                if is_valid:

                    if _file_type != file_type:

                        if self.__reg.internal_mode and _file_type == 'nef':

                            _csPath = csPath + '.nef2str'

                            try:

                                is_valid, message = self.__reg.nefT.nef_to_nmrstar(csPath, _csPath)

                                if is_valid:
                                    csPath = _csPath

                                    _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

                                    self.__reg.has_legacy_sf_issue = False

                                    if star_data_type == 'Saveframe':
                                        self.__reg.has_legacy_sf_issue = True

                                        self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath,
                                                                                   'S', message,
                                                                                   allowEmpty=allow_empty,
                                                                                   hasLegacySfIssue=self.__reg.has_legacy_sf_issue)

                                        _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

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

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

                                        self.__reg.dpA.rescueFormerNef(csListId)
                                        self.__reg.dpA.rescueImmatureStr(csListId)

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

                            except Exception as e:  # pylint: disable=broad-exception-caught

                                err = f"{file_name!r} is not compliant with the {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))

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

                                self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                     f"++ Error  - {file_name} {err}\n")

                        else:

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

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

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

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                     f"++ Error  - {err}\n")

                            is_done = False

                    else:

                        _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

                        self.__reg.has_legacy_sf_issue = False

                        if star_data_type == 'Saveframe':
                            self.__reg.has_legacy_sf_issue = True

                            self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message,
                                                                       allowEmpty=allow_empty,
                                                                       hasLegacySfIssue=self.__reg.has_legacy_sf_issue)

                            _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(csPath)

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

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

                            self.__reg.dpA.rescueFormerNef(csListId)
                            self.__reg.dpA.rescueImmatureStr(csListId)

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

                else:

                    if not self.__reg.dpA.fixFormatIssueOfInputSource(csListId, file_name, file_type, csPath, 'S', message,
                                                                      allowEmpty=allow_empty,
                                                                      hasLegacySfIssue=self.__reg.has_legacy_sf_issue):
                        is_done = False

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

            self.__reg.legacy_dist_restraint_uploaded = False

            if MR_FILE_PATH_LIST_KEY in self.__reg.inputParamDict:

                for mr in self.__reg.inputParamDict[MR_FILE_PATH_LIST_KEY]:

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

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

                    _mrPath = None

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

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

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

                    if is_valid:
                        self.__reg.legacy_dist_restraint_uploaded = True

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

                has_atypical_restraint = False

                if AR_FILE_PATH_LIST_KEY in self.__reg.inputParamDict:

                    for ar in self.__reg.inputParamDict[AR_FILE_PATH_LIST_KEY]:
                        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.__reg.legacy_dist_restraint_uploaded or has_atypical_restraint else 'R'

                file_path_list_len = self.__reg.cs_file_path_list_len

                for mr in self.__reg.inputParamDict[MR_FILE_PATH_LIST_KEY]:

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

                    if os.path.exists(mrPath + '-corrected'):
                        mrPath = mrPath + '-corrected'

                    if self.__reg.op == 'nmr-cs-mr-merge':

                        _mrPath = mrPath + '.cif2str'
                        if not self.__reg.c2S.convert(mrPath, _mrPath,
                                                      originalFileName=mr.get('original_file_name') if isinstance(mr, dict) else None):  # noqa: E501, pylint: disable=line-too-long
                            mrPath = _mrPath

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

                    _mrPath = None

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

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

                    is_valid, message = self.__reg.nefT.validate_file(mrPath, file_subtype)

                    self.__reg.original_error_message.append(message)

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

                    input_source = self.__reg.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_KEY in self.__reg.outputParamDict:
                        if file_path_list_len - self.__reg.cs_file_path_list_len <\
                           len(self.__reg.outputParamDict[MR_FILE_PATH_LIST_KEY]):
                            dstPath = self.__reg.outputParamDict[MR_FILE_PATH_LIST_KEY][
                                file_path_list_len - self.__reg.cs_file_path_list_len]
                            if dstPath is not None and dstPath not in self.__reg.inputParamDict[MR_FILE_PATH_LIST_KEY]:
                                shutil.copyfile(mrPath, dstPath)

                    if is_valid:

                        if _file_type != file_type:

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

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

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

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                     f"++ Error  - {err}\n")

                            is_done = False

                        else:

                            _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(mrPath)

                            self.__reg.has_legacy_sf_issue = False

                            if star_data_type == 'Saveframe':
                                self.__reg.has_legacy_sf_issue = True

                                self.__reg.dpA.fixFormatIssueOfInputSource(file_path_list_len, file_name, file_type,
                                                                           mrPath, file_subtype, message,
                                                                           hasLegacySfIssue=self.__reg.has_legacy_sf_issue)

                                _is_done, star_data_type, star_data = self.__reg.nefT.read_input_file(mrPath)

                            self.__reg.star_data_type.append(star_data_type)
                            self.__reg.star_data.append(star_data)

                            if not (self.__reg.has_legacy_sf_issue and _is_done and star_data_type == 'Entry'):
                                if len(self.__reg.star_data_type) > file_path_list_len:
                                    self.__reg.star_data_type[file_path_list_len] = star_data_type
                                    self.__reg.star_data[file_path_list_len] = star_data
                                else:

                                    self.__reg.dpA.rescueFormerNef(file_path_list_len)
                                    self.__reg.dpA.rescueImmatureStr(file_path_list_len)

                            if not _is_done:
                                is_done = False

                    else:

                        if not self.__reg.dpA.fixFormatIssueOfInputSource(file_path_list_len, file_name, file_type,
                                                                          mrPath, file_subtype, message,
                                                                          hasLegacySfIssue=self.__reg.has_legacy_sf_issue):
                            is_done = False

                    file_path_list_len += 1

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

            if AR_FILE_PATH_LIST_KEY in self.__reg.inputParamDict:

                for ar in self.__reg.inputParamDict[AR_FILE_PATH_LIST_KEY]:
                    arPath = ar['file_name']

                    if arPath.endswith('.gz'):

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

                        if not os.path.exists(_arPath):

                            try:

                                uncompress_gzip_file(arPath, _arPath)

                            except Exception as e:  # pylint: disable=broad-exception-caught

                                self.__reg.report.error.appendDescription('internal_error',
                                                                          f"+{self.__class_name__}.validateInputSource() "
                                                                          "++ Error  - " + str(e))

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateInputSource() "
                                                         f"++ Error  - {str(e)}\n")

                                return False

                        arPath = _arPath

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

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

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

                    ar['file_name'] = arPath

            if AC_FILE_PATH_LIST_KEY in self.__reg.inputParamDict and self.__reg.bmrb_only and self.__reg.conversion_server:

                for acs in self.__reg.inputParamDict[AC_FILE_PATH_LIST_KEY]:
                    acsPath = acs['file_name']

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

                    if codec != 'utf-8':
                        acsPath_ = acsPath + '~'
                        convert_codec(acsPath, acsPath_, codec, 'utf-8')
                        acsPath = acsPath_

                    if is_rtf_file(acsPath):
                        acsPath_ = acsPath + '.rtf2txt'
                        convert_rtf_to_ascii(acsPath, acsPath_)
                        acsPath = acsPath_

                    acs['file_name'] = acsPath

            if self.__reg.bmrb_only and self.__reg.internal_mode and len(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]) > 1:
                for csListId, cs in enumerate(self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY]):
                    if csListId == 0:
                        dst_sf_category_list, _ = self.__reg.nefT.get_inventory_list(self.__reg.star_data[0])
                        if 'assigned_chemical_shifts' in dst_sf_category_list:
                            for sf in self.__reg.star_data[0].get_saveframes_by_category('assigned_chemical_shifts'):
                                self.__reg.star_data[0].remove_saveframe(sf.name)
                        continue
                    if csListId < len(self.__reg.star_data) and self.__reg.star_data_type[csListId] == 'Entry'\
                       and self.__reg.star_data[csListId] is not None:
                        src_sf_category_list, _ = self.__reg.nefT.get_inventory_list(self.__reg.star_data[csListId])

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

            if self.__reg.bmrb_only and self.__reg.internal_mode and self.__reg.srcNmrCifPath is not None:

                is_valid, message = self.__reg.nefT.validate_file(self.__reg.srcNmrCifPath, 'A')  # 'A' for NMR unified data

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

                file_name = self.__reg.srcNmrCifPath
                file_type = 'nmr-star'

                if is_valid:

                    if _file_type == file_type:

                        _is_done, _star_data_type, _star_data = self.__reg.nefT.read_input_file(self.__reg.srcNmrCifPath)

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

                            self.__reg.nmr_cif_sf_category_list, _ = self.__reg.nefT.get_inventory_list(_star_data)
                            dst_sf_category_list, _ = self.__reg.nefT.get_inventory_list(self.__reg.star_data[0])
                            self.__reg.orig_cst_sf = None

                            # give priority to cs data of the combined file over ones of the cs-annotate file
                            if 'assigned_chemical_shifts' in self.__reg.nmr_cif_sf_category_list:
                                if 'assigned_chemical_shifts' in dst_sf_category_list:
                                    dst_sf_tags = []
                                    for sf in self.__reg.star_data[0].get_saveframes_by_category('assigned_chemical_shifts'):
                                        dst_sf_tags.append(copy.copy(sf.tags))
                                        self.__reg.star_data[0].remove_saveframe(sf.name)
                                for idx, _sf in enumerate(_star_data.get_saveframes_by_category('assigned_chemical_shifts')):
                                    if idx < len(dst_sf_tags):
                                        for tag in dst_sf_tags[idx]:
                                            if tag[0] not in EMPTY_VALUE and len(get_first_sf_tag(_sf, tag[0])) == 0:
                                                set_sf_tag(_sf, tag[0], tag[1])
                                    self.__reg.star_data[0].add_saveframe(_sf)

                            # move restraints of the combined file to the primary file
                            for src_sf_category in self.__reg.nmr_cif_sf_category_list:
                                if src_sf_category == 'assigned_chemical_shifts':
                                    continue
                                if src_sf_category == 'constraint_statistics':
                                    for _sf in _star_data.get_saveframes_by_category(src_sf_category):
                                        self.__reg.orig_cst_sf = _sf
                                        break
                                    continue
                                if src_sf_category not in dst_sf_category_list:
                                    for _sf in _star_data.get_saveframes_by_category(src_sf_category):
                                        for sf in self.__reg.star_data[0].frame_list:
                                            if sf.name == _sf.name:
                                                self.__reg.star_data[0].remove_saveframe(_sf.name)
                                                break
                                        self.__reg.star_data[0].add_saveframe(_sf)

        return is_done

    def __convertCsToEntry(self, src_data: Optional[Union[pynmrstar.Entry, pynmrstar.Saveframe, pynmrstar.Loop]] = None,
                           list_id: int = 1
                           ) -> Optional[pynmrstar.Entry]:
        """ Convert NMR-STAR CS loop/saveframe to pynmrstar Entry object.
        """

        if src_data is None:
            return None

        file_type = 'nmr-star'

        def update_entry_info_saveframe(master_entry):
            content_subtype = 'entry_info'

            sf_category = SF_CATEGORIES[file_type][content_subtype]

            orig_ent_sf = next((sf for sf in master_entry.frame_list if sf_category in (sf.category, sf.name)), None)

            if orig_ent_sf is not None:

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

                if 'Sf_category' not in tagNames:
                    orig_ent_sf.add_tag('Sf_category', sf_category)
                if 'Sf_framecode' not in tagNames:
                    orig_ent_sf.add_tag('Sf_framecode', orig_ent_sf.name)
                set_sf_tag(orig_ent_sf, 'ID', self.__reg.entry_id)

            else:

                ent_sf = pynmrstar.Saveframe.from_scratch(sf_category, SF_TAG_PREFIXES[file_type][content_subtype])
                ent_sf.add_tag('Sf_category', sf_category)
                ent_sf.add_tag('Sf_framecode', sf_category)
                ent_sf.add_tag('ID', self.__reg.entry_id)

                master_entry.add_saveframe(ent_sf)

            return master_entry

        if isinstance(src_data, pynmrstar.Entry):
            return update_entry_info_saveframe(src_data)

        content_subtype = 'chem_shift'

        master_entry = pynmrstar.Entry.from_scratch(self.__reg.entry_id)

        if isinstance(src_data, (pynmrstar.Saveframe, pynmrstar.Loop)):

            if isinstance(src_data, pynmrstar.Saveframe):
                set_sf_tag(src_data, 'Sf_category', SF_CATEGORIES[file_type][content_subtype])
                set_sf_tag(src_data, 'Entry_ID', self.__reg.entry_id)
                set_sf_tag(src_data, 'ID', list_id)
                set_sf_tag(src_data, 'Data_file_name', self.__reg.srcName)

                master_entry.add_saveframe(src_data)

            else:
                sf_framecode = f'assigned_chemical_shifts_{list_id}'
                sf_tag_prefix = SF_TAG_PREFIXES[file_type][content_subtype]

                acs_sf = pynmrstar.Saveframe.from_scratch(sf_framecode, sf_tag_prefix)

                acs_sf.add_tag('Sf_category', SF_CATEGORIES[file_type][content_subtype])
                acs_sf.add_tag('Sf_framecode', sf_framecode)
                acs_sf.add_tag('Entry_ID', self.__reg.entry_id)
                acs_sf.add_tag('ID', list_id)
                acs_sf.add_tag('Data_file_name', self.__reg.srcName)

                acs_sf.add_loop(src_data)

                master_entry.add_saveframe(acs_sf)

            src_data = update_entry_info_saveframe(master_entry)

        return src_data

    def detectContentSubType(self) -> bool:
        """ Detect content subtype of NMR data file in any STAR format.
        """

        if len(self.__reg.star_data) != self.__reg.file_path_list_len:
            return False

        for fileListId in range(self.__reg.file_path_list_len):

            input_source = self.__reg.report.input_sources[fileListId]

            self.detectContentSubType__(fileListId, input_source, self.__reg.dirPath)

        return not self.__reg.report.isError()

    def detectContentSubType__(self, file_list_id: int, input_source: NmrDpReportInputSource,
                               dir_path: Optional[str] = None) -> None:
        """ Detect content subtype of NMR data file in any STAR format.
        """

        input_source_dic = input_source.get()

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

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

        self.__reg.sf_category_list, self.__reg.lp_category_list =\
            self.__reg.nefT.get_inventory_list(self.__reg.star_data[file_list_id])

        if self.__reg.combined_mode and file_list_id == 0 and file_type == 'nmr-star'\
           and 'constraint_statistics' in self.__reg.sf_category_list\
           and '_Constraint_file' in self.__reg.lp_category_list:
            _sf = self.__reg.star_data[file_list_id].get_saveframes_by_category('constraint_statistics')[0]
            data_file_name = get_first_sf_tag(_sf, 'Data_file_name')
            if PDB_MR_FILE_NAME_PAT.match(data_file_name) or INTNL_ANY_MR_FILE_NAME_PAT.match(data_file_name):
                entry_id = get_first_sf_tag(_sf, 'Entry_ID')
                if (PDB_ID_PAT.match(entry_id) or DEP_ID_PAT.match(entry_id))\
                   and self.__reg.op != 'nmr-str2cif-annotate':  # DAOTHER-10616
                    self.__reg.remediation_mode = True
                    self.__reg.nefT.set_remediation_mode(True)

        is_valid, messages, corrections =\
            self.__reg.nefT.resolve_sf_names_for_cif(self.__reg.star_data[file_list_id])  # DAOTHER-7389, issue #4
        self.__reg.sf_name_corrections.append(corrections)

        if not is_valid:

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

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

        tags_with_null_str = []

        for sf_category in self.__reg.sf_category_list:  # DAOTHER-5896

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

                if file_type == 'nmr-star' and sf_category == 'assembly' and self.__reg.assembly_name in EMPTY_VALUE:
                    self.__reg.assembly_name = get_first_sf_tag(sf, 'Name', '?')

                for tag in sf.tags:
                    if isinstance(tag[1], str) and len(tag[1]) == 0:
                        tags_with_null_str.append(f'_{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.__reg.report.warning.appendDescription('corrected_format_issue',
                                                        {'file_name': file_name, 'description': warn})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

        for sf_category in self.__reg.sf_category_list:

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

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

                if not self.__reg.bmrb_only:

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

                        if sf_category == 'constraint_statistics':
                            continue

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

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

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

        # initialize loop counter
        lp_counts = {t: 0 for t in NMR_CONTENT_SUBTYPES}

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

        if file_type == 'nmr-star' and lp_counts['spectral_peak'] + lp_counts['spectral_peak_alt'] == 0\
           and '_Spectral_dim' in self.__reg.lp_category_list:
            lp_counts['spectral_peak'] = self.__reg.lp_category_list.count('_Spectral_dim')

        content_subtype = 'poly_seq'

        lp_category = LP_CATEGORIES[file_type][content_subtype]

        if lp_counts[content_subtype] == 0:

            if not self.__reg.has_star_entity and self.__reg.combined_mode:

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

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

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

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

                    if self.__reg.validation_server and lp_category == '_Chem_comp_assembly':
                        err = f"A saveframe with a category {lp_category!r} is missing "\
                            f"that indicates {file_name!r} is not NMR unified data file. "\
                            f"Please re-upload the file as an usual assigned chemical shift file."

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

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.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.__reg.report.error.appendDescription('missing_mandatory_content',
                                                          {'file_name': file_name, 'description': err})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - {err}\n")

        elif lp_counts[content_subtype] > 1:

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

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

            self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - "
                                 f"{file_name} {err}\n")

        if self.__reg.remediation_mode and not self.__reg.bmrb_only:

            if content_type == 'nmr-restraints':

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

                    sf_category = SF_CATEGORIES[file_type][content_subtype]

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

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

                        if content_subtype == 'chem_shift' and not self.__reg.has_star_chem_shift:
                            if self.__reg.star_data[0] is None:
                                self.__reg.star_data[0] = pynmrstar.Entry.from_scratch(self.__reg.entry_id)
                                self.__reg.star_data_type[0] = 'Entry'

                            if sf not in self.__reg.star_data[0].frame_list:
                                self.__reg.star_data[0].add_saveframe(sf)

                                input_source_ = self.__reg.report.input_sources[0]
                                input_source_dic_ = input_source_.get()
                                content_subtypes_ = input_source_dic_['content_subtype']

                                if content_subtypes_ is None:
                                    content_subtypes_ = {content_subtype: 0}

                                content_subtypes_[content_subtype] += 1

                                input_source_.setItemValue('content_subtype', content_subtypes_)

                                for idx, msg in enumerate(self.__reg.suspended_errors_for_lazy_eval):
                                    for k, v in msg.items():
                                        if k == 'missing_mandatory_content':
                                            del self.__reg.suspended_errors_for_lazy_eval[idx]
                                            break

                            cs = self.__reg.inputParamDict[CS_FILE_PATH_LIST_KEY][0]

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

                            if dir_path is None:
                                dir_path = os.path.dirname(cs_path)

                            cs_file_name = os.path.basename(cs_path)

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

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

                            if cs_file_name.endswith('-corrected'):
                                cs_file_name = cs_file_name[:-10]

                            cs_base_name = cs_file_name
                            cs_file_name = cs_base_name + '-corrected.str'
                            cs_file_path = os.path.join(dir_path, cs_file_name)

                            if not os.path.exists(cs_file_path):
                                self.__reg.star_data[0].write_to_file(cs_file_path,
                                                                      show_comments=False,
                                                                      skip_empty_loops=True,
                                                                      skip_empty_tags=False)

                                compress_as_gzip_file(cs_file_path, cs_file_path + '.gz')

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

                            try:

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

                                cs_file_link = os.path.join(rem_dir, cs_base_name + '.str')

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

                                os.symlink(cs_file_path, cs_file_link)

                            except OSError:
                                pass

                        self.__reg.star_data[file_list_id].remove_saveframe(sf.name)

                    lp_counts[content_subtype] = 0

            elif content_type == 'nmr-chemical-shifts' and BMRB_NMR_STAR_FILE_NAME_PAT.match(file_name):

                for content_subtype in NMR_CONTENT_SUBTYPES:

                    if content_subtype == 'chem_shift':
                        continue

                    sf_category = SF_CATEGORIES[file_type][content_subtype]

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

                    for sf in self.__reg.star_data[file_list_id].get_saveframes_by_category(sf_category):
                        self.__reg.star_data[file_list_id].remove_saveframe(sf.name)

                    lp_counts[content_subtype] = 0

        content_subtype = 'chem_shift'

        if lp_counts[content_subtype] == 0 and self.__reg.combined_mode:

            sf_category = SF_CATEGORIES[file_type][content_subtype]
            lp_category = LP_CATEGORIES[file_type][content_subtype]

            if self.__reg.op != 'nmr-str-replace-cs' or not self.__reg.bmrb_only:

                err = f"The saveframe with a category {sf_category!r} is missing, "\
                    f"Deposition of assigned chemical shifts is mandatory. Please re-upload the {file_type.upper()} file."

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

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - {err}\n")

            if self.__reg.remediation_mode:
                dir_path = os.path.dirname(self.__reg.dstPath)

                touch_file = os.path.join(dir_path, '.entry_without_cs')
                if not os.path.exists(touch_file):
                    with open(touch_file, 'w', encoding='utf-8') as ofh:
                        ofh.write('')

        if lp_counts[content_subtype] > 0 and content_type == 'nmr-restraints' and not self.__reg.bmrb_only:

            if self.__reg.remediation_mode\
               and lp_counts['dist_restraint'] + lp_counts['dihed_restraint'] + lp_counts['rdc_restraint'] > 0:

                warn = "The restraint file includes assigned chemical shifts. "\
                    "which will be ignored during remediation."

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

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

            else:

                err = "The restraint file includes assigned chemical shifts. "\
                    f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

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

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - {err}\n")

        content_subtype = 'dist_restraint'

        if lp_counts[content_subtype] == 0 and self.__reg.combined_mode and file_list_id == 0:

            sf_category = SF_CATEGORIES[file_type][content_subtype]
            lp_category = LP_CATEGORIES[file_type][content_subtype]

            if self.__reg.permit_missing_dist_restraint:

                warn = f"The saveframe with a category {sf_category!r} is missing. "\
                       "The wwPDB NEF Working Group strongly recommends the submission of distance restraints "\
                       "used for the structure determination."

                if 'noepk_restraint' in lp_counts and lp_counts['noepk_restraint'] > 0:
                    warn += " '_Homonucl_NOE' category is only useful for describing assigned NOE peak height/volume. "\
                        "Please use the '_Gen_dist_constraint' category to describe general distance restraint."

                if 'other_data_types' in self.__reg.sf_category_list:
                    sf_framecodes_wo_loop = []
                    for sf in self.__reg.star_data[file_list_id].get_saveframes_by_category('other_data_types'):

                        try:
                            loop = sf.get_loop('_Other_data')
                        except KeyError:
                            sf_framecodes_wo_loop.append(get_first_sf_tag(sf, 'sf_framecode'))
                            continue

                        if loop is None:
                            sf_framecodes_wo_loop.append(get_first_sf_tag(sf, 'sf_framecode'))

                    if len(sf_framecodes_wo_loop) > 0:
                        _sf_framecodes_wo_loop = "', '".join(sf_framecodes_wo_loop)
                        warn += f" Uninterpreted restraints are stored in {_sf_framecodes_wo_loop!r} "\
                            f"saveframe{'s' if len(sf_framecodes_wo_loop) > 1 else ''} as raw text format. "\
                            "Please consider incorporating those restraints into well-known formats "\
                            "that OneDep supports, if possible."

                self.__reg.report.warning.appendDescription('missing_content',
                                                            {'file_name': file_name, 'description': warn})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

            elif not self.__reg.validation_server:

                err = f"The saveframe with a category {sf_category!r} is missing, "\
                    f"Deposition of distance restraints is mandatory. Please re-upload the {file_type.upper()} file."

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

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.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.__reg.bmrb_only and not self.__reg.internal_mode:

            err = "The assigned chemical shift file includes restraints. "\
                f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

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

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - {err}\n")

        has_spectral_peak = lp_counts['spectral_peak'] + lp_counts['spectral_peak_alt'] > 0

        if not has_spectral_peak and self.__reg.remediation_mode:
            if 'spectral_peak_list' in self.__reg.sf_category_list:
                has_spectral_peak = True

        if not has_spectral_peak and self.__reg.combined_mode and file_list_id == 0:

            primary_spectra_for_structure_determination =\
                'NOESY or ROESY' if self.__reg.exptl_method != 'SOLID-STATE NMR' else 'DARR, REDOR, TEDOR or RFDR'

            warn = "The wwPDB NMR Validation Task Force strongly encourages the submission of spectral peak lists, "\
                f"in particular those generated from the {primary_spectra_for_structure_determination} spectra."

            self.__reg.report.warning.appendDescription('encouragement',
                                                        {'file_name': file_name, 'description': warn})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Warning  - {warn}\n")

        if has_spectral_peak and content_type == 'nmr-chemical-shifts'\
           and not self.__reg.bmrb_only and not self.__reg.internal_mode:

            err = "The assigned chemical shift file includes spectral peak lists. "\
                f"Please re-upload the {file_type.upper()} file as an NMR unified data file."

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

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.detectContentSubType() ++ Error  - {err}\n")

            if self.__reg.remediation_mode and dir_path is not None:
                touch_file = os.path.join(dir_path, '.entry_with_pk')
                if not os.path.exists(touch_file):
                    with open(touch_file, 'w', encoding='utf-8') as ofh:
                        ofh.write('')

        if self.__reg.combined_mode and file_list_id == 0:

            mr_loops = 0

            for content_subtype in self.__reg.mr_content_subtypes:
                if content_subtype in lp_counts:
                    mr_loops += lp_counts[content_subtype]

            if mr_loops == 0 and not self.__reg.validation_server:

                if 'other_data_types' not in self.__reg.sf_category_list:

                    err = "Deposition of restraints used for the structure determination is mandatory. "\
                        f"Please re-upload the {file_type.upper()} file."

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

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.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)

    def getTypeOfDihedralRestraint(self, data_type: str,  # pylint: disable=no-self-use
                                   peptide: bool, nucleotide: bool, carbohydrate: bool,
                                   atoms: List[dict], plane_like: bool) -> str:
        """ Return type of dihedral angle restraint.
        """

        if data_type in EMPTY_VALUE:
            data_type = getTypeOfDihedralRestraint(peptide, nucleotide, carbohydrate,
                                                   atoms, plane_like)

            if data_type in EMPTY_VALUE or data_type.startswith('pseudo'):
                data_type = 'undefined'
            else:
                data_type = data_type.lower()

        else:
            data_type = data_type.lower()

        if not data_type.endswith('_angle_constraints'):
            data_type += '_angle_constraints'

        return data_type

    def isCyclicPolymer(self, nmr_chain_id: str) -> bool:
        """ Return whether a given chain is cyclic polymer based on coordinate annotation.
            @return: True for cyclic polymer, False otherwise
        """

        if nmr_chain_id in self.__reg.is_cyclic_polymer:
            return self.__reg.is_cyclic_polymer[nmr_chain_id]

        try:

            is_cyclic = self.__isCyclicPolymer__(nmr_chain_id)

            return is_cyclic

        finally:
            self.__reg.is_cyclic_polymer[nmr_chain_id] = is_cyclic

    def __isCyclicPolymer__(self, nmr_chain_id: str) -> bool:
        """ Return whether a given chain is cyclic polymer based on coordinate annotation.
            @return: True for cyclic polymer, False otherwise
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']
        beg_cif_seq_id = cif_ps['seq_id'][0]
        end_cif_seq_id = cif_ps['seq_id'][-1]

        try:

            if self.__reg.cR.hasCategory('struct_conn'):
                filter_items = [{'name': 'ptnr1_label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                {'name': 'ptnr2_label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                {'name': 'ptnr1_label_seq_id', 'type': 'int', 'value': beg_cif_seq_id},
                                {'name': 'ptnr2_label_seq_id', 'type': 'int', 'value': end_cif_seq_id}
                                ]

                if not self.__reg.bmrb_only and self.__reg.cR.hasItem('struct_conn', 'pdbx_leaving_atom_flag'):
                    filter_items.append({'name': 'pdbx_leaving_atom_flag', 'type': 'str', 'value': 'both'})

                struct_conn = self.__reg.cR.getDictListWithFilter('struct_conn',
                                                                  [{'name': 'conn_type_id', 'type': 'str'}
                                                                   ],
                                                                  filter_items)

            else:
                struct_conn = []

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__isCyclicPolymer__() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__isCyclicPolymer__() ++ Error  - {str(e)}\n")

            return False

        if len(struct_conn) == 0:

            label_to_auth_seq = self.__reg.caC['label_to_auth_seq']

            seq_key_1 = (cif_chain_id, beg_cif_seq_id)
            seq_key_2 = (cif_chain_id, end_cif_seq_id)
            close_contact = []

            if seq_key_1 in label_to_auth_seq and seq_key_2 in label_to_auth_seq:
                auth_cif_chain_id, auth_beg_cif_seq_id = label_to_auth_seq[seq_key_1]
                _, auth_end_cif_seq_id = label_to_auth_seq[seq_key_2]

                try:

                    if self.__reg.cR.hasCategory('pdbx_validate_close_contact'):
                        close_contact = self.__reg.cR.getDictListWithFilter('pdbx_validate_close_contact',
                                                                            [{'name': 'dist', 'type': 'float'}
                                                                             ],
                                                                            [{'name': 'PDB_model_num', 'type': 'int',
                                                                              'value': self.__reg.representative_model_id},
                                                                             {'name': 'auth_asym_id_1', 'type': 'str',
                                                                              'value': auth_cif_chain_id},
                                                                             {'name': 'auth_seq_id_1', 'type': 'int',
                                                                              'value': auth_beg_cif_seq_id},
                                                                             {'name': 'auth_atom_id_1', 'type': 'str',
                                                                              'value': 'N'},
                                                                             {'name': 'auth_asym_id_2', 'type': 'str',
                                                                              'value': auth_cif_chain_id},
                                                                             {'name': 'auth_seq_id_2', 'type': 'int',
                                                                              'value': auth_end_cif_seq_id},
                                                                             {'name': 'auth_atom_id_2', 'type': 'str',
                                                                              'value': 'C'}
                                                                             ])

                except Exception as e:  # pylint: disable=broad-exception-caught

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.__isCyclicPolymer__() ++ Error  - " + str(e))

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__isCyclicPolymer__() ++ Error  - {str(e)}\n")

                    return False

            if len(close_contact) == 0:

                bond = self.getCoordBondLength(cif_chain_id, beg_cif_seq_id, 'N', cif_chain_id, end_cif_seq_id, 'C')

                if bond is None:
                    return False

                dist = next((b['distance'] for b in bond if b['model_id'] == self.__reg.representative_model_id), None)

                if dist is None:
                    return False

                return 1.0 < dist < 2.4

            return 1.0 < close_contact[0]['dist'] < 2.4

        return struct_conn[0]['conn_type_id'].startswith('covale')

    def isProtCis(self, nmr_chain_id: str, nmr_seq_id: int) -> bool:
        """ 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.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return False

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            if cif_seq_id is None:
                return False

            try:

                if self.__reg.cR.hasCategory('struct_mon_prot_cis'):
                    alias = not self.__reg.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.__reg.cR.getDictListWithFilter('struct_mon_prot_cis',
                                                                   [{'name': model_num_name, 'type': 'int'}
                                                                    ],
                                                                   [{'name': label_asym_id_2_name, 'type': 'str',
                                                                     'value': cif_chain_id},
                                                                    {'name': label_seq_id_2_name, 'type': 'int',
                                                                     'value': cif_seq_id}
                                                                    ])

                else:
                    prot_cis = []

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.isProtCis() ++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.isProtCis() ++ Error  - {str(e)}\n")

                return False

            return len(prot_cis) > 0

        return False

    def getNmrBondLength(self, nmr_chain_id_1: str, nmr_seq_id_1: int, nmr_atom_id_1: str,
                         nmr_chain_id_2: str, nmr_seq_id_2: int, nmr_atom_id_2: str
                         ) -> Optional[List[dict]]:
        """ Return the bond length of given two NMR atoms.
            @return: the bond length
        """

        intra_chain = nmr_chain_id_1 == nmr_chain_id_2

        s_1 = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id_1)

        if s_1 is None:
            return None

        s_2 = s_1 if intra_chain else self.__reg.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.__reg.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.__reg.cpC['bond_length']:
            return self.__reg.cpC['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 None not in (result_1, result_2):

            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.__reg.cpC['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.__reg.cpC['bond_length'][seq_key] = None
                return None

            bond = self.getCoordBondLength(cif_chain_id_1, cif_seq_id_1, nmr_atom_id_1, cif_chain_id_2, cif_seq_id_2, nmr_atom_id_2)

            if bond is not None:
                self.__reg.cpC['bond_length'][seq_key] = bond

                return bond

        self.__reg.cpC['bond_length'][seq_key] = None

        return None

    def getCoordBondLength(self, cif_chain_id_1: str, cif_seq_id_1: int, cif_atom_id_1: str,
                           cif_chain_id_2: str, cif_seq_id_2: int, cif_atom_id_2: str,
                           label_scheme: bool = True
                           ) -> Optional[List[dict]]:
        """ Return the bond length of given two CIF atoms.
            @return: the bond length
        """

        try:

            model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

            data_items = [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                          {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                          {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                          {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                          ]

            atom_site_1 = self.__reg.cR.getDictListWithFilter('atom_site',
                                                              data_items,
                                                              [{'name': 'label_asym_id' if label_scheme else 'auth_asym_id',
                                                                'type': 'str', 'value': cif_chain_id_1},
                                                               {'name': 'label_seq_id' if label_scheme else 'auth_seq_id',
                                                                'type': 'int', 'value': cif_seq_id_1},
                                                               {'name': 'label_atom_id' if label_scheme else 'auth_atom_id',
                                                                'type': 'str', 'value': cif_atom_id_1},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

            atom_site_2 = self.__reg.cR.getDictListWithFilter('atom_site',
                                                              data_items,
                                                              [{'name': 'label_asym_id' if label_scheme else 'auth_asym_id',
                                                                'type': 'str', 'value': cif_chain_id_2},
                                                               {'name': 'label_seq_id' if label_scheme else 'auth_seq_id',
                                                                'type': 'int', 'value': cif_seq_id_2},
                                                               {'name': 'label_atom_id' if label_scheme else 'auth_atom_id',
                                                                'type': 'str', 'value': cif_atom_id_2},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.getCoordBondLength() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.getCoordBondLength() ++ Error  - {str(e)}\n")

            return None

        model_ids = set(a['model_id'] for a in atom_site_1) | set(a['model_id'] for a in atom_site_2)

        bond = []

        for model_id in model_ids:
            a_1 = next((a for a in atom_site_1 if a['model_id'] == model_id), None)
            a_2 = next((a for a in atom_site_2 if a['model_id'] == model_id), None)

            if None in (a_1, a_2):
                continue

            bond.append({'model_id': model_id, 'distance': float(f"{distance(to_np_array(a_1), to_np_array(a_2)):.3f}")})

        if len(bond) > 0:
            return bond

        return None

    def __getNearestAromaticRing(self, nmr_chain_id: str, nmr_seq_id: int, nmr_atom_id: str
                                 ) -> Optional[dict]:
        """ Return the nearest aromatic ring around a given atom.
            @return: the nearest aromatic ring
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.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.__reg.cpC['near_ring']:
            return self.__reg.cpC['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.__reg.cpC['near_ring'][seq_key] = None
                return None

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                _origin = self.__reg.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.__reg.representative_model_id},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestAromaticRing() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestAromaticRing() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_origin) != 1:
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            o = to_np_array(_origin[0])

            try:

                _neighbor = self.__reg.cR.getDictListWithFilter('atom_site',
                                                                [{'name': 'label_asym_id', 'type': 'str', 'alt_name': 'chain_id'},
                                                                 {'name': 'label_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                                 {'name': 'label_comp_id', 'type': 'starts-with-alnum',
                                                                  'alt_name': 'comp_id'},
                                                                 {'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                                  'alt_name': 'atom_id'},
                                                                 {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                                 {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                                 {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                                 {'name': 'type_symbol', 'type': 'str'}
                                                                 ],
                                                                [{'name': 'Cartn_x', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[0] - CUTOFF_AROMATIC),
                                                                            'max_exclusive': (o[0] + CUTOFF_AROMATIC)}},
                                                                 {'name': 'Cartn_y', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[1] - CUTOFF_AROMATIC),
                                                                            'max_exclusive': (o[1] + CUTOFF_AROMATIC)}},
                                                                 {'name': 'Cartn_z', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[2] - CUTOFF_AROMATIC),
                                                                            'max_exclusive': (o[2] + CUTOFF_AROMATIC)}},
                                                                 {'name': model_num_name, 'type': 'int',
                                                                  'value': self.__reg.representative_model_id},
                                                                 {'name': 'label_alt_id', 'type': 'enum',
                                                                  'enum': (self.__reg.representative_alt_id,)}
                                                                 ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestAromaticRing() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestAromaticRing() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_neighbor) == 0:
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            neighbor = [n for n in _neighbor
                        if n['seq_id'] != cif_seq_id
                        and n['type_symbol'] not in PROTON_BEGIN_CODE
                        and distance(to_np_array(n), o) < CUTOFF_AROMATIC
                        and n['atom_id'] in self.__reg.csStat.getAromaticAtoms(n['comp_id'])]

            if len(neighbor) == 0:
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            if not has_key_value(seq_align_dic, 'model_poly_seq_vs_nmr_poly_seq'):
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            atom_list = []

            for n in neighbor:

                _cif_chain_id = n['chain_id']

                _ps = self.__reg.report.getNmrPolymerSequenceWithModelChainId(_cif_chain_id)

                if _ps is None:
                    continue

                _nmr_chain_id = _ps['chain_id']

                result = next((seq_align for seq_align in seq_align_dic['model_poly_seq_vs_nmr_poly_seq']
                               if seq_align['ref_chain_id'] == _cif_chain_id and seq_align['test_chain_id'] == _nmr_chain_id), None)

                if result is not None:

                    _nmr_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                        in zip(result['ref_seq_id'], result['test_seq_id'])
                                        if ref_seq_id == n['seq_id']), None)

                    atom_list.append({'chain_id': _nmr_chain_id,
                                      'seq_id': _nmr_seq_id,
                                      'cif_chain_id': _cif_chain_id,
                                      'cif_seq_id': n['seq_id'],
                                      'comp_id': n['comp_id'],
                                      'atom_id': n['atom_id'],
                                      'distance': distance(to_np_array(n), o)})

            if len(atom_list) == 0:
                return None

            na = sorted(atom_list, key=itemgetter('distance'))[0]

            na_atom_id = na['atom_id']

            if not self.__reg.ccU.updateChemCompDict(na['comp_id']):
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            # matches with comp_id in CCD

            half_ring_traces = []

            for b1 in self.__reg.ccU.lastBondDictList:

                if b1['aromatic_flag'] != 'Y':
                    continue

                if b1['atom_id_1'] == na_atom_id and b1['atom_id_2'][0] not in PROTON_BEGIN_CODE:
                    na_ = b1['atom_id_2']

                elif b1['atom_id_2'] == na_atom_id and b1['atom_id_1'][0] not in PROTON_BEGIN_CODE:
                    na_ = b1['atom_id_1']

                else:
                    continue

                for b2 in self.__reg.ccU.lastBondDictList:

                    if b2['aromatic_flag'] != 'Y':
                        continue

                    if b2['atom_id_1'] == na_ and b2['atom_id_2'][0] not in PROTON_BEGIN_CODE\
                            and b2['atom_id_2'] != na_atom_id:
                        na__ = b2['atom_id_2']

                    elif b2['atom_id_2'] == na_ and b2['atom_id_1'][0] not in PROTON_BEGIN_CODE\
                            and b2['atom_id_1'] != na_atom_id:
                        na__ = b2['atom_id_1']

                    else:
                        continue

                    for b3 in self.__reg.ccU.lastBondDictList:

                        if b3['aromatic_flag'] != 'Y':
                            continue

                        if b3['atom_id_1'] == na__ and b3['atom_id_2'][0] not in PROTON_BEGIN_CODE\
                                and b3['atom_id_2'] != na_:
                            na___ = b3['atom_id_2']

                        elif b3['atom_id_2'] == na__ and b3['atom_id_1'][0] not in PROTON_BEGIN_CODE\
                                and b3['atom_id_1'] != na_:
                            na___ = b3['atom_id_1']

                        else:
                            continue

                        half_ring_traces.append(f"{na_atom_id}:{na_}:{na__}:{na___}")

            len_half_ring_traces = len(half_ring_traces)

            if len_half_ring_traces < 2:
                self.__reg.cpC['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(f'{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(f'{half_ring_traces[i]}:{half_ring_trace_2[1]}')

            if len(ring_traces) == 0:
                self.__reg.cpC['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.__reg.cR.getDictListWithFilter('atom_site',
                                                          [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                            'alt_name': 'atom_id'},
                                                           {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                           {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                           {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                           {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                           ],
                                                          [{'name': 'label_asym_id', 'type': 'str', 'value': na['cif_chain_id']},
                                                           {'name': 'label_seq_id', 'type': 'int', 'value': na['cif_seq_id']},
                                                           {'name': 'label_comp_id', 'type': 'str', 'value': na['comp_id']},
                                                           {'name': 'label_atom_id', 'type': 'enum', 'enum': ring_atoms},
                                                           {'name': 'label_alt_id', 'type': 'enum',
                                                            'enum': (self.__reg.representative_alt_id,)}
                                                           ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestAromaticRing() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestAromaticRing() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_na) == 0:
                self.__reg.cpC['near_ring'][seq_key] = None
                return None

            model_ids = set(a['model_id'] for a in _na)

            len_model_ids = 0

            dist = ring_dist = ring_angle = 0.0

            for model_id in model_ids:

                rc = numpy.array([0.0] * 3, dtype=float)

                total = 0

                for a in _na:

                    if a['model_id'] == model_id:

                        _a = to_np_array(a)

                        if a['atom_id'] == na_atom_id:
                            dist += distance(_a, o)

                        rc = numpy.add(rc, _a)

                        total += 1

                if total == len(ring_atoms):

                    rc = rc / total

                    ring_dist += distance(rc, o)

                    na_ = next(to_np_array(na_) for na_ in _na if na_['atom_id'] == ring_atoms[0])
                    na__ = next(to_np_array(na__) for na__ in _na if na__['atom_id'] == ring_atoms[1])
                    na___ = next(to_np_array(na___) for na___ in _na if na___['atom_id'] == ring_atoms[-1])

                    ring_vector = numpy.cross(na__ - na_, na___ - na_)

                    ring_angle += math.acos(abs(numpy.dot(to_unit_vector(o - rc), to_unit_vector(ring_vector))))

                    len_model_ids += 1

            if len_model_ids == 0:  # DAOTHER-8840
                return None

            na['ring_atoms'] = ring_atoms
            na['distance'] = float(f"{dist / len_model_ids:.1f}")
            na['ring_distance'] = float(f"{ring_dist / len_model_ids:.1f}")
            na['ring_angle'] = float(f"{numpy.degrees(ring_angle / len_model_ids):.1f}")

            self.__reg.cpC['near_ring'][seq_key] = na
            return na

        self.__reg.cpC['near_ring'][seq_key] = None
        return None

    def __getNearestParaFerroMagneticAtom(self, nmr_chain_id: str, nmr_seq_id: int, nmr_atom_id: str
                                          ) -> Optional[dict]:
        """ Return the nearest paramagnetic/ferromagnetic atom around a given atom.
            @return: the nearest paramagnetic/ferromagnetic atom
        """

        if self.__reg.report.isDiamagnetic():
            return None

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.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.__reg.cpC['near_para_ferro']:
            return self.__reg.cpC['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.__reg.cpC['near_para_ferro'][seq_key] = None
                return None

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                _origin = self.__reg.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.__reg.representative_model_id},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_origin) != 1:
                self.__reg.cpC['near_para_ferro'][seq_key] = None
                return None

            o = to_np_array(_origin[0])

            try:

                _neighbor = self.__reg.cR.getDictListWithFilter('atom_site',
                                                                [{'name': 'auth_asym_id', 'type': 'str',
                                                                  'alt_name': 'chain_id', 'default': REPRESENTATIVE_ASYM_ID},
                                                                 {'name': 'auth_seq_id', 'type': 'int', 'alt_name': 'seq_id'},
                                                                 {'name': 'label_comp_id', 'type': 'starts-with-alnum',
                                                                  'alt_name': 'comp_id'},
                                                                 {'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                                  'alt_name': 'atom_id'},
                                                                 {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                                 {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                                 {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                                 {'name': 'type_symbol', 'type': 'str'}
                                                                 ],
                                                                [{'name': 'Cartn_x', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[0] - CUTOFF_PARAMAGNETIC),
                                                                            'max_exclusive': (o[0] + CUTOFF_PARAMAGNETIC)}},
                                                                 {'name': 'Cartn_y', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[1] - CUTOFF_PARAMAGNETIC),
                                                                            'max_exclusive': (o[1] + CUTOFF_PARAMAGNETIC)}},
                                                                 {'name': 'Cartn_z', 'type': 'range-float',
                                                                  'range': {'min_exclusive': (o[2] - CUTOFF_PARAMAGNETIC),
                                                                            'max_exclusive': (o[2] + CUTOFF_PARAMAGNETIC)}},
                                                                 {'name': model_num_name, 'type': 'int',
                                                                  'value': self.__reg.representative_model_id},
                                                                 {'name': 'label_alt_id', 'type': 'enum',
                                                                  'enum': (self.__reg.representative_alt_id,)}
                                                                 ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_neighbor) == 0:
                self.__reg.cpC['near_para_ferro'][seq_key] = None
                return None

            neighbor = [n for n in _neighbor
                        if n['seq_id'] != cif_seq_id
                        and distance(to_np_array(n), o) < CUTOFF_PARAMAGNETIC
                        and (n['type_symbol'] in PARAMAGNETIC_ELEMENTS
                             or n['type_symbol'] in FERROMAGNETIC_ELEMENTS)]

            if len(neighbor) == 0:
                self.__reg.cpC['near_para_ferro'][seq_key] = None
                return None

            atom_list = []

            for n in neighbor:
                atom_list.append({'chain_id': n['chain_id'], 'seq_id': n['seq_id'],
                                  'comp_id': n['comp_id'], 'atom_id': n['atom_id'],
                                  'distance': distance(to_np_array(n), o)})

            if len(atom_list) == 0:
                return None

            p = sorted(atom_list, key=itemgetter('distance'))[0]

            try:

                _p = self.__reg.cR.getDictListWithFilter('atom_site',
                                                         [{'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                          {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                          {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'}
                                                          ],
                                                         [{'name': 'auth_asym_id', 'type': 'str', 'value': p['chain_id']},
                                                          {'name': 'auth_seq_id', 'type': 'int', 'value': p['seq_id']},
                                                          {'name': 'label_comp_id', 'type': 'str', 'value': p['comp_id']},
                                                          {'name': 'label_atom_id', 'type': 'str', 'value': p['atom_id']},
                                                          {'name': 'label_alt_id', 'type': 'enum',
                                                           'enum': (self.__reg.representative_alt_id,)}
                                                          ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getNearestParaFerroMagneticAtom() "
                                         f"++ Error  - {str(e)}\n")

                return None

            if len(_p) == 0:
                self.__reg.cpC['near_para_ferro'][seq_key] = None
                return None

            dist = 0.0

            for __p in _p:
                dist += distance(to_np_array(__p), o)

            p['distance'] = float(f"{dist / len(_p):.1f}")

            self.__reg.cpC['near_para_ferro'][seq_key] = p
            return p

        self.__reg.cpC['near_para_ferro'][seq_key] = None
        return None

    def testTautomerOfHistidinePerModel(self) -> bool:
        """ Check tautomeric state of a given histidine per model. (DAOTHER-9252)
        """

        src_id = self.__reg.report.getInputSourceIdOfCoord()

        if src_id < 0:
            return False

        cif_input_source = self.__reg.report.input_sources[src_id]
        cif_input_source_dic = cif_input_source.get()

        has_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

        if not has_poly_seq:
            return False

        file_name = cif_input_source_dic['file_name']
        cif_poly_seq = cif_input_source_dic['polymer_sequence']

        if len(self.__reg.cpC['tautomer_per_model']) > 0:

            for inst in self.__reg.cpC['tautomer_per_model']:
                tautomer_per_model = inst['tautomer_per_model']

                try:
                    rep_tautomer = tautomer_per_model[self.__reg.representative_model_id]
                except KeyError:
                    try:
                        rep_tautomer = tautomer_per_model[self.__reg.eff_model_ids[0]]
                    except KeyError:
                        continue

                if any(tautomer != rep_tautomer for tautomer in tautomer_per_model.values()):
                    chain_id, auth_chain_id = inst['chain_id'], inst['auth_chain_id']
                    seq_id, auth_seq_id = inst['seq_id'], inst['auth_seq_id']
                    comp_id = inst['comp_id']
                    cif_seq_code = f"{chain_id}:{seq_id}:{comp_id}"
                    if chain_id != auth_chain_id or seq_id != auth_seq_id:
                        cif_seq_code += f" ({auth_chain_id}:{auth_seq_id}:{comp_id} in author sequence scheme)"

                    err = f"{cif_seq_code} has been instantiated with different tautomeric states across models, "\
                        f"{tautomer_per_model}. Please re-upload the model file."

                    if self.__reg.internal_mode and not self.__reg.conversion_server:

                        self.__reg.report.warning.appendDescription('coordinate_issue',
                                                                    {'file_name': file_name, 'category': 'atom_site',
                                                                     'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                                 f"++ Warning  - {err}\n")

                    else:

                        self.__reg.report.error.appendDescription('coordinate_issue',
                                                                  {'file_name': file_name, 'category': 'atom_site',
                                                                   'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                                 f"++ Error  - {err}\n")

            return True

        model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

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

            auth_chain_id = chain_id
            if 'auth_chain_id' in ps:
                auth_chain_id = ps['auth_chain_id']

            if len(cif_poly_seq) >= LEN_MAJOR_ASYM_ID:
                if auth_chain_id not in LARGE_ASYM_ID:
                    continue

            for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                if not isLikeHis(comp_id, self.__reg.ccU):
                    continue

                if comp_id == 'HIS':
                    hd1_name = 'HD1'
                    he2_name = 'HE2'
                else:
                    _hd1_name = self.__reg.ccU.getBondedAtoms(comp_id, 'ND1', onlyProton=True)
                    _he2_name = self.__reg.ccU.getBondedAtoms(comp_id, 'NE2', onlyProton=True)
                    if len(_hd1_name) != 1 or len(_he2_name) != 1:
                        continue
                    hd1_name = _hd1_name[0]
                    he2_name = _he2_name[0]

                try:
                    auth_seq_id = ps['auth_seq_id'][ps['seq_id'].index(seq_id)]
                except (KeyError, IndexError, ValueError):
                    auth_seq_id = seq_id

                try:

                    protons = self.__reg.cR.getDictListWithFilter('atom_site',
                                                                  [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                                    'alt_name': 'atom_id'},
                                                                   {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'},
                                                                   ],
                                                                  [{'name': 'label_asym_id', 'type': 'str', 'value': chain_id},
                                                                   {'name': 'label_seq_id', 'type': 'int', 'value': seq_id},
                                                                   {'name': 'label_comp_id', 'type': 'str', 'value': comp_id},
                                                                   {'name': 'type_symbol', 'type': 'str', 'value': 'H'},
                                                                   {'name': 'label_alt_id', 'type': 'enum',
                                                                    'enum': (self.__reg.representative_alt_id,)}
                                                                   ])

                except Exception as e:  # pylint: disable=broad-exception-caught

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                                              "++ Error  - " + str(e))

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                             f"++ Error  - {str(e)}\n")

                    return False

                if len(protons) > 0:

                    tautomer_per_model = {}

                    for model_id in self.__reg.eff_model_ids:

                        _protons = [h for h in protons if h['model_id'] == model_id]

                        has_hd1 = has_he2 = False

                        for h in _protons:
                            if h['atom_id'] == hd1_name:
                                has_hd1 = True
                            elif h['atom_id'] == he2_name:
                                has_he2 = True

                        if has_hd1 and has_he2:
                            tautomer_per_model[model_id] = 'biprotonated'

                        elif has_hd1:
                            tautomer_per_model[model_id] = 'pi-tautomer'

                        elif has_he2:
                            tautomer_per_model[model_id] = 'tau-tautomer'

                        else:
                            tautomer_per_model[model_id] = 'unknown'

                    try:
                        rep_tautomer = tautomer_per_model[self.__reg.representative_model_id]
                    except KeyError:
                        try:
                            rep_tautomer = tautomer_per_model[self.__reg.eff_model_ids[0]]
                        except KeyError:
                            continue

                    self.__reg.cpC['tautomer_per_model'].append({'chain_id': chain_id, 'seq_id': seq_id, 'comp_id': comp_id,
                                                                 'auth_chain_id': auth_chain_id, 'auth_seq_id': auth_seq_id,
                                                                 'tautomer_per_model': tautomer_per_model})

                    if any(tautomer != rep_tautomer for tautomer in tautomer_per_model.values()):
                        cif_seq_code = f"{chain_id}:{seq_id}:{comp_id}"
                        if chain_id != auth_chain_id or seq_id != auth_seq_id:
                            cif_seq_code += f" ({auth_chain_id}:{auth_seq_id}:{comp_id} in author sequence scheme)"

                        err = f"{cif_seq_code} has been instantiated with different tautomeric states across models, "\
                            f"{tautomer_per_model}. Please re-upload the model file."

                        if self.__reg.internal_mode and not self.__reg.conversion_server:

                            self.__reg.report.warning.appendDescription('coordinate_issue',
                                                                        {'file_name': file_name, 'category': 'atom_site',
                                                                         'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                                     f"++ Warning  - {err}\n")

                        else:

                            self.__reg.report.error.appendDescription('coordinate_issue',
                                                                      {'file_name': file_name, 'category': 'atom_site',
                                                                       'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testTautomerOfHistidinePerModel() "
                                                     f"++ Error  - {err}\n")

        if self.__reg.coordPropCachePath is not None:
            hash_value = hash(str(self.__reg.cpC))
            if hash_value != self.__reg.cpcHashCode:
                write_as_pickle(self.__reg.cpC, self.__reg.coordPropCachePath)
                self.__reg.cpcHashCode = hash_value

        return True

    def getTautomerOfHistidine(self, nmr_chain_id: str, nmr_seq_id: int) -> str:
        """ Return tautomeric state of a given histidine.
            @return: One of 'biprotonated', 'tau-tautomer', 'pi-tautomer', 'unknown'
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return 'unknown'

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.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.__reg.cpC['tautomer']:
            return self.__reg.cpC['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.__reg.cpC['tautomer'][seq_key] = 'unknown'
                return 'unknown'

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                protons = self.__reg.cR.getDictListWithFilter('atom_site',
                                                              [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                                'alt_name': 'atom_id'}
                                                               ],
                                                              [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                               {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                               {'name': 'label_comp_id', 'type': 'str', 'value': 'HIS'},
                                                               {'name': 'type_symbol', 'type': 'str', 'value': 'H'},
                                                               {'name': model_num_name, 'type': 'int',
                                                                'value': self.__reg.representative_model_id},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.getTautomerOfHistidine() ++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.getTautomerOfHistidine() ++ Error  - {str(e)}\n")

                return 'unknown'

            if len(protons) > 0:

                has_hd1 = 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.__reg.cpC['tautomer'][seq_key] = 'biprotonated'
                    return 'biprotonated'

                if has_hd1:
                    self.__reg.cpC['tautomer'][seq_key] = 'pi-tautomer'
                    return 'pi-tautomer'

                if has_he2:
                    self.__reg.cpC['tautomer'][seq_key] = 'tau-tautomer'
                    return 'tau-tautomer'

        self.__reg.cpC['tautomer'][seq_key] = 'unknown'
        return 'unknown'

    def getRotamerOfValine(self, nmr_chain_id: str, nmr_seq_id: int
                           ) -> List[dict]:
        """ Return rotameric state distribution of a given valine.
            @return: One of 'gauche+', 'trans', 'gauche-', 'unknown'
        """

        none = [{'name': 'chi1', 'unknown': 1.0}]

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'VAL')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'VAL'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.getRotamerOfValine() ++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']

            _rot1 = rot1.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1]
            return [rot1]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def getRotamerOfLeucine(self, nmr_chain_id: str, nmr_seq_id: int
                            ) -> List[dict]:
        """ 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.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'LEU')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'LEU'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.getRotamerOfLeucine() ++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def getRotamerOfIsoleucine(self, nmr_chain_id: str, nmr_seq_id: int
                               ) -> List[dict]:
        """ 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.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'ILE')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'ILE'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.getRotamerOfIsoleucine() ++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def isConsistentSequence(self) -> bool:
        """ Perform sequence consistency test among extracted polymer sequences.
            @return: True for valid sequence, False otherwise
        """

        if self.__reg.bmrb_only and self.__reg.internal_mode:
            return True

        for fileListId in range(self.__reg.file_path_list_len):

            input_source = self.__reg.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_lp = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if (not has_poly_seq) or (not has_poly_seq_in_lp):
                continue

            poly_seq = input_source_dic['polymer_sequence']
            poly_seq_in_lp = input_source_dic['polymer_sequence_in_loop']

            subtype_with_poly_seq = ['poly_seq' if has_poly_seq else None]

            for subtype in poly_seq_in_lp.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 None in (subtype1, subtype2):
                    continue

                # reference polymer sequence exists
                if has_poly_seq and subtype1 == 'poly_seq':
                    poly_seq1 = poly_seq

                    ref_chain_ids = {ps1['chain_id'] for ps1 in poly_seq1}

                    for _poly_seq_in_lp in poly_seq_in_lp[subtype2]:
                        poly_seq2 = _poly_seq_in_lp['polymer_sequence']

                        for ps2 in poly_seq2:
                            chain_id = ps2['chain_id']

                            if chain_id not in ref_chain_ids\
                               and not ('identical_chain_id' in ps2 and chain_id not in ps2['identical_chain_id']):
                                return False

                            for ps1 in poly_seq1:

                                if ps1['chain_id'] != chain_id\
                                   and not ('identical_chain_id' in ps2 and ps1['chain_id'] in ps2['identical_chain_id']):
                                    continue

                                for seq_id, comp_id in zip(ps2['seq_id'], ps2['comp_id']):

                                    if seq_id not in ps1['seq_id']:

                                        if comp_id != '.':
                                            return False

                                    else:
                                        _comp_id = ps1['comp_id'][ps1['seq_id'].index(seq_id)]

                                        if comp_id not in EMPTY_VALUE and _comp_id not in EMPTY_VALUE and comp_id != _comp_id:
                                            return False

                #  brute force check
                else:

                    for _poly_seq_in_lp in poly_seq_in_lp[subtype1]:
                        poly_seq1 = _poly_seq_in_lp['polymer_sequence']

                        for _poly_seq_in_lp2 in poly_seq_in_lp[subtype2]:
                            poly_seq2 = _poly_seq_in_lp2['polymer_sequence']

                            # suppress redundant tests inside the same subtype
                            if subtype1 == subtype2 and _poly_seq_in_lp['list_id'] >= _poly_seq_in_lp2['list_id']:
                                continue

                            for ps2 in poly_seq2:
                                chain_id = ps2['chain_id']

                                for ps1 in poly_seq1:

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

                                    for seq_id, comp_id in zip(ps2['seq_id'], ps2['comp_id']):

                                        if seq_id in ps1['seq_id']:
                                            _comp_id = ps1['comp_id'][ps1['seq_id'].index(seq_id)]

                                            if comp_id not in EMPTY_VALUE and _comp_id not in EMPTY_VALUE and comp_id != _comp_id:
                                                return False

                            # inverse check required for unverified sequences
                            for ps1 in poly_seq1:
                                chain_id = ps1['chain_id']

                                for ps2 in poly_seq2:
                                    if chain_id != ps2['chain_id']:
                                        continue

                                    for seq_id, comp_id in zip(ps1['seq_id'], ps1['comp_id']):

                                        if seq_id in ps2['seq_id']:
                                            j = ps2['seq_id'].index(seq_id)
                                            _comp_id = ps2['comp_id'][j]

                                            if comp_id not in EMPTY_VALUE and _comp_id not in EMPTY_VALUE and comp_id != _comp_id:
                                                return False

        return True

    def equalsToRepCompId(self, comp_id: str, ref_comp_id: str) -> bool:
        """ Return whether given representative comp IDs are equal.
            @return: True for representative comp IDs are matched, False otherwise
        """

        if comp_id in EMPTY_VALUE or ref_comp_id in EMPTY_VALUE:
            return False

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

        elif comp_id not in STD_MON_DICT and self.__reg.ccU.updateChemCompDict(comp_id):
            if 'parent_comp_id' in self.__reg.ccU.lastChemCompDict:  # matches with comp_id in CCD
                if self.__reg.ccU.lastChemCompDict['parent_comp_id'] not in EMPTY_VALUE:
                    comp_id = self.__reg.ccU.lastChemCompDict['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 = f'D{comp_id}'
                    elif ref_comp_id in ('A', 'C', 'G', 'T', 'I', 'U') and len(comp_id) == 2 and comp_id.startswith('D'):
                        comp_id = comp_id[1]

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

        elif ref_comp_id not in STD_MON_DICT and self.__reg.ccU.updateChemCompDict(ref_comp_id):
            if 'parent_comp_id' in self.__reg.ccU.lastChemCompDict:  # matches with comp_id in CCD
                if self.__reg.ccU.lastChemCompDict['parent_comp_id'] not in EMPTY_VALUE:
                    ref_comp_id = self.__reg.ccU.lastChemCompDict['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 = f'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 updateGenDistConstIdInMrStr(self, sf_item: dict) -> bool:
        """ Update _Gen_dist_constraint.ID in NMR-STAR restraint file.
        """

        loop = sf_item['loop']

        lp = pynmrstar.Loop.from_scratch(loop.category)

        lp.add_tag(loop.tags)

        id_col = loop.tags.index('ID')
        if 'Index_ID' not in loop.tags:
            tag = f'{loop.category}.Index_ID'
            for idx in range(len(loop)):
                loop.data[idx].append(idx + 1)
            loop.add_tag(tag)
            lp.add_tag(tag)
        if 'Member_ID' not in loop.tags:
            tag = f'{loop.category}.Member_ID'
            loop.add_tag(tag, update_data=True)
            lp.add_tag(tag)
        if 'Member_logic_code' not in loop.tags:
            tag = f'{loop.category}.Member_logic_code'
            loop.add_tag(tag, update_data=True)
            lp.add_tag(tag)

        index_id_col = loop.tags.index('Index_ID')
        member_id_col = loop.tags.index('Member_ID')
        member_logic_code_col = loop.tags.index('Member_logic_code')

        combination_id_col = loop.tags.index('Combination_ID') if 'Combination_ID' in loop.tags else -1

        chain_id_1_col = loop.tags.index('Auth_asym_ID_1')
        seq_id_1_col = loop.tags.index('Auth_seq_ID_1')
        comp_id_1_col = loop.tags.index('Auth_comp_ID_1')
        atom_id_1_col = loop.tags.index('Auth_atom_ID_1')

        ref_chain_id_1_col = loop.tags.index('Entity_assembly_ID_1')
        ref_seq_id_1_col = loop.tags.index('Comp_index_ID_1')
        ref_comp_id_1_col = loop.tags.index('Comp_ID_1')
        ref_atom_id_1_col = loop.tags.index('Atom_ID_1')

        chain_id_2_col = loop.tags.index('Auth_asym_ID_2')
        seq_id_2_col = loop.tags.index('Auth_seq_ID_2')
        comp_id_2_col = loop.tags.index('Auth_comp_ID_2')
        atom_id_2_col = loop.tags.index('Auth_atom_ID_2')

        ref_chain_id_2_col = loop.tags.index('Entity_assembly_ID_2')
        ref_seq_id_2_col = loop.tags.index('Comp_index_ID_2')
        ref_comp_id_2_col = loop.tags.index('Comp_ID_2')
        ref_atom_id_2_col = loop.tags.index('Atom_ID_2')

        target_val_col = loop.tags.index('Target_val') if 'Target_val' in loop.tags else -1
        target_val_err_col = loop.tags.index('Target_val_uncertainty') if 'Target_val_uncertainty' in loop.tags else -1
        lower_linear_limit_col = loop.tags.index('Lower_linear_limit') if 'Lower_linear_limit' in loop.tags else -1
        upper_linear_limit_col = loop.tags.index('Upper_linear_limit') if 'Upper_linear_limit' in loop.tags else -1
        lower_limit_col = loop.tags.index('Distance_lower_bound_val') if 'Distance_lower_bound_val' in loop.tags else -1
        upper_limit_col = loop.tags.index('Distance_upper_bound_val') if 'Distance_upper_bound_val' in loop.tags else -1
        weight_col = loop.tags.index('Weight') if 'Weight' in loop.tags else -1

        cs_loops = self.__reg.lp_data['chem_shift']

        @functools.lru_cache()
        def get_cs_value(chain_id, seq_id, comp_id, atom_id):
            if cs_loops is None or len(cs_loops) == 0:
                return None

            if isinstance(chain_id, int):
                chain_id = str(chain_id)

            _atom_ids = self.__reg.nefT.get_valid_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

            for lp in cs_loops:
                row = next((row for row in lp['data']
                            if row['Entity_assembly_ID'] == chain_id and row['Comp_index_ID'] == seq_id
                            and row['Comp_ID'] == comp_id and row['Atom_ID'] in _atom_ids), None)

                if row is not None:
                    val = row['Val']
                    return val if val not in EMPTY_VALUE else None

            return None

        def concat_target_val(row):
            return (str(row[target_val_col]) if target_val_col != -1 else '')\
                + (str(row[target_val_err_col]) if target_val_err_col != -1 else '')\
                + (str(row[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                + (str(row[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                + (str(row[lower_limit_col]) if lower_limit_col != -1 else '')\
                + (str(row[upper_limit_col]) if upper_limit_col != -1 else '')\
                + (str(row[weight_col]) if weight_col != -1 else '')

        _rest_id = _member_logic_code = _cs_val1 = _cs_val2 = None
        _atom1, _atom2 = {}, {}
        _values = ''

        modified = has_member_id = False

        sf_item['id'] = 0

        for row in loop:
            _row = row

            sf_item['id'] += 1
            duplicated = False

            try:

                rest_id = row[id_col]
                try:
                    member_id = row[member_id_col]
                except IndexError:
                    member_id = None
                try:
                    member_logic_code = row[member_logic_code_col]
                except IndexError:
                    member_logic_code = None
                values = concat_target_val(row)

                try:
                    atom1 = {'chain_id': row[chain_id_1_col],
                             'seq_id': int(row[seq_id_1_col]),
                             'comp_id': row[comp_id_1_col],
                             'atom_id': row[atom_id_1_col],
                             'ref_chain_id': row[ref_chain_id_1_col],
                             'ref_seq_id': int(row[ref_seq_id_1_col]),
                             'ref_comp_id': row[ref_comp_id_1_col],
                             'ref_atom_id': row[ref_atom_id_1_col]}
                    cs_val1 = get_cs_value(atom1['ref_chain_id'], atom1['ref_seq_id'], atom1['ref_comp_id'], atom1['ref_atom_id'])
                except (ValueError, TypeError):
                    atom1 = {}
                    cs_val1 = None

                try:
                    atom2 = {'chain_id': row[chain_id_2_col],
                             'seq_id': int(row[seq_id_2_col]),
                             'comp_id': row[comp_id_2_col],
                             'atom_id': row[atom_id_2_col],
                             'ref_chain_id': row[ref_chain_id_2_col],
                             'ref_seq_id': int(row[ref_seq_id_2_col]),
                             'ref_comp_id': row[ref_comp_id_2_col],
                             'ref_atom_id': row[ref_atom_id_2_col]}
                    cs_val2 = get_cs_value(atom2['ref_chain_id'], atom2['ref_seq_id'], atom2['ref_comp_id'], atom2['ref_atom_id'])
                except (ValueError, TypeError):
                    atom2 = {}
                    cs_val2 = None

                if member_id not in EMPTY_VALUE:
                    has_member_id = True

                _atoms1 = [atom1, _atom1]
                _atoms2 = [atom2, _atom2]

                if _rest_id is None:
                    pass

                elif rest_id != _rest_id and len(atom1) > 0 and len(atom2) > 0:

                    if (member_id in EMPTY_VALUE or member_logic_code == 'OR'):

                        if atom1['atom_id'][0] in PROTON_BEGIN_CODE and atom2['atom_id'][0] in PROTON_BEGIN_CODE:

                            if (values == _values and not isAmbigAtomSelection(_atoms1, self.__reg.csStat)
                                and not isAmbigAtomSelection(_atoms2, self.__reg.csStat))\
                               or (values == _values and atom1['ref_chain_id'] != atom2['ref_chain_id']
                                   and ((not isAmbigAtomSelection(_atoms1, self.__reg.csStat) and len(_atom2) > 0
                                         and atom1['ref_chain_id'] != _atom2['ref_chain_id']
                                         and atom2['comp_id'] == _atom2['comp_id'])
                                        or (not isAmbigAtomSelection(_atoms2, self.__reg.csStat) and len(_atom1) > 0
                                            and atom2['ref_chain_id'] != _atom1['ref_chain_id']
                                            and atom1['comp_id'] == _atom1['comp_id']))):

                                diff_cs_val1 = cs_val1 is not None and _cs_val1 is not None and cs_val1 != _cs_val1
                                diff_cs_val2 = cs_val2 is not None and _cs_val2 is not None and cs_val2 != _cs_val2

                                if (not isAmbigAtomSelection(_atoms1, self.__reg.csStat) and diff_cs_val1)\
                                   or (not isAmbigAtomSelection(_atoms2, self.__reg.csStat) and diff_cs_val2):
                                    pass

                                else:

                                    try:

                                        _row[member_logic_code_col] = 'OR'

                                        if _member_logic_code in EMPTY_VALUE:
                                            lp.data[-1][member_logic_code_col] = 'OR'

                                    except IndexError:
                                        pass

                                    sf_item['id'] -= 1

                                    modified = True

                        elif values == _values and isIdenticalRestraint(_atoms1, self.__reg.nefT)\
                                and isIdenticalRestraint(_atoms2, self.__reg.nefT):
                            sf_item['id'] -= 1
                            duplicated = True

                elif member_logic_code != 'AND':

                    if not isAmbigAtomSelection(_atoms1, self.__reg.csStat)\
                       and not isAmbigAtomSelection(_atoms2, self.__reg.csStat):

                        if member_logic_code in EMPTY_VALUE:
                            modified = True

                        try:

                            _row[member_logic_code_col] = 'OR'

                            if _member_logic_code in EMPTY_VALUE:
                                lp.data[-1][member_logic_code_col] = 'OR'

                                modified = True

                        except IndexError:
                            pass

                    sf_item['id'] -= 1

                _rest_id, _member_logic_code, _atom1, _atom2, _values, _cs_val1, _cs_val2 =\
                    rest_id, member_logic_code, atom1, atom2, values, cs_val1, cs_val2

            except ValueError:
                _atom1, _atom2 = {}, {}

            if not self.__reg.native_combined:  # DAOTHER-8855
                _row[id_col] = sf_item['id']
            if combination_id_col == -1 or (combination_id_col != -1 and _row[combination_id_col] in EMPTY_VALUE):
                try:
                    _row[member_id_col] = None
                except IndexError:
                    pass

            if duplicated:
                continue

            lp.add_data(_row)

        get_cs_value.cache_clear()

        if not modified and not has_member_id:
            return True

        member_id_dict = {}

        def update_member_id_dict(rows):
            if len(rows) < 2:
                return

            atom_sel1, atom_sel2 = [], []

            for row in rows:

                try:
                    atom1 = {'chain_id': row[chain_id_1_col],
                             'seq_id': int(row[seq_id_1_col]),
                             'comp_id': row[comp_id_1_col],
                             'atom_id': row[atom_id_1_col]}
                except (ValueError, TypeError):
                    atom1 = {}

                try:
                    atom2 = {'chain_id': row[chain_id_2_col],
                             'seq_id': int(row[seq_id_2_col]),
                             'comp_id': row[comp_id_2_col],
                             'atom_id': row[atom_id_2_col]}
                except (ValueError, TypeError):
                    atom2 = {}

                atom_sel1.append(atom1)
                atom_sel2.append(atom2)

            if isAmbigAtomSelection(atom_sel1, self.__reg.csStat)\
               or isAmbigAtomSelection(atom_sel2, self.__reg.csStat):
                for member_id, row in enumerate(rows, start=1):
                    try:
                        index_id = row[index_id_col]
                        member_id_dict[index_id] = member_id
                    except IndexError:
                        pass

        _row = _rest_id = None
        _union_rows = []

        for row in lp:
            rest_id = row[id_col]

            if _rest_id is not None and rest_id == _rest_id:
                if len(_union_rows) == 0:
                    _union_rows.append(_row)

                _union_rows.append(row)

            else:

                if len(_union_rows) > 0:
                    update_member_id_dict(_union_rows)

                _union_rows = []

            _row = row
            _rest_id = rest_id

        if len(_union_rows) > 0:
            update_member_id_dict(_union_rows)

        if len(member_id_dict) > 0:
            for row in lp:
                try:

                    index_id = row[index_id_col]
                    member_logic_code = row[member_logic_code_col]
                    if member_logic_code == 'AND':
                        continue

                    if index_id in member_id_dict:
                        row[member_id_col] = member_id_dict[index_id]

                except IndexError:
                    pass

        def concat_all_val(row):
            return str(row[chain_id_1_col]) + str(row[seq_id_1_col]) + str(row[comp_id_1_col]) + str(row[atom_id_1_col])\
                + str(row[chain_id_2_col]) + str(row[seq_id_2_col]) + str(row[comp_id_2_col]) + str(row[atom_id_2_col])\
                + concat_target_val(row)

        len_data = len(lp)

        try:

            for idx, row in enumerate(lp, start=1):
                if row[member_logic_code_col] != 'OR':
                    continue
                if idx - 2 > 0:
                    _row = lp.data[idx - 2]
                    if concat_all_val(row) == concat_all_val(_row):
                        row[member_logic_code_col] = '.'
                if idx < len_data:
                    _row = lp.data[idx]
                    if concat_all_val(row) == concat_all_val(_row):
                        row[member_logic_code_col] = '.'

        except IndexError:
            pass

        try:

            del sf_item['saveframe'][loop]

            sf_item['saveframe'].add_loop(lp)
            sf_item['loop'] = lp

            return True

        except ValueError:
            return False

    def updateTorsionAngleConstIdInMrStr(self, sf_item: dict) -> bool:  # pylint: disable=no-self-use
        """ Update _Torsion_angle_constraint.ID in NMR-STAR restraint file.
        """

        loop = sf_item['loop']

        lp = pynmrstar.Loop.from_scratch(loop.category)

        lp.add_tag(loop.tags)

        id_col = loop.tags.index('ID')
        if 'Index_ID' not in loop.tags:
            tag = f'{loop.category}.Index_ID'
            for idx in range(len(loop)):
                loop.data[idx].append(idx + 1)
            loop.add_tag(tag)
            lp.add_tag(tag)
        if 'Combination_ID' not in loop.tags:
            tag = f'{loop.category}.Combination_ID'
            loop.add_tag(tag, update_data=True)
            lp.add_tag(tag)

        index_id_col = loop.tags.index('Index_ID')
        combination_id_col = loop.tags.index('Combination_ID')

        chain_id_1_col = loop.tags.index('Auth_asym_ID_1')
        seq_id_1_col = loop.tags.index('Auth_seq_ID_1')
        atom_id_1_col = loop.tags.index('Auth_atom_ID_1')

        chain_id_2_col = loop.tags.index('Auth_asym_ID_2')
        seq_id_2_col = loop.tags.index('Auth_seq_ID_2')
        atom_id_2_col = loop.tags.index('Auth_atom_ID_2')

        chain_id_3_col = loop.tags.index('Auth_asym_ID_3')
        seq_id_3_col = loop.tags.index('Auth_seq_ID_3')
        atom_id_3_col = loop.tags.index('Auth_atom_ID_3')

        chain_id_4_col = loop.tags.index('Auth_asym_ID_4')
        seq_id_4_col = loop.tags.index('Auth_seq_ID_4')
        atom_id_4_col = loop.tags.index('Auth_atom_ID_4')

        target_val_col = loop.tags.index('Angle_target_val') if 'Angle_target_val' in loop.tags else -1
        target_val_err_col = loop.tags.index('Angle_target_val_err') if 'Angle_target_val_err' in loop.tags else -1
        lower_linear_limit_col = loop.tags.index('Angle_lower_linear_limit') if 'Angle_lower_linear_limit' in loop.tags else -1
        upper_linear_limit_col = loop.tags.index('Angle_upper_linear_limit') if 'Angle_upper_linear_limit' in loop.tags else -1
        lower_limit_col = loop.tags.index('Angle_lower_bound_val') if 'Angle_lower_bound_val' in loop.tags else -1
        upper_limit_col = loop.tags.index('Angle_upper_bound_val') if 'Angle_upper_bound_val' in loop.tags else -1
        weight_col = loop.tags.index('Weight') if 'Weight' in loop.tags else -1

        modified = False

        sf_item['id'] = 0
        sf_item['index_id'] = 0

        len_loop = len(loop)

        proc_row = [False] * len_loop

        for idx, row in enumerate(loop):

            if proc_row[idx]:
                continue

            _row = row

            sf_item['id'] += 1
            sf_item['index_id'] += 1

            try:
                combination_id = row[combination_id_col]
            except IndexError:
                combination_id = None

            if combination_id not in EMPTY_VALUE and str(combination_id) != '1':
                sf_item['id'] -= 1

            _row[id_col] = sf_item['id']
            try:
                _row[index_id_col] = sf_item['index_id']
            except IndexError:
                while index_id_col >= len(_row):
                    _row.append(None)
                _row[index_id_col] = sf_item['index_id']

            try:
                key = _row[chain_id_1_col] + str(_row[seq_id_1_col]) + _row[atom_id_1_col]\
                    + _row[chain_id_2_col] + str(_row[seq_id_2_col]) + _row[atom_id_2_col]\
                    + _row[chain_id_3_col] + str(_row[seq_id_3_col]) + _row[atom_id_3_col]\
                    + _row[chain_id_4_col] + str(_row[seq_id_4_col]) + _row[atom_id_4_col]
                values = (str(_row[target_val_col]) if target_val_col != -1 else '')\
                    + (str(_row[target_val_err_col]) if target_val_err_col != -1 else '')\
                    + (str(_row[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                    + (str(_row[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                    + (str(_row[lower_limit_col]) if lower_limit_col != -1 else '')\
                    + (str(_row[upper_limit_col]) if upper_limit_col != -1 else '')\
                    + (str(_row[weight_col]) if weight_col != -1 else '')
            except TypeError:
                return False

            if combination_id in EMPTY_VALUE and idx + 1 < len_loop:
                combination_id = 1

                for idx2 in range(idx + 1, len_loop):

                    if proc_row[idx2]:
                        continue

                    _row_ = loop.data[idx2]

                    try:
                        _key = _row_[chain_id_1_col] + str(_row_[seq_id_1_col]) + _row_[atom_id_1_col]\
                            + _row_[chain_id_2_col] + str(_row_[seq_id_2_col]) + _row_[atom_id_2_col]\
                            + _row_[chain_id_3_col] + str(_row_[seq_id_3_col]) + _row_[atom_id_3_col]\
                            + _row_[chain_id_4_col] + str(_row_[seq_id_4_col]) + _row_[atom_id_4_col]
                        _values = (str(_row_[target_val_col]) if target_val_col != -1 else '')\
                            + (str(_row_[target_val_err_col]) if target_val_err_col != -1 else '')\
                            + (str(_row_[lower_linear_limit_col]) if lower_linear_limit_col != -1 else '')\
                            + (str(_row_[upper_linear_limit_col]) if upper_linear_limit_col != -1 else '')\
                            + (str(_row_[lower_limit_col]) if lower_limit_col != -1 else '')\
                            + (str(_row_[upper_limit_col]) if upper_limit_col != -1 else '')\
                            + (str(_row_[weight_col]) if weight_col != -1 else '')
                    except TypeError:
                        return False

                    if key == _key:
                        modified = True

                        if values == _values:
                            proc_row[idx2] = True
                            continue

                        if combination_id == 1:
                            try:
                                _row[combination_id_col] = combination_id
                            except IndexError:
                                while combination_id_col >= len(_row):
                                    _row.append(None)
                                _row[combination_id_col] = combination_id
                            lp.add_data(_row)

                        sf_item['index_id'] += 1
                        combination_id += 1

                        _row_[id_col] = sf_item['id']
                        try:
                            _row_[index_id_col] = sf_item['index_id']
                        except IndexError:
                            while index_id_col >= len(_row_):
                                _row_.append(None)
                            _row_[index_id_col] = sf_item['index_id']
                        try:
                            _row_[combination_id_col] = combination_id
                        except IndexError:
                            while combination_id_col >= len(_row_):
                                _row_.append(None)
                            _row_[combination_id_col] = combination_id

                        lp.add_data(_row_)

                        proc_row[idx2] = True

                if combination_id == 1:
                    lp.add_data(_row)

            else:
                lp.add_data(_row)

        if not modified:
            return True

        try:

            del sf_item['saveframe'][loop]

            sf_item['saveframe'].add_loop(lp)
            sf_item['loop'] = lp

            return True

        except ValueError:
            return False

    def validateAtomNomenclature(self, file_name: str, file_type: str, content_subtype: str,
                                 sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                 sf_framecode: str, lp_category: str) -> None:
        """ Validate atom nomenclature using NefTranslator and CCD.
        """

        try:

            if file_type == 'nef':  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
                pairs = self.__reg.nefT.get_nef_comp_atom_pair(sf, lp_category,
                                                               allow_empty=content_subtype in ('chem_shift', 'spectral_peak'))[0]
            else:  # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
                pairs = self.__reg.nefT.get_star_comp_atom_pair(sf, lp_category,
                                                                allow_empty=content_subtype in ('chem_shift', 'spectral_peak'))[0]

            for pair in pairs:
                comp_id = pair['comp_id']
                atom_ids = pair['atom_id']

                # standard residue
                if comp_id in STD_MON_DICT:

                    if file_type == 'nef':

                        _atom_ids = []
                        for atom_id in atom_ids:

                            if atom_id in EMPTY_VALUE:
                                continue

                            _atom_id = self.__reg.nefT.get_star_atom(comp_id, atom_id, leave_unmatched=False)[0]

                            if len(_atom_id) == 0:

                                if self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                   and atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                    continue

                                if self.__reg.remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                    continue

                                if self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                   and atom_id == "HO5'":
                                    continue

                                err = f"Invalid atom name {atom_id!r} (comp_id {comp_id!r}) in a loop {lp_category}."

                                self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                           'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                         f"++ Error  - {err}\n")

                            else:
                                _atom_ids.extend(_atom_id)

                        atom_ids = sorted(set(_atom_ids))

                    for atom_id in atom_ids:

                        if atom_id in EMPTY_VALUE:
                            continue

                        if self.__reg.remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                            continue

                        if self.__reg.csStat.peptideLike(comp_id):
                            if atom_id.upper() == 'HN':
                                self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: 'H'})
                                continue
                            if atom_id.upper() == 'CO':
                                self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: 'C'})
                                continue

                            _atom_id = self.__reg.nefT.get_star_atom(comp_id,
                                                                     translateToStdAtomName(atom_id, comp_id, ccU=self.__reg.ccU),
                                                                     leave_unmatched=False)[0]
                            if len(_atom_id) == 1 and atom_id != _atom_id[0]:
                                self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: _atom_id[0]})
                                continue

                        elif len(atom_id) > 2 and atom_id.endswith('"') and atom_id[-2].isdigit():  # 7zew, 7zex: H5" -> H5''
                            self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: f"{atom_id[:-1]}''"})
                            continue

                        atom_id_ = atom_id

                        if (file_type == 'nef' or not self.__reg.combined_mode or self.__reg.transl_pseudo_name)\
                           and self.isNmrAtomName(comp_id, atom_id):
                            atom_id_ = self.getRepAtomId(comp_id, atom_id)

                            if file_type == 'nmr-star' and self.__reg.combined_mode and self.__reg.transl_pseudo_name\
                               and atom_id != atom_id_\
                               and not content_subtype.startswith('spectral_peak'):

                                warn = f"Conventional psuedo atom {comp_id}:{atom_id} is translated to {atom_id_!r} "\
                                    "according to the IUPAC atom nomenclature."

                                self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': warn})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                         f"++ Warning  - {warn}\n")

                                self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: atom_id_})

                        if not self.__reg.nefT.validate_comp_atom(comp_id, atom_id_):

                            if self.__reg.csStat.peptideLike(comp_id) and atom_id_.startswith('H') and atom_id_.endswith('1')\
                               and self.__reg.nefT.validate_comp_atom(comp_id, f'{atom_id_[:-1]}2')\
                               and self.__reg.nefT.validate_comp_atom(comp_id, f'{atom_id_[:-1]}3')\
                               and not content_subtype.startswith('spectral_peak'):

                                _atom_id_ = atom_id_[:-1]
                                _atom_id_1 = f'{_atom_id_}1'
                                _atom_id_2 = f'{_atom_id_}2'
                                _atom_id_3 = f'{_atom_id_}3'

                                warn = f"{comp_id}:{_atom_id_1}/{_atom_id_2} should be {comp_id}:{_atom_id_3}/{_atom_id_2} "\
                                    "according to the IUPAC atom nomenclature, respectively."

                                self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': warn})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                         f"++ Warning  - {warn}\n")

                                # @see: https://bmrb.io/ref_info/atom_nom.tbl
                                self.__reg.dpR.fixAtomNomenclature(comp_id, {_atom_id_1: _atom_id_3})

                            elif self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                    and atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                pass

                            elif self.__reg.remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                pass

                            elif self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                    and atom_id == "HO5'":
                                pass

                            else:
                                is_valid, cc_name, cc_rel_status = self.getChemCompNameAndStatusOf(comp_id)

                                if is_valid:
                                    if cc_rel_status != 'REL':
                                        cc_name = f"(Not available due to CCD status code {cc_rel_status})"
                                cc_name = '' if cc_name is None else ', ' + cc_name

                                if content_subtype.startswith('spectral_peak')\
                                   or (self.__reg.csStat.peptideLike(comp_id)
                                       and atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3')):

                                    err = f"Unmatched atom name {atom_id!r} (comp_id {comp_id!r}{cc_name}) in a loop {lp_category}."

                                    self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                             f"++ Warning  - {err}\n")

                                else:

                                    err = f"Invalid atom name {atom_id!r} (comp_id {comp_id!r}{cc_name}) in a loop {lp_category}."

                                    if self.__reg.remediation_mode and len(self.getAtomIdListInXplor(comp_id, atom_id)) > 0:

                                        self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                                 f"++ Warning  - {err}\n")

                                    else:

                                        self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                                  {'file_name': file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                                 f"++ Error  - {err}\n")

                # non-standard residue
                else:

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

                        ref_atom_ids = [a['atom_id'] for a in self.__reg.ccU.lastAtomDictList]
                        # if a['leaving_atom_flag'] != 'Y']
                        unk_atom_ids = []

                        for atom_id in atom_ids:

                            if atom_id in EMPTY_VALUE:
                                continue

                            if file_type == 'nef':
                                _atom_id = self.__reg.nefT.get_star_atom(comp_id, atom_id, leave_unmatched=False)[0]
                                if len(_atom_id) > 0:
                                    atom_id = _atom_id[0]

                            if atom_id not in ref_atom_ids:

                                if self.__reg.remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                    continue

                                unk_atom_ids.append(atom_id)

                        if len(unk_atom_ids) > 0:
                            is_valid, cc_name, cc_rel_status = self.getChemCompNameAndStatusOf(comp_id)

                            if is_valid:
                                if cc_rel_status != 'REL':
                                    cc_name = f"(Not available due to CCD status code {cc_rel_status})"
                            cc_name = '' if cc_name is None else ', ' + cc_name

                            warn = f"Unknown atom_id {unk_atom_ids!r} (comp_id {comp_id!r}{cc_name})."

                            self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                     f"++ Warning  - {warn}\n")

                        ref_elems = set(a['type_symbol'] for a in self.__reg.ccU.lastAtomDictList
                                        if a['leaving_atom_flag'] != 'Y')

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

                        for atom_id in atom_ids:

                            if atom_id in EMPTY_VALUE:
                                continue

                            if self.__reg.remediation_mode and atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                continue

                            if self.__reg.csStat.peptideLike(comp_id):
                                if atom_id.upper() == 'HN':
                                    self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: 'H'})
                                    continue
                                if atom_id.upper() == 'CO':
                                    self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: 'C'})
                                    continue

                            elif len(atom_id) > 2 and atom_id.endswith('"') and atom_id[-2].isdigit():  # 7zew, 7zex: H5" -> H5''
                                self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: f"{atom_id[:-1]}''"})
                                continue

                            atom_id_ = atom_id

                            if (file_type == 'nef' or not self.__reg.combined_mode or self.__reg.transl_pseudo_name)\
                               and self.isNmrAtomName(comp_id, atom_id) and not content_subtype.startswith('spectral_peak'):
                                atom_id_ = self.getRepAtomId(comp_id, atom_id)

                                if file_type == 'nmr-star' and self.__reg.combined_mode and self.__reg.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.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                             f"++ Warning  - {warn}\n")

                                    self.__reg.dpR.fixAtomNomenclature(comp_id, {atom_id: atom_id_})

                    else:
                        pass

            if file_type == 'nmr-star':

                try:

                    peptide_only = all(len(pair['comp_id']) == 3 and pair['comp_id'] in STD_MON_DICT for pair in pairs)

                    auth_pairs = self.__reg.nefT.get_star_auth_comp_atom_pair(sf, lp_category)[0]

                    for auth_pair in auth_pairs:
                        auth_comp_id = auth_pair['comp_id']
                        if peptide_only and len(auth_comp_id) == 1:
                            comp_id = next((k for k, v in STD_MON_DICT.items() if v == auth_comp_id), auth_comp_id)
                        else:
                            comp_id = auth_comp_id
                        comp_id = translateToStdResName(comp_id, ccU=self.__reg.ccU)
                        auth_atom_ids = auth_pair['atom_id']

                        # standard residue
                        if comp_id in STD_MON_DICT:

                            self.__reg.ccU.updateChemCompDict(comp_id)
                            ref_atom_ids = [a['atom_id'] for a in self.__reg.ccU.lastAtomDictList]

                            _auth_atom_ids = []
                            for auth_atom_id in auth_atom_ids:

                                if auth_atom_id in EMPTY_VALUE:
                                    continue

                                _auth_atom_id = translateToStdAtomName(auth_atom_id, comp_id, ref_atom_ids, ccU=self.__reg.ccU)

                                auth_atom_ids = self.getAtomIdList(comp_id, _auth_atom_id)

                                if len(auth_atom_ids) > 0:
                                    _auth_atom_ids.extend(auth_atom_ids)

                                else:

                                    if self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                       and _auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                        continue

                                    if self.__reg.remediation_mode and _auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    auth_atom_ids = self.getAtomIdListInXplor(comp_id, _auth_atom_id)

                                    if len(auth_atom_ids) > 0:
                                        _auth_atom_ids.extend(auth_atom_ids)

                                    else:

                                        warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {auth_comp_id})."

                                        self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                                 f"++ Warning  - {warn}\n")

                            auth_atom_ids = sorted(set(_auth_atom_ids))

                            for auth_atom_id in auth_atom_ids:

                                if auth_atom_id in EMPTY_VALUE:
                                    continue

                                if not self.__reg.nefT.validate_comp_atom(comp_id,
                                                                          translateToStdAtomName(auth_atom_id, comp_id,
                                                                                                 ref_atom_ids,
                                                                                                 ccU=self.__reg.ccU)):

                                    if self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                       and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                        continue

                                    if self.__reg.remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} (Auth_comp_ID {auth_comp_id})."

                                    self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                             f"++ Warning  - {warn}\n")

                        # non-standard residue
                        else:
                            has_comp_id = False

                            for pair in pairs:

                                if pair['comp_id'] != comp_id:
                                    continue

                                has_comp_id = True

                                atom_ids = pair['atom_id']

                                if (set(auth_atom_ids) | set(atom_ids)) != set(atom_ids):

                                    for auth_atom_id in (set(auth_atom_ids) | set(atom_ids)) - set(atom_ids):

                                        if auth_atom_id in EMPTY_VALUE:
                                            continue

                                        if self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                           and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                            continue

                                        if self.__reg.remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                            continue

                                        if self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                           and atom_id == "HO5'":
                                            continue

                                        warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} "\
                                            f"(Auth_comp_ID {comp_id}, non-standard residue)."

                                        self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                                 f"++ Warning  - {warn}\n")

                                break

                            if not has_comp_id:

                                for auth_atom_id in auth_atom_ids:

                                    if auth_atom_id in EMPTY_VALUE:
                                        continue

                                    if self.__reg.nonblk_bad_nterm and self.__reg.csStat.peptideLike(comp_id)\
                                       and auth_atom_id in ('H1', 'H2', 'H3', 'HT1', 'HT2', 'HT3'):
                                        continue

                                    if self.__reg.remediation_mode and auth_atom_id[0] in ('Q', 'M'):  # DAOTHER-8663, 8751
                                        continue

                                    if self.__reg.remediation_mode and self.__reg.csStat.getTypeOfCompId(comp_id)[1]\
                                       and atom_id == "HO5'":
                                        continue

                                    warn = f"Unmatched Auth_atom_ID {auth_atom_id!r} "\
                                        f"(Auth_comp_ID {comp_id}, non-standard residue)."

                                    self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                             f"++ Warning  - {warn}\n")

                except LookupError:
                    # """
                    # self.__reg.report.error.appendDescription('missing_mandatory_item',
                    #                                           {'file_name': file_name, 'sf_framecode': sf_framecode,
                    #                                            'category': lp_category, 'description': str(e).strip("'")})
                    #
                    # self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                    #                      f"++ LookupError  - {file_name} {sf_framecode} {lp_category} {str(e)}\n")
                    # """
                    pass

                except ValueError as e:

                    self.__reg.report.error.appendDescription('invalid_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': str(e).strip("'")})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                             f"++ ValueError  - {str(e)}\n")

                except UserWarning as e:

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

                    for err in errs:

                        if len(err) == 0:
                            continue

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

                            p = err.index(']') + 2
                            err = err[p:]

                            self.__reg.report.error.appendDescription('invalid_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                     f"++ ValueError  - {err}\n")

                        else:

                            self.__reg.report.error.appendDescription('internal_error',
                                                                      f"+{self.__class_name__}.validateAtomNomenclature() "
                                                                      "++ Error  - " + err)

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                                     f"++ Error  - {err}\n")

                except Exception as e:  # pylint: disable=broad-exception-caught

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateAtomNomenclature() "
                                                              "++ Error  - " + str(e))

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                             f"++ Error  - {str(e)}\n")

        except LookupError as e:

            self.__reg.report.error.appendDescription('missing_mandatory_item',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                 f"++ LookupError  - {file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                     f"++ ValueError  - {str(e)}\n")

        except UserWarning as e:

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

            for err in errs:

                if len(err) == 0:
                    continue

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

                    p = err.index(']') + 2
                    err = err[p:]

                    self.__reg.report.error.appendDescription('invalid_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                             f"++ ValueError  - {err}\n")

                else:

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateAtomNomenclature() "
                                                              "++ Error  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                             f"++ Error  - {err}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.validateAtomNomenclature() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAtomNomenclature() "
                                     f"++ Error  - {str(e)}\n")

    def validateAtomTypeOfCsLoop(self, file_name: str, file_type: str,
                                 sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                 sf_framecode: str, lp_category: str) -> None:
        """ Validate atom type, isotope number on assigned chemical shifts.
        """

        if not self.__reg.combined_mode:
            return

        try:

            # DAOTHER-7389, issue #3, allow empty for 'chem_shift'
            if file_type == 'nef':
                a_types = self.__reg.nefT.get_nef_atom_type_from_cs_loop(sf, allow_empty=True)[0]
            else:
                a_types = self.__reg.nefT.get_star_atom_type_from_cs_loop(sf, allow_empty=True)[0]

            for a_type in a_types:
                atom_type = a_type['atom_type']
                isotope_nums = a_type['isotope_number']
                atom_ids = a_type['atom_id']

                if atom_type not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS.keys():

                    err = f"Invalid atom_type {atom_type!r} in a loop {lp_category}."

                    self.__reg.report.error.appendDescription('invalid_atom_type',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

                else:

                    for isotope_num in isotope_nums:
                        if isotope_num not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type]:

                            err = f"Invalid isotope number {str(isotope_num)!r} (atom_type {atom_type}, "\
                                f"allowed isotope number {ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type]}) in a loop {lp_category}."

                            self.__reg.report.error.appendDescription('invalid_isotope_number',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

                    for atom_id in atom_ids:
                        if not atom_id.startswith(atom_type):

                            if self.__reg.remediation_mode and 1 in isotope_nums\
                               and atom_id[0] in PSE_PRO_BEGIN_CODE:  # DAOTHER-8663, 8751, 9520
                                continue

                            err = f"Invalid atom name {atom_id!r} (atom_type {atom_type!r}) in a loop {lp_category}."

                            self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

        except LookupError as e:

            if not self.__reg.resolve_conflict:
                self.__reg.report.error.appendDescription('missing_mandatory_item',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': str(e).strip("'")})

                self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ LookupError  - "
                                     f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

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

            for err in errs:

                if len(err) == 0:
                    continue

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

                    p = err.index(']') + 2
                    err = err[p:]

                    self.__reg.report.error.appendDescription('invalid_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ ValueError  - {err}\n")

                else:

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateAtomTypeOfCsLoop() "
                                                              "++ Error  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - {err}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAtomTypeOfCsLoop() ++ Error  - {str(e)}\n")

    def validateAmbigCodeOfCsLoop(self, file_name: str,
                                  sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                  sf_framecode: str, lp_category: str) -> bool:
        """ Validate ambiguity code on assigned chemical shifts.
        """

        try:

            need_set_id = False
            valid = True

            a_codes = self.__reg.nefT.get_star_ambig_code_from_cs_loop(sf)[0]

            comp_ids_wo_ambig_code = []

            for a_code in a_codes:
                comp_id = a_code['comp_id']
                ambig_code = a_code['ambig_code']
                atom_ids = a_code['atom_id']

                if ambig_code is None:
                    comp_ids_wo_ambig_code.append(comp_id)

                elif ambig_code == 1 or ambig_code >= 4:
                    need_set_id |= ambig_code in (4, 5, 6, 9)

                # ambig_code is 2 (geminal atoms) or 3 (aromatic ring atoms in opposite side)
                else:

                    for atom_id in atom_ids:

                        _atom_id = atom_id

                        if self.isNmrAtomName(comp_id, atom_id):
                            _atom_id = self.getRepAtomId(comp_id, atom_id)

                        allowed_ambig_code = self.__reg.csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)

                        if ambig_code > allowed_ambig_code > 0:

                            if allowed_ambig_code < 1:

                                if self.__reg.remediation_mode:
                                    pass

                                else:

                                    warn = f"Ambiguity code {str(ambig_code)!r} (comp_id {comp_id}, atom_id {atom_id}) "\
                                        "should be '1' according to the BMRB definition."

                                    self.__reg.report.warning.appendDescription('ambiguity_code_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() "
                                                             f"++ Warning  - {warn}\n")

                                    valid = False

                            else:

                                if self.__reg.remediation_mode:
                                    pass

                                else:

                                    err = f"Invalid ambiguity code {str(ambig_code)!r} (comp_id {comp_id}, atom_id {atom_id}, "\
                                        f"allowed ambig_code {[1, allowed_ambig_code, 4, 5, 6, 9]}) in a loop {lp_category}."

                                    self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() "
                                                             f"++ Error  - {err}\n")

                                    valid = False

            if len(comp_ids_wo_ambig_code) > 0:

                warn = f"Missing ambiguity code for the following residues {comp_ids_wo_ambig_code}."

                self.__reg.report.warning.appendDescription('missing_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                             'category': lp_category, 'description': warn})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ Warning  - {warn}\n")

                valid = False

            if need_set_id and valid:

                list_id = get_first_sf_tag(sf, 'ID')

                try:

                    lp = sf.get_loop(lp_category)

                    ambig_code_col = lp.tags.index('Ambiguity_code')
                    ambig_set_id_col = lp.tags.index('Ambiguity_set_ID')

                    id_col = lp.tags.index('ID')
                    chain_id_col = lp.tags.index('Entity_assembly_ID')
                    seq_id_col = lp.tags.index('Comp_index_ID')
                    atom_type_col = lp.tags.index('Atom_type')

                    aux_lp_category = AUX_LP_CATEGORIES['nmr-star']['chem_shift'][0]

                    if any(True for aux_loop in sf if aux_loop.category == aux_lp_category):

                        aux_loop = sf.get_loop(aux_lp_category)

                        del sf[aux_loop]

                    aux_lp = pynmrstar.Loop.from_scratch(aux_lp_category)

                    aux_items = ['Ambiguous_shift_set_ID', 'Atom_chem_shift_ID', 'Entry_ID', 'Assigned_chem_shift_list_ID']

                    aux_tags = [f'{aux_lp_category}.{item}' for item in aux_items]

                    aux_lp.add_tag(aux_tags)

                    inter_residue_seq_id = {}

                    for _row in lp:

                        ambig_code = _row[ambig_code_col]

                        if ambig_code in EMPTY_VALUE:
                            continue

                        if isinstance(ambig_code, str):
                            ambig_code = int(ambig_code)

                        if ambig_code not in (5, 6, 9):
                            continue

                        chain_id = _row[chain_id_col]
                        seq_id = _row[seq_id_col]

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

                        inter_residue_seq_id[chain_id].add(seq_id)

                    aux_index_id = 0
                    ambig_shift_set_id = {}

                    for _idx, _row in enumerate(lp):

                        ambig_code = _row[ambig_code_col]

                        if ambig_code in EMPTY_VALUE:
                            continue

                        if isinstance(ambig_code, str):
                            ambig_code = int(ambig_code)

                        if ambig_code not in (4, 5):
                            continue

                        chain_id = _row[chain_id_col]
                        seq_id = _row[seq_id_col]
                        atom_type = _row[atom_type_col]

                        if ambig_code == 4:
                            key = (chain_id, str(seq_id), atom_type, ambig_code)
                        else:
                            key = (chain_id, str(inter_residue_seq_id[chain_id]), atom_type, ambig_code)

                        if key not in ambig_shift_set_id:
                            aux_index_id += 1
                            ambig_shift_set_id[key] = aux_index_id

                        lp.data[_idx][ambig_set_id_col] = ambig_shift_set_id[key]

                        _aux_row = [None] * 4
                        _aux_row[0], _aux_row[1], _aux_row[2], _aux_row[3] =\
                            ambig_shift_set_id[key], _row[id_col], self.__reg.entry_id, list_id

                        aux_lp.add_data(_aux_row)

                    if len(aux_lp) > 0:
                        sf.add_loop(aux_lp)
                        return True

                except (KeyError, IndexError, ValueError):
                    pass

        except LookupError as e:

            if not self.__reg.resolve_conflict:
                self.__reg.report.error.appendDescription('missing_mandatory_item',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': str(e).strip("'")})

                self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ LookupError  - "
                                     f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

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

            for err in errs:

                if len(err) == 0:
                    continue

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

                    p = err.index(']') + 2
                    err = err[p:]

                    self.__reg.report.error.appendDescription('invalid_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ ValueError  - {err}\n")

                else:

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() "
                                                              "++ Error  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ Error  - {err}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateAmbigCodeOfCsLoop() ++ Error  - {str(e)}\n")

        return False

    def testIndexConsistency(self, file_name: str,
                             sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                             sf_framecode: str, lp_category: str, index_tag: str) -> None:
        """ Perform consistency test on index of interesting loops.
        """

        try:

            indices = self.__reg.nefT.get_index(sf, lp_category, 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.__reg.report.warning.appendDescription('disordered_index',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                             'category': lp_category, 'description': warn})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ Warning  - {warn}\n")

        except KeyError as e:

            self.__reg.report.error.appendDescription('duplicated_index',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ KeyError  - {str(e)}\n")

        except LookupError:
            # """
            # self.__reg.report.error.appendDescription('missing_mandatory_item',
            #                                           {'file_name': file_name, 'sf_framecode': sf_framecode,
            #                                            'category': lp_category, 'description': str(e).strip("'")})
            #
            # self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ LookupError  - "
            #                      f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")
            # """
            pass

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

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

            for err in errs:

                if len(err) == 0:
                    continue

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

                    p = err.index(']') + 2
                    err = err[p:]

                    self.__reg.report.error.appendDescription('invalid_data',
                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                               'category': lp_category, 'description': err})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ ValueError  - {err}\n")

                elif err.startswith('[Too big loop]'):
                    continue

                else:

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.testIndexConsistency() ++ Error  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ Error  - {err}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testIndexConsistency() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testIndexConsistency() ++ Error  - {str(e)}\n")

    def testDataConsistencyInLoop(self, file_list_id: int, file_name: str, file_type: str, content_subtype: str,
                                  sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                  sf_framecode: str, lp_category: str, parent_pointer: int) -> None:
        """ Perform consistency test on data of interesting loops.
        """

        allowed_tags = ALLOWED_TAGS[file_type][content_subtype]
        disallowed_tags = None

        if content_subtype == 'spectral_peak':

            try:

                _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
                num_dim = int(_num_dim)

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

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

            max_dim = num_dim + 1

            key_items = []
            for dim in range(1, max_dim):
                for k in PK_KEY_ITEMS[file_type]:
                    if k['type'] == 'float':  # position
                        _k = copy.copy(k)
                        if '%s' in k['name']:
                            _k['name'] = k['name'] % dim
                        key_items.append(_k)
            for k in PK_KEY_ITEMS[file_type]:
                if k['type'] == 'positive-int':  # peak_id
                    key_items.append(k)

            data_items = []
            for d in DATA_ITEMS[file_type][content_subtype]:
                data_items.append(d)
            for dim in range(1, max_dim):
                for d in self.__reg.pk_data_items[file_type]:
                    _d = copy.copy(d)
                    if '%s' in d['name']:
                        _d['name'] = d['name'] % dim
                    if 'default-from' in d and '%s' in d['default-from']:  # DAOTHER-7421
                        _d['default-from'] = d['default-from'] % dim
                    data_items.append(_d)

            if max_dim < MAX_DIM_NUM_OF_SPECTRA:
                disallowed_tags = []
                for dim in range(max_dim, MAX_DIM_NUM_OF_SPECTRA):
                    for t in DISALLOWED_PK_TAGS[file_type]:
                        if '%s' in t:
                            t = t % dim
                        disallowed_tags.append(t)

                if self.__reg.bmrb_only:
                    loop = sf.get_loop(lp_category)
                    disallowed_tags = list(set(loop.tags) & set(disallowed_tags))
                    loop.remove_tag(disallowed_tags)

        else:

            key_items = self.__reg.key_items[file_type][content_subtype]
            data_items = DATA_ITEMS[file_type][content_subtype]

            if file_type == 'nmr-star' and content_subtype == 'ccr_dd_restraint':
                loop = sf.get_loop(lp_category)
                if 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                    key_items = copy.copy(key_items)
                    key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                    if key_item is not None:
                        key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

        lp_data = None

        try:

            lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items,
                                                 allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                                 test_on_index=True, enforce_non_zero=True, enforce_sign=True,
                                                 enforce_range=True, enforce_enum=True,
                                                 enforce_allowed_tags=(file_type == 'nmr-star' and not self.__reg.bmrb_only),
                                                 excl_missing_data=self.__reg.excl_missing_data)[0]

            self.__reg.lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode,
                                                        'category': lp_category, 'data': lp_data})

        except KeyError as e:

            self.__reg.report.error.appendDescription('multiple_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ KeyError  - {str(e)}\n")

        except LookupError as e:

            item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

            self.__reg.report.error.appendDescription(item,
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ LookupError  - "
                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ ValueError  - {str(e)}\n")

        except UserWarning as e:

            warns = str(e).strip("'").split('\n')

            has_multiple_data = has_bad_pattern = False

            for warn in warns:

                if len(warn) == 0:
                    continue

                zero = warn.startswith('[Zero value error]')
                nega = warn.startswith('[Negative value error]')
                rang = warn.startswith('[Range value error]')
                enum = warn.startswith('[Enumeration error]')
                mult = warn.startswith('[Multiple data]')
                remo = warn.startswith('[Remove bad pattern]')
                clea = warn.startswith('[Clear bad pattern]')

                if zero or nega or range or enum or mult or remo or clea:

                    p = warn.index(']') + 2
                    warn = warn[p:]

                    if zero or nega or rang:
                        item = 'unusual_data'
                    elif enum:
                        item = 'enum_mismatch'
                    elif remo:
                        if content_subtype == 'chem_shift':
                            warn += ' Your unassigned chemical shifts have been removed.'
                            item = 'incompletely_assigned_chemical_shift'
                        else:
                            item = 'insufficient_data'
                        has_bad_pattern = True
                    elif clea:
                        if content_subtype == 'chem_shift':
                            warn += ' Partially assiged chemical shifts should be resolved or removed.'
                            item = 'incompletely_assigned_chemical_shift'
                        elif content_subtype.startswith('spectral_peak'):

                            if self.__reg.remediation_mode:
                                continue

                            warn += ' Unassigned spectral peaks can be included in your peak list(s).'
                            item = 'incompletely_assigned_spectral_peak'
                        else:
                            item = 'insufficient_data'
                    elif self.__reg.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.__reg.resolve_conflict:

                        self.__reg.report.warning.appendDescription(item,
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                     'category': lp_category, 'description': warn})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ Warning  - {warn}\n")

                    else:

                        self.__reg.report.error.appendDescription(item,
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': warn})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ KeyError  - {warn}\n")

                else:

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.testDataConsistencyInLoop() "
                                                              "++ Error  - " + warn)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ Error  - {warn}\n")

            # try to parse data without constraints
            if has_multiple_data:
                conflict_id = self.__reg.nefT.get_conflict_id(sf, lp_category, key_items)[0]

                if len(conflict_id) > 0:
                    loop = sf if self.__reg.star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)

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

                    index_tag = INDEX_TAGS[file_type][content_subtype]
                    if index_tag is not None:
                        index_col = loop.tags.index(index_tag) if index_tag in loop.tags else -1
                        if index_col != -1:
                            for idx, row in enumerate(loop, start=1):
                                row[index_col] = idx

            # try to parse data without bad patterns
            if has_bad_pattern:
                conflict_id = self.__reg.nefT.get_bad_pattern_id(sf, lp_category, key_items, data_items)[0]

                if len(conflict_id) > 0:
                    loop = sf if self.__reg.star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)

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

            try:

                lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items,
                                                     allowed_tags, disallowed_tags, parent_pointer=parent_pointer,
                                                     enforce_allowed_tags=(file_type == 'nmr-star' and not self.__reg.bmrb_only),
                                                     excl_missing_data=self.__reg.excl_missing_data)[0]

                self.__reg.lp_data[content_subtype].append({'file_name': file_name, 'sf_framecode': sf_framecode,
                                                            'category': lp_category, 'data': lp_data})

            except Exception:  # pylint: disable=broad-exception-caught
                pass

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testDataConsistencyInLoop() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInLoop() ++ Error  - {str(e)}\n")

    def detectConflictDataInLoop(self, file_name: str, file_type: str, content_subtype: str,
                                 sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                 sf_framecode: str, lp_category: str) -> None:
        """ Detect redundant/inconsistent data of interesting loops.
        """

        lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                        if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

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

        key_items = self.__reg.consist_key_items[file_type][content_subtype]

        if file_type == 'nmr-star' and content_subtype == 'ccr_dd_restraint':
            loop = sf.get_loop(lp_category)
            if 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                key_items = copy.copy(key_items)
                key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                if key_item is not None:
                    key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

        conflict_id_set = self.__reg.nefT.get_conflict_id_set(sf, lp_category, key_items)[0]

        if conflict_id_set is None:
            return

        data_items = CONSIST_DATA_ITEMS[file_type][content_subtype]
        index_tag = INDEX_TAGS[file_type][content_subtype]
        id_tag = CONSIST_ID_TAGS[file_type][content_subtype]

        data_unit_name = 'atom pair'

        if content_subtype == 'dist_restraint':
            max_inclusive = DIST_UNCERT_MAX

        elif content_subtype == 'dihed_restraint':
            max_inclusive = ANGLE_UNCERT_MAX

            data_unit_name = 'dihedral angle'

            dh_item_names = ITEM_NAMES_IN_DIHED_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']
            lower_limit_name = dh_item_names['lower_limit']
            upper_limit_name = dh_item_names['upper_limit']

            def ext_atoms(row):
                return ({'chain_id': row[chain_id_1_name], 'seq_id': row[seq_id_1_name],
                         'comp_id': row[comp_id_1_name], 'atom_id': row[atom_id_1_name]},
                        {'chain_id': row[chain_id_2_name], 'seq_id': row[seq_id_2_name],
                         'comp_id': row[comp_id_2_name], 'atom_id': row[atom_id_2_name]},
                        {'chain_id': row[chain_id_3_name], 'seq_id': row[seq_id_3_name],
                         'comp_id': row[comp_id_3_name], 'atom_id': row[atom_id_3_name]},
                        {'chain_id': row[chain_id_4_name], 'seq_id': row[seq_id_4_name],
                         'comp_id': row[comp_id_4_name], 'atom_id': row[atom_id_4_name]})

        elif content_subtype == 'rdc_restraint':
            max_inclusive = RDC_UNCERT_MAX

            data_unit_name = 'bond vector'

        for id_set in conflict_id_set:
            len_id_set = len(id_set)

            if len_id_set < 2:
                continue

            redundant = True

            for i in range(len_id_set - 1):

                for j in range(i + 1, len_id_set):

                    try:
                        row_1 = lp_data[id_set[i]]
                        row_2 = lp_data[id_set[j]]
                    except IndexError:
                        continue

                    conflict = 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 None in (val_1, val_2):
                            redundant = False
                            continue

                        if val_1 == val_2:
                            continue

                        redundant = False

                        _val_1 = str(val_1) if val_1 >= 0.0 else f'({val_1})'
                        _val_2 = str(val_2) if val_2 >= 0.0 else f'({val_2})'

                        if content_subtype == 'dist_restraint':

                            r = abs(val_1 - val_2) / abs(val_1 + val_2)

                            if r >= R_CONFLICTED_DIST_RESTRAINT:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}|/|{_val_1}+{_val_2}| = {r:.1%} "\
                                    "is out of acceptable range, "\
                                    f"{int(R_CONFLICTED_DIST_RESTRAINT * 100)} %, "
                                conflict = True

                            elif r >= R_INCONSISTENT_DIST_RESTRAINT:
                                discrepancy += f"{dname} |{_val_1}-{_val_2}|/|{_val_1}+{_val_2}| = {r:.1%} "\
                                    "is out of typical range, "\
                                    f"{int(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))

                                atom1, atom2, atom3, atom4 = ext_atoms(row_1)

                                data_type = row_1[angle_type_name]

                                peptide, nucleotide, carbohydrate = self.__reg.csStat.getTypeOfCompId(atom2['comp_id'])
                                plane_like = is_like_planality_boundary(row_1, lower_limit_name, upper_limit_name)

                                data_type = self.getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                            [atom1, atom2, atom3, atom4], plane_like)[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 * INCONSIST_OVER_CONFLICTED:
                                discrepancy +=\
                                    f"{dname} |{_val_1}-{_val_2}| = {r:.1f} is out of typical range, "\
                                    f"{max_inclusive * INCONSIST_OVER_CONFLICTED}{'°' if content_subtype == 'dihed_restraint' else 'Hz'}, "  # noqa: E501, pylint: disable=line-too-long
                                inconsist = True

                    if conflict:

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

                        if index_tag in row_1:
                            warn = f"[Check rows of {index_tag} {row_1[index_tag]} vs {row_2[index_tag]}, "\
                                f"{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}, "\
                                f"{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.__reg.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}")})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.detetConflictDataInLoop() ++ Warning  - {warn}\n")

                    elif inconsist:

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

                        if index_tag in row_1:
                            warn = f"[Check rows of {index_tag} {row_1[index_tag]} vs {row_2[index_tag]}, "\
                                f"{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}, "\
                                f"{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.__reg.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}")})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.detetConflictDataInLoop() ++ Warning  - {warn}\n")

            if redundant:

                idx_msg = index_tag + ' '
                if index_tag in lp_data[0]:
                    for row_id in id_set:
                        try:
                            idx_msg += f"{lp_data[row_id][index_tag]} vs "
                        except IndexError:
                            continue
                else:
                    for row_id in id_set:
                        idx_msg += f"{row_id + 1} vs "
                idx_msg = idx_msg[:-4] + ', '
                idx_msg += id_tag + ' '
                for row_id in id_set:
                    try:
                        idx_msg += f"{lp_data[row_id][id_tag]} vs "
                    except IndexError:
                        continue

                if not idx_msg.endswith(' vs '):
                    continue

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

                self.__reg.report.warning.appendDescription('redundant_data',
                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                             'category': lp_category, 'description': warn})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.detetConflictDataInLoop() ++ Warning  - {warn}\n")

    def testParentChildRelation(self, file_name: str, file_type: str, content_subtype: str,
                                parent_keys: set, list_id: int, sf_framecode: str,
                                sf_framecode_dict: dict, sf_tag_data: dict) -> bool:
        """ 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.__reg.report.getTotalErrors()

        key_base = SF_TAG_PREFIXES['nmr-star'][content_subtype].lstrip('_')

        parent_key_name = f'{key_base}.ID'
        child_key_name = f'{key_base}_ID'

        try:

            if parent_key_name in sf_tag_data:
                parent_key = sf_tag_data[parent_key_name]
            else:
                parent_key = list_id

            if parent_key in parent_keys:

                err = f"{parent_key_name} {str(parent_key)!r} must be unique."

                self.__reg.report.error.appendDescription('duplicated_index',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'description': err})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testParentChildRelation() ++ KeyError  - {err}\n")

            index_tag = INDEX_TAGS[file_type][content_subtype]
            lp_category = LP_CATEGORIES[file_type][content_subtype]

            lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is not None:

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

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

                        if row[child_key_name] in sf_framecode_dict:
                            err = err[0:-1] + f" to point the parent {sf_framecode!r} saveframe. "\
                                f"The pointer has been reserved for the {sf_framecode_dict[row[child_key_name]]!r} saveframe."

                        self.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testParentChildRelation() ++ ValueError  - {err}\n")

                        break

            if AUX_LP_CATEGORIES[file_type][content_subtype] is not None:

                for lp_category in AUX_LP_CATEGORIES[file_type][content_subtype]:

                    aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                                     if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                     and lp['category'] == lp_category), None)

                    if aux_data is None:
                        continue

                    for row in aux_data:
                        if child_key_name in row and row[child_key_name] != parent_key:

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

                            if row[child_key_name] in sf_framecode_dict:
                                err = err[0:-1] + f" to point the parent {sf_framecode!r} saveframe. "\
                                    f"The pointer has been reserved for the {sf_framecode_dict[row[child_key_name]]!r} saveframe."

                            self.__reg.report.error.appendDescription('invalid_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testParentChildRelation() ++ ValueError  - {err}\n")

                            break

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testParentChildRelation() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testParentChildRelation() ++ Error  - {str(e)}\n")

        return self.__reg.report.getTotalErrors() == __errors

    def validateCsValue(self, file_list_id: int, file_name: str, file_type: str, content_subtype: str,
                        sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                        sf_framecode: str, lp_category: str) -> bool:
        """ Validate assigned chemical shift value based on BMRB chemical shift statistics.
        """

        no_reason_message = " Neither aromatic ring nor paramagnetic/ferromagnetic atom were found in the vicinity."
        fold_warn_message = " Please check for folded/aliased signals."

        item_names = ITEM_NAMES_IN_CS_LOOP[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']
        value_name = item_names['value']
        ambig_code_name = 'Ambiguity_code'  # NMR-STAR specific
        occupancy_name = 'Occupancy'  # NMR-STAR specific

        full_value_name = f'{lp_category}.{value_name}'

        max_inclusive = 0.01

        modified = False

        has_mr_atom_name_mapping = file_type == 'nmr-star' and self.__reg.remediation_mode\
            and self.__reg.mr_atom_name_mapping is not None and len(self.__reg.mr_atom_name_mapping) > 0

        try:

            details_col = -1

            if file_type == 'nmr-star':

                loop = sf if self.__reg.star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)

                if has_mr_atom_name_mapping:
                    auth_seq_id_col = loop.tags.index('Auth_seq_ID') if 'Auth_seq_ID' in loop.tags else -1
                    auth_comp_id_col = loop.tags.index('Auth_comp_ID') if 'Auth_comp_ID' in loop.tags else -1
                    auth_atom_id_col = loop.tags.index('Auth_atom_ID') if 'Auth_atom_ID' in loop.tags else -1
                    orig_atom_name_col = loop.tags.index('Original_PDB_atom_name') if 'Original_PDB_atom_name' in loop.tags else -1
                    if -1 in (auth_seq_id_col, auth_comp_id_col, auth_atom_id_col, orig_atom_name_col):
                        has_mr_atom_name_mapping = False

                if 'Details' in loop.tags:
                    details_col = loop.tags.index('Details')

                if ambig_code_name in loop.tags:
                    ambig_code_col = loop.tags.index(ambig_code_name)
                    ambig_code_dat = loop.get_tag(ambig_code_name)
                    if len(ambig_code_dat) > 0:
                        ambig_code_set = set()
                        invalid_ambig_code_set = set()
                        for row in ambig_code_dat:
                            if row not in EMPTY_VALUE:
                                if row.isdigit() and int(row) in ALLOWED_AMBIGUITY_CODES:
                                    ambig_code_set.add(int(row))
                                else:
                                    invalid_ambig_code_set.add(row)
                        if len(invalid_ambig_code_set) > 0:
                            if seq_id_name in loop.tags and comp_id_name in loop.tags:
                                seq_key_set = set()
                                seq_key_dat = loop.get_tag([seq_id_name, comp_id_name])
                                for row in seq_key_dat:
                                    seq_key = (row[0], row[1])
                                    seq_key_set.add(seq_key)
                                if len(invalid_ambig_code_set) > len(seq_key_set) * 2:
                                    for row in loop:
                                        row[ambig_code_col] = '.'
                                else:
                                    for row in loop:
                                        if row[ambig_code_col] in invalid_ambig_code_set:
                                            row[ambig_code_col] = '.'
                        if len(ambig_code_set) == 1:
                            if 1 not in ambig_code_set:  # 2lrk
                                comp_id_col = loop.tags.index(comp_id_name)
                                atom_id_col = loop.tags.index(atom_id_name)
                                for row in loop:
                                    comp_id = row[comp_id_col]
                                    _atom_id = atom_id = row[atom_id_col]
                                    if self.isNmrAtomName(comp_id, atom_id):
                                        _atom_id = self.getRepAtomId(comp_id, atom_id)
                                    allowed_ambig_code = self.__reg.csStat.getMaxAmbigCodeWoSetId(comp_id, _atom_id)
                                    if allowed_ambig_code in (0, 1):
                                        row[ambig_code_col] = '1'

            if (file_type == 'nef' or not self.__reg.nonblk_anomalous_cs) and len(self.__reg.lp_data[content_subtype]) > 0:
                lp_data = next(lp['data'] for lp in self.__reg.lp_data[content_subtype]
                               if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode)

            else:

                key_items = self.__reg.key_items[file_type][content_subtype]
                data_items = DATA_ITEMS[file_type][content_subtype]

                try:

                    lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__reg.excl_missing_data)[0]

                except Exception:  # pylint: disable=broad-exception-caught

                    err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. "\
                        "Please fix problems reported."

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

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                             f"++ Error  - {err}\n")

                    return False

            chk_row_tmp = f"[Check row of {chain_id_name} %s, {seq_id_name} %s, {comp_id_name} %s, {atom_id_name} %s"
            row_tmp = f"{chain_id_name} %s, {seq_id_name} %s, {comp_id_name} %s, {atom_id_name} %s"

            methyl_cs_vals = {}
            failed_methyl_cs_keys = []

            for idx, row in enumerate(lp_data):
                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]
                comp_id = row[comp_id_name]
                atom_id = row[atom_id_name]
                value = row[value_name]
                occupancy = '.' if file_type == 'nef' else row[occupancy_name]

                alt_chain_id = set(EMPTY_VALUE)
                alt_chain_id.add(chain_id)
                if chain_id.isalpha():
                    alt_chain_id.add(str(letterToDigit(chain_id)))

                if value in EMPTY_VALUE:
                    continue

                if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                    _atom_id, ambig_code, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id
                        atom_name = atom_id

                        if details is not None:
                            atom_name += f", where {details.rstrip('.')}"

                    else:
                        atom_name = f'{atom_id} (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += f'{atom_id_} '

                        atom_name = f'{atom_name.rstrip()})'

                        # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id
                    atom_name = atom_id

                has_cs_stat = False

                # non-standard residue
                if comp_id not in STD_MON_DICT:

                    if has_mr_atom_name_mapping and atom_id_[0] == 'H':
                        try:
                            _row_ = loop.data[idx]
                            auth_seq_id, auth_comp_id, auth_atom_id, orig_atom_name =\
                                int(_row_[auth_seq_id_col]), _row_[auth_comp_id_col], \
                                _row_[auth_atom_id_col], _row_[orig_atom_name_col].upper()
                            if auth_comp_id not in EMPTY_VALUE and auth_atom_id not in EMPTY_VALUE\
                               and orig_atom_name not in EMPTY_VALUE and auth_atom_id != orig_atom_name:
                                try:
                                    atom_map =\
                                        next(atom_map for atom_map in self.__reg.mr_atom_name_mapping
                                             if atom_map['auth_seq_id'] == auth_seq_id
                                             and atom_map['auth_comp_id'] == auth_comp_id
                                             and atom_map['auth_atom_id'] == auth_atom_id
                                             and atom_map['original_atom_id'] == auth_atom_id)
                                    atom_map['original_atom_id'] = orig_atom_name
                                except StopIteration:
                                    pass
                        except (ValueError, TypeError):
                            pass

                    neighbor_comp_ids =\
                        set(_row[comp_id_name] for _row in lp_data
                            if _row[chain_id_name] == chain_id and abs(_row[seq_id_name] - seq_id) < 4
                            and _row[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__reg.csStat.peptideLike(comp_id2)

                    cs_stats = self.__reg.csStat.get(comp_id)
                    if len(cs_stats) == 0:
                        if self.__reg.ccU.updateChemCompDict(comp_id):
                            parent_comp_id = self.__reg.ccU.lastChemCompDict['parent_comp_id']
                            # DAOTHER-9198: retrieve BMRB chemical shift statittics from parent comp_id if possible (i.e. DNR -> DC)
                            if parent_comp_id in STD_MON_DICT:
                                cs_stats = self.__reg.csStat.get(parent_comp_id)

                    cs_stat = next((cs_stat for cs_stat in cs_stats
                                    if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0), None)

                    if cs_stat is not None:
                        min_value = cs_stat['min']
                        max_value = cs_stat['max']
                        avg_value = cs_stat['avg']
                        std_value = cs_stat['std']

                        has_cs_stat = True

                        if atom_id_[0] in PROTON_BEGIN_CODE and 'methyl' in cs_stat['desc']:
                            methyl_h_list = self.__reg.csStat.getProtonsInSameGroup(comp_id, atom_id)
                            _atom_id = methyl_h_list[0] if len(methyl_h_list) > 0 else atom_id
                            methyl_cs_key = (chain_id, seq_id, _atom_id, occupancy)

                            if methyl_cs_key not in methyl_cs_vals:
                                methyl_cs_vals[methyl_cs_key] = value

                            elif value != methyl_cs_vals[methyl_cs_key] and methyl_cs_key not in failed_methyl_cs_keys:
                                failed_methyl_cs_keys.append(methyl_cs_key)

                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + "] Chemical shift values in the same methyl group "\
                                    f"({full_value_name} {value} vs {methyl_cs_vals[methyl_cs_key]}) are inconsistent."

                                if self.__reg.combined_mode and not self.__reg.remediation_mode:

                                    self.__reg.report.error.appendDescription('invalid_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ ValueError  - {err}\n")

                                else:

                                    _sigma = float(f"{abs(value - methyl_cs_vals[methyl_cs_key]) / max_inclusive:.2f}")

                                    self.__reg.report.warning.appendDescription('conflicted_data',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': err,
                                                                                 'sigma': _sigma})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {err}\n")

                        if std_value is None or std_value <= 0.0:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} "\
                                f"is available to verify {full_value_name} {value} (avg {avg_value})."

                            self.__reg.report.warning.appendDescription('unusual_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                     f"++ Warning  - {warn}\n")

                            continue

                        if avg_value is None:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} "\
                                f"is available to verify {full_value_name} {value}."

                            self.__reg.report.warning.appendDescription('unusual_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                     f"++ Warning  - {warn}\n")

                            continue

                        z_score = float(f"{(value - avg_value) / std_value:.2f}")
                        sigma = abs(z_score)

                        if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):
                            tolerance = std_value

                            if (value < min_value - tolerance or value > max_value + tolerance)\
                               and sigma > self.__reg.cs_anomalous_error_scaled_by_sigma\
                               and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                                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}, "\
                                        f"Z_score {z_score:.2f})."

                                    err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__reg.cifChecked:
                                        err += no_reason_message
                                        err_alt += no_reason_message

                                    err += fold_warn_message
                                    err_alt += fold_warn_message

                                    if self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {err}\n")

                                        if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                           and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                                f"Z_score {z_score:.2f})."\
                                                f"{no_reason_message if self.__reg.cifChecked else ''}"\
                                                f"{fold_warn_message}\n"
                                            if _details in EMPTY_VALUE or (details not in _details):
                                                if _details in EMPTY_VALUE:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ 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}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest aromatic ring "\
                                        f"({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} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({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}). "\
                                        "The nearest aromatic ring "\
                                        f"({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'] - MAGIC_ANGLE) * z_score > 0.0\
                                       or self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                        self.__reg.report.warning.appendDescription('anomalous_data'
                                                                                    if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0  # noqa: E501, pylint: disable=line-too-long
                                                                                    or na['ring_distance'] > 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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                                        if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                           and file_type == 'nmr-star' and details_col != -1\
                                           and ((na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0
                                                or na['ring_distance'] > VICINITY_AROMATIC):
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                                f"Z_score {z_score:.2f}). "\
                                                "The nearest aromatic ring "\
                                                f"({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 EMPTY_VALUE or (details not in _details):
                                                if _details in EMPTY_VALUE:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ 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}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest paramagnetic/ferromagnetic atom "\
                                        f"({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} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({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}). "\
                                        "The nearest paramagnetic/ferromagnetic atom "\
                                        f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    self.__reg.report.warning.appendDescription('anomalous_data'
                                                                                if pa['distance'] > 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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                                    if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                       and file_type == 'nmr-star' and details_col != -1\
                                       and pa['distance'] > VICINITY_PARAMAGNETIC:
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({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 EMPTY_VALUE or (details not in _details):
                                            if _details in EMPTY_VALUE:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                            elif sigma > self.__reg.cs_anomalous_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                                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}, "\
                                        f"Z_score {z_score:.2f})."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__reg.cifChecked:
                                        warn += no_reason_message
                                        warn_alt += no_reason_message

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                                elif pa is None:

                                    if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0 or na['ring_distance'] > 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}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            f"The nearest aromatic ring "\
                                            f"({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} "\
                                            f"({value} ppm, {sigma:.2f} sigma), "\
                                            "which is outside of expected range "\
                                            f"({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}). "\
                                            "The nearest aromatic ring "\
                                            f"({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.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                                else:

                                    if pa['distance'] > 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}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({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} "\
                                            f"({value} ppm, {sigma:.2f} sigma), "\
                                            "which is outside of expected range "\
                                            f"({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}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                            elif sigma > self.__reg.cs_unusual_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                                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} "\
                                    f"({value} ppm, {sigma:.2f} sigma), "\
                                    "which is outside of expected range "\
                                    f"({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'] - MAGIC_ANGLE) * z_score < 0.0 or na['ring_distance'] > VICINITY_AROMATIC:
                                        warn += " The nearest aromatic ring "\
                                            f"({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 += " The nearest aromatic ring "\
                                            f"({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 = warn_alt = None

                                elif pa is not None:

                                    if pa['distance'] > VICINITY_PARAMAGNETIC:
                                        warn += " The nearest paramagnetic/ferromagnetic atom "\
                                            f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."
                                        warn_alt += " The nearest paramagnetic/ferromagnetic atom "\
                                            f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."
                                    else:
                                        warn = warn_alt = None

                                elif self.__reg.cifChecked:
                                    warn += no_reason_message
                                    warn_alt += no_reason_message

                                if warn is not None:
                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                            elif not cs_stat['primary'] and cs_stat['norm_freq'] < 0.03\
                                    and self.__reg.exptl_method != 'SOLID-STATE NMR':

                                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.__reg.report.warning.appendDescription('unusual/rare_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': warn})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                         f"++ Warning  - {warn}\n")

                        else:

                            tolerance = std_value * 10.0  # rare residue/ligand

                            if min_value < max_value and (value < min_value - tolerance or value > max_value + tolerance)\
                               and sigma > self.__reg.cs_anomalous_error_scaled_by_sigma\
                               and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                                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}, "\
                                        f"Z_score {z_score:.2f})."

                                    err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__reg.cifChecked:
                                        err += no_reason_message
                                        err_alt += no_reason_message

                                    err += fold_warn_message
                                    err_alt += fold_warn_message

                                    if self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {err}\n")

                                        if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                           and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                                f"Z_score {z_score:.2f})."\
                                                f"{no_reason_message if self.__reg.cifChecked else ''}"\
                                                f"{fold_warn_message}\n"
                                            if _details in EMPTY_VALUE or (details not in _details):
                                                if _details in EMPTY_VALUE:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                                    else:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ 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}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest aromatic ring "\
                                        f"({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} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({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}). "\
                                        "The nearest aromatic ring "\
                                        f"({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'] - MAGIC_ANGLE) * z_score > 0.0\
                                       or self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                        if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0\
                                           or na['ring_distance'] > VICINITY_AROMATIC:

                                            self.__reg.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})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                     f"++ Warning  - {warn}\n")

                                            if self.__reg.bmrb_only and self.__reg.leave_intl_note and file_type == 'nmr-star'\
                                               and details_col != -1:
                                                _details = loop.data[idx][details_col]
                                                details = f"{full_value_name} {value} is not within expected range "\
                                                    f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                                    f"Z_score {z_score:.2f}). "\
                                                    "The nearest aromatic ring "\
                                                    f"({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 EMPTY_VALUE or (details not in _details):
                                                    if _details in EMPTY_VALUE:
                                                        loop.data[idx][details_col] = details
                                                    else:
                                                        loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                    modified = True

                                    else:

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ ValueError  - {warn}\n")

                                else:

                                    if pa['distance'] > 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}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({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} "\
                                            f"({value} ppm, {sigma:.2f} sigma), "\
                                            "which is outside of expected range "\
                                            f"({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}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                                        if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                           and file_type == 'nmr-star' and details_col != -1:
                                            _details = loop.data[idx][details_col]
                                            details = f"{full_value_name} {value} is not within expected range "\
                                                f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                                f"Z_score {z_score:.2f}). "\
                                                "The nearest paramagnetic/ferromagnetic atom "\
                                                f"({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 EMPTY_VALUE or (details not in _details):
                                                if _details in EMPTY_VALUE:
                                                    loop.data[idx][details_col] = details
                                                else:
                                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                                modified = True

                            elif sigma > self.__reg.cs_anomalous_error_scaled_by_sigma and std_value > max_inclusive:

                                na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                                pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                                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}, "\
                                        f"Z_score {z_score:.2f})."

                                    warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                        f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                    if self.__reg.cifChecked:
                                        warn += no_reason_message
                                        warn_alt += no_reason_message

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                                elif pa is None:

                                    if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0 or na['ring_distance'] > 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}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest aromatic ring "\
                                            f"({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} "\
                                            f"({value} ppm, {sigma:.2f} sigma), "\
                                            "which is outside of expected range "\
                                            f"({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}). "\
                                            "The nearest aromatic ring "\
                                            f"({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.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                                else:

                                    if pa['distance'] > 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}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({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} "\
                                            f"({value} ppm, {sigma:.2f} sigma), "\
                                            "which is outside of expected range "\
                                            f"({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}). "\
                                            "The nearest paramagnetic/ferromagnetic atom "\
                                            f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                            f"is located at a distance of {pa['distance']}Å."

                                        self.__reg.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})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                 f"++ Warning  - {warn}\n")

                # standard residue
                else:

                    cs_stat = next((cs_stat for cs_stat in self.__reg.csStat.get(comp_id, self.__reg.report.isDiamagnetic())
                                    if cs_stat['atom_id'] == atom_id_ and cs_stat['count'] > 0), None)

                    if cs_stat is not None:
                        min_value = cs_stat['min']
                        max_value = cs_stat['max']
                        avg_value = cs_stat['avg']
                        std_value = cs_stat['std']

                        has_cs_stat = True

                        if atom_id_[0] in PROTON_BEGIN_CODE and 'methyl' in cs_stat['desc']:
                            methyl_cs_key = (chain_id, seq_id, atom_id_[:-1], occupancy)

                            if methyl_cs_key not in methyl_cs_vals:
                                methyl_cs_vals[methyl_cs_key] = value

                            elif value != methyl_cs_vals[methyl_cs_key] and methyl_cs_key not in failed_methyl_cs_keys:
                                failed_methyl_cs_keys.append(methyl_cs_key)

                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                    + "] Chemical shift values in the same methyl group "\
                                    f"({full_value_name} {value} vs {methyl_cs_vals[methyl_cs_key]}) are inconsistent."

                                if self.__reg.combined_mode and not self.__reg.remediation_mode:

                                    self.__reg.report.error.appendDescription('invalid_data',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ ValueError  - {err}\n")

                                else:

                                    _sigma = float(f"{abs(value - methyl_cs_vals[methyl_cs_key]) / max_inclusive:.2f}")

                                    self.__reg.report.warning.appendDescription('conflicted_data',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': err,
                                                                                 'sigma': _sigma})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {err}\n")

                        if std_value is None or std_value <= 0.0:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} "\
                                f"is available to verify {full_value_name} {value} (avg {avg_value})."

                            self.__reg.report.warning.appendDescription('unusual_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                     f"++ Warning  - {warn}\n")

                            continue

                        if avg_value is None:

                            warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                                + f"] Insufficient chemical shift statistics on comp_id {comp_id}, atom_id {atom_name} "\
                                f"is available to verify {full_value_name} {value}."

                            self.__reg.report.warning.appendDescription('unusual_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                     f"++ Warning  - {warn}\n")

                            continue

                        z_score = float(f"{(value - avg_value) / std_value:.2f}")
                        sigma = abs(z_score)
                        tolerance = std_value

                        if (value < min_value - tolerance or value > max_value + tolerance)\
                           and sigma > self.__reg.cs_unusual_error_scaled_by_sigma\
                           and std_value > max_inclusive:

                            na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                            pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                            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}, "\
                                    f"Z_score {z_score:.2f})."

                                err_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                    f"({value} ppm, {sigma:.2f} sigma), "\
                                    "which is outside of expected range "\
                                    f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                if self.__reg.cifChecked:
                                    err += no_reason_message
                                    err_alt += no_reason_message

                                err += fold_warn_message
                                err_alt += fold_warn_message

                                if self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {err}\n")

                                    if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                       and file_type == 'nmr-star' and details_col != -1:
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                            f"Z_score {z_score:.2f})."\
                                            f"{no_reason_message if self.__reg.cifChecked else ''}"\
                                            f"{fold_warn_message}\n"
                                        if _details in EMPTY_VALUE or (details not in _details):
                                            if _details in EMPTY_VALUE:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                                else:

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ 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}, "\
                                    f"Z_score {z_score:.2f}). "\
                                    "The nearest aromatic ring "\
                                    f"({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} "\
                                    f"({value} ppm, {sigma:.2f} sigma), "\
                                    "which is outside of expected range "\
                                    f"({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}). "\
                                    "The nearest aromatic ring "\
                                    f"({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'] - MAGIC_ANGLE) * z_score > 0.0\
                                   or self.__reg.nonblk_anomalous_cs or self.__reg.remediation_mode:

                                    self.__reg.report.warning.appendDescription('anomalous_data'
                                                                                if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0
                                                                                or na['ring_distance'] > 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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                                    if self.__reg.bmrb_only and self.__reg.leave_intl_note\
                                       and file_type == 'nmr-star' and details_col != -1\
                                       and ((na['ring_angle'] - MAGIC_ANGLE) * z_score > 0.0 or self.__reg.nonblk_anomalous_cs):
                                        _details = loop.data[idx][details_col]
                                        details = f"{full_value_name} {value} is not within expected range "\
                                            f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                            f"Z_score {z_score:.2f}). "\
                                            "The nearest aromatic ring "\
                                            f"({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 EMPTY_VALUE or (details not in _details):
                                            if _details in EMPTY_VALUE:
                                                loop.data[idx][details_col] = details
                                            else:
                                                loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                            modified = True

                                else:

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ 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}, "\
                                    f"Z_score {z_score:.2f}). "\
                                    "The nearest paramagnetic/ferromagnetic atom "\
                                    f"({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} "\
                                    f"({value} ppm, {sigma:.2f} sigma), "\
                                    "which is outside of expected range "\
                                    f"({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}). "\
                                    "The nearest paramagnetic/ferromagnetic atom "\
                                    f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                    f"is located at a distance of {pa['distance']}Å."

                                self.__reg.report.warning.appendDescription('anomalous_data' if pa['distance'] > VICINITY_PARAMAGNETIC  # noqa: E501, pylint: disable=line-too-long
                                                                            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})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                         f"++ Warning  - {warn}\n")

                                if self.__reg.bmrb_only and self.__reg.leave_intl_note and file_type == 'nmr-star'\
                                   and details_col != -1\
                                   and pa['distance'] > VICINITY_PARAMAGNETIC:
                                    _details = loop.data[idx][details_col]
                                    details = f"{full_value_name} {value} is not within expected range "\
                                        f"(avg {avg_value}, std {std_value}, min {min_value}, max {max_value}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest paramagnetic/ferromagnetic atom "\
                                        f"({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 EMPTY_VALUE or (details not in _details):
                                        if _details in EMPTY_VALUE:
                                            loop.data[idx][details_col] = details
                                        else:
                                            loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                        modified = True

                        # Set 5.0 to be consistent with validation report
                        elif sigma > self.__reg.cs_unusual_error_scaled_by_sigma and std_value > max_inclusive:

                            na = self.__getNearestAromaticRing(chain_id, seq_id, atom_id_)
                            pa = self.__getNearestParaFerroMagneticAtom(chain_id, seq_id, atom_id_)

                            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}, "\
                                    f"Z_score {z_score:.2f})."

                                warn_alt = f"Verify chemical shift value for {chain_id}:{seq_id}:{comp_id}:{atom_name} "\
                                    f"({value} ppm, {sigma:.2f} sigma), "\
                                    "which is outside of expected range "\
                                    f"({avg_value + 5.0 * std_value:.2f} ~ {avg_value - 5.0 * std_value:.2f} ppm, "\
                                    f"avg {avg_value}, std {std_value}, min {min_value}, max {max_value})."

                                if self.__reg.cifChecked:
                                    warn += no_reason_message
                                    warn_alt += no_reason_message

                                self.__reg.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})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                         f"++ Warning  - {warn}\n")

                            elif pa is None:

                                if (na['ring_angle'] - MAGIC_ANGLE) * z_score < 0.0 or na['ring_distance'] > 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}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest aromatic ring "\
                                        f"({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} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({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}). "\
                                        "The nearest aromatic ring "\
                                        f"({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.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                            else:

                                if pa['distance'] > 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}, "\
                                        f"Z_score {z_score:.2f}). "\
                                        "The nearest paramagnetic/ferromagnetic atom "\
                                        f"({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} "\
                                        f"({value} ppm, {sigma:.2f} sigma), "\
                                        "which is outside of expected range "\
                                        f"({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}). "\
                                        "The nearest paramagnetic/ferromagnetic atom "\
                                        f"({pa['chain_id']}:{pa['seq_id']}:{pa['comp_id']}:{pa['atom_id']}) "\
                                        f"is located at a distance of {pa['distance']}Å."

                                    self.__reg.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})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                        elif not cs_stat['primary'] and cs_stat['norm_freq'] < 0.03\
                                and self.__reg.exptl_method != 'SOLID-STATE NMR':

                            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.__reg.report.warning.appendDescription('unusual/rare_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                     f"++ Warning  - {warn}\n")

                if not has_cs_stat:

                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_name)\
                        + f"] No chemical shift statistics is available to verify {full_value_name} {value}."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                             f"++ Warning  - {warn}\n")

                # check ambiguity code
                if file_type == 'nmr-star' and ambig_code_name in row:
                    ambig_code = row[ambig_code_name]

                    if ambig_code in EMPTY_VALUE or ambig_code == 1:
                        continue

                    _atom_id = atom_id

                    if self.isNmrAtomName(comp_id, atom_id):
                        _atom_id = self.getRepAtomId(comp_id, atom_id)

                    allowed_ambig_code = self.__reg.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.__reg.csStat.getGeminalAtom(comp_id, _atom_id)

                        if ambig_code != allowed_ambig_code:

                            if allowed_ambig_code == 1:

                                try:

                                    _row = next(_row for _row in lp_data
                                                if _row[chain_id_name] == chain_id
                                                and _row[seq_id_name] == seq_id
                                                and _row[comp_id_name] == comp_id
                                                and _row[atom_id_name] == _atom_id2)

                                    loop.data[lp_data.index(_row)][loop.tags.index(ambig_code_name)] = 1

                                except StopIteration:
                                    pass

                            elif allowed_ambig_code > 0:

                                if self.__reg.remediation_mode:
                                    pass

                                else:

                                    err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] Invalid {ambig_code_name} {str(ambig_code)!r} "\
                                        f"(allowed ambig_code {[1, allowed_ambig_code, 4, 5, 6, 9]}) in a loop {lp_category}."

                                    self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ ValueError  - {err}\n")

                        try:

                            _row = next(_row for _row in lp_data
                                        if _row[chain_id_name] == chain_id
                                        and _row[seq_id_name] == seq_id
                                        and _row[comp_id_name] == comp_id
                                        and _row[atom_id_name] == _atom_id2)

                            ambig_code2 = _row[ambig_code_name]

                            if ambig_code2 is not None and ambig_code2 != ambig_code:

                                if ambig_code2 < 4:
                                    loop.data[lp_data.index(_row)][loop.tags.index(ambig_code_name)] = ambig_code

                                if self.__reg.remediation_mode:
                                    pass

                                else:

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} indicates {ambig_code_desc}. "\
                                        f"However, {ambig_code_name} {ambig_code2} of {atom_id_name} {_atom_id2} is inconsistent."

                                    self.__reg.report.warning.appendDescription('ambiguity_code_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                        except StopIteration:
                            pass

                    elif ambig_code in (4, 5, 6, 9):

                        ambig_set_id_name = 'Ambiguity_set_ID'

                        if ambig_set_id_name not in row:

                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                + f"] {ambig_code_name} {str(ambig_code)!r} requires {ambig_set_id_name} loop tag."

                            if self.__reg.remediation_mode:

                                self.__reg.report.warning.appendDescription('missing_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                         f"++ Warning  - {err}\n")

                            else:

                                self.__reg.report.error.appendDescription('missing_mandatory_item',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                           'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                         f"++ LookupError  - {file_name} {sf_framecode} {lp_category} {err}\n")

                        else:

                            ambig_set_id = row[ambig_set_id_name]

                            if ambig_set_id in EMPTY_VALUE:

                                if ambig_code in (4, 5):

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} requires {ambig_set_id_name} value."

                                    self.__reg.report.warning.appendDescription('missing_data',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                            else:

                                ambig_set = [_row for _row in lp_data if _row[ambig_set_id_name] == ambig_set_id and _row != row]

                                if len(ambig_set) == 0:

                                    if ambig_code == 4:
                                        ambig_desc = 'of intra-residue atoms '
                                    elif ambig_code == 5:
                                        ambig_desc = 'of inter-residue atoms '
                                    else:
                                        ambig_desc = ''

                                    warn = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                        + f"] {ambig_code_name} {str(ambig_code)!r} requires other rows {ambig_desc}"\
                                        f"sharing {ambig_set_id_name} {ambig_set_id}."

                                    self.__reg.report.warning.appendDescription('missing_data',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                             f"++ Warning  - {warn}\n")

                                # intra-residue ambiguities
                                elif ambig_code == 4:

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.getRepAtomId(comp_id2, atom_id2)

                                        if (chain_id2 != chain_id or seq_id2 != seq_id or comp_id2 != comp_id)\
                                           and _atom_id < _atom_id2:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                "It indicates intra-residue ambiguities. However, row of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2) + ' exists.'

                                            self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                                      {'file_name': file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category, 'description': err})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                     f"++ ValueError  - {err}\n")

                                # inter-residue ambiguities
                                elif ambig_code == 5:

                                    inter_residue_seq_id = False

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.getRepAtomId(comp_id2, atom_id2)

                                        if chain_id2 != chain_id or seq_id2 != seq_id:
                                            inter_residue_seq_id = True
                                            break

                                    if not inter_residue_seq_id:

                                        for _row in ambig_set:
                                            chain_id2 = _row[chain_id_name]
                                            seq_id2 = _row[seq_id_name]
                                            comp_id2 = _row[comp_id_name]
                                            atom_id2 = _row[atom_id_name]

                                            _atom_id2 = atom_id2

                                            if self.isNmrAtomName(comp_id2, atom_id2):
                                                _atom_id2 = self.getRepAtomId(comp_id2, atom_id2)

                                            if chain_id2 == chain_id and seq_id2 == seq_id and _atom_id < _atom_id2:

                                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                    + f", {ambig_code_name} {str(ambig_code)!r}, "\
                                                    f"{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.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                                          {'file_name': file_name,
                                                                                           'sf_framecode': sf_framecode,
                                                                                           'category': lp_category,
                                                                                           'description': err})

                                                if self.__reg.verbose:
                                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                         f"++ ValueError  - {err}\n")

                                # inter-molecular ambiguities
                                elif ambig_code == 6:

                                    for _row in ambig_set:
                                        chain_id2 = _row[chain_id_name]
                                        seq_id2 = _row[seq_id_name]
                                        comp_id2 = _row[comp_id_name]
                                        atom_id2 = _row[atom_id_name]

                                        _atom_id2 = atom_id2

                                        if self.isNmrAtomName(comp_id2, atom_id2):
                                            _atom_id2 = self.getRepAtomId(comp_id2, atom_id2)

                                        if chain_id2 == chain_id\
                                           and (seq_id < seq_id2 or (seq_id == seq_id2 and _atom_id < _atom_id2)):

                                            if chain_id == chain_id2 and seq_id == seq_id2:
                                                if _atom_id2 in self.__reg.csStat.getProtonsInSameGroup(comp_id, _atom_id):
                                                    continue

                                            if not any(True for _row_ in ambig_set if _row_[chain_id_name] != chain_id
                                               and _row_[seq_id_name] == seq_id and _row_[comp_id_name] == comp_id
                                               and _row_[atom_id_name] == atom_id):

                                                err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                    + f", {ambig_code_name} {str(ambig_code)!r}, "\
                                                    f"{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.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                                          {'file_name': file_name,
                                                                                           'sf_framecode': sf_framecode,
                                                                                           'category': lp_category,
                                                                                           'description': err})

                                                if self.__reg.verbose:
                                                    self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                         f"++ ValueError  - {err}\n")

                                for _row in ambig_set:
                                    chain_id2 = _row[chain_id_name]
                                    seq_id2 = _row[seq_id_name]
                                    comp_id2 = _row[comp_id_name]
                                    atom_id2 = _row[atom_id_name]
                                    value2 = _row[value_name]

                                    if comp_id2 not in STD_MON_DICT:
                                        continue

                                    _atom_id2 = atom_id2

                                    if self.isNmrAtomName(comp_id2, atom_id2):
                                        _atom_id2 = self.getRepAtomId(comp_id2, atom_id2)

                                    if _atom_id[0] != _atom_id2[0] and _atom_id < _atom_id2:

                                        if self.__reg.remediation_mode:

                                            chain_id_col = loop.tags.index(chain_id_name)
                                            seq_id_col = loop.tags.index(seq_id_name)
                                            comp_id_col = loop.tags.index(comp_id_name)
                                            atom_id_col = loop.tags.index(atom_id_name)
                                            ambig_code_col = loop.tags.index(ambig_code_name)

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id
                                                       and row[comp_id_col] == comp_id and row[atom_id_col] == atom_id)

                                            row[ambig_code_col] = allowed_ambig_code

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id2
                                                       and row[comp_id_col] == comp_id2 and row[atom_id_col] == atom_id2)

                                            row[ambig_code_col] = allowed_ambig_code

                                            modified = True

                                        else:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {ambig_code_name} {str(ambig_code)!r}, {ambig_set_id_name} {ambig_set_id}] "\
                                                "However, observation nucleus of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2)\
                                                + " is different in the set that share the same ambiguity code "\
                                                f"({_atom_id[0]!r} vs {_atom_id2[0]!r})."

                                            self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                                      {'file_name': file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category, 'description': err})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                     f"++ ValueError  - {err}\n")

                                    elif abs(value2 - value) > CS_UNCERT_MAX and value < value2 and ambig_code <= 4:

                                        if self.__reg.remediation_mode:

                                            chain_id_col = loop.tags.index(chain_id_name)
                                            seq_id_col = loop.tags.index(seq_id_name)
                                            comp_id_col = loop.tags.index(comp_id_name)
                                            atom_id_col = loop.tags.index(atom_id_name)
                                            ambig_code_col = loop.tags.index(ambig_code_name)

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id
                                                       and row[comp_id_col] == comp_id and row[atom_id_col] == atom_id)

                                            row[ambig_code_col] = allowed_ambig_code

                                            row = next(row for row in loop
                                                       if row[chain_id_col] in alt_chain_id and int(row[seq_id_col]) == seq_id2
                                                       and row[comp_id_col] == comp_id2 and row[atom_id_col] == atom_id2)

                                            row[ambig_code_col] = allowed_ambig_code

                                            modified = True

                                        else:

                                            err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                                                + f", {value_name} {value}, {ambig_code_name} {str(ambig_code)!r}, "\
                                                f"{ambig_set_id_name} {ambig_set_id}] "\
                                                f"However, {value_name} {value2} of "\
                                                + row_tmp % (chain_id2, seq_id2, comp_id2, atom_id2)\
                                                + " is noticeably diffrent from others in the set "\
                                                "that share the same ambiguity code "\
                                                f"by {value2 - value:.3f} (tolerance {CS_UNCERT_MAX})."

                                            self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                                      {'file_name': file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category, 'description': err})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                                     f"++ ValueError  - {err}\n")

                    else:

                        err = chk_row_tmp % (chain_id, seq_id, comp_id, atom_id)\
                            + f"] Invalid ambiguity code {str(ambig_code)!r} "\
                            f"(allowed ambig_code {ALLOWED_AMBIGUITY_CODES}) in a loop."

                        self.__reg.report.error.appendDescription('invalid_ambiguity_code',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                                 f"++ ValueError  - {err}\n")

        except StopIteration:

            err = f"Assigned chemical shifts of {sf_framecode!r} saveframe was not parsed properly. Please fix problems reported."

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

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                     f"++ Error  - {err}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.validateCsValue() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.validateCsValue() "
                                     f"++ Error  - {str(e)}\n")

        return modified

    def testRdcVector(self, file_name: str, file_type: str, content_subtype: str, sf_framecode: str, lp_category: str
                      ) -> None:
        """ Perform consistency test on RDC bond vectors.
        """

        item_names = ITEM_NAMES_IN_RDC_LOOP[file_type]
        index_tag = 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']

        def ext_atom_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name],
                    row[atom_id_1_name], row[atom_id_2_name])

        try:

            lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is not None:

                for row in lp_data:
                    chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                        comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row)

                    if atom_id_1 in EMPTY_VALUE or atom_id_2 in EMPTY_VALUE:
                        continue

                    if atom_id_1[0] not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS\
                       or atom_id_2[0] not in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = f"{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.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {err}\n")

                    if chain_id_1 != chain_id_2:

                        if self.__reg.exptl_method == 'SOLID-STATE NMR' and self.__reg.symmetric is None:

                            src_id = self.__reg.report.getInputSourceIdOfCoord()

                            if src_id >= 0:

                                cif_input_source = self.__reg.report.input_sources[src_id]
                                cif_input_source_dic = cif_input_source.get()

                                has_cif_poly_seq = has_key_value(cif_input_source_dic, 'polymer_sequence')

                                if has_cif_poly_seq:

                                    cif_poly_seq = cif_input_source_dic['polymer_sequence']

                                    self.__reg.symmetric = 'no'

                                    for ps in cif_poly_seq:

                                        if 'identical_auth_chain_id' in ps:

                                            if len(ps['identical_auth_chain_id']) + 1 > 2:
                                                self.__reg.symmetric = 'yes'

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        if self.__reg.symmetric == 'no':

                            err = f"{idx_msg}Found inter-chain 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}) "\
                                f"in a loop {lp_category}."

                            self.__reg.report.error.appendDescription('invalid_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {err}\n")

                        else:

                            err = f"{idx_msg}Found inter-chain 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}) "\
                                f"in a loop {lp_category}. "\
                                "However, it might be an artificial RDC constraint on solid-state NMR "\
                                "applied to symmetric samples such as fibrils.\n"

                            self.__reg.report.warning.appendDescription('anomalous_rdc_vector',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Warning  - {err}\n")

                    elif abs(seq_id_1 - seq_id_2) > 1:

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = f"{idx_msg}Found inter-residue 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}) "\
                            f"in a loop {lp_category}."

                        self.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {err}\n")

                    elif abs(seq_id_1 - seq_id_2) == 1:

                        if self.__reg.csStat.peptideLike(comp_id_1) and self.__reg.csStat.peptideLike(comp_id_2)\
                           and ((seq_id_1 < seq_id_2 and atom_id_1 == 'C' and atom_id_2 in RDC_BB_PAIR_CODE)
                                or (seq_id_1 > seq_id_2 and atom_id_1 in RDC_BB_PAIR_CODE and atom_id_2 == 'C')
                                or (seq_id_1 < seq_id_2 and atom_id_1.startswith('HA') and atom_id_2 == 'H')
                                or (seq_id_1 > seq_id_2 and atom_id_1 == 'H' and atom_id_2.startswith('HA'))):
                            pass

                        else:

                            idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                            err = f"{idx_msg}Found inter-residue 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}) "\
                                f"in a loop {lp_category}."

                            self.__reg.report.error.appendDescription('invalid_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {err}\n")

                    elif atom_id_1 == atom_id_2:

                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                        err = f"{idx_msg}Found zero 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.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {err}\n")

                    else:

                        if self.__reg.ccU.updateChemCompDict(comp_id_1):  # matches with comp_id in CCD

                            if not self.__reg.ccU.hasBond(comp_id_1, atom_id_1, atom_id_2):

                                if self.__reg.nefT.validate_comp_atom(comp_id_1, atom_id_1)\
                                   and self.__reg.nefT.validate_comp_atom(comp_id_2, atom_id_2):

                                    idx_msg = f"[Check row of {index_tag} {row[index_tag]}] " if index_tag in row else ''

                                    warn = f"{idx_msg}Found an RDC vector over multiple covalent bonds; "\
                                        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.__reg.report.warning.appendDescription('unusual/rare_data',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.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:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testRdcVector() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testRdcVector() ++ Error  - {str(e)}\n")

    def mapCoordDisulfideBond2Nmr(self, bond_list) -> bool:
        """ Map disulfide bond of coordinate file to NMR data.
        """

        is_done = False

        for fileListId in range(self.__reg.file_path_list_len):

            input_source = self.__reg.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.__reg.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 = SF_CATEGORIES[file_type][content_subtype]
            lp_category = 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']

                ps1 = self.__reg.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_1)

                if ps1 is None:
                    continue

                nmr_chain_id_1 = ps1['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(ps1['seq_id'], ps1['comp_id'])
                                      if seq_id == nmr_seq_id_1), None)

                if nmr_comp_id_1 is None:
                    continue

                ps2 = self.__reg.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_2)

                if ps2 is None:
                    continue

                nmr_chain_id_2 = ps2['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(ps2['seq_id'], ps2['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.__reg.star_data_type[fileListId] == 'Loop':
                    sf = self.__reg.star_data[fileListId]
                    sf_framecode = ''

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordDisulfideBond2Nmr(file_name, file_type, content_subtype,
                                                         sf, sf_framecode, lp_category,
                                                         nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                         nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                elif self.__reg.star_data_type[fileListId] == 'Saveframe':
                    sf = self.__reg.star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordDisulfideBond2Nmr(file_name, file_type, content_subtype,
                                                         sf, sf_framecode, lp_category,
                                                         nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                         nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)
                else:

                    for sf in self.__reg.star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

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

                        ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                            self.__mapCoordDisulfideBond2Nmr(file_name, file_type, content_subtype,
                                                             sf, sf_framecode, lp_category,
                                                             nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                             nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                        if None in (ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2):
                            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 the assigned chemical shift values "\
                        f"({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, "\
                        f"redox_state_pred {disulf['redox_state_pred_1']})."

                    item = 'anomalous_chemical_shift' if disulf['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                    self.__reg.report.warning.appendDescription(item,
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.mapCoordDisulfideBond2Nmr() ++ Warning  - {warn}\n")

                    disulf['warning_description_1'] = f'{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 the assigned chemical shift values "\
                        f"({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, "\
                        f"redox_state_pred {disulf['redox_state_pred_2']})."

                    item = 'anomalous_chemical_shift' if disulf['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                    self.__reg.report.warning.appendDescription(item,
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.mapCoordDisulfideBond2Nmr() ++ Warning  - {warn}\n")

                    disulf['warning_description_2'] = f'{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: str, file_type: str, content_subtype: str,
                                    sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                    sf_framecode: str, lp_category: str,
                                    nmr_chain_id_1: str, nmr_seq_id_1: int, nmr_comp_id_1: str,
                                    nmr_chain_id_2: str, nmr_seq_id_2: int, nmr_comp_id_2: str
                                    ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]:
        """ Map disulfide bond of coordinate file to NMR data.
        """

        ca_chem_shift_1 = cb_chem_shift_1 = ca_chem_shift_2 = cb_chem_shift_2 = None

        key_items = self.__reg.key_items[file_type][content_subtype]
        data_items = DATA_ITEMS[file_type][content_subtype]

        item_names = 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.__reg.report.error.exists(file_name, sf_framecode):

            lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is None:

                try:

                    lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__reg.excl_missing_data)[0]

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

                except Exception:  # pylint: disable=broad-exception-caught
                    pass

            if lp_data is not None:

                for row in lp_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    atom_id = row[atom_id_name]

                    if chain_id == nmr_chain_id_1 and seq_id == nmr_seq_id_1 and comp_id == nmr_comp_id_1:
                        if atom_id == 'CA' and ca_chem_shift_1 is None:
                            ca_chem_shift_1 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = row[value_name]

                    elif chain_id == nmr_chain_id_2 and seq_id == nmr_seq_id_2 and comp_id == nmr_comp_id_2:
                        if atom_id == 'CA' and ca_chem_shift_2 is None:
                            ca_chem_shift_2 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = row[value_name]

                    if None in (ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2):
                        pass
                    else:
                        break

        return ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2

    def mapCoordOtherBond2Nmr(self, bond_list: List[dict]) -> bool:
        """ Map other bond (neither disulfide nor covalent bond) of coordinate file to NMR data.
        """

        is_done = False

        for fileListId in range(self.__reg.file_path_list_len):

            input_source = self.__reg.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.__reg.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 = SF_CATEGORIES[file_type][content_subtype]
            lp_category = 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']

                ps1 = self.__reg.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_1)

                if ps1 is None:
                    continue

                nmr_chain_id_1 = ps1['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(ps1['seq_id'], ps1['comp_id'])
                                      if seq_id == nmr_seq_id_1), None)

                if nmr_comp_id_1 is None:
                    continue

                ps2 = self.__reg.report.getNmrPolymerSequenceWithModelChainId(cif_chain_id_2)

                if ps2 is None:
                    continue

                nmr_chain_id_2 = ps2['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(ps2['seq_id'], ps2['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.__reg.star_data_type[fileListId] == 'Loop':
                    sf = self.__reg.star_data[fileListId]
                    sf_framecode = ''

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordOtherBond2Nmr(file_name, file_type, content_subtype,
                                                     sf, sf_framecode, lp_category,
                                                     nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                     nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                elif self.__reg.star_data_type[fileListId] == 'Saveframe':
                    sf = self.__reg.star_data[fileListId]
                    sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

                    ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                        self.__mapCoordOtherBond2Nmr(file_name, file_type, content_subtype,
                                                     sf, sf_framecode, lp_category,
                                                     nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                     nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                else:

                    for sf in self.__reg.star_data[fileListId].get_saveframes_by_category(sf_category):
                        sf_framecode = get_first_sf_tag(sf, 'sf_framecode')

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

                        ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2 =\
                            self.__mapCoordOtherBond2Nmr(file_name, file_type, content_subtype,
                                                         sf, sf_framecode, lp_category,
                                                         nmr_chain_id_1, nmr_seq_id_1, nmr_comp_id_1,
                                                         nmr_chain_id_2, nmr_seq_id_2, nmr_comp_id_2)

                        if None in (ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2):
                            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 the assigned chemical shift values "\
                        f"({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, "\
                        f"redox_state_pred {other['redox_state_pred_1']})."

                    item = 'anomalous_chemical_shift' if other['redox_state_pred_1'] == 'reduced' else 'unusual_chemical_shift'

                    self.__reg.report.warning.appendDescription(item,
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.mapCoordOtherBond2Nmr() ++ Warning  - {warn}\n")

                    other['warning_description_1'] = f'{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 the assigned chemical shift values "\
                        f"({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, "\
                        f"redox_state_pred {other['redox_state_pred_2']})."

                    item = 'anomalous_chemical_shift' if other['redox_state_pred_2'] == 'reduced' else 'unusual_chemical_shift'

                    self.__reg.report.warning.appendDescription(item,
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.mapCoordOtherBond2Nmr() ++ Warning  - {warn}\n")

                    other['warning_description_2'] = f'{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: str, file_type: str, content_subtype: str,
                                sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                sf_framecode: str, lp_category: str,
                                nmr_chain_id_1: str, nmr_seq_id_1: int, nmr_comp_id_1: str,
                                nmr_chain_id_2: str, nmr_seq_id_2: int, nmr_comp_id_2: str
                                ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]:
        """ Map other bond (neither disulfide nor covalent bond) of coordinate file to NMR data.
        """

        ca_chem_shift_1 = cb_chem_shift_1 = ca_chem_shift_2 = cb_chem_shift_2 = None

        key_items = self.__reg.key_items[file_type][content_subtype]
        data_items = DATA_ITEMS[file_type][content_subtype]

        item_names = 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.__reg.report.error.exists(file_name, sf_framecode):

            lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

            if lp_data is None:

                try:

                    lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                         enforce_allowed_tags=(file_type == 'nmr-star'),
                                                         excl_missing_data=self.__reg.excl_missing_data)[0]

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

                except Exception:  # pylint: disable=broad-exception-caught
                    pass

            if lp_data is not None:

                for row in lp_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    atom_id = row[atom_id_name]

                    if chain_id == nmr_chain_id_1 and seq_id == nmr_seq_id_1 and comp_id == nmr_comp_id_1:
                        if atom_id == 'CA' and ca_chem_shift_1 is None:
                            ca_chem_shift_1 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_1 is None:
                            cb_chem_shift_1 = row[value_name]

                    elif chain_id == nmr_chain_id_2 and seq_id == nmr_seq_id_2 and comp_id == nmr_comp_id_2:
                        if atom_id == 'CA' and ca_chem_shift_2 is None:
                            ca_chem_shift_2 = row[value_name]
                        elif atom_id == 'CB' and cb_chem_shift_2 is None:
                            cb_chem_shift_2 = row[value_name]

                    if None in (ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2):
                        pass
                    else:
                        break

        return ca_chem_shift_1, cb_chem_shift_1, ca_chem_shift_2, cb_chem_shift_2

    def testCoordCovalentBond(self, file_name: str, file_type: str, content_subtype: str, sf_framecode: str, lp_category: str
                              ) -> None:
        """ Perform consistency test on covalent bonds.
        """

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

        def ext_atom_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name],
                    row[atom_id_1_name], row[atom_id_2_name])

        try:

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == lp_category), None)

            if aux_data is not None:

                for row in aux_data:
                    chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                        comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row)

                    bond = self.getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)

                    if bond is None:
                        continue

                    broken_bond = [b for b in bond if b['distance'] > 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.__reg.report.warning.appendDescription('anomalous_bond_length',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.testCoordCovalentBond() ++ Warning  - {warn}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testCoordCovalentBond() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testCoordCovalentBond() ++ Error  - {str(e)}\n")

    def testResidueVariant(self, file_name: str, file_type: str, content_subtype: str,
                           sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                           sf_framecode: str, lp_category: str, cif_poly_seq: List[dict], nmr2ca: dict) -> None:
        """ Perform consistency test on residue variants.
        """

        item_names = 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.__reg.aux_key_items[file_type][content_subtype][lp_category]
        data_items = self.__reg.aux_data_items[file_type][content_subtype][lp_category]
        allowed_tags = AUX_ALLOWED_TAGS[file_type][content_subtype][lp_category]

        try:

            aux_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items,
                                                  allowed_tags, None, None,
                                                  enforce_allowed_tags=(file_type == 'nmr-star'),
                                                  excl_missing_data=self.__reg.excl_missing_data)[0]

            if aux_data is not None:

                for row in aux_data:
                    chain_id = row[chain_id_name]
                    seq_id = row[seq_id_name]
                    comp_id = row[comp_id_name]
                    variant = row[variant_name]

                    if chain_id not in nmr2ca:
                        continue

                    ca = next((ca['seq_align'] for ca in nmr2ca[chain_id]
                               if ('seq_unmap' not in ca or (seq_id not in ca['seq_unmap']))), None)  # DAOTHER-7465

                    if ca is None:
                        continue

                    cif_chain_id = ca['test_chain_id']

                    cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                       in zip(ca['ref_seq_id'], ca['test_seq_id']) if ref_seq_id == seq_id), None)

                    if cif_seq_id is None:
                        continue

                    cif_ps = next(ps for ps in cif_poly_seq if ps['chain_id'] == cif_chain_id)

                    cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                        in zip(cif_ps['seq_id'], cif_ps['comp_id']) if _seq_id == cif_seq_id), None)

                    if cif_comp_id is None:
                        continue

                    seq_key = (cif_chain_id, cif_seq_id)

                    if seq_key in self.__reg.caC['coord_unobs_res']:  # DAOTHER-7665
                        continue

                    coord_atom_site_ = self.__reg.caC['coord_atom_site'].get(seq_key)

                    self.__reg.ccU.updateChemCompDict(comp_id)

                    if file_type == 'nef':

                        if variant in 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.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': warn})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.textResidueVariant() ++ Warning  - {warn}\n")

                                continue

                            atom_id = _variant_[1:]

                            if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                                len_atom_id = len(_atom_id)

                                if len_atom_id == 0:
                                    continue

                                if len_atom_id == 1 and atom_id == _atom_id[0]:
                                    atom_id_ = atom_id
                                    atom_name = atom_id

                                    if details is not None:
                                        atom_name += f", where {details.rstrip('.')}"

                                else:
                                    atom_name = f'{atom_id} (e.g. '

                                    for atom_id_ in _atom_id:
                                        atom_name += f'{atom_id_} '

                                    atom_name = f'{atom_name.rstrip()})'

                                    # representative atom id
                                    atom_id_ = _atom_id[0]

                            else:
                                atom_id_ = atom_id
                                atom_name = atom_id

                            if _variant_[0] == '-':

                                if self.__reg.ccU.lastStatus:  # matches with comp_id in CCD

                                    if not self.__reg.nefT.validate_comp_atom(comp_id, atom_id_):

                                        warn = "Atom ("\
                                            + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                          comp_id_name, comp_id, atom_id_name, atom_name)\
                                            + f", {variant_name} {_variant_!r}) did not match with "\
                                            "chemical component dictionary (CCD)."

                                        self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.textResidueVariant() "
                                                                 f"++ Warning  - {warn}\n")

                                if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                                   and (atom_id_ in coord_atom_site_['atom_id']
                                        or ('auth_atom_id' in coord_atom_site_ and atom_id_ in coord_atom_site_['auth_atom_id']))\
                                   and lp_category != '_Entity_deleted_atom':

                                    err = "Atom ("\
                                        + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                      comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + f", {variant_name} {_variant_!r}) is unexpectedly incorporated in the coordinates."

                                    self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ Error  - {err}\n")

                            else:

                                if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                                   and (atom_id_ not in coord_atom_site_['atom_id']
                                        and (('auth_atom_id' in coord_atom_site_
                                              and atom_id_ not in coord_atom_site_['auth_atom_id'])
                                             or 'auth_atom_id' not in coord_atom_site_)):

                                    err = "Atom ("\
                                        + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                      comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + f") which is a {variant_name} {_variant_!r} is not present in the coordinates."

                                    checked = False
                                    if atom_id_[0] in PROTON_BEGIN_CODE:
                                        cca = next((cca for cca in self.__reg.ccU.lastAtomDictList
                                                    if cca['atom_id'] == atom_id_), None)
                                        bonded_to = self.__reg.ccU.getBondedAtoms(comp_id, atom_id_)
                                        peptide_like = self.__reg.csStat.peptideLike(comp_id)
                                        if cca is not None and len(bonded_to) > 0:
                                            if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                               and (cca['leaving_atom_flag'] != 'Y'
                                                    or (peptide_like
                                                        and cca['n_terminal_atom_flag'] == 'N'
                                                        and cca['c_terminal_atom_flag'] == 'N')):
                                                checked = True
                                                err = "Atom ("\
                                                    + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                                  comp_id_name, comp_id, atom_id_name, atom_name)\
                                                    + f") which is a {variant_name} {_variant_!r} is not properly instantiated "\
                                                    "in the coordinates. Please re-upload the model file."

                                    if self.__reg.remediation_mode and checked:
                                        continue

                                    if content_subtype.startswith('spectral_peak'):

                                        self.__reg.report.warning.appendDescription('hydrogen_not_instantiated' if checked
                                                                                    else 'assigned_peak_atom_not_found',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() "
                                                                 f"++ Warning  - {err}\n")

                                    else:

                                        self.__reg.report.error.appendDescription('hydrogen_not_instantiated' if checked
                                                                                  else 'atom_not_found',
                                                                                  {'file_name': file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ Error  - {err}\n")

                    else:

                        atom_id = variant

                        if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                            _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                            len_atom_id = len(_atom_id)

                            if len_atom_id == 0:
                                continue

                            if len_atom_id == 1 and atom_id == _atom_id[0]:
                                atom_id_ = atom_id
                                atom_name = atom_id

                                if details is not None:
                                    atom_name += f", where {details.rstrip('.')}"

                            else:
                                atom_name = f'{atom_id} (e.g. '

                                for atom_id_ in _atom_id:
                                    atom_name += f'{atom_id_} '

                                atom_name = f'{atom_name.rstrip()})'

                                # representative atom id
                                atom_id_ = _atom_id[0]

                        else:
                            atom_id_ = atom_id
                            atom_name = atom_id

                            if self.__reg.ccU.lastStatus:  # matches with comp_id in CCD

                                if not self.__reg.nefT.validate_comp_atom(comp_id, atom_id_):

                                    warn = "Atom ("\
                                        + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                      comp_id_name, comp_id, atom_id_name, atom_name)\
                                        + ") did not match with chemical component dictionary (CCD)."

                                    self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.textResidueVariant() ++ Warning  - {warn}\n")

                            if coord_atom_site_ is not None and coord_atom_site_['comp_id'] == cif_comp_id\
                               and (atom_id_ in coord_atom_site_['atom_id']
                                    and (('auth_atom_id' in coord_atom_site_ and atom_id_ in coord_atom_site_['auth_atom_id'])
                                         or 'auth_atom_id' not in coord_atom_site_))\
                               and lp_category != '_Entity_deleted_atom':

                                err = "Atom ("\
                                    + self.getReducedAtomNotation(chain_id_name, chain_id, seq_id_name, seq_id,
                                                                  comp_id_name, comp_id, atom_id_name, atom_name)\
                                    + ") is unexpectedly incorporated in the coordinates."

                                self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                           'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ Error  - {err}\n")

        except LookupError as e:

            item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

            self.__reg.report.error.appendDescription(item,
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ LookupError  - "
                                 f"{file_name} {sf_framecode} {lp_category} {str(e)}\n")

        except ValueError as e:

            self.__reg.report.error.appendDescription('invalid_data',
                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                       'category': lp_category, 'description': str(e).strip("'")})

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ ValueError  - {str(e)}\n")

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.testResidueVariant() ++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.testResidueVariant() ++ Error  - {str(e)}\n")

    def validateStrMr(self, file_list_id: int, file_type: str, original_file_name: str, content_subtype: str,
                      _sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                      sf_framecode: str, lp_category: str) -> bool:
        """ Validate data content of NMR-STAR restraint files.
        """

        self.__reg.list_id_counter = incListIdCounter(content_subtype, self.__reg.list_id_counter, reduced=False)

        list_id = self.__reg.list_id_counter[content_subtype]

        restraint_name = getRestraintName(content_subtype)

        _sf_framecode = sf_framecode

        is_sf = True
        if len(sf_framecode) == 0:
            sf_framecode = restraint_name.replace(' ', '_').lower() + f'_{list_id}'
            is_sf = False

        sf = getSaveframe(content_subtype, sf_framecode, list_id, self.__reg.entry_id, original_file_name,
                          reduced=False)

        # merge saveframe tags of the source saveframe
        if is_sf:

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

            for idx, origTagName in enumerate(origTagNames):
                if origTagName in SF_ALLOWED_TAGS[file_type][content_subtype]:
                    set_sf_tag(sf, origTagName, _sf.tags[idx][1])

        try:

            loop = _sf if self.__reg.star_data_type[file_list_id] == 'Loop' else _sf.get_loop(lp_category)

            if not isinstance(loop, pynmrstar.Loop):
                loop = None

        except KeyError:
            loop = None

        _restraint_name = restraint_name.split()

        sf_item = {'file_type': file_type, 'saveframe': sf, 'list_id': list_id,
                   'id': 0, 'index_id': 0,
                   'constraint_type': ' '.join(_restraint_name[:-1])}

        if content_subtype == 'dist_restraint':
            sf_item['constraint_subsubtype'] = 'simple'

        if loop is not None:

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

            has_poly_seq_in_lp = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

            if has_poly_seq_in_lp and content_subtype != 'ph_param_data':
                poly_seq_in_lp = input_source_dic['polymer_sequence_in_loop']

                poly_seq = seq_align = chain_assign = br_seq_align = br_chain_assign = np_seq_align = np_chain_assign = None

                if has_poly_seq_in_lp and content_subtype in poly_seq_in_lp:
                    _poly_seq_in_lp = next((_poly_seq_in_lp for _poly_seq_in_lp in poly_seq_in_lp[content_subtype]
                                            if _poly_seq_in_lp['sf_framecode'] == _sf_framecode), None)

                    if _poly_seq_in_lp is not None:
                        list_id = _poly_seq_in_lp['list_id']
                        poly_seq = _poly_seq_in_lp['polymer_sequence']

                        seq_align, _ = alignPolymerSequence(self.__reg.pA, self.__reg.caC['polymer_sequence'],
                                                            poly_seq, conservative=False)
                        chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                                self.__reg.caC['polymer_sequence'], poly_seq,
                                                                seq_align)

                        if self.__reg.caC['branched'] is not None:
                            br_seq_align, _ = alignPolymerSequence(self.__reg.pA, self.__reg.caC['branched'],
                                                                   poly_seq, conservative=False)
                            br_chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                                       self.__reg.caC['branched'], poly_seq,
                                                                       br_seq_align)

                        if self.__reg.caC['non_polymer'] is not None:
                            np_seq_align, _ = alignPolymerSequence(self.__reg.pA, self.__reg.caC['non_polymer'],
                                                                   poly_seq, conservative=False)
                            np_chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                                       self.__reg.caC['non_polymer'], poly_seq,
                                                                       np_seq_align)

                def get_auth_seq_scheme(chain_id, seq_id):
                    auth_asym_id = auth_seq_id = None

                    if seq_id is not None:

                        if chain_assign is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign
                                                 if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                           and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id =\
                                        next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                              if test_seq_id == seq_id), None)

                        if None in (auth_asym_id, auth_seq_id) and br_seq_align is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign
                                                 if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in br_seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                           and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id =\
                                        next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                              if test_seq_id == seq_id), None)

                        if None in (auth_asym_id, auth_seq_id) and np_seq_align is not None:
                            auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign
                                                 if ca['test_chain_id'] == chain_id), None)
                            if auth_asym_id is not None:
                                sa = next((sa for sa in np_seq_align
                                           if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                           and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                                if sa is not None:
                                    _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                    auth_seq_id =\
                                        next((ref_seq_id for ref_seq_id, test_seq_id in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                              if test_seq_id == seq_id), None)

                    return auth_asym_id, auth_seq_id

                has_ins_code = False

                if poly_seq is not None:

                    for ps in poly_seq:

                        if has_ins_code:
                            break

                        auth_asym_id, _ = get_auth_seq_scheme(ps['chain_id'], ps['seq_id'][0])

                        if self.__reg.caC['polymer_sequence'] is not None\
                           and any(True for cif_ps in self.__reg.caC['polymer_sequence']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                        if self.__reg.caC['branched'] is not None\
                           and any(True for cif_ps in self.__reg.caC['branched']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                        if self.__reg.caC['non_polymer'] is not None\
                           and any(True for cif_ps in self.__reg.caC['non_polymer']
                                   if cif_ps['auth_chain_id'] == auth_asym_id and 'ins_code' in cif_ps):
                            has_ins_code = True

                lp = getLoop(content_subtype, reduced=False, hasInsCode=has_ins_code)

                sf.add_loop(lp)
                sf_item['loop'] = lp

                index_tag = INDEX_TAGS[file_type][content_subtype]
                id_col = loop.tags.index('ID') if 'ID' in loop.tags else -1
                combination_id_col = member_id_col = member_logic_code_col = upper_limit_col = -1
                auth_comp_id_1_col = auth_comp_id_2_col = torsion_angle_name_col = -1
                if content_subtype == 'dist_restraint':
                    if 'Combination_ID' in loop.tags:
                        combination_id_col = loop.tags.index('Combination_ID')
                    if 'Member_ID' in loop.tags:
                        member_id_col = loop.tags.index('Member_ID')
                    if 'Member_logic_code' in loop.tags:
                        member_logic_code_col = loop.tags.index('Member_logic_code')
                    if 'Distance_upper_bound_val' in loop.tags:
                        upper_limit_col = loop.tags.index('Distance_upper_bound_val')
                    if 'Auth_comp_ID_1' in loop.tags:
                        auth_comp_id_1_col = loop.tags.index('Auth_comp_ID_1')
                    if 'Auth_comp_ID_2' in loop.tags:
                        auth_comp_id_2_col = loop.tags.index('Auth_comp_ID_2')
                elif content_subtype == 'dihed_restraint':
                    if 'Torsion_angle_name' in loop.tags:
                        torsion_angle_name_col = loop.tags.index('Torsion_angle_name')

                key_items = [item['name'] for item in NMR_STAR_LP_KEY_ITEMS[content_subtype]]

                if content_subtype == 'ccr_dd_restraint' and 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                    key_items = copy.copy(key_items)
                    key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                    if key_item is not None:
                        key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

                len_key_items = len(key_items)

                atom_dim_num = (len_key_items - 1) // 5  # 5 for entity_assembly_id, entity_id, comp_index_id, comp_id, atom_id tags

                if atom_dim_num == 0:
                    err = f"Unexpected key items {key_items} set for processing {lp_category} loop "\
                        f"in {sf_framecode} saveframe of {original_file_name} file."

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateStrMr() "
                                                              f"++ KeyError  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                             f"++ KeyError  - {err}\n")

                    return False

                key_chain_id_names = [key_items[idx] for idx in range(1, len_key_items, 5)]
                key_entity_id_names = [key_items[idx] for idx in range(2, len_key_items, 5)]
                key_seq_id_names = [key_items[idx] for idx in range(3, len_key_items, 5)]
                key_comp_id_names = [key_items[idx] for idx in range(4, len_key_items, 5)]
                key_atom_id_names = [key_items[idx] for idx in range(5, len_key_items, 5)]

                key_tags = key_chain_id_names
                key_tags.extend(key_seq_id_names)
                key_tags.extend(key_comp_id_names)
                key_tags.extend(key_atom_id_names)

                auth_items = [auth_item['name'] for auth_item in NMR_STAR_LP_DATA_ITEMS[content_subtype]
                              if auth_item['name'].startswith('Auth') or 'auth' in auth_item['name']]

                auth_chain_id_names = [auth_item for auth_item in auth_items
                                       if 'asym' in auth_item or 'entity_assembly' in auth_item]
                auth_seq_id_names = [auth_item for auth_item in auth_items if 'seq' in auth_item]
                auth_comp_id_names = [auth_item for auth_item in auth_items if 'comp' in auth_item]
                auth_atom_id_names = [auth_item for auth_item in auth_items if 'atom' in auth_item and 'atom_name' not in auth_item]
                auth_atom_name_names = [auth_item for auth_item in auth_items if 'atom_name' in auth_item]

                auth_pdb_tags = auth_chain_id_names
                auth_pdb_tags.extend(auth_seq_id_names)
                auth_pdb_tags.extend(auth_comp_id_names)
                auth_pdb_tags.extend(auth_atom_id_names)

                coord_atom_site = self.__reg.caC['coord_atom_site']
                auth_to_star_seq = self.__reg.caC['auth_to_star_seq']
                auth_to_orig_seq = self.__reg.caC['auth_to_orig_seq']
                auth_to_ins_code = self.__reg.caC['auth_to_ins_code'] if has_ins_code else None
                auth_to_star_seq_ann = self.__reg.caC['auth_to_star_seq_ann']
                auth_atom_name_to_id = self.__reg.caC['auth_atom_name_to_id']

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                # split concatenation of auth_seq_id and ins_code (DAOTHER-10418)
                if auth_to_ins_code is not None and len(auth_to_ins_code) > 0\
                   and set(auth_seq_id_names) & set(loop.tags) == set(auth_seq_id_names):
                    auth_dat = loop.get_tag(auth_seq_id_names)

                    if any(True for row in auth_dat if any(True for val in row if isinstance(val, str))):

                        auth_seq_id_cols = [loop.tags.index(auth_seq_id_name) for auth_seq_id_name in auth_seq_id_names]

                        ins_code_names = [auth_item['name'] for auth_item in NMR_STAR_LP_DATA_ITEMS_INS_CODE[content_subtype]
                                          if auth_item['name'].startswith('PDB_ins_code')]

                        for ins_code_name in ins_code_names:
                            if ins_code_name not in loop.tags:
                                loop.add_tag(ins_code_name, update_data=True)

                        ins_code_cols = [loop.tags.index(ins_code_name) for ins_code_name in ins_code_names]

                        for idx, row in enumerate(auth_dat):
                            for col, val in enumerate(row):
                                if isinstance(val, str) and CONCAT_SEQ_ID_INS_CODE_PAT.match(val):
                                    g = CONCAT_SEQ_ID_INS_CODE_PAT.search(val).groups()
                                    loop.data[idx][auth_seq_id_cols[col]] = g[0]
                                    if g[1] not in EMPTY_VALUE:
                                        loop.data[idx][ins_code_cols[col]] = g[1]

                offset_holder = {}

                has_key_seq = False

                if set(key_tags) & set(loop.tags) == set(key_tags):
                    dat = loop.get_tag(key_seq_id_names)
                    if len(dat) > 0:
                        has_key_seq = True
                        for row in dat:
                            try:
                                for d in range(atom_dim_num):
                                    int(row[d])
                            except (ValueError, TypeError):
                                has_key_seq = False
                                break

                has_auth_seq = valid_auth_seq = False

                if set(auth_pdb_tags) & set(loop.tags) == set(auth_pdb_tags):
                    auth_dat = loop.get_tag(auth_pdb_tags)
                    if len(auth_dat) > 0:
                        has_auth_seq = valid_auth_seq = True
                        if not self.__reg.annotation_mode:
                            for row in auth_dat:
                                try:
                                    for d in range(atom_dim_num):
                                        seq_key = (row[d], int(row[atom_dim_num + d]), row[atom_dim_num * 2 + d])
                                        if seq_key not in auth_to_star_seq_ann:
                                            valid_auth_seq = False
                                            break
                                    if not valid_auth_seq:
                                        break
                                except (ValueError, TypeError):
                                    has_auth_seq = valid_auth_seq = False
                                    break

                if has_key_seq or has_auth_seq:

                    has_auth_atom_name = len(auth_atom_name_names) > 0\
                        and set(auth_atom_name_names) & set(loop.tags) == set(auth_atom_name_names)

                    if valid_auth_seq:

                        if has_auth_atom_name:
                            auth_pdb_tags.extend(auth_atom_name_names)

                        dat = loop.get_tag(auth_pdb_tags)

                        prefer_auth_atom_name = False

                        if (self.__reg.annotation_mode or self.__reg.native_combined) and len(auth_atom_name_to_id) > 0:

                            count_auth_name = count_auth_id = 0

                            for row_ in dat:

                                for d in range(atom_dim_num):
                                    chain_id = row_[d]
                                    seq_id = int(row_[atom_dim_num + d])
                                    comp_id = row_[atom_dim_num * 2 + d]
                                    atom_id = row_[atom_dim_num * 3 + d]

                                    seq_key = (chain_id, seq_id, comp_id)

                                    try:
                                        auth_to_star_seq[seq_key]  # pylint: disable=pointless-statement
                                    except KeyError:
                                        comp_id =\
                                            next((_auth_comp_id
                                                  for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                  if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            if count_auth_name + count_auth_id == 0:

                                for row_ in dat:

                                    for d in range(atom_dim_num):
                                        chain_id = row_[d]
                                        seq_id = int(row_[atom_dim_num + d])
                                        comp_id = row_[atom_dim_num * 2 + d]
                                        atom_id = row_[atom_dim_num * 3 + d]

                                        seq_key = (chain_id, seq_id, comp_id)

                                        try:
                                            auth_to_star_seq_ann[seq_key]  # pylint: disable=pointless-statement
                                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                                comp_id = coord_atom_site[_seq_key]['comp_id']
                                        except KeyError:
                                            continue

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                            prefer_auth_atom_name = count_auth_name > count_auth_id

                        for idx, row_ in enumerate(dat):
                            atom_sels = [None] * atom_dim_num

                            for d in range(atom_dim_num):
                                chain_id = auth_chain_id = row_[d]
                                seq_id = int(row_[atom_dim_num + d])
                                comp_id = row_[atom_dim_num * 2 + d]
                                atom_id = row_[atom_dim_num * 3 + d]

                                seq_key = (chain_id, seq_id, comp_id)

                                try:

                                    entity_assembly_id, comp_index_id, _, _ = auth_to_star_seq[seq_key]

                                    if self.__reg.annotation_mode or self.__reg.native_combined:
                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if v[0] == entity_assembly_id
                                                  and v[1] == comp_index_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id

                                except KeyError:
                                    if self.__reg.annotation_mode or self.__reg.native_combined:
                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if chain_id.isdigit() and v[0] == int(chain_id)
                                                  and v[1] == seq_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id
                                        else:
                                            chain_id =\
                                                next((_auth_asym_id
                                                      for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                      if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                            else:
                                                chain_id, comp_id =\
                                                    next(((_auth_asym_id, _auth_comp_id)
                                                          for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                          if _auth_seq_id == seq_id), (chain_id, comp_id))
                                                seq_key = (chain_id, seq_id, comp_id)
                                                if seq_key in auth_to_star_seq:
                                                    row_[d] = chain_id
                                                    row_[atom_dim_num * 2 + d] = comp_id
                                        if seq_key not in auth_to_star_seq:
                                            comp_id = next((_auth_comp_id
                                                            for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                            if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)
                                            _auth_seq_id = next((_auth_seq_id
                                                                 for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                 if _auth_asym_id == chain_id and _auth_comp_id == comp_id), None)
                                            if _auth_seq_id is not None:
                                                seq_key = (chain_id, _auth_seq_id, comp_id)

                                if has_auth_atom_name:
                                    auth_atom_id = row_[atom_dim_num * 4 + d]
                                    if auth_atom_id in EMPTY_VALUE:
                                        auth_atom_id = atom_id
                                else:
                                    auth_atom_id = atom_id

                                _assign, warn = assignCoordPolymerSequenceWithChainId(self.__reg.caC, self.__reg.nefT,
                                                                                      chain_id, seq_id, comp_id, atom_id)

                                rescued = False

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                            self.__reg.report.error.appendDescription('atom_not_found',
                                                                                      {'file_name': original_file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category,
                                                                                       'description': idx_msg + warn})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                     f"++ Error  - {idx_msg + warn}\n")

                                    if content_subtype != 'dihed_restraint' or not self.__reg.remediation_mode:
                                        continue

                                    if d not in (0, 3) or not warn.startswith('[Atom not found]'):
                                        _d = 1 if d == 0 else 2
                                        _chain_id = row_[_d]
                                        _seq_id = int(row_[atom_dim_num + _d])
                                        _comp_id = row_[atom_dim_num * 2 + _d]
                                        _atom_id = row_[atom_dim_num * 3 + _d]

                                        if chain_id != _chain_id or abs(seq_id - _seq_id) != 1:
                                            continue

                                        if not self.__reg.ccU.updateChemCompDict(comp_id.upper()):
                                            continue

                                        cca = next((cca for cca in self.__reg.ccU.lastAtomDictList
                                                    if cca['atom_id'] == atom_id.upper()), None)

                                        if cca is None:
                                            continue

                                        __assign, _warn =\
                                            assignCoordPolymerSequenceWithChainId(self.__reg.caC, self.__reg.nefT,
                                                                                  _chain_id, _seq_id, _comp_id, _atom_id)

                                        if len(__assign) != 1 or _warn is not None:
                                            continue

                                        chainId, cifSeqId, _, _ = __assign[0]
                                        cifSeqId -= _seq_id - seq_id

                                        atom_sels[d] = [{'chain_id': chainId,
                                                         'seq_id': cifSeqId,
                                                         'comp_id': comp_id.upper(),
                                                         'atom_id': atom_id.upper(),
                                                         'auth_atom_id': auth_atom_id}]
                                        warn = None

                                        rescued = True

                                if not rescued:
                                    enableWarning = True
                                    if content_subtype == 'dist_restraint':
                                        if (auth_comp_id_1_col != -1 and loop.data[idx][auth_comp_id_1_col] == 'HOH')\
                                           or (auth_comp_id_2_col != -1 and loop.data[idx][auth_comp_id_2_col] == 'HOH'):
                                            enableWarning = False
                                    elif content_subtype == 'dihed_restraint':
                                        if torsion_angle_name_col != -1 and loop.data[idx][torsion_angle_name_col] == 'PPA':
                                            enableWarning = False

                                    atom_sels[d], warn = selectCoordAtoms(self.__reg.cR, self.__reg.caC, self.__reg.nefT, _assign,
                                                                          auth_chain_id, seq_id, comp_id, atom_id, auth_atom_id,
                                                                          allowAmbig=content_subtype in ('dist_restraint',
                                                                                                         'noepk_restraint'),
                                                                          enableWarning=enableWarning,
                                                                          preferAuthAtomName=prefer_auth_atom_name
                                                                          and comp_id in auth_atom_name_to_id,
                                                                          representativeModelId=self.__reg.representative_model_id,
                                                                          representativeAltId=self.__reg.representative_alt_id,
                                                                          modelNumName=model_num_name)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                            self.__reg.report.error.appendDescription('atom_not_found',
                                                                                      {'file_name': original_file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category,
                                                                                       'description': idx_msg + warn})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                     f"++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Hydrogen not instantiated]'):
                                        self.__reg.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                    {'file_name': original_file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category,
                                                                                     'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Warning  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom nomenclature]'):
                                        self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                                        self.__reg.report.error.appendDescription('invalid_data',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ ValueError  - {idx_msg + warn}\n")

                                    continue

                            if any(True for d in range(atom_dim_num) if atom_sels[d] is None or len(atom_sels[d]) == 0):
                                continue

                            sf_item['id'] += 1

                            if content_subtype == 'dist_restraint':
                                Id = '.'
                                if id_col != -1:
                                    Id = loop.data[idx][id_col]
                                    try:
                                        _Id = int(Id)
                                    except ValueError:
                                        Id = '.'
                                Id = sf_item['id'] if isinstance(Id, str) and Id == '.' else _Id
                                combinationId = '.'
                                if combination_id_col != -1:
                                    combinationId = loop.data[idx][combination_id_col]
                                    try:
                                        int(combinationId)
                                    except ValueError:
                                        combinationId = '.'
                                memberId = '.'
                                if member_id_col != -1:
                                    memberId = loop.data[idx][member_id_col]
                                    try:
                                        int(memberId)
                                    except ValueError:
                                        memberId = '.'
                                valid_atom_sels = atom_sels[0] is not None and atom_sels[1] is not None
                                if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                   and (isAmbigAtomSelection(atom_sels[0], self.__reg.csStat)
                                        or isAmbigAtomSelection(atom_sels[1], self.__reg.csStat)):
                                    memberId = 0
                                memberLogicCode = '.'
                                if member_logic_code_col != -1:
                                    memberLogicCode = loop.data[idx][member_logic_code_col]
                                    if memberLogicCode in EMPTY_VALUE:
                                        memberLogicCode = '.'
                                memberLogicCode = 'OR' if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                    else memberLogicCode

                                if isinstance(memberId, int):
                                    _atom1 = _atom2 = None

                                if valid_atom_sels:
                                    for atom1, atom2 in itertools.product(atom_sels[0], atom_sels[1]):
                                        if isIdenticalRestraint([atom1, atom2]):
                                            continue
                                        if isinstance(memberId, int):
                                            if _atom1 is None or isAmbigAtomSelection([_atom1, atom1], self.__reg.csStat)\
                                               or isAmbigAtomSelection([_atom2, atom2], self.__reg.csStat):
                                                memberId += 1
                                                _atom1, _atom2 = atom1, atom2
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[0] is not None:
                                    atom2 = None
                                    for atom1 in atom_sels[0]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[1] is not None:
                                    atom1 = None
                                    for atom2 in atom_sels[1]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                else:
                                    atom1 = atom2 = None
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__reg.annotation_mode)
                                    lp.add_data(_row)

                            else:

                                sf_item['index_id'] += 1
                                _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                                      None, None, list_id, self.__reg.entry_id,
                                                      loop.tags, loop.data[idx],
                                                      auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                      atom_sels, self.__reg.annotation_mode)
                                lp.add_data(_row)

                    else:

                        if has_auth_atom_name:
                            key_tags.extend(auth_atom_name_names)

                        dat = loop.get_tag(key_tags)

                        prefer_auth_atom_name = False

                        if (self.__reg.annotation_mode or self.__reg.native_combined) and len(auth_atom_name_to_id) > 0:

                            count_auth_name = count_auth_id = 0

                            for row_ in dat:

                                for d in range(atom_dim_num):
                                    chain_id = row_[d]
                                    seq_id = int(row_[atom_dim_num + d])
                                    comp_id = row_[atom_dim_num * 2 + d]
                                    atom_id = row_[atom_dim_num * 3 + d]

                                    seq_key = (chain_id, seq_id, comp_id)

                                    try:

                                        entity_assembly_id, comp_index_id, _, _ = auth_to_star_seq[seq_key]

                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if v[0] == entity_assembly_id
                                                  and v[1] == comp_index_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id

                                    except KeyError:
                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if chain_id.isdigit() and v[0] == int(chain_id)
                                                  and v[1] == seq_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id
                                        else:
                                            chain_id = next((_auth_asym_id
                                                             for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                             if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                            else:
                                                chain_id, comp_id =\
                                                    next(((_auth_asym_id, _auth_comp_id)
                                                          for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                          if _auth_seq_id == seq_id), (chain_id, comp_id))
                                                seq_key = (chain_id, seq_id, comp_id)
                                                if seq_key in auth_to_star_seq:
                                                    row_[d] = chain_id
                                                    row_[atom_dim_num * 2 + d] = comp_id
                                        if seq_key not in auth_to_star_seq:
                                            comp_id = next((_auth_comp_id
                                                            for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                            if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)
                                            _auth_seq_id = next((_auth_seq_id
                                                                 for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                 if _auth_asym_id == chain_id and _auth_comp_id == comp_id), None)
                                            if _auth_seq_id is not None:
                                                seq_key = (chain_id, _auth_seq_id, comp_id)

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            if count_auth_name + count_auth_id == 0:

                                for row_ in dat:

                                    for d in range(atom_dim_num):
                                        chain_id = row_[d]
                                        seq_id = int(row_[atom_dim_num + d])
                                        comp_id = row_[atom_dim_num * 2 + d]
                                        atom_id = row_[atom_dim_num * 3 + d]

                                        seq_key = (chain_id, seq_id, comp_id)

                                        try:
                                            auth_to_star_seq_ann[seq_key]  # pylint: disable=pointless-statement
                                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                                comp_id = coord_atom_site[_seq_key]['comp_id']
                                        except KeyError:
                                            continue

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                            prefer_auth_atom_name = count_auth_name > count_auth_id

                        for idx, row_ in enumerate(dat):
                            atom_sels = [None] * atom_dim_num

                            for d in range(atom_dim_num):
                                chain_id = auth_chain_id = row_[d]
                                seq_id = int(row_[atom_dim_num + d])
                                comp_id = row_[atom_dim_num * 2 + d]
                                atom_id = row_[atom_dim_num * 3 + d]

                                seq_key = (chain_id, seq_id, comp_id)

                                try:

                                    entity_assembly_id, comp_index_id, _, _ = auth_to_star_seq[seq_key]

                                    if self.__reg.annotation_mode or self.__reg.native_combined:
                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if v[0] == entity_assembly_id
                                                  and v[1] == comp_index_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id

                                except KeyError:
                                    if self.__reg.annotation_mode or self.__reg.native_combined:
                                        _auth_asym_id, _auth_seq_id =\
                                            next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                                  if chain_id.isdigit() and v[0] == int(chain_id)
                                                  and v[1] == seq_id and k[2] == comp_id), (None, None))
                                        if _auth_asym_id is not None:
                                            seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                chain_id, seq_id = _auth_asym_id, _auth_seq_id
                                        else:
                                            chain_id = next((_auth_asym_id
                                                             for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                             if _auth_seq_id == seq_id and _auth_comp_id == comp_id), chain_id)
                                            seq_key = (chain_id, seq_id, comp_id)
                                            if seq_key in auth_to_star_seq:
                                                row_[d] = chain_id
                                            else:
                                                chain_id, comp_id =\
                                                    next(((_auth_asym_id, _auth_comp_id)
                                                          for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                          if _auth_seq_id == seq_id), (chain_id, comp_id))
                                                seq_key = (chain_id, seq_id, comp_id)
                                                if seq_key in auth_to_star_seq:
                                                    row_[d] = chain_id
                                                    row_[atom_dim_num * 2 + d] = comp_id
                                        if seq_key not in auth_to_star_seq:
                                            comp_id = next((_auth_comp_id
                                                            for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                            if _auth_asym_id == chain_id and _auth_seq_id == seq_id), comp_id)
                                            _auth_seq_id = next((_auth_seq_id
                                                                 for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                                 if _auth_asym_id == chain_id and _auth_comp_id == comp_id), None)
                                            if _auth_seq_id is not None:
                                                seq_key = (chain_id, _auth_seq_id, comp_id)

                                if has_auth_atom_name:
                                    auth_atom_id = row_[atom_dim_num * 4 + d]
                                    if auth_atom_id in EMPTY_VALUE:
                                        auth_atom_id = atom_id
                                else:
                                    auth_atom_id = atom_id

                                auth_asym_id = auth_seq_id = None

                                if chain_assign is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign
                                                         if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                                   and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)
                                            if auth_seq_id is None:
                                                for offset in range(1, PERIPH_OFFSET_ATTEMPT):
                                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                                        in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                        if test_seq_id == seq_id + offset), None)
                                                    if auth_seq_id is not None:
                                                        auth_seq_id -= offset
                                                        break
                                                    auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                                        in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                        if test_seq_id == seq_id - offset), None)
                                                    if auth_seq_id is not None:
                                                        auth_seq_id += offset
                                                        break

                                if None in (auth_asym_id, auth_seq_id) and br_seq_align is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign
                                                         if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in br_seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                                   and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)

                                if None in (auth_asym_id, auth_seq_id) and np_seq_align is not None:
                                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign
                                                         if ca['test_chain_id'] == chain_id), None)
                                    if auth_asym_id is not None:
                                        sa = next((sa for sa in np_seq_align
                                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                                   and seq_id in sa['test_seq_id']), None)
                                        if sa is not None:
                                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                                if test_seq_id == seq_id), None)

                                if None in (auth_asym_id, auth_seq_id):
                                    if seq_key in auth_to_star_seq:
                                        auth_asym_id, auth_seq_id, _ = seq_key
                                    else:
                                        entity_id_name = key_entity_id_names[d]
                                        if entity_id_name not in loop.tags:
                                            continue
                                        try:
                                            entity_assembly_id = int(chain_id)
                                            entity_id = int(loop.data[idx][loop.tags.index(entity_id_name)])
                                        except ValueError:
                                            continue
                                        k = next((k for k, v in auth_to_star_seq.items()
                                                  if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                        if k is None:
                                            continue
                                        auth_asym_id, auth_seq_id, _ = k

                                chain_id, seq_id = auth_asym_id, auth_seq_id

                                _assign, warn = assignCoordPolymerSequenceWithChainId(self.__reg.caC, self.__reg.nefT,
                                                                                      chain_id, seq_id, comp_id, atom_id)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                            self.__reg.report.error.appendDescription('atom_not_found',
                                                                                      {'file_name': original_file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category,
                                                                                       'description': idx_msg + warn})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                     f"++ Error  - {idx_msg + warn}\n")

                                    continue

                                enableWarning = True
                                if content_subtype == 'dist_restraint':
                                    if (auth_comp_id_1_col != -1 and loop.data[idx][auth_comp_id_1_col] == 'HOH')\
                                       or (auth_comp_id_2_col != -1 and loop.data[idx][auth_comp_id_2_col] == 'HOH'):
                                        enableWarning = False
                                elif content_subtype == 'dihed_restraint':
                                    if torsion_angle_name_col != -1 and loop.data[idx][torsion_angle_name_col] == 'PPA':
                                        enableWarning = False

                                atom_sels[d], warn = selectCoordAtoms(self.__reg.cR, self.__reg.caC, self.__reg.nefT, _assign,
                                                                      auth_chain_id, seq_id, comp_id, atom_id, auth_atom_id,
                                                                      allowAmbig=content_subtype in ('dist_restraint',
                                                                                                     'noepk_restraint'),
                                                                      enableWarning=enableWarning,
                                                                      preferAuthAtomName=prefer_auth_atom_name,
                                                                      representativeModelId=self.__reg.representative_model_id,
                                                                      representativeAltId=self.__reg.representative_alt_id,
                                                                      modelNumName=model_num_name)

                                if warn is not None:

                                    _index_tag = index_tag if index_tag is not None else 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'ID'
                                        try:
                                            _index_tag_col = loop.tags.index(_index_tag)
                                            idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                        except ValueError:
                                            _index_tag = 'Index_ID'
                                            idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                    if warn.startswith('[Atom not found]'):
                                        if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                            self.__reg.report.error.appendDescription('atom_not_found',
                                                                                      {'file_name': original_file_name,
                                                                                       'sf_framecode': sf_framecode,
                                                                                       'category': lp_category,
                                                                                       'description': idx_msg + warn})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                     f"++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Hydrogen not instantiated]'):
                                        self.__reg.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                    {'file_name': original_file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category,
                                                                                     'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Warning  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom nomenclature]'):
                                        self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Error  - {idx_msg + warn}\n")

                                    elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                                        self.__reg.report.error.appendDescription('invalid_data',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ ValueError  - {idx_msg + warn}\n")

                                    continue

                            if any(True for d in range(atom_dim_num) if atom_sels[d] is None or len(atom_sels[d]) == 0):
                                continue

                            sf_item['id'] += 1

                            if content_subtype == 'dist_restraint':
                                Id = '.'
                                if id_col != -1:
                                    Id = loop.data[idx][id_col]
                                    try:
                                        _Id = int(Id)
                                    except ValueError:
                                        Id = '.'
                                Id = sf_item['id'] if isinstance(Id, str) and Id == '.' else _Id
                                combinationId = '.'
                                if combination_id_col != -1:
                                    combinationId = loop.data[idx][combination_id_col]
                                    try:
                                        int(combinationId)
                                    except ValueError:
                                        combinationId = '.'
                                memberId = '.'
                                if member_id_col != -1:
                                    memberId = loop.data[idx][member_id_col]
                                    try:
                                        int(memberId)
                                    except ValueError:
                                        memberId = '.'
                                valid_atom_sels = atom_sels[0] is not None and atom_sels[1] is not None
                                if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                   and (isAmbigAtomSelection(atom_sels[0], self.__reg.csStat)
                                        or isAmbigAtomSelection(atom_sels[1], self.__reg.csStat)):
                                    memberId = 0
                                memberLogicCode = '.'
                                if member_logic_code_col != -1:
                                    memberLogicCode = loop.data[idx][member_logic_code_col]
                                    if memberLogicCode in EMPTY_VALUE:
                                        memberLogicCode = '.'
                                memberLogicCode = 'OR' if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                    else memberLogicCode

                                if isinstance(memberId, int):
                                    _atom1 = _atom2 = None

                                if valid_atom_sels:
                                    for atom1, atom2 in itertools.product(atom_sels[0], atom_sels[1]):
                                        if isIdenticalRestraint([atom1, atom2]):
                                            continue
                                        if isinstance(memberId, int):
                                            if _atom1 is None or isAmbigAtomSelection([_atom1, atom1], self.__reg.csStat)\
                                               or isAmbigAtomSelection([_atom2, atom2], self.__reg.csStat):
                                                memberId += 1
                                                _atom1, _atom2 = atom1, atom2
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[0] is not None:
                                    atom2 = None
                                    for atom1 in atom_sels[0]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                elif atom_sels[1] is not None:
                                    atom1 = None
                                    for atom2 in atom_sels[1]:
                                        sf_item['index_id'] += 1
                                        _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                              memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                              loop.tags, loop.data[idx],
                                                              auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                              [atom1, atom2], self.__reg.annotation_mode)
                                        lp.add_data(_row)

                                else:
                                    atom1 = atom2 = None
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__reg.annotation_mode)
                                    lp.add_data(_row)

                            else:

                                sf_item['index_id'] += 1
                                _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                                      None, None, list_id, self.__reg.entry_id,
                                                      loop.tags, loop.data[idx],
                                                      auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                      atom_sels, self.__reg.annotation_mode)
                                lp.add_data(_row)

                else:  # nothing to do because of insufficient sequence tags

                    del sf[lp]
                    lp = loop

                    sf.add_loop(lp)
                    sf_item['loop'] = lp

            elif content_subtype == 'ph_param_data':

                lp = getLoop(content_subtype, reduced=False, hasInsCode=False)

                sf.add_loop(lp)
                sf_item['loop'] = lp

                for row in loop:
                    sf_item['id'] += 1
                    sf_item['index_id'] += 1

                    _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                          None, None, list_id, self.__reg.entry_id,
                                          loop.tags, row,
                                          {}, {}, {}, {},
                                          [None], self.__reg.annotation_mode)
                    lp.add_data(_row)

            else:

                auth_to_star_seq = self.__reg.caC['auth_to_star_seq']

                key_items = [item['name'] for item in NMR_STAR_LP_KEY_ITEMS[content_subtype]]

                if content_subtype == 'ccr_dd_restraint' and 'Dipole_2_chem_comp_index_ID_2' in loop.tags:
                    key_items = copy.copy(key_items)
                    key_item = next((key_item for key_item in key_items if key_item['name'] == 'Dipole_2_comp_index_ID_2'), None)
                    if key_item is not None:
                        key_item['name'] = 'Dipole_2_chem_comp_index_ID_2'

                len_key_items = len(key_items)

                atom_dim_num = (len_key_items - 1) // 5  # 5 for entity_assembly_id, entity_id, comp_index_id, comp_id, atom_id tags

                if atom_dim_num == 0:
                    err = f"Unexpected key items {key_items} set for processing {lp_category} loop "\
                        f"in {sf_framecode} saveframe of {original_file_name} file."

                    self.__reg.report.error.appendDescription('internal_error',
                                                              f"+{self.__class_name__}.validateStrMr() "
                                                              f"++ KeyError  - " + err)

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                             f"++ KeyError  - {err}\n")

                    return False

                prefer_auth_atom_name = True

                coord_atom_site = self.__reg.caC['coord_atom_site']
                auth_to_orig_seq = self.__reg.caC['auth_to_orig_seq']
                auth_to_ins_code = None

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                offset_holder = {}

                index_tag = INDEX_TAGS[file_type][content_subtype]
                id_col = loop.tags.index('ID') if 'ID' in loop.tags else -1
                combination_id_col = member_id_col = member_logic_code_col = upper_limit_col = -1
                auth_comp_id_1_col = auth_comp_id_2_col = torsion_angle_name_col = -1
                if content_subtype == 'dist_restraint':
                    if 'Combination_ID' in loop.tags:
                        combination_id_col = loop.tags.index('Combination_ID')
                    if 'Member_ID' in loop.tags:
                        member_id_col = loop.tags.index('Member_ID')
                    if 'Member_logic_code' in loop.tags:
                        member_logic_code_col = loop.tags.index('Member_logic_code')
                    if 'Distance_upper_bound_val' in loop.tags:
                        upper_limit_col = loop.tags.index('Distance_upper_bound_val')
                    if 'Auth_comp_ID_1' in loop.tags:
                        auth_comp_id_1_col = loop.tags.index('Auth_comp_ID_1')
                    if 'Auth_comp_ID_2' in loop.tags:
                        auth_comp_id_2_col = loop.tags.index('Auth_comp_ID_2')
                elif content_subtype == 'dihed_restraint':
                    if 'Torsion_angle_name' in loop.tags:
                        torsion_angle_name_col = loop.tags.index('Torsion_angle_name')

                auth_items = [auth_item['name'] for auth_item in NMR_STAR_LP_DATA_ITEMS[content_subtype]
                              if auth_item['name'].startswith('Auth') or 'auth' in auth_item['name']]

                auth_chain_id_names = [auth_item for auth_item in auth_items
                                       if 'asym' in auth_item or 'entity_assembly' in auth_item]
                auth_seq_id_names = [auth_item for auth_item in auth_items if 'seq' in auth_item]
                auth_comp_id_names = [auth_item for auth_item in auth_items if 'comp' in auth_item]
                auth_atom_id_names = [auth_item for auth_item in auth_items if 'atom' in auth_item and 'atom_name' not in auth_item]

                has_auth_chain_tags = set(auth_chain_id_names) & set(loop.tags) == set(auth_chain_id_names)
                has_auth_seq_tags = set(auth_seq_id_names) & set(loop.tags) == set(auth_seq_id_names)
                has_auth_comp_tags = set(auth_comp_id_names) & set(loop.tags) == set(auth_comp_id_names)
                has_auth_atom_tags = set(auth_atom_id_names) & set(loop.tags) == set(auth_atom_id_names)

                if not has_auth_chain_tags and self.__reg.caC['polymer_sequence'] is not None\
                   and len(self.__reg.caC['polymer_sequence']) == 1:
                    auth_chain_id = self.__reg.caC['polymer_sequence'][0]['auth_chain_id']
                    for auth_chain_id_name in auth_chain_id_names:
                        if auth_chain_id_name in loop.tags:
                            continue
                        loop.add_tag(auth_chain_id_name)
                        for row in loop:
                            row.append(auth_chain_id)
                    has_auth_chain_tags = True

                has_valid_comp_id = True
                if has_auth_chain_tags and has_auth_seq_tags and has_auth_atom_tags and not has_auth_comp_tags:
                    for d, auth_comp_id_name in enumerate(auth_comp_id_names):
                        if auth_comp_id_name in loop.tags:
                            tags = [auth_chain_id_names[d], auth_seq_id_names[d], auth_comp_id_names[d]]
                            for idx, row in enumerate(loop.get_tag(tags)):
                                if row[2] not in EMPTY_VALUE:
                                    continue
                                try:
                                    chain_id, seq_id = row[0], int(row[1])
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == chain_id and _auth_seq_id == seq_id), None)
                                except (TypeError, ValueError):
                                    comp_id = None
                                    has_valid_comp_id = False
                                loop.data[idx][loop.tags.index(auth_comp_id_names[d])] = comp_id
                            continue
                        loop.add_tag(auth_comp_id_name)
                        tags = [auth_chain_id_names[d], auth_seq_id_names[d]]
                        for idx, row in enumerate(loop.get_tag(tags)):
                            try:
                                chain_id, seq_id = row[0], int(row[1])
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == chain_id and _auth_seq_id == seq_id), None)
                            except (TypeError, ValueError):
                                comp_id = None
                            loop.data[idx].append(comp_id)
                            if comp_id is None:
                                has_valid_comp_id = False
                        has_auth_comp_tags = True

                if has_auth_chain_tags and has_auth_seq_tags and has_auth_atom_tags and has_auth_comp_tags and has_valid_comp_id:
                    is_valid = True

                    lp = getLoop(content_subtype, reduced=False)

                    sf.add_loop(lp)
                    sf_item['loop'] = lp

                    auth_items = []
                    for d in range(atom_dim_num):
                        auth_items.extend([auth_chain_id_names[d], auth_seq_id_names[d],
                                           auth_comp_id_names[d], auth_atom_id_names[d]])
                    for idx, row in enumerate(loop.get_tag(auth_items)):
                        atom_sels = [None] * atom_dim_num

                        for d in range(atom_dim_num):

                            try:

                                chain_id = auth_chain_id = row[d * 4]
                                seq_id = int(row[d * 4 + 1])
                                comp_id = row[d * 4 + 2]
                                atom_id = row[d * 4 + 3]

                                seq_key = (chain_id, seq_id, comp_id)

                                entity_assembly_id, comp_index_id, _, _ =\
                                    auth_to_star_seq[seq_key]

                                if self.__reg.annotation_mode or self.__reg.native_combined:
                                    _auth_asym_id, _auth_seq_id =\
                                        next(((k[0], k[1]) for k, v in auth_to_star_seq.items()
                                              if v[0] == entity_assembly_id and v[1] == comp_index_id
                                              and k[2] == comp_id), (None, None))
                                    if _auth_asym_id is not None:
                                        seq_key = (_auth_asym_id, _auth_seq_id, comp_id)
                                        if seq_key in auth_to_star_seq:
                                            chain_id, seq_id = _auth_asym_id, _auth_seq_id

                            except KeyError:
                                continue
                            except ValueError:
                                is_valid = False
                                continue

                            auth_atom_id = atom_id

                            auth_asym_id = auth_seq_id = None

                            if seq_key in auth_to_star_seq:
                                auth_asym_id, auth_seq_id, _ = seq_key
                            else:
                                k = next((k for k, v in auth_to_star_seq.items()
                                          if v[0] == entity_assembly_id and v[1] == seq_id and v[2] == entity_id), None)
                                if k is None:
                                    continue
                                auth_asym_id, auth_seq_id, _ = k

                            chain_id, seq_id = auth_asym_id, auth_seq_id

                            _assign, warn = assignCoordPolymerSequenceWithChainId(self.__reg.caC, self.__reg.nefT,
                                                                                  chain_id, seq_id, comp_id, atom_id)

                            if warn is not None:

                                _index_tag = index_tag if index_tag is not None else 'ID'
                                try:
                                    _index_tag_col = loop.tags.index(_index_tag)
                                    idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                except ValueError:
                                    _index_tag = 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'Index_ID'
                                        idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                if warn.startswith('[Atom not found]'):
                                    if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                        self.__reg.report.error.appendDescription('atom_not_found',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Error  - {idx_msg + warn}\n")

                                continue

                            enableWarning = True
                            if content_subtype == 'dist_restraint':
                                if (auth_comp_id_1_col != -1 and loop.data[idx][auth_comp_id_1_col] == 'HOH')\
                                   or (auth_comp_id_2_col != -1 and loop.data[idx][auth_comp_id_2_col] == 'HOH'):
                                    enableWarning = False
                            elif content_subtype == 'dihed_restraint':
                                if torsion_angle_name_col != -1 and loop.data[idx][torsion_angle_name_col] == 'PPA':
                                    enableWarning = False

                            atom_sels[d], warn = selectCoordAtoms(self.__reg.cR, self.__reg.caC, self.__reg.nefT,
                                                                  _assign, auth_chain_id, seq_id, comp_id, atom_id, auth_atom_id,
                                                                  allowAmbig=content_subtype in ('dist_restraint',
                                                                                                 'noepk_restraint'),
                                                                  enableWarning=enableWarning,
                                                                  preferAuthAtomName=prefer_auth_atom_name,
                                                                  representativeModelId=self.__reg.representative_model_id,
                                                                  representativeAltId=self.__reg.representative_alt_id,
                                                                  modelNumName=model_num_name)

                            if warn is not None:

                                _index_tag = index_tag if index_tag is not None else 'ID'
                                try:
                                    _index_tag_col = loop.tags.index(_index_tag)
                                    idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                except ValueError:
                                    _index_tag = 'ID'
                                    try:
                                        _index_tag_col = loop.tags.index(_index_tag)
                                        idx_msg = f"[Check row of {_index_tag} {loop.data[idx][_index_tag_col]}] "
                                    except ValueError:
                                        _index_tag = 'Index_ID'
                                        idx_msg = f"[Check row of {_index_tag} {idx + 1}] "

                                if warn.startswith('[Atom not found]'):
                                    if not self.__reg.remediation_mode or 'Macromolecules page' not in warn:
                                        self.__reg.report.error.appendDescription('atom_not_found',
                                                                                  {'file_name': original_file_name,
                                                                                   'sf_framecode': sf_framecode,
                                                                                   'category': lp_category,
                                                                                   'description': idx_msg + warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                                 f"++ Error  - {idx_msg + warn}\n")

                                elif warn.startswith('[Hydrogen not instantiated]'):
                                    self.__reg.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                {'file_name': original_file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category,
                                                                                 'description': idx_msg + warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                             f"++ Warning  - {idx_msg + warn}\n")

                                elif warn.startswith('[Invalid atom nomenclature]'):
                                    self.__reg.report.error.appendDescription('invalid_atom_nomenclature',
                                                                              {'file_name': original_file_name,
                                                                               'sf_framecode': sf_framecode,
                                                                               'category': lp_category,
                                                                               'description': idx_msg + warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                             f"++ Error  - {idx_msg + warn}\n")

                                elif warn.startswith('[Invalid atom selection]') or warn.startswith('[Invalid data]'):
                                    self.__reg.report.error.appendDescription('invalid_data',
                                                                              {'file_name': original_file_name,
                                                                               'sf_framecode': sf_framecode,
                                                                               'category': lp_category,
                                                                               'description': idx_msg + warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.validateStrMr() "
                                                             f"++ ValueError  - {idx_msg + warn}\n")

                                continue

                        if any(True for d in range(atom_dim_num) if atom_sels[d] is None or len(atom_sels[d]) == 0):
                            continue

                        sf_item['id'] += 1

                        if content_subtype == 'dist_restraint':
                            Id = '.'
                            if id_col != -1:
                                Id = loop.data[idx][id_col]
                                try:
                                    _Id = int(Id)
                                except ValueError:
                                    Id = '.'
                            Id = sf_item['id'] if isinstance(Id, str) and Id == '.' else _Id
                            combinationId = '.'
                            if combination_id_col != -1:
                                combinationId = loop.data[idx][combination_id_col]
                                try:
                                    int(combinationId)
                                except ValueError:
                                    combinationId = '.'
                            memberId = '.'
                            if member_id_col != -1:
                                memberId = loop.data[idx][member_id_col]
                                try:
                                    int(memberId)
                                except ValueError:
                                    memberId = '.'
                            valid_atom_sels = atom_sels[0] is not None and atom_sels[1] is not None
                            if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                               and (isAmbigAtomSelection(atom_sels[0], self.__reg.csStat)
                                    or isAmbigAtomSelection(atom_sels[1], self.__reg.csStat)):
                                memberId = 0
                            memberLogicCode = '.'
                            if member_logic_code_col != -1:
                                memberLogicCode = loop.data[idx][member_logic_code_col]
                                if memberLogicCode in EMPTY_VALUE:
                                    memberLogicCode = '.'
                            memberLogicCode = 'OR' if valid_atom_sels and len(atom_sels[0]) * len(atom_sels[1]) > 1\
                                else memberLogicCode

                            if isinstance(memberId, int):
                                _atom1 = _atom2 = None

                            if valid_atom_sels:
                                for atom1, atom2 in itertools.product(atom_sels[0], atom_sels[1]):
                                    if isIdenticalRestraint([atom1, atom2]):
                                        continue
                                    if isinstance(memberId, int):
                                        if _atom1 is None or isAmbigAtomSelection([_atom1, atom1], self.__reg.csStat)\
                                           or isAmbigAtomSelection([_atom2, atom2], self.__reg.csStat):
                                            memberId += 1
                                            _atom1, _atom2 = atom1, atom2
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__reg.annotation_mode)
                                    lp.add_data(_row)

                            elif atom_sels[0] is not None:
                                atom2 = None
                                for atom1 in atom_sels[0]:
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__reg.annotation_mode)
                                    lp.add_data(_row)

                            elif atom_sels[1] is not None:
                                atom1 = None
                                for atom2 in atom_sels[1]:
                                    sf_item['index_id'] += 1
                                    _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                          memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                          loop.tags, loop.data[idx],
                                                          auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                          [atom1, atom2], self.__reg.annotation_mode)
                                    lp.add_data(_row)

                            else:
                                atom1 = atom2 = None
                                sf_item['index_id'] += 1
                                _row = getRowForStrMr(content_subtype, Id, sf_item['index_id'],
                                                      memberId, memberLogicCode, list_id, self.__reg.entry_id,
                                                      loop.tags, loop.data[idx],
                                                      auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                      [atom1, atom2], self.__reg.annotation_mode)
                                lp.add_data(_row)

                        else:

                            sf_item['index_id'] += 1
                            _row = getRowForStrMr(content_subtype, sf_item['id'], sf_item['index_id'],
                                                  None, None, list_id, self.__reg.entry_id,
                                                  loop.tags, loop.data[idx],
                                                  auth_to_star_seq, auth_to_orig_seq, auth_to_ins_code, offset_holder,
                                                  atom_sels, self.__reg.annotation_mode)
                            lp.add_data(_row)

                    if not is_valid:

                        lp = loop

                        sf_item['loop'] = lp

                else:  # nothing to do because of missing polymer sequence for this loop

                    lp = loop

                    sf_item['loop'] = lp

            if content_subtype == 'dist_restraint':

                sf_item['loop'] = lp

                use_member_logic_code = sf_item['file_type'] in ('nm-res-xpl', 'nm-res-cns', 'nm-res-cha')
                if use_member_logic_code:
                    lp = sf_item['loop']
                    if 'Member_logic_code' not in lp.tags:
                        use_member_logic_code = False
                    else:
                        dat = lp.get_tag(['Member_logic_code'])
                        use_member_logic_code = any(True for row in dat if row not in EMPTY_VALUE)

                if not use_member_logic_code:
                    if not self.updateGenDistConstIdInMrStr(sf_item):
                        err = "Atoms in distance restraints can not be properly identified. Please re-upload the NMR-STAR file."
                        self.__reg.report.error.appendDescription('missing_mandatory_content',
                                                                  {'file_name': original_file_name,
                                                                   'sf_framecode': sf_framecode,
                                                                   'category': lp_category,
                                                                   'description': err})

                sf_item['constraint_type'] = 'distance'
                sf_item['constraint_subsubtype'] = 'simple'
                constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                if len(constraint_type) > 0 and constraint_type not in EMPTY_VALUE:
                    sf_item['constraint_subtype'] = constraint_type

                item_names = ITEM_NAMES_IN_DIST_LOOP[file_type]
                id_col = lp.tags.index('ID')
                member_logic_code_col = lp.tags.index('Member_logic_code') if 'Member_logic_code' in lp.tags else -1
                auth_asym_id_1_col = lp.tags.index('Auth_asym_ID_1')
                auth_seq_id_1_col = lp.tags.index('Auth_seq_ID_1')
                auth_asym_id_2_col = lp.tags.index('Auth_asym_ID_2')
                auth_seq_id_2_col = lp.tags.index('Auth_seq_ID_2')
                comp_id_1_col = lp.tags.index(item_names['comp_id_1'])
                comp_id_2_col = lp.tags.index(item_names['comp_id_2'])
                atom_id_1_col = lp.tags.index(item_names['atom_id_1'])
                atom_id_2_col = lp.tags.index(item_names['atom_id_2'])

                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                has_or_code = False

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in EMPTY_VALUE and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                            has_or_code = True
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in EMPTY_VALUE:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'dist', dst_func)
                        else:
                            if getPotentialType(file_type, 'dist', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

                if has_or_code:

                    prev_id = -1
                    for row in lp:
                        if member_logic_code_col != -1 and row[member_logic_code_col] == 'OR':
                            _id = int(row[id_col])
                            if _id != prev_id:
                                _atom1 = {'chain_id': row[auth_asym_id_1_col],
                                          'seq_id':
                                          int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in EMPTY_VALUE else None,
                                          'comp_id': row[comp_id_1_col],
                                          'atom_id': row[atom_id_1_col]}
                                _atom2 = {'chain_id': row[auth_asym_id_2_col],
                                          'seq_id':
                                          int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in EMPTY_VALUE else None,
                                          'comp_id': row[comp_id_2_col],
                                          'atom_id': row[atom_id_2_col]}
                                prev_id = _id
                                continue
                            atom1 = {'chain_id': row[auth_asym_id_1_col],
                                     'seq_id': int(row[auth_seq_id_1_col]) if row[auth_seq_id_1_col] not in EMPTY_VALUE else None,
                                     'comp_id': row[comp_id_1_col],
                                     'atom_id': row[atom_id_1_col]}
                            atom2 = {'chain_id': row[auth_asym_id_2_col],
                                     'seq_id': int(row[auth_seq_id_2_col]) if row[auth_seq_id_2_col] not in EMPTY_VALUE else None,
                                     'comp_id': row[comp_id_2_col],
                                     'atom_id': row[atom_id_2_col]}
                            if isAmbigAtomSelection([_atom1, atom1], self.__reg.csStat)\
                               or isAmbigAtomSelection([_atom2, atom2], self.__reg.csStat):
                                sf_item['constraint_subsubtype'] = 'ambi'
                                break
                            _atom1, _atom2 = atom1, atom2

                    if sf_item['constraint_subsubtype'] == 'ambi':

                        if 'pre' in sf_framecode or 'paramag' in sf_framecode:
                            sf_item['constraint_subtype'] = 'paramagnetic relaxation'
                        if 'cidnp' in sf_framecode:
                            sf_item['constraint_subtype'] = 'photo cidnp'
                        if 'csp' in sf_framecode or 'perturb' in sf_framecode:
                            sf_item['constraint_subtype'] = 'chemical shift perturbation'
                        if 'mutat' in sf_framecode:
                            sf_item['constraint_subtype'] = 'mutation'
                        if 'protect' in sf_framecode:
                            sf_item['constraint_subtype'] = 'hydrogen exchange protection'
                        if 'symm' in sf_framecode:
                            sf_item['constraint_subtype'] = 'symmetry'

                        if 'pre' in original_file_name or 'paramag' in original_file_name:
                            sf_item['constraint_subtype'] = 'paramagnetic relaxation'
                        if 'cidnp' in original_file_name:
                            sf_item['constraint_subtype'] = 'photo cidnp'
                        if 'csp' in original_file_name or 'perturb' in original_file_name:
                            sf_item['constraint_subtype'] = 'chemical shift perturbation'
                        if 'mutat' in original_file_name:
                            sf_item['constraint_subtype'] = 'mutation'
                        if 'protect' in original_file_name:
                            sf_item['constraint_subtype'] = 'hydrogen exchange protection'
                        if 'symm' in original_file_name:
                            sf_item['constraint_subtype'] = 'symmetry'

                if sf_item['constraint_subsubtype'] == 'simple':

                    metal_coord = disele_bond = disulf_bond = hydrog_bond = False

                    for row in lp:
                        comp_id_1 = row[comp_id_1_col]
                        comp_id_2 = row[comp_id_2_col]
                        atom_id_1 = row[atom_id_1_col]
                        atom_id_2 = row[atom_id_2_col]

                        if atom_id_1 in EMPTY_VALUE or atom_id_2 in EMPTY_VALUE:
                            continue

                        atom_id_1_ = atom_id_1[0]
                        atom_id_2_ = atom_id_2[0]
                        if comp_id_1 == atom_id_1 or comp_id_2 == atom_id_2:
                            metal_coord = True
                        elif 'SE' in (atom_id_1, atom_id_2):
                            disele_bond = True
                        elif 'SG' in (atom_id_1, atom_id_2):
                            disulf_bond = True
                        elif (atom_id_1_ == 'F' and atom_id_2_ in PROTON_BEGIN_CODE)\
                                or (atom_id_2_ == 'F' and atom_id_1_ in PROTON_BEGIN_CODE):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ in PROTON_BEGIN_CODE)\
                                or (atom_id_2_ == 'O' and atom_id_1_ in PROTON_BEGIN_CODE):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'N' and atom_id_2_ in PROTON_BEGIN_CODE)\
                                or (atom_id_2_ == 'N' and atom_id_1_ in PROTON_BEGIN_CODE):
                            hydrog_bond = True
                        elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):
                            hydrog_bond = True

                    if not metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                        if 'build' in sf_framecode and 'up' in sf_framecode:
                            if 'roe' in sf_framecode:
                                sf_item['constraint_subtype'] = 'ROE build-up'
                            else:
                                sf_item['constraint_subtype'] = 'NOE build-up'

                        elif 'not' in sf_framecode and 'seen' in sf_framecode:
                            sf_item['constraint_subtype'] = 'NOE not seen'

                        elif 'roe' in sf_framecode:
                            sf_item['constraint_subtype'] = 'ROE'

                        elif 'build' in original_file_name and 'up' in original_file_name:
                            if 'roe' in original_file_name:
                                sf_item['constraint_subtype'] = 'ROE build-up'
                            else:
                                sf_item['constraint_subtype'] = 'NOE build-up'

                        elif 'not' in original_file_name and 'seen' in original_file_name:
                            sf_item['constraint_subtype'] = 'NOE not seen'

                        elif 'roe' in original_file_name:
                            sf_item['constraint_subtype'] = 'ROE'

                        sf_item['constraint_subtype'] = 'NOE'

                    elif metal_coord and not disele_bond and not disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'metal coordination'

                    elif not metal_coord and disele_bond and not disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'diselenide bond'

                    elif not metal_coord and not disele_bond and disulf_bond and not hydrog_bond:
                        sf_item['constraint_subtype'] = 'disulfide bond'

                    elif not metal_coord and not disele_bond and not disulf_bond and hydrog_bond:
                        sf_item['constraint_subtype'] = 'hydrogen bond'

            elif content_subtype == 'dihed_restraint':
                self.updateTorsionAngleConstIdInMrStr(sf_item)

                auth_to_entity_type = self.__reg.caC['auth_to_entity_type']

                sf_item['constraint_type'] = 'dihedral angle'

                item_names = ITEM_NAMES_IN_DIHED_LOOP[file_type]
                id_col = lp.tags.index('ID')
                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in EMPTY_VALUE and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in EMPTY_VALUE:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'dihed', dst_func)
                        else:
                            if getPotentialType(file_type, 'dihed', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

                auth_asym_id_col = lp.tags.index('Auth_asym_ID_2')
                auth_seq_id_col = lp.tags.index('Auth_seq_ID_2')
                auth_comp_id_col = lp.tags.index('Auth_comp_ID_2')

                _protein_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in EMPTY_VALUE else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'peptide' in entity_type:
                            _protein_angles += 1
                        else:
                            _other_angles += 1

                if _protein_angles > _other_angles:
                    sf_item['constraint_type'] = 'protein dihedral angle'

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

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'backbone chemical shifts'
                        sf.add_tag('Constraint_subtype', 'backbone chemical shifts')

                _na_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in EMPTY_VALUE else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'nucleotide' in entity_type:
                            _na_angles += 1
                        else:
                            _other_angles += 1

                if _na_angles > _other_angles:
                    sf_item['constraint_type'] = 'nucleic acid dihedral angle'

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

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'unknown'
                        sf.add_tag('Constraint_type', 'unknown')

                _br_angles = _other_angles = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    auth_asym_id = row[auth_asym_id_col]
                    auth_seq_id = int(row[auth_seq_id_col]) if row[auth_seq_id_col] not in EMPTY_VALUE else None
                    auth_comp_id = row[auth_comp_id_col]

                    seq_key = (auth_asym_id, auth_seq_id, auth_comp_id)

                    if seq_key in auth_to_entity_type:
                        entity_type = auth_to_entity_type[seq_key]

                        if 'saccharide' in entity_type:
                            _br_angles += 1
                        else:
                            _other_angles += 1

                if _br_angles > _other_angles:
                    sf_item['constraint_type'] = 'carbohydrate dihedral angle'  # DAOTHER-9471

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

                    if 'Constraint_type' not in tagNames:
                        sf_item['constraint_subtype'] = 'unknown'
                        sf.add_tag('Constraint_type', 'unknown')

            elif content_subtype == 'rdc_restraint':

                sf_item['constraint_type'] = 'dipolar coupling'  # DAOTHER-9471
                sf_item['constraint_subtype'] = 'RDC'

                item_names = ITEM_NAMES_IN_RDC_LOOP[file_type]
                id_col = lp.tags.index('ID')
                try:
                    target_value_col = lp.tags.index(item_names['target_value'])
                except ValueError:
                    target_value_col = -1
                try:
                    lower_limit_col = lp.tags.index(item_names['lower_limit'])
                except ValueError:
                    lower_limit_col = -1
                try:
                    upper_limit_col = lp.tags.index(item_names['upper_limit'])
                except ValueError:
                    upper_limit_col = -1
                try:
                    lower_linear_limit_col = lp.tags.index(item_names['lower_linear_limit'])
                except ValueError:
                    lower_linear_limit_col = -1
                try:
                    upper_linear_limit_col = lp.tags.index(item_names['upper_linear_limit'])
                except ValueError:
                    upper_linear_limit_col = -1

                potential_type = get_first_sf_tag(sf, 'Potential_type')
                has_potential_type = len(potential_type) > 0 and potential_type not in EMPTY_VALUE and potential_type != 'unknown'

                _potential_type = None
                count = 0

                prev_id = -1
                for row in lp:
                    _id = int(row[id_col])
                    if _id == prev_id:
                        continue
                    prev_id = _id
                    count += 1
                    if not has_potential_type:
                        dst_func = {}
                        if target_value_col != -1 and row[target_value_col] not in EMPTY_VALUE:
                            dst_func['target_value'] = float(row[target_value_col])
                        if lower_limit_col != -1 and row[lower_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_limit'] = float(row[lower_limit_col])
                        if upper_limit_col != -1 and row[upper_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_limit'] = float(row[upper_limit_col])
                        if lower_linear_limit_col != -1 and row[lower_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['lower_linear_limit'] = float(row[lower_linear_limit_col])
                        if upper_linear_limit_col != -1 and row[upper_linear_limit_col] not in EMPTY_VALUE:
                            dst_func['upper_linear_limit'] = float(row[upper_linear_limit_col])
                        if _potential_type is None:
                            _potential_type = getPotentialType(file_type, 'rdc', dst_func)
                        else:
                            if getPotentialType(file_type, 'rdc', dst_func) != _potential_type:
                                has_potential_type = True

                if not has_potential_type and _potential_type is not None:
                    set_sf_tag(sf, 'Potential_type', _potential_type)

                sf_item['id'] = count

            else:

                sf_item['id'] = len(lp)

            # merge other loops of the source saveframe
            if is_sf:

                for loop in _sf.loops:

                    if loop.category == lp_category:
                        continue

                    if loop.category in LINKED_LP_CATEGORIES[file_type][content_subtype]:
                        sf.add_loop(loop)

        self.__reg.mr_sf_dict_holder[content_subtype].append(sf_item)

        return True

    def validateStrPk(self, file_list_id: int, file_type: str, content_subtype: str, list_id: int,
                      sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                      sf_framecode: str, lp_category: str) -> bool:
        """ Validate spectral peak lists in NMR-STAR restraint files.
        """

        _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
        num_dim = int(_num_dim)

        if num_dim not in range(1, MAX_DIM_NUM_OF_SPECTRA):
            return False

        max_dim = num_dim + 1

        lp_category = '_Peak_row_format' if content_subtype == 'spectral_peak' else '_Assigned_peak_chem_shift'

        try:

            loop = sf.get_loop(lp_category)

        except KeyError:
            return False

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

        has_poly_seq_in_lp = has_key_value(input_source_dic, 'polymer_sequence_in_loop')

        if not has_poly_seq_in_lp:
            return False

        coord_atom_site = self.__reg.caC['coord_atom_site']
        auth_to_star_seq = self.__reg.caC['auth_to_star_seq']
        auth_to_star_seq_ann = self.__reg.caC['auth_to_star_seq_ann']
        auth_atom_name_to_id = self.__reg.caC['auth_atom_name_to_id']
        auth_atom_name_to_id_ext = self.__reg.caC['auth_atom_name_to_id_ext']

        poly_seq_in_lp = input_source_dic['polymer_sequence_in_loop']

        seq_align = chain_assign = br_seq_align = br_chain_assign = np_seq_align = np_chain_assign = None

        if content_subtype in poly_seq_in_lp:
            _poly_seq_in_lp = next((_poly_seq_in_lp for _poly_seq_in_lp in poly_seq_in_lp[content_subtype]
                                    if _poly_seq_in_lp['sf_framecode'] == sf_framecode), None)

            if _poly_seq_in_lp is not None:
                poly_seq = _poly_seq_in_lp['polymer_sequence']

                seq_align, _ =\
                    alignPolymerSequence(self.__reg.pA, self.__reg.caC['polymer_sequence'], poly_seq, conservative=False)
                chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                        self.__reg.caC['polymer_sequence'], poly_seq, seq_align)

                if self.__reg.caC['branched'] is not None:
                    br_seq_align, _ =\
                        alignPolymerSequence(self.__reg.pA, self.__reg.caC['branched'], poly_seq, conservative=False)
                    br_chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                               self.__reg.caC['branched'], poly_seq, br_seq_align)

                if self.__reg.caC['non_polymer'] is not None:
                    np_seq_align, _ =\
                        alignPolymerSequence(self.__reg.pA, self.__reg.caC['non_polymer'], poly_seq, conservative=False)
                    np_chain_assign, _ = assignPolymerSequence(self.__reg.pA, self.__reg.ccU, file_type,
                                                               self.__reg.caC['non_polymer'], poly_seq, np_seq_align)

        @functools.lru_cache()
        def get_auth_seq_scheme(chain_id, seq_id):
            auth_asym_id = auth_seq_id = None

            if seq_id is not None:

                if chain_assign is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if None in (auth_asym_id, auth_seq_id) and br_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in br_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in br_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

                if None in (auth_asym_id, auth_seq_id) and np_seq_align is not None:
                    auth_asym_id = next((ca['ref_chain_id'] for ca in np_chain_assign if ca['test_chain_id'] == chain_id), None)
                    if auth_asym_id is not None:
                        sa = next((sa for sa in np_seq_align
                                   if sa['ref_chain_id'] == auth_asym_id and sa['test_chain_id'] == chain_id
                                   and seq_id in sa['test_seq_id'] and sa['sequence_coverage'] >= LOW_SEQ_COVERAGE), None)
                        if sa is not None:
                            _ref_seq_id_name = 'ref_auth_seq_id' if 'ref_auth_seq_id' in sa else 'ref_seq_id'
                            auth_seq_id = next((ref_seq_id for ref_seq_id, test_seq_id
                                                in zip(sa[_ref_seq_id_name], sa['test_seq_id'])
                                                if test_seq_id == seq_id), None)

            return auth_asym_id, auth_seq_id

        list_items = ['Details', 'Entry_ID', 'Spectral_peak_list_ID']

        if content_subtype == 'spectral_peak':

            core_items = ['Index_ID', 'ID', 'Volume', 'Volume_uncertainty', 'Height', 'Height_uncertainty']
            aux_items = [item for item in ['Figure_of_merit', 'Restraint'] if item in loop.tags]

            position_item_temps = ['Position_%s', 'Position_uncertainty_%s', 'Line_width_%s', 'Line_width_uncertainty_%s']

            position_items = []

            for dim in range(1, max_dim):
                for idx, position_item_temp in enumerate(position_item_temps):
                    position_item = position_item_temp % dim
                    if idx == 0:
                        position_items.append(position_item)
                    elif position_item in loop.tags:
                        position_items.append(position_item)

            assign_item_temps = ['Entity_assembly_ID_%s', 'Entity_ID_%s', 'Comp_index_ID_%s',
                                 'Seq_ID_%s', 'Comp_ID_%s', 'Atom_ID_%s']
            ambigutity_item_temps = ['Ambiguity_code_%s', 'Ambiguity_set_ID_%s']

            assign_items = []

            for dim in range(1, max_dim):
                for assign_item_temp in assign_item_temps:
                    assign_items.append(assign_item_temp % dim)
                for ambigutity_item_temp in ambigutity_item_temps:
                    ambigutity_item = ambigutity_item_temp % dim
                    if ambigutity_item in loop.tags:
                        assign_items.append(ambigutity_item)

            auth_assign_item_temps = ['Auth_asym_ID_%s', 'Auth_seq_ID_%s', 'Auth_comp_ID_%s', 'Auth_atom_ID_%s']

            auth_assign_items = []

            for dim in range(1, max_dim):
                for auth_assign_item_temp in auth_assign_item_temps:
                    auth_assign_items.append(auth_assign_item_temp % dim)

        else:

            core_items = ['Peak_ID', 'Spectral_dim_ID', 'Set_ID', 'Magnetization_linkage_ID', 'Val']
            aux_items = [item for item in ['Contribution_fractional_val', 'Figure_of_merit',
                                           'Assigned_chem_shift_list_ID', 'Atom_chem_shift_ID']
                         if item in loop.tags]

            assign_items = ['Entity_assembly_ID', 'Entity_ID', 'Comp_index_ID', 'Comp_ID', 'Atom_ID']
            ambigutity_items = ['Ambiguity_code', 'Ambiguity_set_ID']
            for ambiguity_item in ambigutity_items:
                if ambiguity_item in loop.tags:
                    assign_items.append(ambiguity_item)

            auth_assign_items = ['Auth_entity_ID', 'Auth_seq_ID', 'Auth_comp_ID', 'Auth_atom_ID']

        items = core_items
        if len(aux_items) > 0:
            items.extend(aux_items)
        if content_subtype == 'spectral_peak':
            items.extend(position_items)
        items.extend(assign_items)
        items.extend(auth_assign_items)
        items.extend(list_items)

        lp = pynmrstar.Loop.from_scratch(lp_category)

        tags = [f'{lp_category}.{item}' for item in items]

        lp.add_tag(tags)

        prefer_auth_atom_name = False

        if (self.__reg.annotation_mode or self.__reg.native_combined) and len(auth_atom_name_to_id) > 0:

            count_auth_name = count_auth_id = 0

            for row in loop:

                if content_subtype == 'spectral_peak':

                    for dim in range(1, max_dim):
                        has_auth_seq = valid_auth_seq = True
                        for auth_assign_item_temp in auth_assign_item_temps:
                            auth_assign_item = auth_assign_item_temp % dim
                            if auth_assign_item not in loop.tags:
                                has_auth_seq = valid_auth_seq = False
                                break
                        if has_auth_seq:
                            try:
                                auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                                auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                                comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                                atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq:
                                        valid_auth_seq = False
                            except (ValueError, TypeError):
                                has_auth_seq = valid_auth_seq = False

                        if valid_auth_seq:

                            if atom_id not in EMPTY_VALUE:

                                if comp_id in auth_atom_name_to_id:
                                    if atom_id in auth_atom_name_to_id[comp_id]:
                                        count_auth_name += 1
                                    if atom_id in auth_atom_name_to_id[comp_id].values():
                                        count_auth_id += 1

                        else:

                            chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                            for col, assign_item_temp in enumerate(assign_item_temps):
                                assign_item = assign_item_temp % dim
                                if assign_item not in loop.tags:
                                    continue
                                if col == 0:
                                    chain_id = row[loop.tags.index(assign_item)]
                                elif col == 1:
                                    continue
                                elif col == 2:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                                elif col == 3:
                                    if seq_id is None:
                                        try:
                                            seq_id = int(row[loop.tags.index(assign_item)])
                                        except (ValueError, TypeError):
                                            pass
                                elif col == 4:
                                    comp_id = row[loop.tags.index(assign_item)]
                                    if comp_id not in EMPTY_VALUE:
                                        comp_id = comp_id.upper()
                                else:
                                    atom_id = row[loop.tags.index(assign_item)]

                            if None not in (chain_id, seq_id):
                                auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                            if None not in (auth_asym_id, auth_seq_id):
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                    seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key in auth_to_star_seq:

                                    if atom_id not in EMPTY_VALUE:

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

                else:

                    has_auth_seq = valid_auth_seq = True
                    for auth_assign_item in auth_assign_items:
                        if auth_assign_item not in loop.tags:
                            has_auth_seq = valid_auth_seq = False
                            break
                    if has_auth_seq:
                        try:
                            auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                            auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                            comp_id = row[loop.tags.index(auth_assign_items[2])]
                            atom_id = row[loop.tags.index(auth_assign_items[3])]
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                        except (ValueError, TypeError):
                            has_auth_seq = valid_auth_seq = False

                    if valid_auth_seq:

                        if atom_id not in EMPTY_VALUE:

                            if comp_id in auth_atom_name_to_id:
                                if atom_id in auth_atom_name_to_id[comp_id]:
                                    count_auth_name += 1
                                if atom_id in auth_atom_name_to_id[comp_id].values():
                                    count_auth_id += 1

                    else:

                        chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                        for col, assign_item in enumerate(assign_items):
                            if assign_item not in loop.tags:
                                continue
                            if col == 0:
                                chain_id = row[loop.tags.index(assign_item)]
                            elif col == 1:
                                continue
                            elif col == 2:
                                try:
                                    seq_id = int(row[loop.tags.index(assign_item)])
                                except (ValueError, TypeError):
                                    pass
                            elif col == 3:
                                comp_id = row[loop.tags.index(assign_item)]
                                if comp_id not in EMPTY_VALUE:
                                    comp_id = comp_id.upper()
                            else:
                                atom_id = row[loop.tags.index(assign_item)]

                        if None not in (chain_id, seq_id):
                            auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                        if None not in (auth_asym_id, auth_seq_id):
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key in auth_to_star_seq:

                                if atom_id not in EMPTY_VALUE:

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

            if count_auth_name + count_auth_id == 0:

                for row in loop:

                    if content_subtype == 'spectral_peak':

                        for dim in range(1, max_dim):
                            has_auth_seq = valid_auth_seq = True
                            for auth_assign_item_temp in auth_assign_item_temps:
                                auth_assign_item = auth_assign_item_temp % dim
                                if auth_assign_item not in loop.tags:
                                    has_auth_seq = valid_auth_seq = False
                                    break
                            if has_auth_seq:
                                try:
                                    auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                                    auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                                    comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                                    atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq_ann:
                                        valid_auth_seq = False
                                    else:
                                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                                            comp_id = coord_atom_site[_seq_key]['comp_id']
                                except (ValueError, TypeError):
                                    has_auth_seq = valid_auth_seq = False

                            if valid_auth_seq:

                                if atom_id not in EMPTY_VALUE:

                                    if comp_id in auth_atom_name_to_id:
                                        if atom_id in auth_atom_name_to_id[comp_id]:
                                            count_auth_name += 1
                                        if atom_id in auth_atom_name_to_id[comp_id].values():
                                            count_auth_id += 1

                            else:

                                chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                                for col, assign_item_temp in enumerate(assign_item_temps):
                                    assign_item = assign_item_temp % dim
                                    if assign_item not in loop.tags:
                                        continue
                                    if col == 0:
                                        chain_id = row[loop.tags.index(assign_item)]
                                    elif col == 1:
                                        continue
                                    elif col == 2:
                                        try:
                                            seq_id = int(row[loop.tags.index(assign_item)])
                                        except (ValueError, TypeError):
                                            pass
                                    elif col == 3:
                                        if seq_id is None:
                                            try:
                                                seq_id = int(row[loop.tags.index(assign_item)])
                                            except (ValueError, TypeError):
                                                pass
                                    elif col == 4:
                                        comp_id = row[loop.tags.index(assign_item)]
                                        if comp_id not in EMPTY_VALUE:
                                            comp_id = comp_id.upper()
                                    else:
                                        atom_id = row[loop.tags.index(assign_item)]

                                if None not in (chain_id, seq_id):
                                    auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                                if None not in (auth_asym_id, auth_seq_id):
                                    seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                    if seq_key in auth_to_star_seq_ann:
                                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                                            comp_id = coord_atom_site[_seq_key]['comp_id']

                                        if atom_id not in EMPTY_VALUE:

                                            if comp_id in auth_atom_name_to_id:
                                                if atom_id in auth_atom_name_to_id[comp_id]:
                                                    count_auth_name += 1
                                                if atom_id in auth_atom_name_to_id[comp_id].values():
                                                    count_auth_id += 1

                    else:

                        has_auth_seq = valid_auth_seq = True
                        for auth_assign_item in auth_assign_items:
                            if auth_assign_item not in loop.tags:
                                has_auth_seq = valid_auth_seq = False
                                break
                        if has_auth_seq:
                            try:
                                auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                                auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                                comp_id = row[loop.tags.index(auth_assign_items[2])]
                                atom_id = row[loop.tags.index(auth_assign_items[3])]
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                                else:
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:  # DAOTHER-8817
                                        comp_id = coord_atom_site[_seq_key]['comp_id']
                            except (ValueError, TypeError):
                                has_auth_seq = valid_auth_seq = False

                        if valid_auth_seq:

                            if atom_id not in EMPTY_VALUE:

                                if comp_id in auth_atom_name_to_id:
                                    if atom_id in auth_atom_name_to_id[comp_id]:
                                        count_auth_name += 1
                                    if atom_id in auth_atom_name_to_id[comp_id].values():
                                        count_auth_id += 1

                        else:

                            chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                            for col, assign_item in enumerate(assign_items):
                                if assign_item not in loop.tags:
                                    continue
                                if col == 0:
                                    chain_id = row[loop.tags.index(assign_item)]
                                elif col == 1:
                                    continue
                                elif col == 2:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                                elif col == 3:
                                    comp_id = row[loop.tags.index(assign_item)]
                                    if comp_id not in EMPTY_VALUE:
                                        comp_id = comp_id.upper()
                                else:
                                    atom_id = row[loop.tags.index(assign_item)]

                            if None not in (chain_id, seq_id):
                                auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                            if None not in (auth_asym_id, auth_seq_id):
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                                if seq_key in auth_to_star_seq_ann:
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:  # DAOTHER-8817
                                        comp_id = coord_atom_site[_seq_key]['comp_id']

                                    if atom_id not in EMPTY_VALUE:

                                        if comp_id in auth_atom_name_to_id:
                                            if atom_id in auth_atom_name_to_id[comp_id]:
                                                count_auth_name += 1
                                            if atom_id in auth_atom_name_to_id[comp_id].values():
                                                count_auth_id += 1

            prefer_auth_atom_name = count_auth_name > count_auth_id

        index = 1

        for idx, row in enumerate(loop):

            _row = [None] * len(tags)

            for col, item in enumerate(loop.tags):
                if item in items:
                    _row[items.index(item)] = row[col]

            if content_subtype == 'spectral_peak':

                _row[0] = index

                for dim in range(1, max_dim):
                    has_auth_seq = valid_auth_seq = True
                    for auth_assign_item_temp in auth_assign_item_temps:
                        auth_assign_item = auth_assign_item_temp % dim
                        if auth_assign_item not in loop.tags:
                            has_auth_seq = valid_auth_seq = False
                            break
                    if has_auth_seq:
                        try:
                            auth_asym_id_ = row[loop.tags.index(auth_assign_item_temps[0] % dim)]
                            auth_seq_id_ = int(row[loop.tags.index(auth_assign_item_temps[1] % dim)])
                            comp_id = row[loop.tags.index(auth_assign_item_temps[2] % dim)]
                            atom_id = row[loop.tags.index(auth_assign_item_temps[3] % dim)]
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key not in auth_to_star_seq:
                                if self.__reg.annotation_mode:
                                    comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                    if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key not in auth_to_star_seq:
                                        valid_auth_seq = False
                                elif seq_key not in auth_to_star_seq_ann:
                                    valid_auth_seq = False
                        except (ValueError, TypeError):
                            has_auth_seq = valid_auth_seq = False

                    if valid_auth_seq:
                        try:
                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if 'chain_id' in _coord_atom_site:
                                    auth_asym_id = _coord_atom_site['chain_id']
                                comp_id = _coord_atom_site['comp_id']
                        except KeyError:
                            entity_assembly_id = seq_id = entity_id = None
                            if self.__reg.annotation_mode:
                                auth_asym_id_ =\
                                    next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                          if _auth_seq_id == auth_seq_id_ and _auth_comp_id == comp_id), auth_asym_id_)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key in auth_to_star_seq:
                                    row[loop.tags.index(auth_assign_item_temps[0] % dim)] = auth_asym_id
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                else:
                                    auth_asym_id_, comp_id =\
                                        next(((_auth_asym_id, _auth_comp_id)
                                              for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                              if _auth_seq_id == auth_seq_id_), (auth_asym_id_, comp_id))
                                    seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                    if seq_key in auth_to_star_seq:
                                        row[loop.tags.index(auth_assign_item_temps[0] % dim)] = auth_asym_id
                                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]

                        if prefer_auth_atom_name:
                            _atom_id = atom_id
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                   and _atom_id in auth_atom_name_to_id[comp_id]:
                                    if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                        atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                   and comp_id == _coord_atom_site['comp_id']:
                                    atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                # DAOTHER-8751, 8817 (D_1300043061)
                                elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                     and _atom_id in _coord_atom_site['alt_atom_id']\
                                     and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:  # noqa: E501, pylint: disable=line-too-long
                                    cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                    and cca_row[6] == _seq_key[1]), None)
                                    if cca_row is not None:
                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                    if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                       and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                        atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                    else:
                                        atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                elif 'split_comp_id' in _coord_atom_site:
                                    for _comp_id in _coord_atom_site['split_comp_id']:
                                        if _comp_id == comp_id:
                                            continue
                                        __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                        if __seq_key not in coord_atom_site:
                                            continue
                                        __coord_atom_site = coord_atom_site[__seq_key]
                                        if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                           and _atom_id in __coord_atom_site['alt_atom_id']:
                                            comp_id = _comp_id
                                            cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                            and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                            break
                                        if _atom_id in __coord_atom_site['atom_id']:
                                            comp_id = _comp_id
                                            cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                            and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            break

                        for col, assign_item_temp in enumerate(assign_item_temps):
                            assign_item = assign_item_temp % dim
                            if col == 0:
                                _row[items.index(assign_item)] = entity_assembly_id
                            elif col == 1:
                                _row[items.index(assign_item)] = entity_id
                            elif col in (2, 3):
                                _row[items.index(assign_item)] = seq_id
                            elif col == 4:
                                _row[items.index(assign_item)] = comp_id
                            else:
                                _row[items.index(assign_item)] = atom_id

                    else:

                        chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                        for col, assign_item_temp in enumerate(assign_item_temps):
                            assign_item = assign_item_temp % dim
                            if assign_item not in loop.tags:
                                continue
                            if col == 0:
                                chain_id = row[loop.tags.index(assign_item)]
                            elif col == 1:
                                continue
                            elif col == 2:
                                try:
                                    seq_id = int(row[loop.tags.index(assign_item)])
                                except (ValueError, TypeError):
                                    pass
                            elif col == 3:
                                if seq_id is None:
                                    try:
                                        seq_id = int(row[loop.tags.index(assign_item)])
                                    except (ValueError, TypeError):
                                        pass
                            elif col == 4:
                                comp_id = row[loop.tags.index(assign_item)]
                                if comp_id not in EMPTY_VALUE:
                                    comp_id = comp_id.upper()
                            else:
                                atom_id = row[loop.tags.index(assign_item)]

                        if None not in (chain_id, seq_id):
                            auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                        if None not in (auth_asym_id, auth_seq_id):
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if self.__reg.annotation_mode and seq_key not in auth_to_star_seq:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                                seq_key = (auth_asym_id, auth_seq_id, comp_id)
                            if seq_key in auth_to_star_seq:
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                                _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                if _seq_key in coord_atom_site:  # DAOTHER-8817
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if 'chain_id' in _coord_atom_site:
                                        auth_asym_id = _coord_atom_site['chain_id']
                                    comp_id = _coord_atom_site['comp_id']

                                if prefer_auth_atom_name:
                                    _atom_id = atom_id
                                    _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                    if _seq_key in coord_atom_site:
                                        _coord_atom_site = coord_atom_site[_seq_key]
                                        if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                           and _atom_id in auth_atom_name_to_id[comp_id]:
                                            if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                                atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                        if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                           and comp_id == _coord_atom_site['comp_id']:
                                            atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                        # DAOTHER-8751, 8817 (D_1300043061)
                                        elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                             and _atom_id in _coord_atom_site['alt_atom_id']\
                                             and comp_id == _coord_atom_site['alt_comp_id'][
                                                 _coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                            cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                            if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                            and cca_row[6] == _seq_key[1]), None)
                                            if cca_row is not None:
                                                entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                            if comp_id in auth_atom_name_to_id_ext\
                                               and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                               and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                                atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                            else:
                                                atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]  # noqa: E501, pylint: disable=line-too-long
                                        elif 'split_comp_id' in _coord_atom_site:
                                            for _comp_id in _coord_atom_site['split_comp_id']:
                                                if _comp_id == comp_id:
                                                    continue
                                                __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                                if __seq_key not in coord_atom_site:
                                                    continue
                                                __coord_atom_site = coord_atom_site[__seq_key]
                                                if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                                   and _atom_id in __coord_atom_site['alt_atom_id']:
                                                    comp_id = _comp_id
                                                    cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                                    and cca_row[6] == _seq_key[1]), None)
                                                    if cca_row is not None:
                                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                    atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]  # noqa: E501, pylint: disable=line-too-long
                                                    break
                                                if _atom_id in __coord_atom_site['atom_id']:
                                                    comp_id = _comp_id
                                                    cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                                    if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                                    and cca_row[6] == _seq_key[1]), None)
                                                    if cca_row is not None:
                                                        entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                    break

                                for col, assign_item_temp in enumerate(assign_item_temps):
                                    assign_item = assign_item_temp % dim
                                    if col == 0:
                                        _row[items.index(assign_item)] = entity_assembly_id
                                    elif col == 1:
                                        _row[items.index(assign_item)] = entity_id
                                    elif col in (2, 3):
                                        _row[items.index(assign_item)] = seq_id
                                    elif col == 4:
                                        _row[items.index(assign_item)] = comp_id
                                    else:
                                        _row[items.index(assign_item)] = atom_id

                                for col, auth_assign_item_temp in enumerate(auth_assign_item_temps):
                                    auth_assign_item = auth_assign_item_temp % dim
                                    if col == 0:
                                        _row[items.index(auth_assign_item)] = auth_asym_id
                                    elif col == 1:
                                        _row[items.index(auth_assign_item)] = auth_seq_id
                                    elif col == 2:
                                        _row[items.index(auth_assign_item)] = comp_id
                                    else:
                                        _row[items.index(auth_assign_item)] = atom_id

            else:

                has_auth_seq = valid_auth_seq = True
                for auth_assign_item in auth_assign_items:
                    if auth_assign_item not in loop.tags:
                        has_auth_seq = valid_auth_seq = False
                        break
                if has_auth_seq:
                    try:
                        auth_asym_id_ = row[loop.tags.index(auth_assign_items[0])]
                        auth_seq_id_ = int(row[loop.tags.index(auth_assign_items[1])])
                        comp_id = row[loop.tags.index(auth_assign_items[2])]
                        atom_id = row[loop.tags.index(auth_assign_items[3])]
                        seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                        if seq_key not in auth_to_star_seq:
                            if self.__reg.annotation_mode:
                                comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                if _auth_asym_id == auth_asym_id_ and _auth_seq_id == auth_seq_id_), comp_id)
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key not in auth_to_star_seq:
                                    valid_auth_seq = False
                            elif seq_key not in auth_to_star_seq_ann:
                                valid_auth_seq = False
                    except (ValueError, TypeError):
                        has_auth_seq = valid_auth_seq = False

                if valid_auth_seq:
                    try:
                        entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                        if _seq_key in coord_atom_site:  # DAOTHER-8817
                            _coord_atom_site = coord_atom_site[_seq_key]
                            if 'chain_id' in _coord_atom_site:
                                auth_asym_id = _coord_atom_site['chain_id']
                            comp_id = _coord_atom_site['comp_id']
                    except KeyError:
                        entity_assembly_id = seq_id = entity_id = None
                        if self.__reg.annotation_mode:
                            auth_asym_id_ = next((_auth_asym_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                  if _auth_seq_id == auth_seq_id_ and _auth_comp_id == comp_id), auth_asym_id_)
                            seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                            if seq_key in auth_to_star_seq:
                                row[loop.tags.index(auth_assign_items[0])] = auth_asym_id_
                                entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            else:
                                auth_asym_id_, comp_id = next(((_auth_asym_id, _auth_comp_id)
                                                               for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                                               if _auth_seq_id == auth_seq_id_), (auth_asym_id_, comp_id))
                                seq_key = (auth_asym_id_, auth_seq_id_, comp_id)
                                if seq_key in auth_to_star_seq:
                                    row[loop.tags.index(auth_assign_items[0])] = auth_asym_id_
                                    entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]

                    if prefer_auth_atom_name:
                        _atom_id = atom_id
                        _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                        if _seq_key in coord_atom_site:
                            _coord_atom_site = coord_atom_site[_seq_key]
                            if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                               and _atom_id in auth_atom_name_to_id[comp_id]:
                                if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                    atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                            if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                               and comp_id == _coord_atom_site['comp_id']:
                                atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                            # DAOTHER-8751, 8817 (D_1300043061)
                            elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                 and _atom_id in _coord_atom_site['alt_atom_id']\
                                 and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:
                                cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                and cca_row[6] == _seq_key[1]), None)
                                if cca_row is not None:
                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                   and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                    atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                else:
                                    atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                            elif 'split_comp_id' in _coord_atom_site:
                                for _comp_id in _coord_atom_site['split_comp_id']:
                                    if _comp_id == comp_id:
                                        continue
                                    __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                    if __seq_key not in coord_atom_site:
                                        continue
                                    __coord_atom_site = coord_atom_site[__seq_key]
                                    if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                       and _atom_id in __coord_atom_site['alt_atom_id']:
                                        comp_id = _comp_id
                                        cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                        and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]
                                        break
                                    if _atom_id in __coord_atom_site['atom_id']:
                                        comp_id = _comp_id
                                        cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                        and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        break

                    for col, assign_item in enumerate(assign_items):
                        if col == 0:
                            _row[items.index(assign_item)] = entity_assembly_id
                        elif col == 1:
                            _row[items.index(assign_item)] = entity_id
                        elif col == 2:
                            _row[items.index(assign_item)] = seq_id
                        elif col == 3:
                            _row[items.index(assign_item)] = comp_id
                        else:
                            _row[items.index(assign_item)] = atom_id

                else:

                    chain_id = seq_id = comp_id = atom_id = auth_asym_id = auth_seq_id = None

                    for col, assign_item in enumerate(assign_items):
                        if assign_item not in loop.tags:
                            continue
                        if col == 0:
                            chain_id = row[loop.tags.index(assign_item)]
                        elif col == 1:
                            continue
                        elif col == 2:
                            try:
                                seq_id = int(row[loop.tags.index(assign_item)])
                            except (ValueError, TypeError):
                                pass
                        elif col == 3:
                            comp_id = row[loop.tags.index(assign_item)]
                            if comp_id not in EMPTY_VALUE:
                                comp_id = comp_id.upper()
                        else:
                            atom_id = row[loop.tags.index(assign_item)]

                    if None not in (chain_id, seq_id):
                        auth_asym_id, auth_seq_id = get_auth_seq_scheme(chain_id, seq_id)

                    if None not in (auth_asym_id, auth_seq_id):
                        seq_key = (auth_asym_id, auth_seq_id, comp_id)
                        if self.__reg.annotation_mode and seq_key not in auth_to_star_seq:
                            comp_id = next((_auth_comp_id for _auth_asym_id, _auth_seq_id, _auth_comp_id in auth_to_star_seq
                                            if _auth_asym_id == auth_asym_id and _auth_seq_id == auth_seq_id), comp_id)
                            seq_key = (auth_asym_id, auth_seq_id, comp_id)
                        if seq_key in auth_to_star_seq:
                            entity_assembly_id, seq_id, entity_id, _ = auth_to_star_seq[seq_key]
                            _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                            if _seq_key in coord_atom_site:  # DAOTHER-8817
                                _coord_atom_site = coord_atom_site[_seq_key]
                                if 'chain_id' in _coord_atom_site:
                                    auth_asym_id = _coord_atom_site['chain_id']
                                comp_id = _coord_atom_site['comp_id']

                            if prefer_auth_atom_name:
                                _atom_id = atom_id
                                _seq_key = seq_key if seq_key in coord_atom_site else (seq_key[0], seq_key[1])
                                if _seq_key in coord_atom_site:
                                    _coord_atom_site = coord_atom_site[_seq_key]
                                    if comp_id in auth_atom_name_to_id and comp_id == _coord_atom_site['comp_id']\
                                       and _atom_id in auth_atom_name_to_id[comp_id]:
                                        if auth_atom_name_to_id[comp_id][_atom_id] in _coord_atom_site['atom_id']:
                                            atom_id = auth_atom_name_to_id[comp_id][_atom_id]
                                    if 'alt_atom_id' in _coord_atom_site and _atom_id in _coord_atom_site['alt_atom_id']\
                                       and comp_id == _coord_atom_site['comp_id']:
                                        atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    # DAOTHER-8751, 8817 (D_1300043061)
                                    elif 'alt_comp_id' in _coord_atom_site and 'alt_atom_id' in _coord_atom_site\
                                         and _atom_id in _coord_atom_site['alt_atom_id']\
                                         and comp_id == _coord_atom_site['alt_comp_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]:  # noqa: E501, pylint: disable=line-too-long
                                        cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                        if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                        and cca_row[6] == _seq_key[1]), None)
                                        if cca_row is not None:
                                            entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                        if comp_id in auth_atom_name_to_id_ext and _atom_id in auth_atom_name_to_id_ext[comp_id]\
                                           and len(set(_coord_atom_site['alt_comp_id'])) > 1:
                                            atom_id = auth_atom_name_to_id_ext[comp_id][_atom_id]
                                        else:
                                            atom_id = _coord_atom_site['atom_id'][_coord_atom_site['alt_atom_id'].index(_atom_id)]
                                    elif 'split_comp_id' in _coord_atom_site:
                                        for _comp_id in _coord_atom_site['split_comp_id']:
                                            if _comp_id == comp_id:
                                                continue
                                            __seq_key = (_seq_key[0], _seq_key[1], _comp_id)
                                            if __seq_key not in coord_atom_site:
                                                continue
                                            __coord_atom_site = coord_atom_site[__seq_key]
                                            if 'alt_comp_id' in __coord_atom_site and 'alt_atom_id' in __coord_atom_site\
                                               and _atom_id in __coord_atom_site['alt_atom_id']:
                                                comp_id = _comp_id
                                                cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                                and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                atom_id = __coord_atom_site['atom_id'][__coord_atom_site['alt_atom_id'].index(_atom_id)]  # noqa: E501, pylint: disable=line-too-long
                                                break
                                            if _atom_id in __coord_atom_site['atom_id']:
                                                comp_id = _comp_id
                                                cca_row = next((cca_row for cca_row in self.__reg.chem_comp_asm_dat
                                                                if cca_row[4] == comp_id and cca_row[5] == _seq_key[0]
                                                                and cca_row[6] == _seq_key[1]), None)
                                                if cca_row is not None:
                                                    entity_assembly_id, entity_id, seq_id = cca_row[0], cca_row[1], cca_row[2]
                                                break

                            for col, assign_item in enumerate(assign_items):
                                if col == 0:
                                    _row[items.index(assign_item)] = entity_assembly_id
                                elif col == 1:
                                    _row[items.index(assign_item)] = entity_id
                                elif col == 2:
                                    _row[items.index(assign_item)] = seq_id
                                elif col == 3:
                                    _row[items.index(assign_item)] = comp_id
                                else:
                                    _row[items.index(assign_item)] = atom_id

                            for col, auth_assign_item in enumerate(auth_assign_items):
                                if col == 0:
                                    _row[items.index(auth_assign_item)] = auth_asym_id
                                elif col == 1:
                                    _row[items.index(auth_assign_item)] = auth_seq_id
                                elif col == 2:
                                    _row[items.index(auth_assign_item)] = comp_id
                                else:
                                    _row[items.index(auth_assign_item)] = atom_id

            _row[-2] = self.__reg.entry_id
            _row[-1] = list_id

            lp.add_data(_row)

            index += 1

        del sf[loop]

        sf.add_loop(lp)

        self.__reg.c2S.set_entry_id(sf, self.__reg.entry_id)
        self.__reg.c2S.set_local_sf_id(sf, list_id)

        get_auth_seq_scheme.cache_clear()

        return True

    def testCoordAtomIdConsistency(self, file_list_id: int, file_name: str, file_type: str, content_subtype: str,
                                   sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                   list_id: int, sf_framecode: str, lp_category: str, cif_poly_seq: List[dict],
                                   seq_align_dic: dict, nmr2ca: dict, ref_chain_id: str) -> bool:
        """ Perform consistency test on atom names of coordinate file.
        """

        modified = False

        index_tag = INDEX_TAGS[file_type][content_subtype] if content_subtype != 'poly_seq' else None

        if file_type == 'nef' or not self.__reg.nonblk_bad_nterm:

            if content_subtype != 'poly_seq':
                lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)
            else:
                lp_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                and lp['category'] == lp_category), None)

        else:

            if content_subtype == 'spectral_peak':

                try:

                    _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
                    num_dim = int(_num_dim)

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

                except ValueError:  # raised error already at testIndexConsistency()
                    return False

                max_dim = num_dim + 1

                key_items = []
                for dim in range(1, max_dim):
                    for k in PK_KEY_ITEMS[file_type]:
                        if k['type'] == 'float':  # position
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            key_items.append(_k)
                for k in PK_KEY_ITEMS[file_type]:
                    if k['type'] == 'positive-int':  # peak_id
                        key_items.append(k)

                data_items = []
                for d in DATA_ITEMS[file_type][content_subtype]:
                    data_items.append(d)
                for dim in range(1, max_dim):
                    for d in self.__reg.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.__reg.key_items[file_type][content_subtype]
                    data_items = DATA_ITEMS[file_type][content_subtype]
                else:
                    key_items = self.__reg.aux_key_items[file_type][content_subtype][lp_category]
                    data_items = self.__reg.aux_data_items[file_type][content_subtype][lp_category]

            try:

                lp_data = self.__reg.nefT.check_data(sf, lp_category, key_items, data_items, None, None, None,
                                                     enforce_allowed_tags=(file_type == 'nmr-star'),
                                                     excl_missing_data=self.__reg.excl_missing_data)[0]

            except Exception:  # pylint: disable=broad-exception-caught
                return False

        if lp_data is None:
            return False

        has_seq_align = False

        sa_name = f'nmr_poly_seq_vs_{content_subtype}'

        if has_key_value(seq_align_dic, sa_name):

            for seq_align in seq_align_dic[sa_name]:

                if seq_align['list_id'] == list_id:
                    has_seq_align = True
                    break

        if not has_seq_align and content_subtype != 'poly_seq':
            return False

        auth_to_star_seq = self.__reg.caC['auth_to_star_seq']
        auth_to_label_seq = self.__reg.caC['auth_to_label_seq']
        auth_to_orig_seq = self.__reg.caC['auth_to_orig_seq']
        label_to_auth_seq = self.__reg.caC['label_to_auth_seq']
        coord_atom_site = self.__reg.caC['coord_atom_site']
        coord_unobs_res = self.__reg.caC['coord_unobs_res']
        coord_unobs_atom = self.__reg.caC['coord_unobs_atom'] if 'coord_unobs_atom' in self.__reg.caC else {}

        if auth_to_star_seq is None:
            return False

        item_names = []

        if content_subtype == 'chem_shift':
            max_dim = 2

            item_names.append(ITEM_NAMES_IN_CS_LOOP[file_type])

        else:

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

            elif content_subtype == 'dihed_restraint':
                max_dim = 5

            elif content_subtype == 'spectral_peak':

                try:

                    _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
                    num_dim = int(_num_dim)

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

                except ValueError:  # raised error already at testIndexConsistency()
                    return False

                max_dim = num_dim + 1

            else:
                return False

            for j in range(1, max_dim):
                _item_names = {}
                for k, v in ITEM_NAMES_IN_PK_LOOP[file_type].items():
                    if '%s' in v:
                        v = v % j
                    _item_names[k] = v
                item_names.append(_item_names)

        num_dim = max_dim - 1

        chain_id_names, seq_id_names, comp_id_names, atom_id_names = [], [], [], []
        if file_type == 'nmr-star':
            alt_seq_id_names = []

        for j in range(num_dim):
            chain_id_names.append(item_names[j]['chain_id'])
            seq_id_names.append(item_names[j]['seq_id'])
            comp_id_names.append(item_names[j]['comp_id'])
            atom_id_names.append(item_names[j]['atom_id'])
            if file_type == 'nmr-star':
                alt_seq_id_names.append(item_names[j]['alt_seq_id'])

        details_col = -1

        if file_type == 'nmr-star':

            loop = sf if self.__reg.star_data_type[file_list_id] == 'Loop' else sf.get_loop(lp_category)

            if 'Details' in loop.tags:
                details_col = loop.tags.index('Details')

        def get_coord_atom_site_of(chain_id, seq_id, comp_id):

            if (chain_id, seq_id, comp_id) in auth_to_star_seq:
                seq_key = (chain_id, seq_id)

                if seq_key in coord_unobs_res:  # DAOTHER-7665
                    return True, None, None

                if seq_key not in coord_atom_site:
                    return True, None, None

                # 2lit: 1:104:LYS (nmr), A:99:LYS (model) overlaps A:104:HEC (model)
                # if seq_key not in auth_to_label_seq:
                #     return True, None, None

                coord_atom_site_ = coord_atom_site[seq_key]

                cif_comp_id = coord_atom_site_['comp_id']

                if comp_id == cif_comp_id:
                    return True, seq_key, coord_atom_site_

                _seq_key = (chain_id, seq_id, comp_id)

                if _seq_key in auth_to_orig_seq:
                    _seq_key_ = auth_to_orig_seq[_seq_key]

                    seq_key = (chain_id, _seq_key_[0])

                    if seq_key in coord_atom_site:
                        _coord_atom_site_ = coord_atom_site[seq_key]

                        if _coord_atom_site_['comp_id'] == comp_id:
                            return True, seq_key, _coord_atom_site_

            if (chain_id, seq_id) in label_to_auth_seq:
                _chain_id, _seq_id = label_to_auth_seq[(chain_id, seq_id)]

                if (_chain_id, _seq_id, comp_id) in auth_to_star_seq:
                    seq_key = (_chain_id, _seq_id)

                    if seq_key in coord_unobs_res:  # DAOTHER-7665
                        return True, None, None

                    if seq_key not in coord_atom_site:
                        return True, None, None

                    # 2lit: 1:104:LYS (nmr), A:99:LYS (model) overlaps A:104:HEC (model)
                    # if seq_key not in auth_to_label_seq:
                    #     return True, None, None

                    coord_atom_site_ = coord_atom_site[seq_key]

                    cif_comp_id = coord_atom_site_['comp_id']

                    if comp_id == cif_comp_id:
                        return True, seq_key, coord_atom_site_

            return False, None, None

        offset = {}

        for idx, row in enumerate(lp_data):

            for j in range(num_dim):
                try:
                    chain_id = row[chain_id_names[j]]
                    seq_id = alt_seq_id = row[seq_id_names[j]]
                    comp_id = row[comp_id_names[j]]
                    atom_id = row[atom_id_names[j]]
                    if file_type == 'nmr-star' and alt_seq_id_names[j] in row:
                        alt_seq_id = row[alt_seq_id_names[j]]
                except KeyError:
                    continue

                if content_subtype.startswith('spectral_peak')\
                   and (chain_id in EMPTY_VALUE or seq_id in EMPTY_VALUE or comp_id in EMPTY_VALUE or atom_id in 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_auth_seq_id' if 'test_auth_seq_id' in ca else 'test_seq_id'])
                                   if ref_seq_id == seq_id), None)

                if cif_seq_id is None and ca['sequence_coverage'] >= LOW_SEQ_COVERAGE:
                    continue

                cif_ps = next(ps for ps in cif_poly_seq if ps['chain_id'] == cif_chain_id)

                if 'auth_chain_id' in cif_ps:
                    cif_chain_id = cif_ps['auth_chain_id']

                if ca['sequence_coverage'] < LOW_SEQ_COVERAGE:  # DAOTHER-8751, issue #2

                    if 'auth_seq_id' in cif_ps:
                        cif_seq_id, cif_comp_id = next(((_seq_id, _comp_id) for _auth_seq_id, _seq_id, _comp_id
                                                        in zip(cif_ps['auth_seq_id'], cif_ps['seq_id'], cif_ps['comp_id'])
                                                        if _auth_seq_id == seq_id), (None, None))
                    else:
                        cif_seq_id, cif_comp_id = next(((_seq_id, _comp_id) for _seq_id, _comp_id
                                                        in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                                        if _seq_id == seq_id), (None, None))

                    if None in (cif_seq_id, cif_comp_id):
                        continue

                else:

                    cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                        in zip(cif_ps['auth_seq_id' if 'auth_seq_id' in cif_ps else 'seq_id'], cif_ps['comp_id'])
                                        if _seq_id == cif_seq_id), None)

                    if cif_comp_id is None:
                        continue

                    if cif_comp_id != comp_id and seq_id != cif_seq_id:
                        cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                            in zip(cif_ps['auth_seq_id' if 'auth_seq_id' in cif_ps else 'seq_id'],
                                                   cif_ps['comp_id'])
                                            if _seq_id == seq_id), None)

                        if cif_comp_id is None:
                            continue

                        if cif_comp_id == comp_id:
                            cif_seq_id = seq_id

                if ca['sequence_coverage'] < LOW_SEQ_COVERAGE:

                    if 'auth_seq_id' in cif_ps:
                        cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                            in zip(cif_ps['auth_seq_id'], cif_ps['comp_id'])
                                            if _seq_id == seq_id), None)
                    else:
                        cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                            in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                            if _seq_id == seq_id), None)

                    if cif_comp_id is None:
                        continue

                if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                    _atom_id, _, details = self.__getAtomIdListWithAmbigCode(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id
                        atom_name = atom_id

                        if details is not None:
                            atom_name += f", where {details.rstrip('.')}"

                    else:
                        atom_name = f'{atom_id} (e.g. '

                        for atom_id_ in _atom_id:
                            atom_name += f'{atom_id_} '

                        atom_name = f'{atom_name.rstrip()})'

                        # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id
                    atom_name = atom_id

                if len(auth_to_star_seq) == 0:
                    continue

                found, seq_key, coord_atom_site_ = get_coord_atom_site_of(cif_chain_id, cif_seq_id, comp_id)

                if found:

                    if seq_key is None:
                        continue

                    if seq_key in auth_to_label_seq:

                        offset[chain_id] = seq_key[1] - seq_id

                        cif_chain_id, cif_seq_id = auth_to_label_seq[seq_key]
                        cif_comp_id = comp_id

                        if seq_key in coord_unobs_res:  # DAOTHER-7665
                            continue

                else:

                    if chain_id in offset:
                        _, seq_key, coord_atom_site_ = get_coord_atom_site_of(cif_chain_id, cif_seq_id + offset[chain_id], comp_id)

                        if seq_key is not None and seq_key in coord_unobs_res:
                            continue

                    elif seq_key is not None:
                        seq_key = (cif_chain_id, cif_seq_id)

                        if seq_key in coord_unobs_res:  # DAOTHER-7665
                            continue

                        coord_atom_site_ = coord_atom_site.get(seq_key)

                    else:

                        for _offset in range(1, PERIPH_OFFSET_ATTEMPT):
                            if (cif_chain_id, cif_seq_id + _offset) in label_to_auth_seq:
                                _, _cif_seq_id = label_to_auth_seq[(cif_chain_id, cif_seq_id + _offset)]
                                if (cif_chain_id, _cif_seq_id) in auth_to_label_seq:
                                    cif_seq_id = _cif_seq_id - _offset

                                    seq_key = (cif_chain_id, cif_seq_id)
                                    break

                        if seq_key is not None and seq_key in coord_unobs_res:  # DAOTHER-7665
                            continue

                    if file_type == 'nmr-star' and seq_id != alt_seq_id:

                        if coord_atom_site_ is None or coord_atom_site_['comp_id'] != cif_comp_id\
                           or (atom_id_ not in coord_atom_site_['atom_id']
                               and (('auth_atom_id' in coord_atom_site_ and atom_id_ not in coord_atom_site_['auth_atom_id'])
                                    or 'auth_atom_id' not in coord_atom_site_)):

                            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                                               in zip(ca['ref_seq_id'], ca['test_auth_seq_id'])
                                               if ref_seq_id == alt_seq_id), None)

                            if cif_seq_id is None:
                                continue

                            cif_ps = next(ps for ps in cif_poly_seq if ps['chain_id'] == cif_chain_id)

                            cif_comp_id = next((_comp_id for _seq_id, _comp_id
                                                in zip(cif_ps['seq_id'], cif_ps['comp_id'])
                                                if _seq_id == cif_seq_id), None)

                            if cif_comp_id is None:
                                continue

                            seq_key = (cif_chain_id, cif_seq_id)

                            if seq_key in coord_unobs_res:  # DAOTHER-7665
                                continue

                            coord_atom_site_ = coord_atom_site.get(seq_key)

                if coord_atom_site_ is None and file_type == 'nmr-star':

                    if max_dim == 2:
                        auth_asym_id_name = 'Auth_asym_ID'
                        auth_seq_id_name = 'Auth_seq_ID'
                    else:
                        auth_asym_id_name = f'Auth_asym_ID_{j + 1}'
                        auth_seq_id_name = f'Auth_seq_ID_{j + 1}'

                    if auth_asym_id_name in row and auth_seq_id_name in row\
                       and row[auth_asym_id_name] not in EMPTY_VALUE\
                       and row[auth_seq_id_name] not in EMPTY_VALUE\
                       and (isinstance(row[auth_seq_id_name], int) or row[auth_seq_id_name].isdigit()):
                        cif_chain_id = row[auth_asym_id_name]
                        cif_seq_id = row[auth_seq_id_name]
                        if isinstance(cif_seq_id, str):
                            cif_seq_id = int(cif_seq_id)

                        _, seq_key, coord_atom_site_ = get_coord_atom_site_of(cif_chain_id, cif_seq_id, comp_id)

                        if coord_atom_site_ is not None:
                            cif_comp_id = coord_atom_site_['comp_id']

                if coord_atom_site_ is None or coord_atom_site_['comp_id'] != cif_comp_id\
                   or (atom_id_ not in coord_atom_site_['atom_id']
                       and (('auth_atom_id' in coord_atom_site_ and atom_id_ not in coord_atom_site_['auth_atom_id'])
                            or 'auth_atom_id' not in coord_atom_site_)):

                    idx_msg = ''
                    if index_tag is not None and index_tag in row:
                        idx_msg = f"[Check row of {index_tag} {row[index_tag]}] "

                    err = f"{idx_msg}Atom ("\
                        + self.getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                      comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                        + ") is not present in the coordinates."

                    cyclic = self.isCyclicPolymer(ref_chain_id)

                    if self.__reg.nonblk_bad_nterm\
                       and (seq_id == 1 or cif_seq_id == 1 or ((seq_key[0], seq_key[1] - 1) if seq_key is not None
                                                               else (cif_chain_id, cif_seq_id - 1)) in coord_unobs_res)\
                       and atom_id_ in AMINO_PROTON_CODE\
                       and (cyclic or comp_id == 'PRO'
                            or (atom_id_ in PROTON_BEGIN_CODE
                                or (coord_atom_site_ is not None and 'auth_atom_id' not in coord_atom_site_))):  # DAOTHER-7665

                        err += " However, it is acceptable if corresponding atom name, H1, is given during biocuration "

                        if cyclic:
                            err += "because of a cyclic-peptide."
                        elif comp_id == 'PRO':
                            err += "because polymer sequence starts with the Proline residue."
                        else:  # DAOTHER-7665
                            err += "because polymer sequence starts with the residue in the coordinates."

                        self.__reg.report.warning.appendDescription('auth_atom_nomenclature_mismatch',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                     'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                 f"++ Warning  - {err}\n")

                        if cyclic and self.__reg.bmrb_only and self.__reg.leave_intl_note\
                           and file_type == 'nmr-star' and seq_id == 1 and details_col != -1:
                            _details = loop.data[idx][details_col]
                            details = f"{chain_id}:{seq_id}:{comp_id}:{atom_name} is not present in the coordinates. "\
                                "However, it is acceptable if an appropriate atom name, H1, is given because of a cyclic-peptide.\n"
                            if _details in EMPTY_VALUE or (details not in _details):
                                if _details in EMPTY_VALUE:
                                    loop.data[idx][details_col] = details
                                else:
                                    loop.data[idx][details_col] += ('' if '\n' in _details else '\n') + details
                                modified = True

                    elif self.__reg.nonblk_bad_nterm\
                            and (seq_id == 1 or cif_seq_id == 1
                                 or ((seq_key[0], seq_key[1] - 1)
                                     if seq_key is not None else (cif_chain_id, cif_seq_id - 1)) in coord_unobs_res)\
                            and atom_id_ == 'P':
                        continue

                    elif ca['conflict'] == 0:  # no conflict in sequenc alignment

                        if comp_id in STD_MON_DICT:

                            checked = coord_issue = False
                            if atom_id_[0] in PROTON_BEGIN_CODE:
                                self.__reg.ccU.updateChemCompDict(comp_id)
                                cca = next((cca for cca in self.__reg.ccU.lastAtomDictList if cca['atom_id'] == atom_id_), None)
                                bonded_to = self.__reg.ccU.getBondedAtoms(comp_id, atom_id_)
                                peptide_like = self.__reg.csStat.peptideLike(comp_id)
                                if cca is not None and len(bonded_to) > 0:
                                    if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                       and (cca['leaving_atom_flag'] != 'Y'
                                            or (peptide_like
                                                and cca['n_terminal_atom_flag'] == 'N'
                                                and cca['c_terminal_atom_flag'] == 'N')):
                                        checked = True
                                        err = f"{idx_msg}Atom ("\
                                            + self.getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                          comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                                            + ") is not properly instantiated in the coordinates. Please re-upload the model file."

                            if (self.__reg.remediation_mode or self.__reg.combined_mode) and checked:
                                continue

                            if not checked and err.endswith("not present in the coordinates."):

                                if atom_id_[0] in PROTON_BEGIN_CODE:
                                    bonded_to = self.__reg.ccU.getBondedAtoms(comp_id, atom_id_)
                                    if len(bonded_to) > 0 and coord_atom_site_ is not None\
                                       and bonded_to[0] not in coord_atom_site_['atom_id']:
                                        err += " Additionally, the attached atom ("\
                                            + self.getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                          comp_id_names[j], comp_id,
                                                                          atom_id_names[j], bonded_to[0])\
                                            + ") is not instantiated in the coordinates. Please re-upload the model file."
                                        coord_issue = True

                                elif 'coord_unobs_atom' in self.__reg.caC:
                                    if seq_key in coord_unobs_atom and atom_id_ in coord_unobs_atom[seq_key]['atom_ids']:
                                        coord_issue = True

                            _atom_id, _, _ = self.__getAtomIdListWithAmbigCode(comp_id, f'{atom_id_}%')

                            if content_subtype.startswith('spectral_peak')\
                               or (len(_atom_id) > 0 and coord_atom_site_ is not None
                                   and _atom_id[0] in coord_atom_site_['atom_id']):

                                if len(_atom_id) > 0 and coord_atom_site_ is not None\
                                   and _atom_id[0] in coord_atom_site_['atom_id']:
                                    item = 'atom_nomenclature_mismatch'
                                elif content_subtype.startswith('spectral_peak'):
                                    item = 'hydrogen_not_instantiated' if checked\
                                        else 'coordinate_issue' if coord_issue else 'assigned_peak_atom_not_found'
                                else:
                                    item = 'hydrogen_not_instantiated' if checked\
                                        else 'coordinate_issue' if coord_issue else 'atom_nomenclature_mismatch'

                                self.__reg.report.warning.appendDescription(item,
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                         f"++ Warning  - {err}\n")

                            else:

                                item = 'hydrogen_not_instantiated' if checked\
                                    else 'coordinate_issue' if coord_issue else 'atom_not_found'

                                if self.__reg.internal_mode and item in ('hydrogen_not_instantiated', 'coordinate_issue'):

                                    self.__reg.report.warning.appendDescription(item,
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                             f"++ Warning  - {err}\n")

                                else:

                                    if item == 'atom_not_found' and self.__reg.internal_mode\
                                       and file_type == 'nmr-star' and details_col != -1:
                                        _details = loop.data[idx][details_col]
                                        if _details == 'UNMAPPED':
                                            continue

                                    if item == 'atom_not_found' and self.__reg.op == 'nmr-str-replace-cs' and file_list_id > 0:
                                        item = 'atom_nomenclature_mismatch'

                                        self.__reg.report.warning.appendDescription(item,
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                                 f"++ Warning  - {err}\n")

                                        continue

                                    self.__reg.report.error.appendDescription(item,
                                                                              {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                               'category': lp_category, 'description': err})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                             f"++ Error  - {err}\n")

                        else:

                            if self.__reg.combined_mode and self.__reg.remediation_mode\
                               and self.__reg.ccU.updateChemCompDict(comp_id):
                                cca = next((cca for cca in self.__reg.ccU.lastAtomDictList if cca['atom_id'] == atom_id_), None)
                                bonded_to = self.__reg.ccU.getBondedAtoms(comp_id, atom_id_)
                                peptide_like = self.__reg.csStat.peptideLike(comp_id)
                                if cca is not None and len(bonded_to) > 0:
                                    if coord_atom_site_ is not None and bonded_to[0] in coord_atom_site_['atom_id']\
                                       and (cca['leaving_atom_flag'] != 'Y'
                                            or (peptide_like
                                                and cca['n_terminal_atom_flag'] == 'N'
                                                and cca['c_terminal_atom_flag'] == 'N')):
                                        err = f"{idx_msg}Atom ("\
                                            + self.getReducedAtomNotation(chain_id_names[j], chain_id, seq_id_names[j], seq_id,
                                                                          comp_id_names[j], comp_id, atom_id_names[j], atom_name)\
                                            + ") is not properly instantiated in the coordinates. Please re-upload the model file."

                                        self.__reg.report.warning.appendDescription('hydrogen_not_instantiated',
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'category': lp_category, 'description': err})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                                 f"++ Warning  - {err}\n")

                                        continue

                            self.__reg.report.warning.appendDescription('atom_nomenclature_mismatch',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testCoordAtomIdConsistency() "
                                                     f"++ Warning  - {err}\n")

        return modified

    def testDataConsistencyInAuxLoopOfSpectralPeak(self, file_name: str, file_type: str, sf_framecode: str,
                                                   num_dim: int, lp_category: str, aux_data: List[List[dict]]) -> None:
        """ 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.__reg.report.error.appendDescription('missing_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': err})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                         f"++ Error  - {err}\n")

            try:

                min_points = [None] * num_dim
                max_points = [None] * num_dim
                min_limits = [None] * num_dim
                max_limits = [None] * num_dim
                abs_positions = [None] * num_dim

                first_point_in_hz = True
                for i in range(1, max_dim):

                    for sp_dim in aux_data:

                        if file_type == 'nef':

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

                            first_point = sp_dim.get('value_first_point')
                            sp_width = sp_dim.get('spectral_width')
                            sp_freq = sp_dim.get('spectrometer_frequency')

                            if 'axis_unit' in sp_dim and sp_dim['axis_unit'] == 'Hz'\
                               and None not in (sp_freq, first_point, sp_width):
                                if first_point / sp_freq - sp_width / sp_freq < -1.0:
                                    first_point_in_hz = False
                                    break

                        else:

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

                            first_point = sp_dim.get('Value_first_point')
                            sp_width = sp_dim.get('Sweep_width')
                            sp_freq = sp_dim.get('Spectrometer_frequency')

                            if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                               and None not in (sp_freq, first_point, sp_width):
                                if first_point / sp_freq - sp_width / sp_freq < -1.0:
                                    first_point_in_hz = False
                                    break

                for i in range(1, max_dim):

                    for sp_dim in aux_data:

                        if file_type == 'nef':

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

                            first_point = sp_dim.get('value_first_point')
                            sp_width = sp_dim.get('spectral_width')
                            sp_freq = sp_dim.get('spectrometer_frequency')
                            abs_positions[i - 1] = False if 'absolute_peak_positions' not in sp_dim\
                                else sp_dim['absolute_peak_positions']

                            if 'axis_unit' in sp_dim and sp_dim['axis_unit'] == 'Hz'\
                               and None not in (sp_freq, first_point, sp_width):
                                if first_point_in_hz:
                                    first_point /= sp_freq
                                sp_width /= sp_freq

                        else:

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

                            first_point = sp_dim.get('Value_first_point')
                            sp_width = sp_dim.get('Sweep_width')
                            sp_freq = sp_dim.get('Spectrometer_frequency')
                            abs_positions[i - 1] = False if 'Absolute_peak_positions' not in sp_dim\
                                else sp_dim['Absolute_peak_positions']

                            if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                               and None not in (sp_freq, first_point, sp_width):
                                if first_point_in_hz:
                                    first_point /= sp_freq
                                sp_width /= sp_freq

                        min_point = max_point = min_limit = max_limit = None

                        if None not in (first_point, sp_width):

                            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_positions are true
                            tolerance = (sp_width * (1.0 if self.__reg.bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)
                            min_point = last_point - tolerance
                            max_point = first_point + tolerance

                            min_limit = min_point
                            max_limit = max_point

                            if None not in (sp_freq, min_point, max_point):
                                center_point = (max_point - min_point) / 2.0
                                min_limit = center_point - HARD_PROBE_LIMIT / 2.0 / sp_freq
                                max_limit = center_point + 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 PK_KEY_ITEMS[file_type]:
                        if k['type'] == 'float':  # position
                            _k = copy.copy(k)
                            if '%s' in k['name']:
                                _k['name'] = k['name'] % dim
                            key_items.append(_k)

                position_names = [k['name'] for k in key_items]
                id_tag = CONSIST_ID_TAGS[file_type][content_subtype]

                lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                                if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)

                if lp_data is not None:

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

                            if None in (min_points[j], max_points[j]):
                                continue

                            position = row[position_names[j]]

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

                                err = f"[Check row of {id_tag} {row[id_tag]}] {position_names[j]} {position} "\
                                    "is not within expected range "\
                                    f"(min_position {min_points[j]}, max_position {max_points[j]}, "\
                                    f"absolute_peak_positions {abs_positions[j]}). "\
                                    "Please check for reference frequency and spectral width."

                                self.__reg.report.warning.appendDescription('anomalous_data',
                                                                            {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                             'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                                         f"++ Warning  - {err}\n")

                            if None in (min_limits[j], max_limits[j]):
                                continue

                            if position < min_limits[j] or position > max_limits[j]:

                                err = f"[Check row of {id_tag} {row[id_tag]}] {position_names[j]} {position} "\
                                    "is not within expected range "\
                                    f"(min_position {min_limits[j]}, max_position {max_limits[j]}, "\
                                    f"absolute_peak_positions {abs_positions[j]}), "\
                                    f"which exceeds limit of current probe design ({HARD_PROBE_LIMIT / 1000.0} kHz). "\
                                    "Please check for reference frequency and spectral width."

                                self.__reg.report.error.appendDescription('invalid_data',
                                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                           'category': lp_category, 'description': err})

                                if self.__reg.verbose:
                                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                                         f"++ ValueError  - {err}\n")

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                         f"++ Error  - {str(e)}\n")

        if (file_type == 'nef' and lp_category == '_nef_spectrum_dimension_transfer')\
           or (file_type == 'nmr-star' and lp_category == '_Spectral_dim_transfer'):

            for row in aux_data:
                for name in [key['name'] for key in self.__reg.aux_key_items[file_type][content_subtype][lp_category]]:
                    if row[name] not in range(1, max_dim):

                        err = f"{name} {row[name]!r} must be one of {range(1, max_dim)}."

                        self.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeak() "
                                                 f"++ ValueError  - {err}\n")

    def testDataConsistencyInAuxLoopOfSpectralPeakAlt(self, file_name: str, file_type: str, sf_framecode: str,
                                                      num_dim: int, lp_category: str, aux_data: List[List[dict]],
                                                      sf: pynmrstar.Saveframe,
                                                      parent_pointer: int) -> None:
        """ 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.__reg.report.error.appendDescription('missing_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': err})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                         f"++ Error  - {err}\n")

            try:

                min_points = [None] * num_dim
                max_points = [None] * num_dim
                min_limits = [None] * num_dim
                max_limits = [None] * num_dim
                abs_positions = [None] * num_dim

                first_point_in_hz = True
                for i in range(1, max_dim):

                    for sp_dim in aux_data:

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

                        first_point = sp_dim.get('Value_first_point')
                        sp_width = sp_dim.get('Sweep_width')
                        sp_freq = sp_dim.get('Spectrometer_frequency')

                        if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                           and None not in (sp_freq, first_point, sp_width):
                            if first_point / sp_freq - sp_width / sp_freq < -1.0:
                                first_point_in_hz = False
                                break

                for i in range(1, max_dim):

                    for sp_dim in aux_data:

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

                        first_point = sp_dim.get('Value_first_point')
                        sp_width = sp_dim.get('Sweep_width')
                        sp_freq = sp_dim.get('Spectrometer_frequency')
                        abs_positions[i - 1] = False if 'Absolute_peak_positions' not in sp_dim\
                            else sp_dim['Absolute_peak_positions']

                        if 'Sweep_width_units' in sp_dim and sp_dim['Sweep_width_units'] == 'Hz'\
                           and None not in (sp_freq, first_point, sp_width):
                            if first_point_in_hz:
                                first_point /= sp_freq
                            sp_width /= sp_freq

                        min_point = max_point = min_limit = max_limit = None

                        if None not in (first_point, sp_width):

                            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_positions are true
                            torelance = (sp_width * (1.0 if self.__reg.bmrb_only else 3.0) if abs_positions[i - 1] else 0.0)
                            min_point = last_point - torelance
                            max_point = first_point + torelance

                            min_limit = min_point
                            max_limit = max_point

                            if None not in (sp_freq, min_point, max_point):
                                center_point = (max_point - min_point) / 2.0
                                min_limit = center_point - HARD_PROBE_LIMIT / 2.0 / sp_freq
                                max_limit = center_point + HARD_PROBE_LIMIT / 2.0 / sp_freq

                        if min_point is not None:
                            min_points[i - 1] = float(f"{min_point:.7f}")
                        if max_point is not None:
                            max_points[i - 1] = float(f"{max_point:.7f}")

                        if min_limit is not None:
                            min_limits[i - 1] = float(f"{min_limit:.7f}")
                        if max_limit is not None:
                            max_limits[i - 1] = float(f"{max_limit:.7f}")

                        break

                _pk_char_category = '_Peak_char'

                _pk_char_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                                      if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                                      and lp['category'] == _pk_char_category), None)

                if _pk_char_data is None and any(True for loop in sf.loops if loop.category == _pk_char_category):

                    key_items = self.__reg.aux_key_items[file_type][content_subtype][_pk_char_category]
                    data_items = self.__reg.aux_data_items[file_type][content_subtype][_pk_char_category]
                    allowed_tags = AUX_ALLOWED_TAGS[file_type][content_subtype][_pk_char_category]

                    _pk_char_data = self.__reg.nefT.check_data(sf, _pk_char_category, key_items, data_items,
                                                               allowed_tags, None, parent_pointer=parent_pointer,
                                                               enforce_allowed_tags=(file_type == 'nmr-star'),
                                                               excl_missing_data=self.__reg.excl_missing_data)[0]

                pk_id_name = 'Peak_ID'
                dim_id_name = 'Spectral_dim_ID'
                position_name = 'Chem_shift_val'

                if _pk_char_data is not None:

                    for row in _pk_char_data:

                        j = row[dim_id_name] - 1

                        if j >= num_dim or None in (min_points[j], max_points[j]):
                            continue

                        position = row[position_name]

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

                            warn = f"[Check row of {pk_id_name} {row[pk_id_name]}] {position_name} {position} "\
                                "is not within expected range "\
                                f"(min_position {min_points[j]}, max_position {max_points[j]}, "\
                                f"absolute_peak_positions {abs_positions[j]}). "\
                                "Please check for reference frequency and spectral width."

                            self.__reg.report.warning.appendDescription('anomalous_data',
                                                                        {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                         'category': lp_category, 'description': warn})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                                     f"++ Warning  - {warn}\n")

                        if None in (min_limits[j], max_limits[j]):
                            continue

                        if position < min_limits[j] or position > max_limits[j]:

                            err = f"[Check row of {pk_id_name} {row[pk_id_name]}] {position_name} {position} "\
                                "is not within expected range "\
                                f"(min_position {min_limits[j]}, max_position {max_limits[j]}, "\
                                f"absolute_peak_positions {abs_positions[j]}), "\
                                f"which exceeds limit of current probe design ({HARD_PROBE_LIMIT / 1000.0} kHz). "\
                                "Please check for reference frequency and spectral width."

                            self.__reg.report.error.appendDescription('invalid_data',
                                                                      {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                       'category': lp_category, 'description': err})

                            if self.__reg.verbose:
                                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                                     f"++ ValueError  - {err}\n")

            except LookupError as e:

                item = 'format_issue' if 'Unauthorized' in str(e) else 'missing_mandatory_item'

                self.__reg.report.error.appendDescription(item,
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': str(e).strip("'")})

                self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                     f"++ LookupError  - {file_name} {sf_framecode} {lp_category} {str(e)}\n")

            except ValueError as e:

                self.__reg.report.error.appendDescription('invalid_data',
                                                          {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                           'category': lp_category, 'description': str(e).strip("'")})

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                         f"++ ValueError  - {str(e)}\n")

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                         f"++ Error  - {str(e)}\n")

        if lp_category == '_Spectral_dim_transfer':

            for row in aux_data:
                for name in [key['name'] for key in self.__reg.aux_key_items[file_type][content_subtype][lp_category]]:
                    if row[name] not in range(1, max_dim):

                        err = f"{name} {row[name]!r} must be one of {range(1, max_dim)}."

                        self.__reg.report.error.appendDescription('invalid_data',
                                                                  {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                   'category': lp_category, 'description': err})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.testDataConsistencyInAuxLoopOfSpectralPeakAlt() "
                                                 f"++ ValueError  - {err}\n")

    def __hasCoordSeq(self, nmr_chain_id: str, nmr_seq_id: str) -> bool:
        """ Return whether a given sequence is in the coordinates.
            @return: True for corresponding sequence in the coordinates exist, False otherwise
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return False

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return False

        result = next((seq_align for seq_align in seq_align_dic['nmr_poly_seq_vs_model_poly_seq']
                       if seq_align['ref_chain_id'] == nmr_chain_id and seq_align['test_chain_id'] == cif_chain_id), None)

        if result is not None:

            cif_seq_id = next((test_seq_id for ref_seq_id, test_seq_id
                               in zip(result['ref_seq_id'], result['test_seq_id'])
                               if ref_seq_id == nmr_seq_id), None)

            return cif_seq_id is not None

        return False

    def __extractCoordStructConf(self, nmr_chain_id: str, nmr_seq_ids: List[int]
                                 ) -> List[Optional[str]]:
        """ Extract conformational annotations of coordinate file.
        """

        if nmr_chain_id in self.__reg.nmr_struct_conf:
            return self.__reg.nmr_struct_conf[nmr_chain_id]

        nmr_struct_conf = [None] * len(nmr_seq_ids)

        cif_ps = self.__reg.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.__reg.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.__reg.nmr_struct_conf[nmr_chain_id] = nmr_struct_conf

        return nmr_struct_conf

    def __getCoordCompId(self, nmr_chain_id: str, nmr_seq_id: int
                         ) -> Optional[str]:
        """ Return comp ID of coordinate file for a given NMR sequence.
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return None

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.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 __getTautomerOfHistidine(self, nmr_chain_id: str, nmr_seq_id: int) -> str:
        """ Return tautomeric state of a given histidine.
            @return: One of 'biprotonated', 'tau-tautomer', 'pi-tautomer', 'unknown'
        """

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return 'unknown'

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.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.__reg.cpC['tautomer']:
            return self.__reg.cpC['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.__reg.cpC['tautomer'][seq_key] = 'unknown'
                return 'unknown'

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                protons = self.__reg.cR.getDictListWithFilter('atom_site',
                                                              [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                                'alt_name': 'atom_id'}
                                                               ],
                                                              [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                               {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                               {'name': 'label_comp_id', 'type': 'str', 'value': 'HIS'},
                                                               {'name': 'type_symbol', 'type': 'str', 'value': 'H'},
                                                               {'name': model_num_name, 'type': 'int',
                                                                'value': self.__reg.representative_model_id},
                                                               {'name': 'label_alt_id', 'type': 'enum',
                                                                'enum': (self.__reg.representative_alt_id,)}
                                                               ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getTautomerOfHistidine() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__getTautomerOfHistidine() ++ Error  - {str(e)}\n")

                return 'unknown'

            if len(protons) > 0:

                has_hd1 = 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.__reg.cpC['tautomer'][seq_key] = 'biprotonated'
                    return 'biprotonated'

                if has_hd1:
                    self.__reg.cpC['tautomer'][seq_key] = 'pi-tautomer'
                    return 'pi-tautomer'

                if has_he2:
                    self.__reg.cpC['tautomer'][seq_key] = 'tau-tautomer'
                    return 'tau-tautomer'

        self.__reg.cpC['tautomer'][seq_key] = 'unknown'
        return 'unknown'

    def __getRotamerOfValine(self, nmr_chain_id: str, nmr_seq_id: int
                             ) -> List[dict]:
        """ Return rotameric state distribution of a given valine.
            @return: One of 'gauche+', 'trans', 'gauche-', 'unknown'
        """

        none = [{'name': 'chi1', 'unknown': 1.0}]

        cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'VAL')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'VAL'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getRotamerOfValine() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']

            _rot1 = rot1.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1]
            return [rot1]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def __getRotamerOfLeucine(self, nmr_chain_id: str, nmr_seq_id: int
                              ) -> List[dict]:
        """ 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.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'LEU')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'LEU'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getRotamerOfLeucine() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def __getRotamerOfIsoleucine(self, nmr_chain_id: str, nmr_seq_id: int
                                 ) -> List[dict]:
        """ 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.__reg.report.getModelPolymerSequenceWithNmrChainId(nmr_chain_id)

        if cif_ps is None:
            return none

        cif_chain_id = cif_ps['chain_id']

        seq_align_dic = self.__reg.report.sequence_alignment.get()

        if not has_key_value(seq_align_dic, 'nmr_poly_seq_vs_model_poly_seq'):
            return none

        seq_key = (nmr_chain_id, nmr_seq_id, 'ILE')

        if seq_key in self.__reg.cpC['rotamer']:
            return self.__reg.cpC['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.__reg.cpC['rotamer'][seq_key] = none
                return none

            try:

                model_num_name = 'pdbx_PDB_model_num' if 'pdbx_PDB_model_num' in self.__reg.coord_atom_site_tags else 'ndb_model'

                atoms = self.__reg.cR.getDictListWithFilter('atom_site',
                                                            [{'name': 'label_atom_id', 'type': 'starts-with-alnum',
                                                              'alt_name': 'atom_id'},
                                                             {'name': 'Cartn_x', 'type': 'float', 'alt_name': 'x'},
                                                             {'name': 'Cartn_y', 'type': 'float', 'alt_name': 'y'},
                                                             {'name': 'Cartn_z', 'type': 'float', 'alt_name': 'z'},
                                                             {'name': model_num_name, 'type': 'int', 'alt_name': 'model_id'}
                                                             ],
                                                            [{'name': 'label_asym_id', 'type': 'str', 'value': cif_chain_id},
                                                             {'name': 'label_seq_id', 'type': 'int', 'value': cif_seq_id},
                                                             {'name': 'label_comp_id', 'type': 'str', 'value': 'ILE'},
                                                             {'name': 'label_alt_id', 'type': 'enum',
                                                              'enum': (self.__reg.representative_alt_id,)}
                                                             ])

            except Exception as e:  # pylint: disable=broad-exception-caught

                self.__reg.report.error.appendDescription('internal_error',
                                                          f"+{self.__class_name__}.__getRotamerOfIsoleucine() "
                                                          "++ Error  - " + str(e))

                if self.__reg.verbose:
                    self.__reg.log.write(f"+{self.__class_name__}.__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.__reg.cpC['rotamer'][seq_key] = none
                return none

            if rot1['unknown'] == 0.0:
                del rot1['unknown']
            if rot2['unknown'] == 0.0:
                del rot2['unknown']

            _rot1 = rot1.copy()
            _rot2 = rot2.copy()

            for k, v in _rot1.items():
                if k == 'name':
                    continue
                rot1[k] = float(f"{v/total_models:.3f}")

            for k, v in _rot2.items():
                if k == 'name':
                    continue
                rot2[k] = float(f"{v/total_models:.3f}")

            self.__reg.cpC['rotamer'][seq_key] = [rot1, rot2]
            return [rot1, rot2]

        self.__reg.cpC['rotamer'][seq_key] = none
        return none

    def calculateStatsOfExptlData(self, file_list_id: int, file_name: str, file_type: str, content_subtype: str,
                                  sf: Union[pynmrstar.Saveframe, pynmrstar.Loop],
                                  list_id: int, sf_framecode: str, lp_category: str, seq_align_dic: dict, asm: list) -> None:
        """ Calculate statistics of experimental data.
        """

        index_tag = INDEX_TAGS[file_type][content_subtype]

        _list_id = list_id
        if file_type == 'nmr-star' and self.__reg.combined_mode:
            val = get_first_sf_tag(sf, 'ID')
            if isinstance(val, int):
                _list_id = val
            elif len(val) > 0:
                try:
                    _list_id = int(val)
                except ValueError:
                    return

        if content_subtype != 'poly_seq':
            lp_data = next((lp['data'] for lp in self.__reg.lp_data[content_subtype]
                            if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode), None)
        else:
            lp_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                           if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                           and lp['category'] == lp_category), None)

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

            if content_subtype == 'spectral_peak':

                ent = {'list_id': _list_id, 'sf_framecode': sf_framecode, 'number_of_rows': 0}

                try:

                    _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
                    num_dim = int(_num_dim)

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

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

                self.__calculateStatsOfSpectralPeak(file_list_id, sf_framecode, num_dim, lp_data, ent)

                has_err = self.__reg.report.error.exists(file_name, sf_framecode)
                has_warn = self.__reg.report.warning.exists(file_name, sf_framecode)

                original_file_name = get_first_sf_tag(sf, 'Data_file_name')
                if len(original_file_name) > 0:
                    has_err |= self.__reg.report.error.exists(None, sf_framecode)
                    has_warn |= self.__reg.report.warning.exists(None, sf_framecode)

                if has_err:
                    status = 'Error'
                    ent['error_descriptions'] =\
                        self.__reg.report.error.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
                    if has_warn:
                        ent['warning_descriptions'] =\
                            self.__reg.report.warning.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
                elif has_warn:
                    status = 'Warning'
                    ent['warning_descriptions'] =\
                        self.__reg.report.warning.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
                else:
                    status = 'OK'

                ent['status'] = status

                asm.append(ent)

            return

        ambig = False

        if file_type == 'nmr-star' and self.__reg.star_data_type[0] == 'Entry':

            _sf_category = 'constraint_statistics'
            _lp_category = '_Constraint_file'

            try:

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

                if 'Block_ID' in tagNames:
                    block_id = get_first_sf_tag(sf, 'Block_ID')

                    _sf = self.__reg.star_data[0].get_saveframes_by_category(_sf_category)

                    _loop = _sf[0].get_loop(_lp_category)

                    _block_id_col = _loop.tags.index('Block_ID')
                    _constraint_type_col = _loop.tags.index('Constraint_type')
                    _constraint_subtype_col = _loop.tags.index('Constraint_subtype')
                    _constraint_subsubtype_col = _loop.tags.index('Constraint_subsubtype')

                    _row = next((_row for _row in _loop if _row[_block_id_col] == block_id), None)

                    if _row is not None:
                        _constraint_type = _row[_constraint_type_col]
                        _constraint_subtype = _row[_constraint_subtype_col]
                        _constraint_subsubtype = _row[_constraint_subsubtype_col]

                        if (_constraint_type == 'distance' and _constraint_subtype not in ('NOE', 'ROE'))\
                           or ('dihedral angle' in _constraint_type and _constraint_subtype == 'unknown'):
                            ambig = True

                        if _constraint_subsubtype not in EMPTY_VALUE and _constraint_subsubtype == 'ambi':
                            ambig = True

            except (IndexError, ValueError):
                pass

        sf_tag_data = next((t['data'] for t in self.__reg.sf_tag_data[content_subtype]
                            if t['file_name'] == file_name and t['sf_framecode'] == sf_framecode), None)

        ent = {'list_id': _list_id, 'sf_framecode': sf_framecode, 'number_of_rows': len(lp_data)}

        if content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint'):

            if len(sf_framecode) == 0:
                ent['exp_type'] = 'Unknown'
            else:
                ctype = get_first_sf_tag(sf, 'restraint_origin' if file_type == 'nef' else 'Constraint_type')
                if len(ctype) > 0:
                    ent['exp_type'] = ctype
                else:
                    ent['exp_type'] = 'Unknown'

        elif content_subtype.startswith('spectral_peak'):

            if len(sf_framecode) == 0:
                ent['exp_type'] = 'Unknown'
            else:
                ctype = get_first_sf_tag(sf, 'experiment_type' if file_type == 'nef' else 'Experiment_type')
                if len(ctype) > 0:
                    ent['exp_type'] = ctype
                else:
                    ent['exp_type'] = 'Unknown'

                if file_type == 'nmr-star':
                    exp_class = get_first_sf_tag(sf, 'Experiment_class')
                    if len(exp_class) > 0:
                        ent['exp_class'] = exp_class

        if content_subtype in ('chem_shift', 'dist_restraint', 'dihed_restraint', 'rdc_restraint',
                               'spectral_peak', 'spectral_peak_alt'):

            sa_name = f'nmr_poly_seq_vs_{content_subtype}'

            if has_key_value(seq_align_dic, sa_name):

                low_seq_coverage = ''

                seq_coverage = []

                for seq_align in seq_align_dic[sa_name]:

                    if seq_align['list_id'] == list_id:

                        sc = {}
                        sc['chain_id'] = seq_align['chain_id']
                        sc['length'] = seq_align['length']
                        sc['sequence_coverage'] = seq_align['sequence_coverage']

                        if seq_align['sequence_coverage'] < LOW_SEQ_COVERAGE and seq_align['length'] > 1 and not ambig:
                            if ('exp_type' not in ent)\
                               or (ent['exp_type'] not in ('disulfide bound', 'disulfide_bond',
                                                           'paramagnetic relaxation', 'pre', 'symmetry',
                                                           'J-couplings', 'jcoupling')):
                                low_seq_coverage += f"coverage {seq_align['sequence_coverage']} "\
                                    f"for chain_id {seq_align['chain_id']}, "\
                                    f"length {seq_align['length']}, "

                        seq_coverage.append(sc)

                if len(seq_coverage) > 0:

                    ent['sequence_coverage'] = seq_coverage

                    if len(low_seq_coverage) > 0 and not ambig:

                        warn = "Sequence coverage of NMR experimental data is relatively low ("\
                            + low_seq_coverage[:-2] + f") in {sf_framecode!r} saveframe."

                        self.__reg.report.warning.appendDescription('insufficient_data',
                                                                    {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                     'category': lp_category, 'description': warn})

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.calculateStatsOfExptlData() ++ Warning  - {warn}\n")

                if content_subtype == 'chem_shift':

                    try:

                        item_names = ITEM_NAMES_IN_CS_LOOP[file_type]

                        anomalous_errs =\
                            self.__reg.report.error.getValueListWithSf('anomalous_data', file_name, sf_framecode, key='Z_score')
                        anomalous_warns =\
                            self.__reg.report.warning.getValueListWithSf('anomalous_data', file_name, sf_framecode, key='Z_score')
                        unusual_warns =\
                            self.__reg.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.__reg.csStat.peptideLike(comp_id)

                                if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                        if anomalous_warns is not None:

                            for a_warn in anomalous_warns:
                                ann = {}
                                ann['level'] = 'anomalous'
                                ann['chain_id'] = a_warn['row_location'][item_names['chain_id']]
                                ann['seq_id'] = int(a_warn['row_location'][item_names['seq_id']])
                                ann['comp_id'] = a_warn['row_location'][item_names['comp_id']]
                                ann['atom_id'] = a_warn['row_location'][item_names['atom_id']]
                                ann['value'] = a_warn['value']
                                ann['z_score'] = a_warn['z_score']

                                comp_id = ann['comp_id']
                                atom_id = ann['atom_id'].split(' ')[0]

                                polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                                if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                        if unusual_warns is not None:

                            for u_warn in unusual_warns:
                                ann = {}
                                ann['level'] = 'unusual'
                                ann['chain_id'] = u_warn['row_location'][item_names['chain_id']]
                                ann['seq_id'] = int(u_warn['row_location'][item_names['seq_id']])
                                ann['comp_id'] = u_warn['row_location'][item_names['comp_id']]
                                ann['atom_id'] = u_warn['row_location'][item_names['atom_id']]
                                ann['value'] = u_warn['value']
                                ann['z_score'] = u_warn['z_score']

                                comp_id = ann['comp_id']
                                atom_id = ann['atom_id'].split(' ')[0]

                                polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                                if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):
                                    non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                    if atom_id in non_rep_methyl_pros:
                                        continue

                                cs_ann.append(ann)

                    except Exception as e:  # pylint: disable=broad-exception-caught

                        self.__reg.report.error.appendDescription('internal_error',
                                                                  f"+{self.__class_name__}.calculateStatsOfExptlData() "
                                                                  "++ Error  - " + str(e))

                        if self.__reg.verbose:
                            self.__reg.log.write(f"+{self.__class_name__}.calculateStatsOfExptlData() ++ Error  - {str(e)}\n")

                    self.__calculateStatsOfAssignedChemShift(file_list_id, sf_framecode, lp_data, cs_ann, ent)

                elif content_subtype in ('dist_restraint', 'dihed_restraint', 'rdc_restraint')\
                        and len(lp_data) <= MAX_ROWS_TO_PERFORM_REDUNDANCY_CHECK:

                    conflict_id_set =\
                        self.__reg.nefT.get_conflict_id_set(sf, lp_category,
                                                            self.__reg.consist_key_items[file_type][content_subtype])[0]

                    conflict_warns = self.__reg.report.warning.getValueListWithSf('conflicted_data', file_name, sf_framecode)
                    inconsist_warns = self.__reg.report.warning.getValueListWithSf('inconsistent_data', file_name, sf_framecode)
                    redundant_warns = self.__reg.report.warning.getValueListWithSf('redundant_data', file_name, sf_framecode)

                    inconsistent = set()
                    redundant = set()

                    if conflict_warns is not None:

                        for item in conflict_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    inconsistent.add(int(index))

                    if inconsist_warns is not None:

                        for item in inconsist_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    inconsistent.add(int(index))

                    if redundant_warns is not None:

                        for item in redundant_warns:
                            if 'row_locations' in item:
                                for index in item['row_locations'][index_tag]:
                                    redundant.add(int(index))

                    if content_subtype == 'dist_restraint':
                        self.__calculateStatsOfDistanceRestraint(file_list_id, sf_framecode,
                                                                 lp_data, conflict_id_set, inconsistent, redundant, ent)

                    elif content_subtype == 'dihed_restraint':
                        self.__calculateStatsOfDihedralRestraint(file_list_id, sf_framecode,
                                                                 lp_data, conflict_id_set, inconsistent, redundant, ent)

                    elif content_subtype == 'rdc_restraint':
                        self.__calculateStatsOfRdcRestraint(file_list_id, sf_framecode,
                                                            lp_data, conflict_id_set, inconsistent, redundant, ent)

            if content_subtype.startswith('spectral_peak'):

                try:

                    _num_dim = get_first_sf_tag(sf, NUM_DIM_ITEMS[file_type])
                    num_dim = int(_num_dim)

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

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

                if content_subtype == 'spectral_peak':
                    self.__calculateStatsOfSpectralPeak(file_list_id, sf_framecode, num_dim, lp_data, ent)
                elif content_subtype == 'spectral_peak_alt':
                    self.__calculateStatsOfSpectralPeakAlt(file_list_id, sf_framecode, num_dim, lp_data, ent)

        elif content_subtype == 'poly_seq':
            self.__calculateStatsOfCovalentBond(file_list_id, sf_framecode, lp_category, lp_data, ent)

        elif content_subtype == 'chem_shift_ref':
            ent['loop'] = lp_data
            ent['saveframe_tag'] = sf_tag_data

        has_err = self.__reg.report.error.exists(file_name, sf_framecode)
        has_warn = self.__reg.report.warning.exists(file_name, sf_framecode)

        original_file_name = get_first_sf_tag(sf, 'Data_file_name')
        if len(original_file_name) > 0:
            has_err |= self.__reg.report.error.exists(None, sf_framecode)
            has_warn |= self.__reg.report.warning.exists(None, sf_framecode)

        if has_err:
            status = 'Error'
            ent['error_descriptions'] =\
                self.__reg.report.error.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
            if has_warn:
                ent['warning_descriptions'] =\
                    self.__reg.report.warning.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
        elif has_warn:
            status = 'Warning'
            ent['warning_descriptions'] =\
                self.__reg.report.warning.getCombinedDescriptions(file_name, sf_framecode, original_file_name)
        else:
            status = 'OK'

        ent['status'] = status

        asm.append(ent)

    def __calculateStatsOfAssignedChemShift(self, file_list_id: int, sf_framecode: str,
                                            lp_data: List[dict], cs_ann: List[dict], ent: dict) -> None:
        """ Calculate statistics of assigned chemical shifts.
        """

        input_source = self.__reg.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 = 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']
        alt_chain_id_name = item_names['alt_chain_id'] if file_type == 'nmr-star' else None
        chain_id_map = {}

        try:

            count = {}

            for row in lp_data:

                if row[atom_type] in EMPTY_VALUE or row[iso_number] in EMPTY_VALUE or row[value_name] in EMPTY_VALUE:
                    continue

                data_type = f'{row[iso_number]}{row[atom_type].lower()}_chemical_shifts'

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

            if len(count) > 0:
                ent['number_of_assignments'] = count

            poly_seq = input_source_dic['polymer_sequence']

            if poly_seq is None:
                return

            if 'sequence_coverage' in ent:

                completeness = []

                for sc in ent['sequence_coverage']:

                    cc = {}

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))

                    cc['chain_id'] = chain_id

                    # all atoms

                    all_c, excluded_comp_id, excluded_atom_id = [], [], []

                    h1_col = c13_col = n15_col = p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = f'all_{data_type}'
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        all_c.append(atom_group)

                        col += 1

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                            if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):

                                all_atoms = self.__reg.csStat.getAllAtoms(comp_id, excl_minor_atom=True, primary=polypeptide_like)
                                non_excl_atoms = self.__reg.csStat.getAllAtoms(comp_id, excl_minor_atom=False)
                                non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                for a in all_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                        all_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        all_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        all_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        all_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id:
                                        if alt_chain_id_name is None or alt_chain_id_name not in row\
                                           or row[alt_chain_id_name] != _chain_id:
                                            continue
                                        _chain_id = chain_id_map[_chain_id] = row[chain_id_name]

                                    if row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in EMPTY_VALUE:
                                        continue

                                    atom_id = row[atom_id_name]

                                    if None in (row[iso_number], row[atom_type]):
                                        continue

                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in atom_set:
                                                continue

                                            atom_set.add(a)

                                            if a in all_atoms:

                                                if data_type == '1H' and h1_col != -1\
                                                   and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                                    all_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    all_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    all_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    all_c[p31_col]['number_of_assigned_shifts'] += 1

                                            elif a in non_excl_atoms:
                                                excluded_atom_id.append({'seq_id': seq_id, 'comp_id': comp_id,
                                                                         'atom_id': a, 'value': row[value_name]})

                                    else:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if atom_id in all_atoms:

                                            if data_type == '1H' and h1_col != -1\
                                               and atom_id not in non_rep_methyl_pros and atom_id[0] in PROTON_BEGIN_CODE:
                                                all_c[h1_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '13C' and c13_col != -1:
                                                all_c[c13_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '15N' and n15_col != -1:
                                                all_c[n15_col]['number_of_assigned_shifts'] += 1

                                            elif data_type == '31P' and p31_col != -1:
                                                all_c[p31_col]['number_of_assigned_shifts'] += 1

                                        elif atom_id in non_excl_atoms:
                                            excluded_atom_id.append({'seq_id': seq_id, 'comp_id': comp_id,
                                                                     'atom_id': atom_id, 'value': row[value_name]})

                            else:
                                excluded_comp_id.append({'seq_id': seq_id, 'comp_id': comp_id})

                        for c in all_c:
                            if c['number_of_target_shifts'] > 0:
                                c['completeness'] =\
                                    float(f"{float(c['number_of_assigned_shifts']) / c['number_of_target_shifts']:.3f}")
                            else:
                                c['completeness'] = None

                    cc['completeness_of_all_assignments'] = all_c

                    cc['excluded_comp_id_in_statistics'] = excluded_comp_id if len(excluded_comp_id) > 0 else None
                    cc['excluded_atom_id_in_statistics'] = excluded_atom_id if len(excluded_atom_id) > 0 else None

                    # backbone atoms (bb)

                    bb_c = []

                    h1_col = c13_col = n15_col = p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = f'backbone_{data_type}'
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        bb_c.append(atom_group)

                        col += 1

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                            if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):

                                bb_atoms = self.__reg.csStat.getBackBoneAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                for a in bb_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                        bb_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        bb_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        bb_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        bb_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id:
                                        if alt_chain_id_name is None or alt_chain_id_name not in row\
                                           or row[alt_chain_id_name] != _chain_id:
                                            continue
                                        _chain_id = chain_id_map[_chain_id] = row[chain_id_name]

                                    if row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in EMPTY_VALUE:
                                        continue

                                    atom_id = row[atom_id_name]

                                    if None in (row[iso_number], row[atom_type]):
                                        continue

                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in bb_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1\
                                                   and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                                    bb_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    bb_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    bb_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    bb_c[p31_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in bb_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1\
                                           and atom_id not in non_rep_methyl_pros and atom_id[0] in PROTON_BEGIN_CODE:
                                            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 = c13_col = n15_col = p31_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = f'sidechain_{data_type}'
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        elif data_type.startswith('31p'):
                            p31_col = col

                        sc_c.append(atom_group)

                        col += 1

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                            if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):

                                sc_atoms = self.__reg.csStat.getSideChainAtoms(comp_id, excl_minor_atom=True)
                                non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                for a in sc_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                        sc_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        sc_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        sc_c[n15_col]['number_of_target_shifts'] += 1

                                    elif p31_col != -1 and a.startswith('P'):
                                        sc_c[p31_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id:
                                        if alt_chain_id_name is None or alt_chain_id_name not in row\
                                           or row[alt_chain_id_name] != _chain_id:
                                            continue
                                        _chain_id = chain_id_map[_chain_id] = row[chain_id_name]

                                    if row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in EMPTY_VALUE:
                                        continue

                                    atom_id = row[atom_id_name]

                                    if None in (row[iso_number], row[atom_type]):
                                        continue

                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in sc_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1\
                                                   and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                                    sc_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    sc_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    sc_c[n15_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '31P' and p31_col != -1:
                                                    sc_c[p31_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in sc_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1\
                                           and atom_id not in non_rep_methyl_pros and atom_id[0] in PROTON_BEGIN_CODE:
                                            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 = c13_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = f'methyl_{data_type}'
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        else:
                            continue

                        ch3_c.append(atom_group)

                        col += 1

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                            if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):

                                ch3_atoms = self.__reg.csStat.getMethylAtoms(comp_id)
                                non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                for a in ch3_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                        ch3_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        ch3_c[c13_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id:
                                        if alt_chain_id_name is None or alt_chain_id_name not in row\
                                           or row[alt_chain_id_name] != _chain_id:
                                            continue
                                        _chain_id = chain_id_map[_chain_id] = row[chain_id_name]

                                    if row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in EMPTY_VALUE:
                                        continue

                                    atom_id = row[atom_id_name]

                                    if None in (row[iso_number], row[atom_type]):
                                        continue

                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in ch3_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1\
                                                   and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                                    ch3_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    ch3_c[c13_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in ch3_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1\
                                           and atom_id not in non_rep_methyl_pros and atom_id[0] in PROTON_BEGIN_CODE:
                                            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 = c13_col = n15_col = -1

                    col = 0

                    for data_type in count:

                        atom_group = {}
                        atom_group['atom_group'] = f'aromatic_{data_type}'
                        atom_group['number_of_assigned_shifts'] = 0
                        atom_group['number_of_target_shifts'] = 0
                        atom_group['completeness'] = 0.0

                        if data_type.startswith('1h'):
                            h1_col = col

                        elif data_type.startswith('13c'):
                            c13_col = col

                        elif data_type.startswith('15n'):
                            n15_col = col

                        aro_c.append(atom_group)

                        col += 1

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            polypeptide_like = self.__reg.csStat.peptideLike(comp_id)

                            if self.__reg.csStat.hasSufficientStat(comp_id, polypeptide_like):

                                aro_atoms = self.__reg.csStat.getAromaticAtoms(comp_id,
                                                                               excl_minor_atom=True, primary=polypeptide_like)
                                non_rep_methyl_pros = self.__reg.csStat.getNonRepMethylProtons(comp_id)

                                for a in aro_atoms:

                                    if h1_col != -1 and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                        aro_c[h1_col]['number_of_target_shifts'] += 1

                                    elif c13_col != -1 and a.startswith('C'):
                                        aro_c[c13_col]['number_of_target_shifts'] += 1

                                    elif n15_col != -1 and a.startswith('N'):
                                        aro_c[n15_col]['number_of_target_shifts'] += 1

                                atom_set = set()

                                for row in lp_data:

                                    if row[chain_id_name] != _chain_id:
                                        if alt_chain_id_name is None or alt_chain_id_name not in row\
                                           or row[alt_chain_id_name] != _chain_id:
                                            continue
                                        _chain_id = chain_id_map[_chain_id] = row[chain_id_name]

                                    if row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                       or row[value_name] in EMPTY_VALUE:
                                        continue

                                    atom_id = row[atom_id_name]

                                    if None in (row[iso_number], row[atom_type]):
                                        continue

                                    data_type = str(row[iso_number]) + row[atom_type]

                                    if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                        atom_ids = self.getAtomIdList(comp_id, atom_id)

                                        if len(atom_ids) == 0:
                                            continue

                                        for a in atom_ids:

                                            if a in aro_atoms:

                                                if a in atom_set:
                                                    continue

                                                atom_set.add(a)

                                                if data_type == '1H' and h1_col != -1\
                                                   and a not in non_rep_methyl_pros and a[0] in PROTON_BEGIN_CODE:
                                                    aro_c[h1_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '13C' and c13_col != -1:
                                                    aro_c[c13_col]['number_of_assigned_shifts'] += 1

                                                elif data_type == '15N' and n15_col != -1:
                                                    aro_c[n15_col]['number_of_assigned_shifts'] += 1

                                    elif atom_id in aro_atoms:

                                        if atom_id in atom_set:
                                            continue

                                        atom_set.add(atom_id)

                                        if data_type == '1H' and h1_col != -1\
                                           and atom_id not in non_rep_methyl_pros and atom_id[0] in PROTON_BEGIN_CODE:
                                            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 = min_val = 0.0

            for row in lp_data:

                if row[atom_type] in EMPTY_VALUE or row[iso_number] in EMPTY_VALUE or row[value_name] in EMPTY_VALUE:
                    continue

                data_type = f'{row[iso_number]}{row[atom_type].lower()}_chemical_shifts'

                chain_id = row[chain_id_name]
                seq_id = row[seq_id_name]
                comp_id = row[comp_id_name]
                atom_id = row[atom_id_name]
                value = row[value_name]

                _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                _chain_id = chain_id_map.get(_chain_id, _chain_id)

                if value in EMPTY_VALUE:
                    continue

                if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                    _atom_id = self.getAtomIdList(comp_id, atom_id)

                    len_atom_id = len(_atom_id)

                    if len_atom_id == 0:
                        continue

                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                        atom_id_ = atom_id

                    else:  # representative atom id
                        atom_id_ = _atom_id[0]

                else:
                    atom_id_ = atom_id

                has_cs_stat = False

                # non-standard residue
                if comp_id not in STD_MON_DICT:

                    neighbor_comp_ids = set(_row[comp_id_name] for _row in lp_data
                                            if _row[chain_id_name] == _chain_id
                                            and abs(_row[seq_id_name] - seq_id) < 4 and _row[seq_id_name] != seq_id)

                    polypeptide_like = False

                    for comp_id2 in neighbor_comp_ids:
                        polypeptide_like |= self.__reg.csStat.peptideLike(comp_id2)

                    for cs_stat in self.__reg.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.__reg.csStat.get(comp_id, self.__reg.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 None in (std_value, avg_value) or std_value <= 0.0:
                    continue

                z_score = (value - avg_value) / std_value

                if z_score > max_val:
                    max_val = z_score

                elif z_score < min_val:
                    min_val = z_score

                z_scores[data_type].append(z_score)

            target_scale = (max_val - min_val) / 20.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals, count_of_vals = [], []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = len([z for z in z_scores[k] if v <= z < v + scale])

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed, 'annotations': cs_ann}

            if 'sequence_coverage' in ent:

                # prediction of redox state of CYS

                cys_redox_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                    _chain_id = chain_id_map.get(_chain_id, _chain_id)

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            if comp_id not in ('CYS', 'DCY'):
                                continue

                            cys = {'chain_id': chain_id, 'seq_id': seq_id}

                            ca_chem_shift = cb_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id\
                                   and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CA':
                                        ca_chem_shift = row[value_name]
                                    elif atom_id == 'CB':
                                        cb_chem_shift = row[value_name]

                                if None in (ca_chem_shift, cb_chem_shift):
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            cys['ca_chem_shift'] = ca_chem_shift
                            cys['cb_chem_shift'] = cb_chem_shift

                            if cb_chem_shift is not None:
                                if cb_chem_shift < 32.0:
                                    cys['redox_state_pred'] = 'reduced'
                                elif cb_chem_shift > 35.0:
                                    cys['redox_state_pred'] = 'oxidized'
                                else:
                                    cys['redox_state_pred'] = 'ambiguous'
                            elif ca_chem_shift is not None:
                                cys['redox_state_pred'] = 'ambiguous'
                            else:
                                cys['redox_state_pred'] = 'unknown'

                            if cys['redox_state_pred'] == 'ambiguous':
                                oxi, red = predict_redox_state_of_cystein(ca_chem_shift, cb_chem_shift)
                                if oxi < 0.001:
                                    cys['redox_state_pred'] = 'reduced'
                                elif red < 0.001:
                                    cys['redox_state_pred'] = 'oxidized'
                                else:
                                    cys['redox_state_pred'] = f"oxidized {oxi:.1%}, reduced {red:.1%}"

                            if self.__hasCoordSeq(chain_id, seq_id):
                                cys['in_disulfide_bond'] = False
                                if has_key_value(input_source_dic, 'disulfide_bond'):
                                    if any(True for b in input_source_dic['disulfide_bond']
                                           if (b['chain_id_1'] == chain_id and b['seq_id_1'] == seq_id)
                                           or (b['chain_id_2'] == chain_id and b['seq_id_2'] == seq_id)):
                                        cys['in_disulfide_bond'] = True

                                cys['in_other_bond'] = False
                                if has_key_value(input_source_dic, 'other_bond'):
                                    if any(True for b in input_source_dic['other_bond']
                                           if (b['chain_id_1'] == chain_id and b['seq_id_1'] == seq_id)
                                           or (b['chain_id_2'] == chain_id and b['seq_id_2'] == seq_id)):
                                        cys['in_other_bond'] = True

                            cys_redox_state.append(cys)

                    if len(cys_redox_state) > 0:
                        ent['cys_redox_state'] = cys_redox_state

                # prediction of cis-trans peptide of PRO

                pro_cis_trans = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                    _chain_id = chain_id_map.get(_chain_id, _chain_id)

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            if comp_id != 'PRO':
                                continue

                            pro = {'chain_id': chain_id, 'seq_id': seq_id}

                            cb_chem_shift = cg_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id\
                                   and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CB':
                                        cb_chem_shift = row[value_name]
                                    elif atom_id == 'CG':
                                        cg_chem_shift = row[value_name]

                                if None in (cb_chem_shift, cg_chem_shift):
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            pro['cb_chem_shift'] = cb_chem_shift
                            pro['cg_chem_shift'] = cg_chem_shift

                            if (cb_chem_shift is not None) and (cg_chem_shift is not None):
                                delta = cb_chem_shift - cg_chem_shift
                                if delta < 4.8:
                                    pro['cis_trans_pred'] = 'trans'
                                elif delta > 9.15:
                                    pro['cis_trans_pred'] = 'cis'
                                else:
                                    pro['cis_trans_pred'] = 'ambiguous'
                            elif (cb_chem_shift is not None) or (cg_chem_shift is not None):
                                pro['cis_trans_pred'] = 'ambiguous'
                            else:
                                pro['cis_trans_pred'] = 'unknown'

                            if pro['cis_trans_pred'] == 'ambiguous':
                                cis, trs = predict_cis_trans_peptide_of_proline(cb_chem_shift, cg_chem_shift)
                                if cis < 0.001:
                                    pro['cis_trans_pred'] = 'trans'
                                elif trs < 0.001:
                                    pro['cis_trans_pred'] = 'cis'
                                else:
                                    pro['cis_trans_pred'] = f"cis {cis:.1%}, trans {trs:.1%}"

                            if self.__hasCoordSeq(chain_id, seq_id):
                                in_cis_peptide_bond = self.isProtCis(chain_id, seq_id)

                                pro['in_cis_peptide_bond'] = in_cis_peptide_bond

                                if pro['cis_trans_pred'] != 'unknown':

                                    if (in_cis_peptide_bond and pro['cis_trans_pred'] != 'cis')\
                                       or (not in_cis_peptide_bond and pro['cis_trans_pred'] != 'trans'):
                                        item = None
                                        if ',' in pro['cis_trans_pred']:
                                            if (in_cis_peptide_bond and cis > trs)\
                                               or (not in_cis_peptide_bond and trs > cis):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                        if item is not None:

                                            shifts = ''
                                            if cb_chem_shift is not None:
                                                shifts += f"CB {cb_chem_shift} ppm, "
                                            if cg_chem_shift is not None:
                                                shifts += f"CG {cg_chem_shift} ppm, "

                                            warn = f"{'cis' if in_cis_peptide_bond else 'trans'}-peptide bond of "\
                                                f"{chain_id}:{seq_id}:{comp_id} can not be verified with "\
                                                "the assigned chemical shift values "\
                                                f"({shifts}cis_trans_pred {pro['cis_trans_pred']})."

                                            self.__reg.report.warning.appendDescription(item,
                                                                                        {'file_name': file_name,
                                                                                         'sf_framecode': sf_framecode,
                                                                                         'description': warn})

                                            if self.__reg.verbose:
                                                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "  # noqa: E501, pylint: disable=line-too-long
                                                                     f"++ Warning  - {warn}\n")

                            pro_cis_trans.append(pro)

                    if len(pro_cis_trans) > 0:
                        ent['pro_cis_trans'] = pro_cis_trans

                # prediction of tautomeric state of HIS

                his_tautomeric_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                    _chain_id = chain_id_map.get(_chain_id, _chain_id)

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            if comp_id != 'HIS':
                                continue

                            his = {'chain_id': chain_id, 'seq_id': seq_id}

                            cg_chem_shift = cd2_chem_shift = nd1_chem_shift = ne2_chem_shift = None

                            for row in lp_data:

                                atom_id = row[atom_id_name]

                                if row[chain_id_name] == _chain_id\
                                   and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                    if atom_id == 'CG':
                                        cg_chem_shift = row[value_name]
                                    elif atom_id == 'CD2':
                                        cd2_chem_shift = row[value_name]
                                    elif atom_id == 'ND1':
                                        nd1_chem_shift = row[value_name]
                                    elif atom_id == 'NE2':
                                        ne2_chem_shift = row[value_name]

                                if None in (cg_chem_shift, cd2_chem_shift, nd1_chem_shift, ne2_chem_shift):
                                    if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                        break
                                else:
                                    break

                            his['cg_chem_shift'] = cg_chem_shift
                            his['cd2_chem_shift'] = cd2_chem_shift
                            his['nd1_chem_shift'] = nd1_chem_shift
                            his['ne2_chem_shift'] = ne2_chem_shift

                            if (cg_chem_shift is not None) or (cd2_chem_shift is not None)\
                               or (nd1_chem_shift is not None) or (ne2_chem_shift is not None):
                                bip, tau, pi = predict_tautomer_state_of_histidine(cg_chem_shift, cd2_chem_shift,
                                                                                   nd1_chem_shift, ne2_chem_shift)
                                if tau < 0.001 and pi < 0.001:
                                    his['tautomeric_state_pred'] = 'biprotonated'
                                elif bip < 0.001 and pi < 0.001:
                                    his['tautomeric_state_pred'] = 'tau-tautomer'
                                elif bip < 0.001 and tau < 0.001:
                                    his['tautomeric_state_pred'] = 'pi-tautomer'
                                else:
                                    his['tautomeric_state_pred'] =\
                                        f"biprotonated {bip:.1%}, tau-tautomer {tau:.1%}, pi-tautomer {pi:.1%}"
                            else:
                                his['tautomeric_state_pred'] = 'unknown'

                            his['tautomeric_state'] = self.__getTautomerOfHistidine(chain_id, seq_id)

                            if his['tautomeric_state_pred'] != 'unknown':
                                item = None
                                if his['tautomeric_state_pred'] != his['tautomeric_state'] and his['tautomeric_state'] != 'unknown':
                                    if ',' in his['tautomeric_state_pred']:
                                        if (his['tautomeric_state'] == 'biprotonated' and bip > tau and bip > pi)\
                                           or (his['tautomeric_state'] == 'tau-tautomer' and tau > bip and tau > pi)\
                                           or (his['tautomeric_state'] == 'pi-tautomer' and pi > bip and pi > tau):
                                            pass
                                        else:
                                            item = 'unusual_chemical_shift'
                                    else:
                                        item = 'anomalous_chemical_shift'

                                if item is not None:

                                    shifts = ''
                                    if cg_chem_shift is not None:
                                        shifts += f"CG {cg_chem_shift} ppm, "
                                    if cd2_chem_shift is not None:
                                        shifts += f"CD2 {cd2_chem_shift} ppm, "
                                    if nd1_chem_shift is not None:
                                        shifts += f"ND1 {nd1_chem_shift} ppm, "
                                    if ne2_chem_shift is not None:
                                        shifts += f"NE2 {ne2_chem_shift} ppm, "

                                    warn = f"Tautomeric state {his['tautomeric_state']} of {chain_id}:{seq_id}:{comp_id} "\
                                        "can not be verified with the assigned chemical shift values "\
                                        f"({shifts}tautomeric_state_pred {his['tautomeric_state_pred']})."

                                    self.__reg.report.warning.appendDescription(item,
                                                                                {'file_name': file_name,
                                                                                 'sf_framecode': sf_framecode,
                                                                                 'description': warn})

                                    if self.__reg.verbose:
                                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                                             f"++ Warning  - {warn}\n")

                            his_tautomeric_state.append(his)

                if len(his_tautomeric_state) > 0:
                    ent['his_tautomeric_state'] = his_tautomeric_state

                # prediction of rotameric state of VAL/LEU/ILE

                ilv_comp_ids = ('VAL', 'LEU', 'ILE')

                ilv_rotameric_state = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                    _chain_id = chain_id_map.get(_chain_id, _chain_id)

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        for seq_id, comp_id in zip(ps['seq_id'], ps['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 = cg2_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id\
                                       and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id\
                                       and atom_id.startswith('CG'):

                                        _atom_id = atom_id

                                        if self.isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.getRepAtomId(comp_id, atom_id)

                                        if _atom_id == 'CG1':
                                            cg1_chem_shift = row[value_name]
                                        elif _atom_id == 'CG2':
                                            cg2_chem_shift = row[value_name]

                                    if None in (cg1_chem_shift, cg2_chem_shift):
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cg1_chem_shift'] = cg1_chem_shift
                                ilv['cg2_chem_shift'] = cg2_chem_shift

                                if (cg1_chem_shift is not None) or (cg2_chem_shift is not None):
                                    gp, t, gm = predict_rotamer_state_of_valine(cg1_chem_shift, cg2_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfValine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi1')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm)\
                                               or (_rotameric_state == 'trans' and t > gm and t > gp)\
                                               or (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cg1_chem_shift is not None:
                                            shifts += f"CG1 {cg1_chem_shift} ppm, "
                                        if cg2_chem_shift is not None:
                                            shifts += f"CG2 {cg2_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} "\
                                            "can not be verified with the assigned chemical shift values "\
                                            f"({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.__reg.report.warning.appendDescription(item,
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                                                 f"++ Warning  - {warn}\n")

                            elif comp_id == 'LEU':

                                cd1_chem_shift = cd2_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id\
                                       and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id\
                                       and atom_id.startswith('CD'):

                                        _atom_id = atom_id

                                        if self.isNmrAtomName(comp_id, atom_id):
                                            _atom_id = self.getRepAtomId(comp_id, atom_id)

                                        if _atom_id == 'CD1':
                                            cd1_chem_shift = row[value_name]
                                        elif _atom_id == 'CD2':
                                            cd2_chem_shift = row[value_name]

                                    if None in (cd1_chem_shift, cd2_chem_shift):
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cd1_chem_shift'] = cd1_chem_shift
                                ilv['cd2_chem_shift'] = cd2_chem_shift

                                if (cd1_chem_shift is not None) or (cd2_chem_shift is not None):
                                    gp, t, gm = predict_rotamer_state_of_leucine(cd1_chem_shift, cd2_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfLeucine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi2')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm)\
                                               or (_rotameric_state == 'trans' and t > gm and t > gp)\
                                               or (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cd1_chem_shift is not None:
                                            shifts += f"CD1 {cd1_chem_shift} ppm, "
                                        if cd2_chem_shift is not None:
                                            shifts += f"CD2 {cd2_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} "\
                                            "can not be verified with the assigned chemical shift values "\
                                            f"({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.__reg.report.warning.appendDescription(item,
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                                                 f"++ Warning  - {warn}\n")

                            else:

                                cd1_chem_shift = None

                                for row in lp_data:

                                    atom_id = row[atom_id_name]

                                    if row[chain_id_name] == _chain_id\
                                       and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                        if atom_id == 'CD1':
                                            cd1_chem_shift = row[value_name]

                                    if cd1_chem_shift is None:
                                        if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                            break
                                    else:
                                        break

                                ilv['cd1_chem_shift'] = cd1_chem_shift

                                if cd1_chem_shift is not None:
                                    gp, t, gm = predict_rotamer_state_of_isoleucine(cd1_chem_shift)
                                    if t < 0.001 and gm < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche+'
                                    elif gm < 0.001 and gp < 0.001:
                                        ilv['rotameric_state_pred'] = 'trans'
                                    elif gp < 0.001 and t < 0.001:
                                        ilv['rotameric_state_pred'] = 'gauche-'
                                    else:
                                        ilv['rotameric_state_pred'] = f"gauche+ {gp:.1%}, trans {t:.1%}, gauche- {gm:.1%}"
                                else:
                                    ilv['rotameric_state_pred'] = 'unknown'

                                ilv['rotameric_state'] = self.__getRotamerOfIsoleucine(chain_id, seq_id)

                                r = next(r for r in ilv['rotameric_state'] if r['name'] == 'chi2')
                                if 'unknown' in r:
                                    _rotameric_state = 'unknown'
                                else:
                                    _gp = r['gauche+']
                                    _t = r['trans']
                                    _gm = r['gauche-']
                                    if _gp > _t and _gp > _gm:
                                        _rotameric_state = 'gauche+'
                                    elif _t > _gm and _t > _gp:
                                        _rotameric_state = 'trans'
                                    elif _gm > _gp and _gm > _t:
                                        _rotameric_state = 'gauche-'
                                    else:
                                        _rotameric_state = 'unknown'

                                if ilv['rotameric_state_pred'] != 'unknown':
                                    item = None
                                    if _rotameric_state not in (ilv['rotameric_state_pred'], 'unknown'):
                                        if ',' in ilv['rotameric_state_pred']:
                                            if (_rotameric_state == 'gauche+' and gp > t and gp > gm)\
                                               or (_rotameric_state == 'trans' and t > gm and t > gp)\
                                               or (_rotameric_state == 'gauche-' and gm > gp and gm > t):
                                                pass
                                            else:
                                                item = 'unusual_chemical_shift'
                                        else:
                                            item = 'anomalous_chemical_shift'

                                    if item is not None:

                                        shifts = ''
                                        if cd1_chem_shift is not None:
                                            shifts += f"CD1 {cd1_chem_shift} ppm, "

                                        warn = f"Rotameric state {_rotameric_state} of {chain_id}:{seq_id}:{comp_id} "\
                                            "can not be verified with the assigned chemical shift values "\
                                            f"({shifts}rotameric_state_pred {ilv['rotameric_state_pred']})."

                                        self.__reg.report.warning.appendDescription(item,
                                                                                    {'file_name': file_name,
                                                                                     'sf_framecode': sf_framecode,
                                                                                     'description': warn})

                                        if self.__reg.verbose:
                                            self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                                                 f"++ Warning  - {warn}\n")

                            ilv_rotameric_state.append(ilv)

                if len(ilv_rotameric_state) > 0:
                    ent['ilv_rotameric_state'] = ilv_rotameric_state

                # random coil index

                rci_atom_ids = ('HA', 'HA1', 'HA2', 'HA3', 'H', 'HN', 'NH', 'C', 'CO', 'N', 'CA', 'CB')

                rci = []

                for sc in ent['sequence_coverage']:

                    chain_id = sc['chain_id']

                    _chain_id = chain_id if file_type == 'nef' or self.__reg.remediation_mode else str(letterToDigit(chain_id))
                    _chain_id = chain_id_map.get(_chain_id, _chain_id)

                    ps = next((ps for ps in poly_seq if ps['chain_id'] == chain_id), None)

                    if ps is not None:

                        rci_residues, rci_assignments, seq_ids_wo_assign, oxidized_cys_seq_ids = [], [], [], []

                        for seq_id, comp_id in zip(ps['seq_id'], ps['comp_id']):

                            if comp_id not in EMPTY_VALUE:
                                if comp_id not in STD_MON_DICT:
                                    continue
                                if not self.__reg.csStat.peptideLike(comp_id):
                                    continue
                                rci_residues.append([comp_id, seq_id])
                            else:
                                _comp_id = self.__getCoordCompId(chain_id, seq_id)
                                if _comp_id is not None:
                                    if _comp_id not in STD_MON_DICT:
                                        continue
                                    if not self.__reg.csStat.peptideLike(_comp_id):
                                        continue
                                    rci_residues.append([_comp_id, seq_id])
                                else:
                                    continue

                            has_bb_atoms = False

                            for row in lp_data:

                                if row[chain_id_name] != _chain_id\
                                   or row[seq_id_name] != seq_id or row[comp_id_name] != comp_id\
                                   or row[value_name] in EMPTY_VALUE:
                                    continue

                                atom_id = row[atom_id_name]

                                if file_type == 'nef' or self.isNmrAtomName(comp_id, atom_id):
                                    _atom_id = self.getAtomIdList(comp_id, atom_id)

                                    len_atom_id = len(_atom_id)

                                    if len_atom_id == 0:
                                        continue

                                    if len_atom_id == 1 and atom_id == _atom_id[0]:
                                        atom_id_ = atom_id

                                    else:  # representative atom id
                                        atom_id_ = _atom_id[0]

                                else:
                                    atom_id_ = atom_id

                                if atom_id_ not in rci_atom_ids:
                                    continue

                                rci_assignments.append([comp_id, seq_id, atom_id, row[atom_type], row[value_name]])

                                has_bb_atoms = True

                            if has_bb_atoms:

                                if comp_id in ('CYS', 'DCY'):

                                    ca_chem_shift = cb_chem_shift = None

                                    for row in lp_data:

                                        atom_id = row[atom_id_name]

                                        if row[chain_id_name] == _chain_id\
                                           and row[seq_id_name] == seq_id and row[comp_id_name] == comp_id:
                                            if atom_id == 'CA':
                                                ca_chem_shift = row[value_name]
                                            elif atom_id == 'CB':
                                                cb_chem_shift = row[value_name]

                                        if None in (ca_chem_shift, cb_chem_shift):
                                            if row[chain_id_name] == _chain_id and row[seq_id_name] > seq_id:
                                                break
                                        else:
                                            break

                                    ambig_redox_state = False

                                    if cb_chem_shift is not None:
                                        if cb_chem_shift < 32.0:
                                            pass
                                        elif cb_chem_shift > 35.0:
                                            oxidized_cys_seq_ids.append(seq_id)
                                        else:
                                            ambig_redox_state = True
                                    elif ca_chem_shift is not None:
                                        ambig_redox_state = True

                                    if ambig_redox_state:
                                        oxi, red = predict_redox_state_of_cystein(ca_chem_shift, cb_chem_shift)
                                        if oxi < 0.001:
                                            pass
                                        elif red < 0.001 or oxi > 0.5:
                                            oxidized_cys_seq_ids.append(seq_id)

                            else:
                                seq_ids_wo_assign.append(seq_id)

                        if len(rci_assignments) > 0:
                            result = self.__rci.calculate(rci_residues, rci_assignments, oxidized_cys_seq_ids, seq_ids_wo_assign)

                            if 'rci' in result and len(result['rci']) > 0:
                                result['chain_id'] = chain_id
                                result['comp_id'] = [res[0] for res in rci_residues]
                                struct_conf = self.__extractCoordStructConf(chain_id, ps['seq_id'])
                                len_struct_conf = len(struct_conf)
                                result['struct_conf'] = []
                                for seq_id in result['seq_id']:
                                    pos = ps['seq_id'].index(seq_id)
                                    if pos < len_struct_conf:
                                        result['struct_conf'].append(struct_conf[pos])

                                cif_ps = self.__reg.report.getModelPolymerSequenceWithNmrChainId(chain_id)

                                if cif_ps is not None and 'well_defined_region' in cif_ps:

                                    _score = 0.0
                                    dom_idx = -1

                                    for i, r in enumerate(cif_ps['well_defined_region']):
                                        try:
                                            score = r['percent_of_core'] / r['medoid_rmsd']
                                            if score > _score:
                                                _score = score
                                                dom_idx = i
                                        except Exception:  # pylint: disable=broad-exception-caught
                                            continue

                                    if dom_idx != -1:
                                        result['rmsd_in_well_defined_region'] =\
                                            cif_ps['well_defined_region'][dom_idx]['medoid_rmsd']

                                rci.append(result)

                if len(rci) > 0:
                    ent['random_coil_index'] = rci

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = LP_CATEGORIES[file_type]['chem_shift']
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)
                lp = next(lp for lp in sf.loops if lp.category == lp_category)

                ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID', 'Atom_ID', 'Original_PDB_atom_name']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfAssignedChemShift() "
                                     f"++ Error  - {str(e)}\n")

    def __calculateStatsOfDistanceRestraint(self, file_list_id: int, sf_framecode: str, lp_data: List[dict],
                                            conflict_id_set: Optional[List[int]], inconsistent: Set[int],
                                            redundant: Set[int], ent: dict) -> None:
        """ Calculate statistics of distance restraints.
        """

        input_source = self.__reg.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 = 'dist_restraint'

        index_tag = INDEX_TAGS[file_type][content_subtype]
        item_names = ITEM_NAMES_IN_DIST_LOOP[file_type]
        combination_id_name = item_names['combination_id']
        chain_id_1_name = item_names['chain_id_1']
        chain_id_2_name = item_names['chain_id_2']
        seq_id_1_name = item_names['seq_id_1']
        seq_id_2_name = item_names['seq_id_2']
        comp_id_1_name = item_names['comp_id_1']
        comp_id_2_name = item_names['comp_id_2']
        atom_id_1_name = item_names['atom_id_1']
        atom_id_2_name = item_names['atom_id_2']
        member_id_name = item_names['member_id'] if file_type == 'nmr-star' else None
        member_logic_code_name = item_names['member_logic_code'] if file_type == 'nmr-star' else None
        target_value_name = item_names['target_value']
        if 'target_value_alt' in item_names and target_value_name not in lp_data[0].keys():
            target_value_name = item_names['target_value_alt']
        lower_limit_name = item_names['lower_limit']
        upper_limit_name = item_names['upper_limit']
        lower_linear_limit_name = item_names['lower_linear_limit']
        upper_linear_limit_name = item_names['upper_linear_limit']
        weight_name = WEIGHT_TAGS[file_type][content_subtype]
        id_tag = CONSIST_ID_TAGS[file_type][content_subtype]

        def ext_atom_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name],
                    row[atom_id_1_name], row[atom_id_2_name])

        def get_est_value_range(row):
            target_value = row.get(target_value_name)
            upper_limit = lower_limit = None

            if target_value is None:

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0
                    upper_limit = row[lower_limit_name]
                    lower_limit = row[upper_limit_name]

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                elif has_key_value(row, upper_linear_limit_name):
                    target_value = row[upper_linear_limit_name]
                    upper_limit = target_value

                elif has_key_value(row, upper_limit_name):
                    target_value = row[upper_limit_name]
                    upper_limit = target_value

                elif has_key_value(row, lower_linear_limit_name):
                    target_value = row[lower_linear_limit_name]
                    lower_limit = target_value

                elif has_key_value(row, lower_limit_name):
                    target_value = row[lower_limit_name]
                    lower_limit = target_value

            return target_value, upper_limit, lower_limit

        def get_est_target_value(row):
            value = row.get(target_value_name)

            if value is None:

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                elif has_key_value(row, upper_linear_limit_name):
                    value = row[upper_linear_limit_name]

                elif has_key_value(row, upper_limit_name):
                    value = row[upper_limit_name]

                elif has_key_value(row, lower_linear_limit_name):
                    value = row[lower_linear_limit_name]

                elif has_key_value(row, lower_limit_name):
                    value = row[lower_limit_name]

            return value

        len_lp_data = len(lp_data)

        try:

            max_val = -100.0
            min_val = 100.0

            count, comb_count, inco_count, redu_count, weights, potential_types =\
                {}, {}, {}, {}, {}, {}

            set_id = set()

            count_per_residue, count_on_map, count_on_asym_map = [], [], []

            has_inter_chain_constraint = False

            poly_seq = input_source_dic['polymer_sequence']

            if poly_seq is not None:

                for ps in poly_seq:
                    struct_conf = self.__extractCoordStructConf(ps['chain_id'], ps['seq_id'])
                    count_per_residue.append({'chain_id': ps['chain_id'], 'seq_id': ps['seq_id'], 'comp_id': ps['comp_id'],
                                              'struct_conf': struct_conf})
                    count_on_map.append({'chain_id': ps['chain_id'], 'seq_id': ps['seq_id'], 'comp_id': ps['comp_id'],
                                         'struct_conf': struct_conf})

                if len(poly_seq) > 1:
                    for ps1, ps2 in itertools.combinations(poly_seq, 2):
                        count_on_asym_map.append({'chain_id_1': ps1['chain_id'], 'chain_id_2': ps2['chain_id'],
                                                  'seq_id_1': ps1['seq_id'], 'seq_id_2': ps2['seq_id'],
                                                  'comp_id_1': ps1['comp_id'], 'comp_id_2': ps2['comp_id'],
                                                  'struct_conf_1': self.__extractCoordStructConf(ps1['chain_id'], ps1['seq_id']),
                                                  'struct_conf_2': self.__extractCoordStructConf(ps2['chain_id'], ps2['seq_id'])})

            _rest_id = -1
            _atom1 = _atom2 = None

            for idx, row in enumerate(lp_data):
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)
                member_id = row.get(member_id_name) if file_type == 'nmr-star' else None
                member_logic_code = row.get(member_logic_code_name) if file_type == 'nmr-star' else None

                chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                    comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row)

                if 'HOH' in (comp_id_1, comp_id_2):
                    continue

                weight = row.get(weight_name)

                rest_id = row[id_tag]
                set_id.add(rest_id)

                if (member_logic_code is not None and member_logic_code == 'OR') or rest_id == _rest_id:
                    atom1 = {'chain_id': chain_id_1,
                             'seq_id': int(seq_id_1) if seq_id_1 not in EMPTY_VALUE else None,
                             'comp_id': comp_id_1,
                             'atom_id': atom_id_1}
                    atom2 = {'chain_id': chain_id_2,
                             'seq_id': int(seq_id_2) if seq_id_2 not in EMPTY_VALUE else None,
                             'comp_id': comp_id_2,
                             'atom_id': atom_id_2}
                    if None not in (_atom1, _atom2):
                        if not isAmbigAtomSelection([_atom1, atom1], self.__reg.csStat)\
                           and not isAmbigAtomSelection([_atom2, atom2], self.__reg.csStat):
                            _rest_id, _atom1, _atom2 = rest_id, atom1, atom2
                            continue
                    _atom1, _atom2 = atom1, atom2

                _rest_id = rest_id

                target_value, upper_limit, lower_limit = get_est_value_range(row)

                if target_value is None:
                    continue

                max_val = max(max_val, target_value)
                min_val = min(min_val, target_value)

                data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, idx, target_value, upper_limit, lower_limit,
                                                              member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                              chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                if 'hydrogen_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Hydrogen bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'disulfide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Disulfide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'diselenide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Diselenide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                elif 'other_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    values = ''
                    if has_key_value(row, target_value_name):
                        values += f"{target_value_name} {row[target_value_name]}, "
                    if has_key_value(row, lower_limit_name):
                        values += f"{lower_limit_name} {row[lower_limit_name]}, "
                    if has_key_value(row, upper_limit_name):
                        values += f"{upper_limit_name} {row[upper_limit_name]}, "
                    if has_key_value(row, lower_linear_limit_name):
                        values += f"{lower_linear_limit_name} {row[lower_linear_limit_name]}, "
                    if has_key_value(row, upper_linear_limit_name):
                        values += f"{upper_linear_limit_name} {row[upper_linear_limit_name]}, "

                    warn = "Other bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({values[:-2]})."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() ++ Warning  - {warn}\n")

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in 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

                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'log-harmonic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if poly_seq 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 poly_seq is not None:
                ent['constraints_per_residue'] = count_per_residue
                ent['constraints_on_contact_map'] = count_on_map
            if has_inter_chain_constraint:
                ent['constraints_on_asym_contact_map'] = count_on_asym_map
            ent['range'] = {'max_value': float(f'{max_val:.2f}'), 'min_value': float(f'{min_val:.2f}')}
            if len(weights) > 0:
                _weights = {}
                for k, v in weights.items():
                    _weights[k] = collections.Counter(v).most_common()
                ent['weight_of_constraints'] = _weights
            if len(potential_types) > 0:
                _potential_types = {}
                for k, v in potential_types.items():
                    _potential_types[k] = collections.Counter(v).most_common()
                ent['potential_type_of_constraints'] = _potential_types

            target_scale = (max_val - min_val) / 10.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals, count_of_vals = [], []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = 0

                for idx, row in enumerate(lp_data):
                    member_id = row.get(member_id_name) if file_type == 'nmr-star' else None

                    chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                        comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row)

                    target_value, upper_limit, lower_limit = get_est_value_range(row)

                    if target_value is None or target_value < v or target_value >= v + scale:
                        continue

                    data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, idx, target_value, upper_limit, lower_limit,
                                                                  member_id, chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                  chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                    if data_type in _count:
                        _count[data_type] += 1
                    else:
                        _count[data_type] = 1

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                # max_inclusive = DIST_UNCERT_MAX

                max_val = min_val = 0.0

                dist_ann = []

                for id_set in conflict_id_set:
                    len_id_set = len(id_set)

                    if len_id_set < 2:
                        continue

                    for i in range(len_id_set - 1):

                        for j in range(i + 1, len_id_set):
                            if id_set[j] >= len_lp_data:
                                continue
                            row_1 = lp_data[id_set[i]]
                            row_2 = lp_data[id_set[j]]

                            target_value_1 = get_est_target_value(row_1)
                            target_value_2 = get_est_target_value(row_2)

                            if None in (target_value_1, target_value_2):
                                continue

                            if target_value_1 == target_value_2:
                                continue

                            discrepancy = abs(target_value_1 - target_value_2) / abs(target_value_1 + target_value_2) * 100.0

                            max_val = max(max_val, discrepancy)

                            if discrepancy >= R_INCONSISTENT_DIST_RESTRAINT * 100.0:
                                ann = {}
                                ann['level'] = 'conflicted' if discrepancy >= R_CONFLICTED_DIST_RESTRAINT * 100.0\
                                    else 'inconsistent'
                                ann['chain_id_1'] = row_1[chain_id_1_name]
                                ann['seq_id_1'] = row_1[seq_id_1_name]
                                ann['comp_id_1'] = row_1[comp_id_1_name]
                                ann['atom_id_1'] = row_1[atom_id_1_name]
                                if row_1[chain_id_1_name] != row_2[chain_id_2_name]:
                                    ann['chain_id_2'] = row_2[chain_id_2_name]
                                    ann['seq_id_2'] = row_2[seq_id_2_name]
                                    ann['comp_id_2'] = row_2[comp_id_2_name]
                                elif row_1[seq_id_1_name] != row_2[seq_id_2_name]:
                                    ann['seq_id_2'] = row_2[seq_id_2_name]
                                    ann['comp_id_2'] = row_2[comp_id_2_name]
                                ann['atom_id_2'] = row_2[atom_id_2_name]
                                ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                dist_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals, count_of_vals = [], []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_id_1 = id_set[i]
                                    row_id_2 = id_set[j]
                                    if row_id_2 >= len_lp_data:
                                        continue
                                    row_1 = lp_data[row_id_1]
                                    row_2 = lp_data[row_id_2]

                                    target_value_1 = get_est_target_value(row_1)
                                    target_value_2 = get_est_target_value(row_2)

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if None in (target_value_1, target_value_2):
                                        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 = get_est_target_value(row_1)

                                    if target_value is None:
                                        continue

                                    member_id = row_1.get(member_id_name) if file_type == 'nmr-star' else None

                                    chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                                        comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row_1)

                                    data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, row_id_1,
                                                                                  target_value, upper_limit, lower_limit,
                                                                                  member_id,
                                                                                  chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                                  chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                                    if data_type in _count:
                                        _count[data_type] += 1
                                    else:
                                        _count[data_type] = 1

                            if 0.0 <= v < scale and redundant:

                                target_value, upper_limit, lower_limit = get_est_value_range(row_1)

                                if target_value is None:
                                    continue

                                member_id = row_1.get(member_id_name) if file_type == 'nmr-star' else None

                                chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                                    comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row_1)

                                data_type = self.__getTypeOfDistanceRestraint(file_type, lp_data, row_id_1,
                                                                              target_value, upper_limit, lower_limit,
                                                                              member_id,
                                                                              chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                                              chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                                if data_type in _count:
                                    _count[data_type] += 1
                                else:
                                    _count[data_type] = 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals,
                                                           'number_of_values': transposed,
                                                           'annotations': dist_ann}

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)
                lp = next(lp for lp in sf.loops if lp.category == lp_category)

                ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID_1', 'Atom_ID_1', 'Auth_atom_name_1'],
                                                                      ['Comp_ID_2', 'Atom_ID_2', 'Auth_atom_name_2']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDistanceRestraint() "
                                     f"++ Error  - {str(e)}\n")

    def __calculateStatsOfCovalentBond(self, file_list_id: int, sf_framecode: str, lp_category: str, lp_data: List[dict], ent: dict
                                       ) -> None:
        """ Calculate statistics of covalent bonds.
        """

        input_source = self.__reg.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 = ITEM_NAMES_IN_DIST_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']

        def ext_atom_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name],
                    row[atom_id_1_name], row[atom_id_2_name])

        try:

            count = {}

            count_on_map, count_on_asym_map = [], []

            has_inter_chain_constraint = False

            poly_seq = input_source_dic['polymer_sequence']

            if poly_seq is not None:

                for ps in poly_seq:
                    struct_conf = self.__extractCoordStructConf(ps['chain_id'], ps['seq_id'])
                    count_on_map.append({'chain_id': ps['chain_id'], 'seq_id': ps['seq_id'], 'comp_id': ps['comp_id'],
                                         'struct_conf': struct_conf})

                if len(poly_seq) > 1:
                    for ps1, ps2 in itertools.combinations(poly_seq, 2):
                        count_on_asym_map.append({'chain_id_1': ps1['chain_id'], 'chain_id_2': ps2['chain_id'],
                                                  'seq_id_1': ps1['seq_id'], 'seq_id_2': ps2['seq_id'],
                                                  'comp_id_1': ps1['comp_id'], 'comp_id_2': ps2['comp_id'],
                                                  'struct_conf_1': self.__extractCoordStructConf(ps1['chain_id'], ps1['seq_id']),
                                                  'struct_conf_2': self.__extractCoordStructConf(ps2['chain_id'], ps2['seq_id'])})

            for idx, row in enumerate(lp_data):
                chain_id_1, chain_id_2, seq_id_1, seq_id_2, \
                    comp_id_1, comp_id_2, atom_id_1, atom_id_2 = ext_atom_names(row)

                bond = self.getNmrBondLength(chain_id_1, seq_id_1, atom_id_1, chain_id_2, seq_id_2, atom_id_2)

                if bond is None:
                    continue

                dist = next((b['distance'] for b in bond if b['model_id'] == self.__reg.representative_model_id), None)

                if dist is None:
                    dist = bond[0]['distance']

                data_type = self.__getTypeOfCovalentBond(file_type, lp_data, idx, dist,
                                                         chain_id_1, seq_id_1, comp_id_1, atom_id_1,
                                                         chain_id_2, seq_id_2, comp_id_2, atom_id_2)

                if 'hydrogen_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Hydrogen bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'disulfide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Disulfide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'diselenide_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Diselenide bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                elif 'other_bonds' in data_type and ('too close!' in data_type or 'too far!' in data_type):

                    warn = "Other bond constraint "\
                        f"({chain_id_1}:{seq_id_1}:{comp_id_1}:{atom_id_1}, {chain_id_2}:{seq_id_2}:{comp_id_2}:{atom_id_2}) "\
                        f"is too {'close each other' if 'close' in data_type else 'far apart'} ({dist}Å)."

                    self.__reg.report.warning.appendDescription('unusual_data',
                                                                {'file_name': file_name, 'sf_framecode': sf_framecode,
                                                                 'category': lp_category, 'description': warn})

                    if self.__reg.verbose:
                        self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfCovalentBond() ++ Warning  - {warn}\n")

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if poly_seq 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 poly_seq 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:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfCovalentBond() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfCovalentBond() ++ Error  - {str(e)}\n")

    def __getTypeOfDistanceRestraint(self, file_type: str, lp_data: List[dict], row_id: int,
                                     target_value: float, upper_limit: float, lower_limit: float, member_id: Optional[int],
                                     chain_id_1: str, seq_id_1: int, comp_id_1: str, atom_id_1: str,
                                     chain_id_2: str, seq_id_2: int, comp_id_2: str, atom_id_2: str) -> str:
        """ Return type of distance restraint.
        """

        item_names = ITEM_NAMES_IN_DIST_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']

        def ext_comp_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name])

        hydrogen_bond_type = disulfide_bond_type = diselenide_bond_type = other_bond_type = None

        hydrogen_bond = disulfide_bond = diselenide_bond = other_bond = symmetry = False

        if chain_id_1 != chain_id_2 or seq_id_1 != seq_id_2:

            atom_id_1_ = atom_id_1[0]
            atom_id_2_ = atom_id_2[0]

            if upper_limit is not None:
                target_value -= 0.4

            if lower_limit is not None:
                target_value += 0.4

            balanced = (upper_limit is None and lower_limit is None)\
                or (upper_limit is not None and lower_limit is not None)\
                or (upper_limit is not None and upper_limit == 0.0)\
                or (lower_limit is not None and lower_limit == 0.0)

            delta_minus = 0.1 if upper_limit is not None and lower_limit is not None else 0.0

            ambig = member_id is not None or (upper_limit is not None
                                              and (upper_limit <= DIST_AMBIG_LOW or upper_limit >= DIST_AMBIG_UP))

            if not ambig:

                if (atom_id_1_ == 'F' and atom_id_2_ in PROTON_BEGIN_CODE)\
                   or (atom_id_2_ == 'F' and atom_id_1_ in PROTON_BEGIN_CODE):

                    if 1.2 - delta_minus <= target_value <= 1.5:
                        hydrogen_bond_type = 'F...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.2 - delta_minus:
                        hydrogen_bond_type = 'F...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 2.0:
                        hydrogen_bond_type = 'F...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):

                    if 2.2 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'F...h-F'
                        hydrogen_bond = True
                    elif target_value < 2.2 - delta_minus:
                        hydrogen_bond_type = 'F...h-F (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 3.0:
                        hydrogen_bond_type = 'F...h-F (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ in PROTON_BEGIN_CODE)\
                        or (atom_id_2_ == 'O' and atom_id_1_ in PROTON_BEGIN_CODE):

                    if 1.5 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'O...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.5 - delta_minus:
                        hydrogen_bond_type = 'O...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 4.0:
                        hydrogen_bond_type = 'O...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'O...h-N'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'O...h-N (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'O...h-N (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'O...h-O'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'O...h-O (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'O...h-O (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'N' and atom_id_2_ in PROTON_BEGIN_CODE)\
                        or (atom_id_2_ == 'N' and atom_id_1_ in PROTON_BEGIN_CODE):

                    if 1.5 - delta_minus <= target_value <= 2.5:
                        hydrogen_bond_type = 'N...H-x'
                        hydrogen_bond = True
                    elif target_value < 1.5 - delta_minus:
                        hydrogen_bond_type = 'N...H-x (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 4.0:
                        hydrogen_bond_type = 'N...H-x (too far!)'
                        hydrogen_bond = True

                elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):

                    if 2.5 - delta_minus <= target_value <= 3.5:
                        hydrogen_bond_type = 'N...h_N'
                        hydrogen_bond = True
                    elif target_value < 2.5 - delta_minus:
                        hydrogen_bond_type = 'N...h_N (too close!)'
                        hydrogen_bond = True
                    elif target_value <= 5.0:
                        hydrogen_bond_type = 'N...h_N (too far!)'
                        hydrogen_bond = True

                elif atom_id_1_ == 'S' and atom_id_2_ == 'S' and not atom_id_1.startswith('SE') and not atom_id_2.startswith('SE'):

                    if 1.9 - delta_minus <= target_value <= 2.3:
                        disulfide_bond_type = 'S...S'
                        disulfide_bond = True
                    elif target_value < 1.9 - delta_minus:
                        disulfide_bond_type = 'S...S (too close!)'
                        disulfide_bond = True
                    elif target_value <= 3.6:
                        disulfide_bond_type = 'S...S (too far!)'
                        disulfide_bond = True

                elif atom_id_1.startswith('SE') and atom_id_2.startswith('SE'):

                    if 2.1 - delta_minus <= target_value <= 2.6:
                        diselenide_bond_type = 'Se...Se'
                        diselenide_bond = True
                    elif target_value < 2.1 - delta_minus:
                        diselenide_bond_type = 'Se...Se (too close!)'
                        diselenide_bond = True
                    elif target_value <= 4.2:
                        diselenide_bond_type = 'Se...Se (too far!)'
                        diselenide_bond = True

                elif (atom_id_1_ == 'N' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'N' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 1.9 - delta_minus <= target_value <= 2.1 or not balanced:
                        other_bond_type = f'N...{metal}'
                        other_bond = True
                    elif target_value < 1.9 - delta_minus:
                        other_bond_type = f'N...{metal} (too close!)'
                        other_bond = True
                    elif target_value <= 3.2:
                        other_bond_type = f'N...{metal} (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'O' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'O' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.0 - delta_minus <= target_value <= 2.2 or not balanced:
                        other_bond_type = f'O...{metal}'
                        other_bond = True
                    elif target_value < 2.0 - delta_minus:
                        other_bond_type = f'O...{metal} (too close!)'
                        other_bond = True
                    elif target_value <= 3.4:
                        other_bond_type = f'O...{metal} (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'P' and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'P' and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.1 - delta_minus <= target_value <= 2.5 or not balanced:
                        other_bond_type = f'P...{metal}'
                        other_bond = True
                    elif target_value < 2.1 - delta_minus:
                        other_bond_type = f'P...{metal} (too close!)'
                        other_bond = True
                    elif target_value <= 4.0:
                        other_bond_type = f'P...{metal} (too far!)'
                        other_bond = True

                elif (atom_id_1_ == 'S' and not atom_id_1.startswith('SE')
                      and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2_ == 'S' and not atom_id_2.startswith('SE')
                            and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.2 - delta_minus <= target_value <= 2.6 or not balanced:
                        other_bond_type = f'S...{metal}'
                        other_bond = True
                    elif target_value < 2.2 - delta_minus:
                        other_bond_type = f'S...{metal} (too close!)'
                        other_bond = True
                    elif target_value <= 4.2:
                        other_bond_type = f'S...{metal} (too far!)'
                        other_bond = True

                elif (atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2))\
                        or (atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                    metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                    metal = metal.title()

                    if 2.3 - delta_minus <= target_value <= 2.7 or not balanced:
                        other_bond_type = f'Se...{metal}'
                        other_bond = True
                    elif target_value < 2.3 - delta_minus:
                        other_bond_type = f'Se...{metal} (too close!)'
                        other_bond = True
                    elif target_value <= 4.4:
                        other_bond_type = f'Se...{metal} (too far!)'
                        other_bond = True

                elif chain_id_1 != chain_id_2:

                    for idx, row in enumerate(lp_data):

                        if idx == row_id:
                            continue

                        _chain_id_1, _chain_id_2, _seq_id_1, _seq_id_2, \
                            _comp_id_1, _comp_id_2 = ext_comp_names(row)

                        if _chain_id_1 != _chain_id_2 and _chain_id_1 != chain_id_1 and _chain_id_2 != chain_id_2:

                            if seq_id_1 == _seq_id_1 and comp_id_1 == _comp_id_1\
                               and seq_id_2 == _seq_id_2 and comp_id_2 == _comp_id_2:
                                symmetry = True
                                break

                            if seq_id_1 == _seq_id_2 and comp_id_1 == _comp_id_2\
                               and seq_id_2 == _seq_id_1 and comp_id_2 == _comp_id_1:
                                symmetry = True
                                break

        range_of_seq = abs(seq_id_1 - seq_id_2)

        if hydrogen_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_hydrogen_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_hydrogen_bonds'
            else:
                data_type = 'hydrogen_bonds'
            data_type += f'_{hydrogen_bond_type}'
        elif disulfide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_disulfide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_disulfide_bonds'
            else:
                data_type = 'disulfide_bonds'
            data_type += f'_{disulfide_bond_type}'
        elif diselenide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_diselenide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_diselenide_bonds'
            else:
                data_type = 'diselenide_bonds'
            data_type += f'_{diselenide_bond_type}'
        elif other_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_other_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_other_bonds'
            else:
                data_type = 'other_bonds'
            data_type += f'_{other_bond_type}'
        elif symmetry:
            data_type = 'symmetric_constraints'
        elif chain_id_1 != chain_id_2:
            data_type = 'inter-chain_constraints'
        elif range_of_seq == 0:
            data_type = 'intra-residue_constraints'
        elif range_of_seq < 5:

            if file_type == 'nef' or (self.isNmrAtomName(comp_id_1, atom_id_1)
                                      or self.isNmrAtomName(comp_id_2, atom_id_2)):
                _atom_id_1 = self.getAtomIdList(comp_id_1, atom_id_1)
                _atom_id_2 = self.getAtomIdList(comp_id_2, atom_id_2)

                if len(_atom_id_1) > 0 and len(_atom_id_2) > 0:
                    is_sc_atom_1 = _atom_id_1[0] in self.__reg.csStat.getSideChainAtoms(comp_id_1)
                    is_sc_atom_2 = _atom_id_2[0] in self.__reg.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.__reg.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.__reg.csStat.getBackBoneAtoms(comp_id_2)

                else:
                    is_bb_atom_1 = is_bb_atom_2 = is_sc_atom_1 = is_sc_atom_2 = False

            else:
                is_sc_atom_1 = atom_id_1 in self.__reg.csStat.getSideChainAtoms(comp_id_1)
                is_sc_atom_2 = atom_id_2 in self.__reg.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.__reg.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.__reg.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: str, lp_data: List[dict], row_id: int, target_value: float,
                                chain_id_1: str, seq_id_1: int, comp_id_1: str, atom_id_1: str,
                                chain_id_2: str, seq_id_2: int, comp_id_2: str, atom_id_2: str) -> str:
        """ Return type of covalent bond.
        """

        item_names = ITEM_NAMES_IN_DIST_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']

        def ext_comp_names(row):
            return (row[chain_id_1_name], row[chain_id_2_name],
                    row[seq_id_1_name], row[seq_id_2_name],
                    row[comp_id_1_name], row[comp_id_2_name])

        hydrogen_bond_type = disulfide_bond_type = diselenide_bond_type = other_bond_type = None

        hydrogen_bond = disulfide_bond = diselenide_bond = other_bond = symmetry = False

        if chain_id_1 != chain_id_2 or seq_id_1 != seq_id_2:

            atom_id_1_ = atom_id_1[0]
            atom_id_2_ = atom_id_2[0]

            if (atom_id_1_ == 'F' and atom_id_2_ in PROTON_BEGIN_CODE) or (atom_id_2_ == 'F' and atom_id_1_ in PROTON_BEGIN_CODE):

                if 1.2 <= target_value <= 1.5:
                    hydrogen_bond_type = 'F...H-x'
                    hydrogen_bond = True
                elif target_value < 1.2:
                    hydrogen_bond_type = 'F...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 2.0:
                    hydrogen_bond_type = 'F...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'F' and atom_id_2_ == 'F') or (atom_id_2_ == 'F' and atom_id_1_ == 'F'):

                if 2.2 <= target_value <= 2.5:
                    hydrogen_bond_type = 'F...h-F'
                    hydrogen_bond = True
                elif target_value < 2.2:
                    hydrogen_bond_type = 'F...h-F (too close!)'
                    hydrogen_bond = True
                elif target_value <= 3.0:
                    hydrogen_bond_type = 'F...h-F (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ in PROTON_BEGIN_CODE) or (atom_id_2_ == 'O' and atom_id_1_ in PROTON_BEGIN_CODE):

                if 1.5 <= target_value <= 2.5:
                    hydrogen_bond_type = 'O...H-x'
                    hydrogen_bond = True
                elif target_value < 1.5:
                    hydrogen_bond_type = 'O...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 4.0:
                    hydrogen_bond_type = 'O...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ == 'N') or (atom_id_2_ == 'O' and atom_id_1_ == 'N'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'O...h-N'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'O...h-N (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'O...h-N (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'O' and atom_id_2_ == 'O') or (atom_id_2_ == 'O' and atom_id_1_ == 'O'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'O...h-O'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'O...h-O (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'O...h-O (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'N' and atom_id_2_ in PROTON_BEGIN_CODE) or (atom_id_2_ == 'N' and atom_id_1_ in PROTON_BEGIN_CODE):

                if 1.5 <= target_value <= 2.5:
                    hydrogen_bond_type = 'N...H-x'
                    hydrogen_bond = True
                elif target_value < 1.5:
                    hydrogen_bond_type = 'N...H-x (too close!)'
                    hydrogen_bond = True
                elif target_value <= 4.0:
                    hydrogen_bond_type = 'N...H-x (too far!)'
                    hydrogen_bond = True

            elif (atom_id_1_ == 'N' and atom_id_2_ == 'N') or (atom_id_2_ == 'N' and atom_id_1_ == 'N'):

                if 2.5 <= target_value <= 3.5:
                    hydrogen_bond_type = 'N...h_N'
                    hydrogen_bond = True
                elif target_value < 2.5:
                    hydrogen_bond_type = 'N...h_N (too close!)'
                    hydrogen_bond = True
                elif target_value <= 5.0:
                    hydrogen_bond_type = 'N...h_N (too far!)'
                    hydrogen_bond = True

            elif atom_id_1_ == 'S' and atom_id_2_ == 'S' and not atom_id_1.startswith('SE') and not atom_id_2.startswith('SE'):

                if 1.9 <= target_value <= 2.3:
                    disulfide_bond_type = 'S...S'
                    disulfide_bond = True
                elif target_value < 1.9:
                    disulfide_bond_type = 'S...S (too close!)'
                    disulfide_bond = True
                elif target_value <= 3.6:
                    disulfide_bond_type = 'S...S (too far!)'
                    disulfide_bond = True

            elif atom_id_1.startswith('SE') and atom_id_2.startswith('SE'):

                if 2.1 <= target_value <= 2.6:
                    diselenide_bond_type = 'Se...Se'
                    diselenide_bond = True
                elif target_value < 2.1:
                    diselenide_bond_type = 'Se...Se (too close!)'
                    diselenide_bond = True
                elif target_value <= 4.2:
                    diselenide_bond_type = 'Se...Se (too far!)'
                    diselenide_bond = True

            elif (atom_id_1_ == 'N' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'N' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 1.9 <= target_value <= 2.1:
                    other_bond_type = f'N...{metal}'
                    other_bond = True
                elif target_value < 1.9:
                    other_bond_type = f'N...{metal} (too close!)'
                    other_bond = True
                elif target_value <= 3.2:
                    other_bond_type = f'N...{metal} (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'O' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'O' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.0 <= target_value <= 2.2:
                    other_bond_type = f'O...{metal}'
                    other_bond = True
                elif target_value < 2.0:
                    other_bond_type = f'O...{metal} (too close!)'
                    other_bond = True
                elif target_value <= 3.4:
                    other_bond_type = f'O...{metal} (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'P' and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'P' and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.1 <= target_value <= 2.5:
                    other_bond_type = f'P...{metal}'
                    other_bond = True
                elif target_value < 2.1:
                    other_bond_type = f'P...{metal} (too close!)'
                    other_bond = True
                elif target_value <= 4.0:
                    other_bond_type = f'P...{metal} (too far!)'
                    other_bond = True

            elif (atom_id_1_ == 'S' and not atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2_ == 'S' and not atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.2 <= target_value <= 2.6:
                    other_bond_type = f'S...{metal}'
                    other_bond = True
                elif target_value < 2.2:
                    other_bond_type = f'S...{metal} (too close!)'
                    other_bond = True
                elif target_value <= 4.2:
                    other_bond_type = f'S...{metal} (too far!)'
                    other_bond = True

            elif (atom_id_1.startswith('SE') and not is_non_metal_element(comp_id_2, atom_id_2))\
                    or (atom_id_2.startswith('SE') and not is_non_metal_element(comp_id_1, atom_id_1)):

                metal = atom_id_2 if is_non_metal_element(comp_id_1, atom_id_1) else atom_id_1
                metal = metal.title()

                if 2.3 <= target_value <= 2.7:
                    other_bond_type = f'Se...{metal}'
                    other_bond = True
                elif target_value < 2.3:
                    other_bond_type = f'Se...{metal} (too close!)'
                    other_bond = True
                elif target_value <= 4.4:
                    other_bond_type = f'Se...{metal} (too far!)'
                    other_bond = True

            elif chain_id_1 != chain_id_2:

                for idx, row in enumerate(lp_data):

                    if idx == row_id:
                        continue

                    _chain_id_1, _chain_id_2, _seq_id_1, _seq_id_2, \
                        _comp_id_1, _comp_id_2 = ext_comp_names(row)

                    if _chain_id_1 != _chain_id_2 and _chain_id_1 != chain_id_1 and _chain_id_2 != chain_id_2:

                        if seq_id_1 == _seq_id_1 and comp_id_1 == _comp_id_1\
                           and seq_id_2 == _seq_id_2 and comp_id_2 == _comp_id_2:
                            symmetry = True
                            break

                        if seq_id_1 == _seq_id_2 and comp_id_1 == _comp_id_2\
                           and seq_id_2 == _seq_id_1 and comp_id_2 == _comp_id_1:
                            symmetry = True
                            break

        range_of_seq = abs(seq_id_1 - seq_id_2)

        if hydrogen_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_hydrogen_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_hydrogen_bonds'
            else:
                data_type = 'hydrogen_bonds'
            data_type += f'_{hydrogen_bond_type}'
        elif disulfide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_disulfide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_disulfide_bonds'
            else:
                data_type = 'disulfide_bonds'
            data_type += f'_{disulfide_bond_type}'
        elif diselenide_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_diselenide_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_diselenide_bonds'
            else:
                data_type = 'diselenide_bonds'
            data_type += f'_{diselenide_bond_type}'
        elif other_bond:
            if chain_id_1 != chain_id_2:
                data_type = 'inter-chain_other_bonds'
            elif range_of_seq > 5:
                data_type = 'long_range_other_bonds'
            else:
                data_type = 'other_bonds'
            data_type += f'_{other_bond_type}'
        elif symmetry:
            data_type = 'symmetric_constraints'
        elif chain_id_1 != chain_id_2:
            data_type = 'inter-chain_constraints'
        elif range_of_seq == 0:
            data_type = 'intra-residue_constraints'
        elif range_of_seq < 5:

            if file_type == 'nef' or (self.isNmrAtomName(comp_id_1, atom_id_1)
                                      or self.isNmrAtomName(comp_id_2, atom_id_2)):
                _atom_id_1 = self.getAtomIdList(comp_id_1, atom_id_1)
                _atom_id_2 = self.getAtomIdList(comp_id_2, atom_id_2)

                if len(_atom_id_1) > 0 and len(_atom_id_2) > 0:
                    is_sc_atom_1 = _atom_id_1[0] in self.__reg.csStat.getSideChainAtoms(comp_id_1)
                    is_sc_atom_2 = _atom_id_2[0] in self.__reg.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.__reg.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.__reg.csStat.getBackBoneAtoms(comp_id_2)

                else:
                    is_bb_atom_1 = is_bb_atom_2 = is_sc_atom_1 = is_sc_atom_2 = False

            else:
                is_sc_atom_1 = atom_id_1 in self.__reg.csStat.getSideChainAtoms(comp_id_1)
                is_sc_atom_2 = atom_id_2 in self.__reg.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.__reg.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.__reg.csStat.getBackBoneAtoms(comp_id_2)

            is_bb_bb = is_bb_atom_1 and is_bb_atom_2
            is_bb_sc = (is_bb_atom_1 and is_sc_atom_2) or (is_sc_atom_1 and is_bb_atom_2)
            is_sc_sc = is_sc_atom_1 and is_sc_atom_2

            if range_of_seq == 1:
                data_type = 'sequential_constraints'
            else:
                data_type = 'medium_range_constraints'

            if is_bb_bb:
                data_type += '_backbone-backbone'
            elif is_bb_sc:
                data_type += '_backbone-sidechain'
            elif is_sc_sc:
                data_type += '_sidechain-sidechain'
        else:
            data_type = 'long_range_constraints'

        return data_type

    def __calculateStatsOfDihedralRestraint(self, file_list_id: int, sf_framecode: str, lp_data: List[dict],
                                            conflict_id_set: Optional[List[int]], inconsistent: Set[int],
                                            redundant: Set[int], ent: dict) -> None:
        """ Calculate statistics of dihedral angle restraints.
        """

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

        file_type = input_source_dic['file_type']

        content_subtype = 'dihed_restraint'

        index_tag = INDEX_TAGS[file_type][content_subtype]
        item_names = POTENTIAL_ITEMS[file_type][content_subtype]
        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']

        def get_est_target_value(row):
            value = row.get(target_value_name)

            if value is None:

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                elif has_key_value(row, upper_linear_limit_name):
                    value = row[upper_linear_limit_name]

                elif has_key_value(row, upper_limit_name):
                    value = row[upper_limit_name]

                elif has_key_value(row, lower_linear_limit_name):
                    value = row[lower_linear_limit_name]

                elif has_key_value(row, lower_limit_name):
                    value = row[lower_limit_name]

            return value

        dh_item_names = ITEM_NAMES_IN_DIHED_LOOP[file_type]
        combination_id_name = dh_item_names['combination_id']
        chain_id_1_name = dh_item_names['chain_id_1']
        chain_id_2_name = dh_item_names['chain_id_2']
        chain_id_3_name = dh_item_names['chain_id_3']
        chain_id_4_name = dh_item_names['chain_id_4']
        seq_id_1_name = dh_item_names['seq_id_1']
        seq_id_2_name = dh_item_names['seq_id_2']
        seq_id_3_name = dh_item_names['seq_id_3']
        seq_id_4_name = dh_item_names['seq_id_4']
        comp_id_1_name = dh_item_names['comp_id_1']
        comp_id_2_name = dh_item_names['comp_id_2']
        comp_id_3_name = dh_item_names['comp_id_3']
        comp_id_4_name = dh_item_names['comp_id_4']
        atom_id_1_name = dh_item_names['atom_id_1']
        atom_id_2_name = dh_item_names['atom_id_2']
        atom_id_3_name = dh_item_names['atom_id_3']
        atom_id_4_name = dh_item_names['atom_id_4']
        angle_type_name = dh_item_names['angle_type']
        weight_name = WEIGHT_TAGS[file_type][content_subtype]
        id_tag = CONSIST_ID_TAGS[file_type][content_subtype]

        def ext_atoms(row):
            return ({'chain_id': row[chain_id_1_name], 'seq_id': row[seq_id_1_name],
                     'comp_id': row[comp_id_1_name], 'atom_id': row[atom_id_1_name]},
                    {'chain_id': row[chain_id_2_name], 'seq_id': row[seq_id_2_name],
                     'comp_id': row[comp_id_2_name], 'atom_id': row[atom_id_2_name]},
                    {'chain_id': row[chain_id_3_name], 'seq_id': row[seq_id_3_name],
                     'comp_id': row[comp_id_3_name], 'atom_id': row[atom_id_3_name]},
                    {'chain_id': row[chain_id_4_name], 'seq_id': row[seq_id_4_name],
                     'comp_id': row[comp_id_4_name], 'atom_id': row[atom_id_4_name]})

        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 = [], [], [], [], []

            poly_seq = input_source_dic['polymer_sequence']

            if poly_seq is not None:

                for ps in poly_seq:
                    struct_conf = self.__extractCoordStructConf(ps['chain_id'], ps['seq_id'])
                    value_per_residue.append({'chain_id': ps['chain_id'], 'seq_id': ps['seq_id'], 'comp_id': ps['comp_id'],
                                              'struct_conf': struct_conf})

            for row in lp_data:
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)

                target_value = row.get(target_value_name)

                if target_value is None:

                    if has_key_value(row, lower_limit_name)\
                            and has_key_value(row, upper_limit_name):
                        target_value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                    elif has_key_value(row, lower_linear_limit_name)\
                            and has_key_value(row, upper_linear_limit_name):
                        target_value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                    else:
                        continue

                target_value = float(f"{target_value:.1f}")

                while target_value > 180.0:
                    target_value -= 360.0
                while target_value < -180.0:
                    target_value += 360.0

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    lower_limit = row[lower_limit_name]
                    upper_limit = row[upper_limit_name]

                    while lower_limit - target_value > 180.0:
                        lower_limit -= 360.0
                    while lower_limit - target_value < -180.0:
                        lower_limit += 360.0

                    while upper_limit - target_value > 180.0:
                        upper_limit -= 360.0
                    while upper_limit - target_value < -180.0:
                        upper_limit += 360.0

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    lower_limit = row[lower_linear_limit_name]
                    upper_limit = row[upper_linear_limit_name]

                    while lower_limit - target_value > 180.0:
                        lower_limit -= 360.0
                    while lower_limit - target_value < -180.0:
                        lower_limit += 360.0

                    while upper_limit - target_value > 180.0:
                        upper_limit -= 360.0
                    while upper_limit - target_value < -180.0:
                        upper_limit += 360.0

                else:
                    lower_limit = upper_limit = None

                data_type = row[angle_type_name]

                if data_type == 'PPA':
                    continue

                atom1, atom2, atom3, atom4 = ext_atoms(row)

                weight = row.get(weight_name)
                set_id.add(row[id_tag])

                peptide, nucleotide, carbohydrate = self.__reg.csStat.getTypeOfCompId(atom2['comp_id'])
                plane_like = is_like_planality_boundary(row, lower_limit_name, upper_limit_name)

                data_type =\
                    self.getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                    [atom1, atom2, atom3, atom4], plane_like)

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in 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

                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

                chain_id = atom1['chain_id']
                seq_ids = [atom1['seq_id'], atom2['seq_id'], atom3['seq_id'], atom4['seq_id']]
                comp_ids = [atom1['comp_id'], atom2['comp_id'], atom3['comp_id'], atom4['comp_id']]
                seq_id_common = collections.Counter(seq_ids).most_common()
                comp_id_common = collections.Counter(comp_ids).most_common()

                if data_type.startswith('phi_'):
                    phi = {}
                    phi['chain_id'] = chain_id
                    phi['seq_id'] = seq_id_common[0][0]
                    phi['comp_id'] = comp_id_common[0][0]
                    phi['value'] = target_value
                    phi['error'] = None if None in (lower_limit, upper_limit) else [lower_limit, upper_limit]
                    phi_list.append(phi)

                elif data_type.startswith('psi_'):
                    psi = {}
                    psi['chain_id'] = chain_id
                    psi['seq_id'] = seq_id_common[0][0]
                    psi['comp_id'] = comp_id_common[0][0]
                    psi['value'] = target_value
                    psi['error'] = None if None in (lower_limit, upper_limit) else [lower_limit, upper_limit]
                    psi_list.append(psi)

                elif data_type.startswith('chi1_'):
                    chi1 = {}
                    chi1['chain_id'] = chain_id
                    chi1['seq_id'] = seq_ids[0]
                    chi1['comp_id'] = comp_ids[0]
                    chi1['value'] = target_value
                    chi1['error'] = None if None in (lower_limit, upper_limit) else [lower_limit, upper_limit]
                    chi1_list.append(chi1)

                elif data_type.startswith('chi2_'):
                    chi2 = {}
                    chi2['chain_id'] = chain_id
                    chi2['seq_id'] = seq_ids[0]
                    chi2['comp_id'] = comp_ids[0]
                    chi2['value'] = target_value
                    chi2['error'] = None if None in (lower_limit, upper_limit) 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

                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'parabolic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if poly_seq 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 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 = f'{data_type}_{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 poly_seq 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'],
                                                       f"{phi['chain_id']}:{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'],
                                                         f"{chi1['chain_id']}:{chi1['seq_id']}:{chi1['comp_id']}"])

                        if (chi1['error'] is not None) or (chi2['error'] is not None):

                            if comp_id not in chi1_chi2_error:
                                chi1_chi2_error[comp_id] = []

                            chi1_chi2_error[comp_id].append([chi1['value'], chi2['value'],
                                                            None if chi1['error'] is None else chi1['error'][0],
                                                            None if chi1['error'] is None else chi1['error'][1],
                                                            None if chi2['error'] is None else chi2['error'][0],
                                                            None if chi2['error'] is None else chi2['error'][1]])

                if len(chi1_chi2_value) > 0:

                    chi1_chi2_plot = {}

                    chi1_chi2_plot['values'] = chi1_chi2_value

                    if len(chi1_chi2_error) > 0:
                        chi1_chi2_plot['errors'] = chi1_chi2_error

                    ent['chi1_chi2_plot'] = chi1_chi2_plot

            if conflict_id_set is not None:

                max_inclusive = ANGLE_UNCERT_MAX

                max_val = 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 = get_est_target_value(row_1)
                            target_value_2 = get_est_target_value(row_2)

                            if None in (target_value_1, target_value_2):
                                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))

                            atom1, atom2, atom3, atom4 = ext_atoms(row_1)

                            data_type = row_1[angle_type_name]

                            peptide, nucleotide, carbohydrate = self.__reg.csStat.getTypeOfCompId(atom2['comp_id'])
                            plane_like = is_like_planality_boundary(row_1, lower_limit_name, upper_limit_name)

                            data_type = self.getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                        [atom1, atom2, atom3, atom4], plane_like)[0]

                            if data_type.startswith('phi') or data_type.startswith('psi') or data_type.startswith('omega'):

                                max_val = max(max_val, discrepancy)

                                if discrepancy > max_inclusive * INCONSIST_OVER_CONFLICTED:
                                    ann = {}
                                    ann['level'] = 'conflicted' if discrepancy > max_inclusive else 'inconsistent'
                                    ann['chain_id'] = atom2['chain_id']
                                    ann['seq_id'] = atom2['seq_id']
                                    ann['comp_id'] = atom2['comp_id']
                                    ann['atom_id_1'] = atom1['atom_id']
                                    ann['atom_id_2'] = atom2['atom_id']
                                    ann['atom_id_3'] = atom3['atom_id']
                                    ann['atom_id_4'] = atom4['atom_id']
                                    ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                    dihed_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals, count_of_vals = [], []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_1 = lp_data[id_set[i]]
                                    row_2 = lp_data[id_set[j]]

                                    target_value_1 = get_est_target_value(row_1)
                                    target_value_2 = get_est_target_value(row_2)

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if None in (target_value_1, target_value_2):
                                        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

                                    atom1, atom2, atom3, atom4 = ext_atoms(row_1)

                                    peptide, nucleotide, carbohydrate = self.__reg.csStat.getTypeOfCompId(atom2['comp_id'])
                                    plane_like = is_like_planality_boundary(row_1, lower_limit_name, upper_limit_name)

                                    data_type = self.getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                                [atom1, atom2, atom3, atom4], plane_like)[0]

                                    if data_type in _count:
                                        _count[data_type] += 1
                                    else:
                                        _count[data_type] = 1

                            if 0.0 <= v < scale and redundant:

                                atom1, atom2, atom3, atom4 = ext_atoms(row_1)

                                peptide, nucleotide, carbohydrate = self.__reg.csStat.getTypeOfCompId(atom2['comp_id'])
                                plane_like = is_like_planality_boundary(row_1, lower_limit_name, upper_limit_name)

                                data_type = self.getTypeOfDihedralRestraint(data_type, peptide, nucleotide, carbohydrate,
                                                                            [atom1, atom2, atom3, atom4], plane_like)[0]

                                if data_type in _count:
                                    _count[data_type] += 1
                                else:
                                    _count[data_type] = 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals,
                                                           'number_of_values': transposed,
                                                           'annotations': dihed_ann}

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)
                lp = next(lp for lp in sf.loops if lp.category == lp_category)

                ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID_1', 'Atom_ID_1', 'Auth_atom_name_1'],
                                                                      ['Comp_ID_2', 'Atom_ID_2', 'Auth_atom_name_2'],
                                                                      ['Comp_ID_3', 'Atom_ID_3', 'Auth_atom_name_3'],
                                                                      ['Comp_ID_4', 'Atom_ID_4', 'Auth_atom_name_4']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfDihedralRestraint() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfDihedralRestraint() "
                                     f"++ Error  - {str(e)}\n")

    def __calculateStatsOfRdcRestraint(self, file_list_id: int, sf_framecode: str, lp_data: List[dict],
                                       conflict_id_set: List[int], inconsistent: Set[int], redundant: Set[int], ent: dict
                                       ) -> None:
        """ Calculate statistics of RDC restraints.
        """

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

        file_type = input_source_dic['file_type']

        content_subtype = 'rdc_restraint'

        index_tag = INDEX_TAGS[file_type][content_subtype]
        item_names = 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']

        def get_est_target_value(row):
            value = row.get(target_value_name)

            if value is None:

                if has_key_value(row, lower_limit_name)\
                        and has_key_value(row, upper_limit_name):
                    value = (row[lower_limit_name] + row[upper_limit_name]) / 2.0

                elif has_key_value(row, lower_linear_limit_name)\
                        and has_key_value(row, upper_linear_limit_name):
                    value = (row[lower_linear_limit_name] + row[upper_linear_limit_name]) / 2.0

                elif has_key_value(row, upper_linear_limit_name):
                    value = row[upper_linear_limit_name]

                elif has_key_value(row, upper_limit_name):
                    value = row[upper_limit_name]

                elif has_key_value(row, lower_linear_limit_name):
                    value = row[lower_linear_limit_name]

                elif has_key_value(row, lower_limit_name):
                    value = row[lower_limit_name]

            return value

        try:

            max_val = min_val = 0.0

            max_val_ = -100.0
            min_val_ = 100.0

            for row in lp_data:
                target_value = get_est_target_value(row)

                if target_value is None:
                    continue

                max_val = max(max_val, target_value)
                min_val = min(min_val, target_value)

                max_val_ = max(max_val_, target_value)
                min_val_ = min(min_val_, target_value)

            item_names = ITEM_NAMES_IN_RDC_LOOP[file_type]
            combination_id_name = item_names['combination_id']
            chain_id_1_name = item_names['chain_id_1']
            seq_id_1_name = item_names['seq_id_1']
            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']
            weight_name = WEIGHT_TAGS[file_type][content_subtype]
            id_tag = CONSIST_ID_TAGS[file_type][content_subtype]

            def ext_atom_names(row):
                return (row[chain_id_1_name], row[seq_id_1_name],
                        row[atom_id_1_name], row[atom_id_2_name])

            count, comb_count, inco_count, redu_count, weights, potential_types =\
                {}, {}, {}, {}, {}, {}

            set_id = set()

            value_per_residue = []

            poly_seq = input_source_dic['polymer_sequence']

            if poly_seq is not None:

                for ps in poly_seq:
                    struct_conf = self.__extractCoordStructConf(ps['chain_id'], ps['seq_id'])
                    value_per_residue.append({'chain_id': ps['chain_id'], 'seq_id': ps['seq_id'], 'comp_id': ps['comp_id'],
                                              'struct_conf': struct_conf})

            for row in lp_data:
                index = row.get(index_tag)
                combination_id = row.get(combination_id_name)

                chain_id_1, seq_id_1, atom_id_1, atom_id_2 = ext_atom_names(row)

                weight = row.get(weight_name)
                set_id.add(row[id_tag])

                data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                if data_type in count:
                    count[data_type] += 1
                else:
                    count[data_type] = 1

                if (combination_id is not None) and (combination_id not in 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 = row.get(target_value_name)
                lower_limit = row.get(lower_limit_name)
                upper_limit = row.get(upper_limit_name)
                lower_linear_limit = row.get(lower_linear_limit_name)
                upper_linear_limit = row.get(upper_linear_limit_name)

                if (lower_limit is not None) and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'square-well-parabolic'
                elif (lower_limit is not None) and (upper_limit is not None)\
                        and (lower_linear_limit is not None) and (upper_linear_limit is not None):
                    potential_type = 'square-well-parabolic-linear'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'upper-bound-parabolic'
                elif (lower_limit is not None) and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic'
                elif lower_limit is None and (upper_limit is not None)\
                        and lower_linear_limit is None and (upper_linear_limit is not None):
                    potential_type = 'upper-bound-parabolic-linear'
                elif (lower_limit is not None) and upper_limit is None\
                        and (lower_linear_limit is not None) and upper_linear_limit is None:
                    potential_type = 'lower-bound-parabolic-linear'
                elif (target_value is not None) and lower_limit is None and upper_limit is None\
                        and lower_linear_limit is None and upper_linear_limit is None:
                    potential_type = 'parabolic'
                else:
                    potential_type = 'undefined'

                if potential_type is not None:
                    if data_type not in potential_types:
                        potential_types[data_type] = []
                    potential_types[data_type].append(potential_type)

                if poly_seq 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 = f'{data_type}_{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 poly_seq is not None:
                ent['constraints_per_residue'] = value_per_residue
            ent['range'] = {'max_value': float(f'{max_val_:.2f}'), 'min_value': float(f'{min_val_:.2f}')}
            if len(weights) > 0:
                _weights = {}
                for k, v in weights.items():
                    _weights[k] = collections.Counter(v).most_common()
                ent['weight_of_constraints'] = _weights
            if len(potential_types) > 0:
                _potential_types = {}
                for k, v in potential_types.items():
                    _potential_types[k] = collections.Counter(v).most_common()
                ent['potential_type_of_constraints'] = _potential_types

            target_scale = (max_val - min_val) / 12.0

            if target_scale <= 0.0:
                return

            scale = 1.0

            while scale < target_scale:
                scale *= 2.0

            while scale > target_scale:
                scale /= 2.0

            range_of_vals, count_of_vals = [], []

            v = 0.0
            while v < min_val:
                v += scale

            while v > min_val:
                v -= scale

            while v <= max_val:

                _count = count.copy()

                for k in count:
                    _count[k] = 0

                for row in lp_data:
                    target_value = get_est_target_value(row)

                    if targe_value is None or target_value < v or target_value >= v + scale:
                        continue

                    atom_id_1 = row[atom_id_1_name]
                    atom_id_2 = row[atom_id_2_name]

                    data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                    if data_type in _count:
                        _count[data_type] += 1
                    else:
                        _count[data_type] = 1

                range_of_vals.append(v)
                count_of_vals.append(_count)

                v += scale

            transposed = {}

            for k in count:
                transposed[k] = []

                for count_of_val in count_of_vals:
                    transposed[k].append(count_of_val[k])

            if len(range_of_vals) > 1:
                ent['histogram'] = {'range_of_values': range_of_vals, 'number_of_values': transposed}

            if conflict_id_set is not None:

                max_inclusive = RDC_UNCERT_MAX

                max_val = 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 = get_est_target_value(row_1)
                            target_value_2 = get_est_target_value(row_2)

                            if None in (target_value_1, target_value_2):
                                continue

                            if target_value_1 == target_value_2:
                                continue

                            discrepancy = abs(target_value_1 - target_value_2)

                            max_val = max(max_val, discrepancy)

                            if discrepancy > max_inclusive * INCONSIST_OVER_CONFLICTED:
                                ann = {}
                                ann['level'] = 'conflicted' if discrepancy > max_inclusive else 'inconsistent'
                                ann['chain_id'] = row_1[chain_id_1_name]
                                ann['seq_id'] = row_1[seq_id_1_name]
                                ann['comp_id'] = row_1[comp_id_1_name]
                                ann['atom_id_1'] = row_1[atom_id_1_name]
                                ann['atom_id_2'] = row_1[atom_id_2_name]
                                ann['discrepancy'] = float(f"{discrepancy:.1f}")

                                rdc_ann.append(ann)

                if max_val > 0.0:
                    target_scale = (max_val - min_val) / 10.0

                    scale = 1.0

                    while scale < target_scale:
                        scale *= 2.0

                    while scale > target_scale:
                        scale /= 2.0

                    range_of_vals, count_of_vals = [], []

                    v = 0.0
                    while v < min_val:
                        v += scale

                    while v > min_val:
                        v -= scale

                    while v <= max_val:

                        _count = count.copy()

                        for k in count:
                            _count[k] = 0

                        for id_set in conflict_id_set:
                            len_id_set = len(id_set)

                            if len_id_set < 2:
                                continue

                            redundant = True

                            for i in range(len_id_set - 1):

                                for j in range(i + 1, len_id_set):
                                    row_1 = lp_data[id_set[i]]
                                    row_2 = lp_data[id_set[j]]

                                    target_value_1 = get_est_target_value(row_1)
                                    target_value_2 = get_est_target_value(row_2)

                                    if target_value_1 is None and target_value_2 is None:
                                        continue

                                    if None in (target_value_1, target_value_2):
                                        redundant = False
                                        continue

                                    if target_value_1 == target_value_2:
                                        continue

                                    redundant = False

                                    discrepancy = abs(target_value_1 - target_value_2)

                                    if discrepancy < v or discrepancy >= v + scale:
                                        continue

                                    atom_id_1 = row_1[atom_id_1_name]
                                    atom_id_2 = row_1[atom_id_2_name]

                                    data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                                    _count[data_type] += 1

                            if 0.0 <= v < scale and redundant:

                                atom_id_1 = row_1[atom_id_1_name]
                                atom_id_2 = row_1[atom_id_2_name]

                                data_type = self.__getTypeOfRdcRestraint(atom_id_1, atom_id_2)

                                _count[data_type] += 1

                        range_of_vals.append(v)
                        count_of_vals.append(_count)

                        v += scale

                    transposed = {}

                    for k in count:
                        transposed[k] = []

                        for count_of_val in count_of_vals:
                            transposed[k].append(count_of_val[k])

                    if len(range_of_vals) > 1:
                        ent['histogram_of_discrepancy'] = {'range_of_values': range_of_vals,
                                                           'number_of_values': transposed,
                                                           'annotations': rdc_ann}

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)
                lp = next(lp for lp in sf.loops if lp.category == lp_category)

                ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID_1', 'Atom_ID_1', 'Auth_atom_name_1'],
                                                                      ['Comp_ID_2', 'Atom_ID_2', 'Auth_atom_name_2']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfRdcRestraint() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfRdcRestraint() ++ Error  - {str(e)}\n")

    def __getTypeOfRdcRestraint(self, atom_id_1: str, atom_id_2: str) -> str:  # pylint: disable=no-self-use
        """ Return type of RDC restraint.
        """

        try:
            iso_number_1 = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id_1[0]][0]
            iso_number_2 = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_id_2[0]][0]
        except KeyError:
            pass

        if iso_number_1 < iso_number_2:
            vector_type = f'{atom_id_1}-{atom_id_2}'
        elif iso_number_2 < iso_number_1:
            vector_type = f'{atom_id_2}-{atom_id_1}'
        else:
            sorted_atom_ids = sorted([atom_id_1, atom_id_2])
            vector_type = f'{sorted_atom_ids[0]}-{sorted_atom_ids[1]}'

        return f'{vector_type}_bond_vectors'

    def __calculateStatsOfSpectralPeak(self, file_list_id: int, sf_framecode: str,
                                       num_dim: int, lp_data: Optional[List[dict]], ent: dict) -> None:
        """ Calculate statistics of spectral peaks.
        """

        input_source = self.__reg.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 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'] = []
            ent['spectral_dim_transfer'] = []

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == AUX_LP_CATEGORIES[file_type][content_subtype][1]), None)

            mag_link = []

            if aux_data is not None:
                for sp_dim_trans in aux_data:
                    if file_type == 'nef':
                        if sp_dim_trans['transfer_type'] == 'onebond':
                            # or sp_dim_trans['transfer_type'].startswith('j')
                            # or sp_dim_trans['transfer_type'].startswith('relayed'):
                            dim_1 = sp_dim_trans['dimension_1']
                            dim_2 = sp_dim_trans['dimension_2']
                            mag_link.append((dim_1, dim_2))
                        ent['spectral_dim_transfer'].append({'id_1': sp_dim_trans['dimension_1'],
                                                             'id_2': sp_dim_trans['dimension_2'],
                                                             'indirect': sp_dim_trans.get('is_indirect'),
                                                             'type': sp_dim_trans.get('transfer_type')})
                    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))
                        ent['spectral_dim_transfer'].append({'id_1': sp_dim_trans['Spectral_dim_ID_1'],
                                                             'id_2': sp_dim_trans['Spectral_dim_ID_2'],
                                                             'indirect': sp_dim_trans.get('Indirect'),
                                                             'type': sp_dim_trans.get('Type')})

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == AUX_LP_CATEGORIES[file_type][content_subtype][0]), None)

            if aux_data is not None:
                for i in range(1, max_dim):
                    for sp_dim in aux_data:
                        sp_freq = center_point = under_sampling_type = encoding_code = encoded_src_dim_id = mag_link_id = None
                        if file_type == 'nef':
                            if sp_dim['dimension_id'] != i:
                                continue
                            axis_code = sp_dim['axis_code']
                            atom_type = ''.join(j for j in axis_code if not j.isdigit())
                            atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                            axis_unit = 'Hz' if 'axis_unit' not in sp_dim else sp_dim['axis_unit']
                            first_point = sp_dim.get('value_first_point')
                            sp_width = sp_dim.get('spectral_width')
                            if 'spectrometer_frequency' in sp_dim:
                                sp_freq = sp_dim['spectrometer_frequency']
                            if 'folding' in sp_dim:
                                under_sampling_type = sp_dim['folding']
                        else:
                            if sp_dim['ID'] != i:
                                continue
                            axis_code = sp_dim['Axis_code']
                            atom_type = sp_dim.get('Atom_type')
                            if atom_type in EMPTY_VALUE:
                                atom_type = ''.join(j for j in axis_code if not j.isdigit())
                            atom_isotope_number = sp_dim.get('Atom_isotope_number')
                            if atom_isotope_number in EMPTY_VALUE:
                                if atom_type not in EMPTY_VALUE and atom_type[0] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                    atom_isotope_number = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type[0]][0]
                                else:
                                    try:
                                        atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                                    except ValueError:
                                        pass
                            axis_unit = 'Hz' if 'Sweep_width_units' not in sp_dim else sp_dim['Sweep_width_units']
                            first_point = sp_dim.get('Value_first_point')
                            sp_width = sp_dim.get('Sweep_width')
                            if 'Spectrometer_frequency' in sp_dim:
                                sp_freq = sp_dim['Spectrometer_frequency']
                            if 'Under_sampling_type' in sp_dim:
                                under_sampling_type = sp_dim['Under_sampling_type']
                            if 'Center_frequency_offset' in sp_dim:
                                center_point = sp_dim['Center_frequency_offset']
                                if center_point in EMPTY_VALUE:
                                    center_point = None
                            if 'Encoding_code' in sp_dim:
                                encoding_code = sp_dim['Encoding_code']
                                if encoding_code in 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 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 EMPTY_VALUE:
                                    mag_link_id = None

                        if sp_freq is not None and sp_freq in EMPTY_VALUE:
                            sp_freq = None

                        if center_point is None:
                            center_point = None if None in (first_point, sp_width) else (first_point - sp_width / 2.0)

                        if under_sampling_type is not None and under_sampling_type in 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 None not in (sp_freq, first_point, center_point, sp_width):
                            first_point /= sp_freq
                            center_point /= sp_freq
                            sp_width /= sp_freq

                        last_point = None if None in (first_point, sp_width) else (first_point - sp_width)

                        if None in (center_point, last_point):
                            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 = _sp_dim.get('Atom_type')
                                        if _atom_type in EMPTY_VALUE:
                                            _atom_type = ''.join(j for j in _axis_code if not j.isdigit())

                                    if _atom_type == 'C':
                                        _center_point = None
                                        if file_type == 'nef':
                                            _axis_unit = _sp_dim.get('axis_unit', 'Hz')
                                            _first_point = _sp_dim.get('value_first_point')
                                            _sp_width = None if 'axis_unit' not in _sp_dim else _sp_dim.get('spectral_width')
                                            if 'spectrometer_frequency' in _sp_dim:
                                                _sp_freq = _sp_dim['spectrometer_frequency']
                                        else:
                                            _axis_unit = _sp_dim.get('Sweep_width_units', 'Hz')
                                            _first_point = _sp_dim.get('Value_first_point')
                                            _sp_width = None if 'Sweep_width_units' not in _sp_dim else _sp_dim.get('Sweep_width')
                                            if 'Spectrometer_frequency' in _sp_dim:
                                                _sp_freq = _sp_dim['Spectrometer_frequency']
                                            if 'Center_frequency_offset' in _sp_dim:
                                                _center_point = _sp_dim['Center_frequency_offset']
                                                if _center_point in EMPTY_VALUE:
                                                    _center_point = None

                                        if _sp_freq is not None and _sp_freq in EMPTY_VALUE:
                                            _sp_freq = None

                                        if _center_point is None:
                                            _center_point = None if None in (_first_point, _sp_width)\
                                                else (_first_point - _sp_width / 2.0)

                                        if _axis_unit == 'Hz' and None not in (_sp_freq, _first_point, _center_point, _sp_width):
                                            _first_point /= _sp_freq
                                            _center_point /= _sp_freq
                                            _sp_width /= _sp_freq

                                        _last_point = None if None in (_first_point, _sp_width) else (_first_point - _sp_width)

                                        if None in (_center_point, _last_point):
                                            spectral_region = 'H'
                                        elif C_AROMATIC_CENTER_MIN_TOR < _center_point <= C_AROMATIC_CENTER_MAX\
                                                and _sp_width < 60.0:
                                            spectral_region = 'H-aromatic'
                                        elif C_METHYL_CENTER_MIN < _center_point <= C_METHYL_CENTER_MAX\
                                                and _sp_width < 30.0:
                                            spectral_region = 'H-methyl'
                                        elif C_ALIPHATIC_CENTER_MIN < _center_point <= C_ALIPHATIC_CENTER_MAX\
                                                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 C_CARBONYL_CENTER_MIN <= center_point <= C_CARBONYL_CENTER_MAX:
                                spectral_region = 'CO'
                            elif C_AROMATIC_CENTER_MIN_TOR < center_point <= C_AROMATIC_CENTER_MAX and sp_width < 60.0:
                                spectral_region = 'C-aromatic'
                            elif C_METHYL_CENTER_MIN < center_point <= C_METHYL_CENTER_MAX and sp_width < 30.0:
                                spectral_region = 'C-methyl'
                            elif C_ALIPHATIC_CENTER_MIN < center_point <= C_ALIPHATIC_CENTER_MAX 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

            if lp_data is not None:

                count = {'assigned_spectral_peaks': 0, 'unassigned_spectral_peaks': 0}

                for j in range(num_dim):
                    chain_id_names.append(item_names[j]['chain_id'])
                    seq_id_names.append(item_names[j]['seq_id'])
                    comp_id_names.append(item_names[j]['comp_id'])
                    atom_id_names.append(item_names[j]['atom_id'])

                for row in lp_data:

                    has_assignment = True

                    for j in range(num_dim):

                        if not (chain_id_names[j] in row and seq_id_names[j] in row
                                and comp_id_names[j] in row and atom_id_names[j] in row):
                            has_assignment = False
                            break

                        chain_id = row[chain_id_names[j]]
                        seq_id = row[seq_id_names[j]]
                        comp_id = row[comp_id_names[j]]
                        atom_id = row[atom_id_names[j]]

                        if chain_id in EMPTY_VALUE or seq_id in EMPTY_VALUE or comp_id in EMPTY_VALUE or atom_id in 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

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)

                try:

                    lp = next(lp for lp in sf.loops if lp.category == lp_category)

                    list_of_tags = []
                    for dim in range(1, max_dim):
                        list_of_tags.append([f'Comp_ID_{dim}', f'Atom_ID_{dim}', f'Auth_atom_ID_{dim}'])

                    ent['atom_name_mapping'] = get_atom_name_mapping(lp, list_of_tags)

                except StopIteration:

                    lp_category = '_Assigned_peak_chem_shift'
                    lp = next((lp for lp in sf.loops if lp.category == lp_category), None)

                    if lp is not None:
                        ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID', 'Atom_ID', 'Auth_atom_ID']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfSpectralPeak() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfSpectralPeak() ++ Error  - {str(e)}\n")

    def __calculateStatsOfSpectralPeakAlt(self, file_list_id: int, sf_framecode: str, num_dim: int, lp_data: List[dict], ent: dict
                                          ) -> None:
        """ Calculate statistics of spectral peaks.
        """

        input_source = self.__reg.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 = ITEM_NAMES_IN_CS_LOOP[file_type]
        chain_id_name = item_names['chain_id']
        seq_id_name = item_names['seq_id']
        comp_id_name = item_names['comp_id']
        atom_id_name = item_names['atom_id']

        try:

            ent['number_of_spectral_dimensions'] = num_dim
            ent['spectral_dim'] = []
            ent['spectral_dim_transfer'] = []

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == AUX_LP_CATEGORIES[file_type][content_subtype][1]), None)

            mag_link = []

            if aux_data is not None:
                for sp_dim_trans in aux_data:
                    if sp_dim_trans['Type'] == 'onebond':
                        # or sp_dim_trans['Type'].startswith('j') or sp_dim_trans['Type'].startswith('relayed'):
                        dim_1 = sp_dim_trans['Spectral_dim_ID_1']
                        dim_2 = sp_dim_trans['Spectral_dim_ID_2']
                        mag_link.append((dim_1, dim_2))
                    ent['spectral_dim_transfer'].append({'id_1': sp_dim_trans['Spectral_dim_ID_1'],
                                                         'id_2': sp_dim_trans['Spectral_dim_ID_2'],
                                                         'indirect': sp_dim_trans.get('Indirect'),
                                                         'type': sp_dim_trans.get('Type')})

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == AUX_LP_CATEGORIES[file_type][content_subtype][0]), None)

            if aux_data is not None:
                for i in range(1, max_dim):
                    for sp_dim in aux_data:
                        sp_freq = center_point = under_sampling_type = encoding_code = encoded_src_dim_id = mag_link_id = None
                        if sp_dim['ID'] != i:
                            continue
                        axis_code = sp_dim['Axis_code']
                        atom_type = sp_dim.get('Atom_type')
                        if atom_type in EMPTY_VALUE:
                            atom_type = ''.join(j for j in axis_code if not j.isdigit())
                        atom_isotope_number = sp_dim.get('Atom_isotope_number')
                        if atom_isotope_number in EMPTY_VALUE:
                            if atom_type not in EMPTY_VALUE and atom_type[0] in ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS:
                                atom_isotope_number = ISOTOPE_NUMBERS_OF_NMR_OBS_NUCS[atom_type[0]][0]
                            else:
                                try:
                                    atom_isotope_number = int(''.join(j for j in axis_code if j.isdigit()))
                                except ValueError:
                                    pass
                        axis_unit = 'Hz' if 'Sweep_width_units' not in sp_dim else sp_dim['Sweep_width_units']
                        first_point = sp_dim.get('Value_first_point')
                        sp_width = sp_dim.get('Sweep_width')
                        if 'Spectrometer_frequency' in sp_dim:
                            sp_freq = sp_dim['Spectrometer_frequency']
                        if 'Under_sampling_type' in sp_dim:
                            under_sampling_type = sp_dim['Under_sampling_type']
                        if 'Center_frequency_offset' in sp_dim:
                            center_point = sp_dim['Center_frequency_offset']
                            if center_point in EMPTY_VALUE:
                                center_point = None
                        if 'Encoding_code' in sp_dim:
                            encoding_code = sp_dim['Encoding_code']
                            if encoding_code in 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 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 EMPTY_VALUE:
                                mag_link_id = None

                        if sp_freq is not None and sp_freq in EMPTY_VALUE:
                            sp_freq = None

                        if center_point is None:
                            center_point = None if None in (first_point, sp_width) else (first_point - sp_width / 2.0)

                        if under_sampling_type is not None and under_sampling_type in 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 None not in (sp_freq, first_point, center_point, sp_width):
                            first_point /= sp_freq
                            center_point /= sp_freq
                            sp_width /= sp_freq

                        last_point = None if None in (first_point, sp_width) else (first_point - sp_width)

                        if None in (center_point, last_point):
                            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 = _sp_dim.get('Atom_type')
                                    if _atom_type in EMPTY_VALUE:
                                        _atom_type = ''.join(j for j in _axis_code if not j.isdigit())

                                    if _atom_type == 'C':
                                        _center_point = None
                                        _axis_unit = _sp_dim.get('Sweep_width_units', 'Hz')
                                        _first_point = _sp_dim.get('Value_first_point')
                                        _sp_width = None if 'Sweep_width_units' not in _sp_dim else _sp_dim.get('Sweep_width')
                                        if 'Spectrometer_frequency' in _sp_dim:
                                            _sp_freq = _sp_dim['Spectrometer_frequency']
                                        if 'Center_frequency_offset' in _sp_dim:
                                            _center_point = _sp_dim['Center_frequency_offset']
                                            if _center_point in EMPTY_VALUE:
                                                _center_point = None

                                        if _sp_freq is not None and _sp_freq in EMPTY_VALUE:
                                            _sp_freq = None

                                        if _center_point is None:
                                            _center_point = None if None in (_first_point, _sp_width)\
                                                else (_first_point - _sp_width / 2.0)

                                        if _axis_unit == 'Hz' and None not in (_sp_freq, _first_point, _center_point, _sp_width):
                                            _first_point /= _sp_freq
                                            _center_point /= _sp_freq
                                            _sp_width /= _sp_freq

                                        _last_point = None if None in (_first_point, _sp_width) else (_first_point - _sp_width)

                                        if None in (_center_point, _last_point):
                                            spectral_region = 'H'
                                        elif C_AROMATIC_CENTER_MIN_TOR < _center_point <= C_AROMATIC_CENTER_MAX\
                                                and _sp_width < 60.0:
                                            spectral_region = 'H-aromatic'
                                        elif C_METHYL_CENTER_MIN < _center_point <= C_METHYL_CENTER_MAX\
                                                and _sp_width < 30.0:
                                            spectral_region = 'H-methyl'
                                        elif C_ALIPHATIC_CENTER_MIN < _center_point <= C_ALIPHATIC_CENTER_MAX\
                                                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 C_CARBONYL_CENTER_MIN <= center_point <= C_CARBONYL_CENTER_MAX:
                                spectral_region = 'CO'
                            elif C_AROMATIC_CENTER_MIN_TOR < center_point <= C_AROMATIC_CENTER_MAX and sp_width < 60.0:
                                spectral_region = 'C-aromatic'
                            elif C_METHYL_CENTER_MIN < center_point <= C_METHYL_CENTER_MAX and sp_width < 30.0:
                                spectral_region = 'C-methyl'
                            elif C_ALIPHATIC_CENTER_MIN < center_point <= C_ALIPHATIC_CENTER_MAX and sp_width < 90.0:
                                spectral_region = 'C-aliphatic'
                            else:
                                spectral_region = 'C'
                        else:
                            spectral_region = atom_type

                        spectral_dim['spectral_region'] = spectral_region

                        ent['spectral_dim'].append(spectral_dim)

                        break

            count = {'assigned_spectral_peaks': 0, 'unassigned_spectral_peaks': 0}

            aux_data = next((lp['data'] for lp in self.__reg.aux_data[content_subtype]
                             if lp['file_name'] == file_name and lp['sf_framecode'] == sf_framecode
                             and lp['category'] == '_Assigned_peak_chem_shift'), None)

            pk_id_name = 'Peak_ID'
            dim_id_name = 'Spectral_dim_ID'

            pk_id_set = set()

            for row in lp_data:

                has_assignment = aux_data is not None

                pk_id = row['ID']

                if pk_id in pk_id_set:
                    continue

                if has_assignment:

                    for j in range(num_dim):

                        try:
                            k = next(k for k in aux_data if k[pk_id_name] == pk_id and int(k[dim_id_name]) - 1 == j)
                        except StopIteration:
                            has_assignment = False
                            break

                        if 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 EMPTY_VALUE or seq_id in EMPTY_VALUE or comp_id in EMPTY_VALUE or atom_id in 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

            if file_type == 'nmr-star' and self.__reg.star_data_type[file_list_id] == 'Entry':
                lp_category = '_Assigned_peak_chem_shift'
                sf = self.__reg.star_data[file_list_id].get_saveframe_by_name(sf_framecode)
                lp = next((lp for lp in sf.loops if lp.category == lp_category), None)

                if lp is not None:
                    ent['atom_name_mapping'] = get_atom_name_mapping(lp, [['Comp_ID', 'Atom_ID', 'Auth_atom_ID']])

        except Exception as e:  # pylint: disable=broad-exception-caught

            self.__reg.report.error.appendDescription('internal_error',
                                                      f"+{self.__class_name__}.__calculateStatsOfSpectralPeakAlt() "
                                                      "++ Error  - " + str(e))

            if self.__reg.verbose:
                self.__reg.log.write(f"+{self.__class_name__}.__calculateStatsOfSpectralPeakAlt() "
                                     f"++ Error  - {str(e)}\n")

    def getDatumCounter(self, master_entry: pynmrstar.Entry) -> dict:
        """ Return Datum counter dictionary.
        """

        file_type = 'nmr-star'

        datum_counter = copy.copy(DEFAULT_DATUM_COUNTER)

        def get_loop_size(content_subtype):
            sf_category = SF_CATEGORIES[file_type][content_subtype]
            lp_category = LP_CATEGORIES[file_type][content_subtype]
            size = 0
            for sf in master_entry.get_saveframes_by_category(sf_category):
                try:
                    lp = sf.get_loop(lp_category)
                except KeyError:
                    continue
                size += len(lp)
            return size

        for content_subtype in self.__reg.nmr_rep_content_subtypes:

            if content_subtype == 'chem_shift':
                sf_category = SF_CATEGORIES[file_type][content_subtype]
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    try:
                        lp = sf.get_loop(lp_category)

                        dat = lp.get_tag(['Atom_isotope_number', 'Atom_type'])
                        for row in dat:
                            if row[0] not in EMPTY_VALUE and row[1] not in EMPTY_VALUE:
                                t = f'{row[0]}{row[1].title()} chemical shifts'
                                if t in datum_counter:
                                    datum_counter[t] += 1
                    except KeyError:
                        continue
            elif content_subtype == 'dist_restraint':
                sf_category = SF_CATEGORIES[file_type][content_subtype]
                lp_category = LP_CATEGORIES[file_type][content_subtype]
                for sf in master_entry.get_saveframes_by_category(sf_category):
                    constraint_type = get_first_sf_tag(sf, 'Constraint_type')
                    try:
                        lp = sf.get_loop(lp_category)

                        if constraint_type == 'hydrogen bond':
                            datum_counter['hydrogen bond distance constraints'] += len(lp)
                        elif constraint_type == 'symmetry':
                            datum_counter['symmetry constraints'] += len(lp)
                        else:
                            if 'Combination_ID' in lp.tags and 'Member_ID' in lp.tags:
                                dat = lp.get_tag(['Combination_ID', 'Member_ID'])
                                for row in dat:
                                    if row[0] in EMPTY_VALUE and row[1] in EMPTY_VALUE:
                                        datum_counter['distance constraints'] += 1
                                    else:
                                        datum_counter['ambiguous distance constraints'] += 1
                            else:
                                datum_counter['distance constraints'] += len(lp)
                    except KeyError:
                        continue
            elif content_subtype == 'dihed_restraint':
                datum_counter['torsion angle constraints'] += get_loop_size(content_subtype)
            elif content_subtype in ('rdc_restraint' 'rdc_raw_data'):
                datum_counter['residual dipolar couplings'] += get_loop_size(content_subtype)
            elif content_subtype == 'noepk_restraint':
                datum_counter['homonuclear NOE values'] += get_loop_size(content_subtype)
            elif content_subtype == 'jcoup_restraint':
                datum_counter['coupling constants'] += get_loop_size(content_subtype)
            elif content_subtype == 'csa_restraint':
                datum_counter['chemical shift anisotropy values'] += get_loop_size(content_subtype)
            elif content_subtype == 'ddc_restraint':
                datum_counter['dipolar coupling values'] += get_loop_size(content_subtype)
            elif content_subtype in ('hvycs_restraint', 'procs_restraint'):
                datum_counter['chemical shift constraints'] += get_loop_size(content_subtype)
            elif content_subtype == 'csp_restraint':
                datum_counter['chemical shift perturbation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_noe_data':
                datum_counter['heteronuclear NOE values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t1_data':
                datum_counter['T1 relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t2_data':
                datum_counter['T2 relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'heteronucl_t1r_data':
                datum_counter['T1rho relaxation values'] += get_loop_size(content_subtype)
            elif content_subtype == 'order_param_data':
                datum_counter['order parameters'] += get_loop_size(content_subtype)
            elif content_subtype == 'ph_titr_data':
                datum_counter['pKa values'] += get_loop_size(content_subtype)
            elif content_subtype == 'ph_param_data':
                datum_counter['pH NMR parameter values'] += get_loop_size(content_subtype)
            elif content_subtype == 'coupling_const_data':
                datum_counter['coupling constants'] += get_loop_size(content_subtype)
            elif content_subtype == 'ccrd_dd_restraint':
                datum_counter['dipole-dipole cross correlation relaxation values'] += get_loop_size(content_subtype)

        return {k: v for k, v in datum_counter.items() if v > 0}
